/* * Asm81 * * Copyright (c) 2010 Benjamin Moody * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "miniasm.h" #include "pack.h" #include "prgfile.h" #include "utils.h" #ifdef __GNUC__ # define UNUSED __attribute__((unused)) #else # define UNUSED #endif static char** search_dirs; static int num_search_dirs; static char** stdin_cache; static int stdin_cache_size; static int stdin_cache_nlines; static int stdin_cache_linenum; /**************** Filename management ****************/ #if defined(__MSDOS__) || defined(__WIN32__) #define DIR_SEPARATOR '\\' static int is_dir_separator(char c) { return (c == '\\' || c == '/'); } static int is_absolute(const char* filename) { if (filename[0] && filename[1] == ':' && is_dir_separator(filename[2])) return 1; else return is_dir_separator(filename[0]); } #else #define DIR_SEPARATOR '/' static int is_dir_separator(char c) { return (c == '/'); } static int is_absolute(const char* filename) { return (filename[0] == '/'); } #endif /**************** Miniasm file input functions ****************/ struct _asfile { char* name; FILE* fp; }; static asfile* try_open(const char* dirname, const char* filename, const char* mode) { asfile* f = xmalloc(sizeof(asfile)); if (dirname) { f->name = xmalloc(strlen(dirname) + strlen(filename) + 2); sprintf(f->name, "%s%c%s", dirname, DIR_SEPARATOR, filename); } else { f->name = xstrdup(filename); } f->fp = fopen(f->name, mode); if (f->fp) return f; else { xfree(f->name); xfree(f); return NULL; } } static asfile* asfile_open(const char* filename, asfile* parent, int local, const char* mode) { asfile* f; int i; char* dname; if (!strcmp(filename, "-")) { f = xmalloc(sizeof(asfile)); f->name = NULL; f->fp = stdin; return f; } if (!local) { for (i = 0; i < num_search_dirs; i++) { if ((f = try_open(search_dirs[i], filename, mode))) return f; } } if (!local || is_absolute(filename) || !parent || !parent->name) { if ((f = try_open(NULL, filename, mode))) return f; else return NULL; } dname = NULL; for (i = strlen(parent->name); i >= 0; i--) { if (is_dir_separator(parent->name[i])) { dname = xstrndup(parent->name, i); break; } } f = try_open(dname, filename, mode); xfree(dname); return f; } asfile* miniasm_open(const char* filename, asfile* parent, int local, void* data UNUSED) { return asfile_open(filename, parent, local, "rt"); } static void asfile_close(asfile* filep) { if (filep->fp != stdin) fclose(filep->fp); xfree(filep->name); xfree(filep); } void miniasm_close(asfile* filep, void* data UNUSED) { asfile_close(filep); } static char* xgets(asfile* filep) { int len = 0, len_a = 256; int c; char* str; if (feof(filep->fp) || ferror(filep->fp)) { return NULL; } str = (char*) xmalloc(len_a * sizeof(char)); do { c = fgetc(filep->fp); if (c != EOF) { if (len >= len_a) { len_a *= 2; str = (char*) xrealloc(str, len_a * sizeof(char)); } str[len] = c; len++; } else break; } while (c != '\n' && c != '\r'); if (c == '\r') { c = fgetc(filep->fp); if (c == '\n') { if (len >= len_a) { len_a *= 2; str = (char*) xrealloc(str, len_a * sizeof(char)); } str[len] = c; len++; } else ungetc(c, filep->fp); } if (len) { if (len >= len_a) { len_a = len + 1; str = (char*) xrealloc(str, len_a * sizeof(char)); } str[len] = 0; return str; } xfree(str); return NULL; } char* miniasm_gets(asfile* filep, void* data UNUSED) { char* str; if (filep->fp != stdin) return xgets(filep); while (stdin_cache_linenum >= stdin_cache_nlines) { if (stdin_cache_nlines >= stdin_cache_size) { stdin_cache_size = (stdin_cache_nlines + 1) * 2; stdin_cache = xrealloc(stdin_cache, stdin_cache_size * sizeof(char*)); } str = xgets(filep); if (str) { stdin_cache_nlines++; stdin_cache[stdin_cache_nlines - 1] = str; } else { return NULL; } } str = xstrdup(stdin_cache[stdin_cache_linenum]); stdin_cache_linenum++; return str; } void miniasm_gets_free(char* str) { xfree(str); } int miniasm_rewind(asfile* filep, void* data UNUSED) { if (filep->fp == stdin) { stdin_cache_linenum = 0; return 0; } else return fseek(filep->fp, 0L, SEEK_SET); } int miniasm_get_bin_size(const char* filename, asfile* parent, int local, long int* lengthp, void* data UNUSED) { asfile* filep; filep = asfile_open(filename, parent, local, "rb"); if (!filep) return 1; if (fseek(filep->fp, 0L, SEEK_END)) { asfile_close(filep); return 1; } *lengthp = ftell(filep->fp); asfile_close(filep); return 0; } int miniasm_read_bin(const char* filename, asfile* parent, int local, unsigned char* buf, long int n, void* data UNUSED) { asfile* filep; filep = asfile_open(filename, parent, local, "rb"); if (!filep) return 1; if (n == (long) fread(buf, 1, n, filep->fp)) { asfile_close(filep); return 0; } else { asfile_close(filep); return 1; } } /**************** Miniasm output functions ****************/ typedef struct _outputinfo { const char* infilename; const char* version; const char* lstfilename; FILE* lstfile; int lstfileerr; const char* expfilename; FILE* expfile; int expfileerr; int insyms; } outputinfo; static FILE* open_output_file(const char* fname, const char* mode) { FILE* f; if (!strcmp(fname, "-")) return stdout; else { f = fopen(fname, mode); if (!f) perror(fname); return f; } } void miniasm_write_message(const char* string, void* data) { outputinfo* oi = data; fprintf(stderr, "%s\n", string); if (oi->lstfile) fprintf(oi->lstfile, "*** %s\n", string); } void miniasm_begin_listing(int passnum UNUSED, int final, void* data) { outputinfo* oi = data; if (oi->lstfile && oi->lstfile != stdout) { fclose(oi->lstfile); oi->lstfile = NULL; } if (!oi->lstfilename || oi->lstfileerr) return; oi->lstfile = open_output_file(oi->lstfilename, "wt"); if (!oi->lstfile) { oi->lstfileerr = 1; } else if (oi->lstfile == stdout && !final) { oi->lstfile = NULL; return; } else { fprintf(oi->lstfile, "Asm81 version 1.0\n"); fprintf(oi->lstfile, "Source File: %s\n", oi->infilename); fprintf(oi->lstfile, "Assembling for ROM version: %s\n\n", oi->version); oi->insyms = 0; } } void miniasm_write_listing(asfile* filep UNUSED, int linenum, const char* text, long int lineaddr, long int linecount, const unsigned char* linebytes, void* data) { outputinfo* oi = data; int i; const char* p; if (!oi->lstfile) return; fprintf(oi->lstfile, "%-5d %08lX", linenum, lineaddr); for (i = 0; i < linecount && i < 5; i++) { if (linebytes) fprintf(oi->lstfile, " %02X", linebytes[i]); else fprintf(oi->lstfile, " XX"); } for (; i < 5; i++) fputs(" ", oi->lstfile); fputs(" ", oi->lstfile); for (p = text; *p; p++) if (*p != '\n' && *p != '\r') fputc(*p, oi->lstfile); for (; i < linecount; i++) { if (!(i % 5)) fputs("\n ", oi->lstfile); if (linebytes) fprintf(oi->lstfile, " %02X", linebytes[i]); else fprintf(oi->lstfile, " XX"); } fputc('\n', oi->lstfile); } void miniasm_write_symbol(const char* name, long int value, int exported, void* data) { outputinfo* oi = data; if (oi->lstfile) { if (!oi->insyms) fprintf(oi->lstfile, "\n"); oi->insyms = 1; fprintf(oi->lstfile, "%08lX %s\n", value, name); } if (exported) { if (!oi->expfile && oi->expfilename && !oi->expfileerr) { oi->expfile = open_output_file(oi->expfilename, "wt"); if (!oi->expfile) oi->expfileerr = 1; } if (oi->expfile) fprintf(oi->expfile, "%-24s .equ 0%lXh\n", name, value); } } /**************** Generating output filenames ****************/ static char* build_filename(const char* infilename, const char* outfilename, const char* version, const char* suffix) { const char* p; char* prefix; char* result; int i, j; if (outfilename) { if ((p = strrchr(outfilename, '.'))) { suffix = p + 1; prefix = xstrndup(outfilename, p - outfilename); } else { return xstrdup(outfilename); } } else if (infilename && strcmp(infilename, "-")) { if ((p = strrchr(infilename, '.'))) { prefix = xstrndup(infilename, p - infilename); } else { prefix = xstrdup(infilename); } } else { prefix = xstrdup("output"); } if (version && strcmp(version, "ANY")) { result = xmalloc(strlen(prefix) + strlen(version) + strlen(suffix) + 3); strcpy(result, prefix); i = strlen(result); result[i++] = '-'; for (j = 0; version[j]; j++) { if (version[j] == ',') result[i++] = '+'; else if (version[j] != ' ') result[i++] = version[j]; } result[i++] = '.'; strcpy(result + i, suffix); } else { result = xmalloc(strlen(prefix) + strlen(suffix) + 2); sprintf(result, "%s.%s", prefix, suffix); } xfree(prefix); return result; } /**************** Assembling program ****************/ typedef struct _binoutput { unsigned char* data; long int start; long int end; } binoutput; typedef struct _defsymbol { char* name; long int value; } defsymbol; static int assemble_program(const char* infilename, const char* lstfilename, const char* expfilename, const char* version, int ndefs, const defsymbol* defs, binoutput* bin) { outputinfo oi; asctx* ctx; char *lfn, *efn; const unsigned char* buf; long int start, end; int i; int npass, nerrs, nwarn; char *q; int major, minor; char letter; oi.infilename = infilename; oi.version = version; oi.lstfilename = lfn = build_filename(infilename, lstfilename, version, "lst"); oi.lstfile = NULL; oi.lstfileerr = 0; oi.expfilename = efn = build_filename(infilename, expfilename, version, "exp"); oi.expfile = NULL; oi.expfileerr = 0; ctx = miniasm_init(MINIASM_WRITE_LISTING | MINIASM_WRITE_SYMBOLS, &oi); miniasm_define(ctx, "_ASM81", 1); for (i = 0; i < ndefs; i++) miniasm_define(ctx, defs[i].name, defs[i].value); q = xmalloc(10 + strlen(version)); sprintf(q, "TI81_VER_%s", version); for (i = 0; q[i]; i++) { if ((q[i] < 'A' || q[i] > 'Z') && (q[i] < 'a' || q[i] > 'z') && (q[i] < '0' || q[i] > '9')) q[i] = '_'; } miniasm_define(ctx, q, 1); xfree(q); i = sscanf(version, "%d.%d%c", &major, &minor, &letter); if (i > 0) miniasm_define(ctx, "TI81_VER_MAJOR", major); if (i > 1) miniasm_define(ctx, "TI81_VER_MINOR", minor); if (i > 2 && letter >= 'A' && letter <= 'Z') miniasm_define(ctx, "TI81_VER_SUFFIX", letter - 'A' + 0x4D); fprintf(stderr, " * Assembling for version %s...\n", version); miniasm_assemble(ctx, infilename, &buf, &start, &end, &npass, &nerrs, &nwarn); if (oi.lstfile && oi.lstfile != stdout) fclose(oi.lstfile); if (oi.expfile && oi.expfile != stdout) fclose(oi.expfile); xfree(lfn); xfree(efn); fprintf(stderr, " * Assembled for version %s in %d passes" " (%d errors, %d warnings)\n", version, npass, nerrs, nwarn); if (nerrs) { bin->data = NULL; bin->start = bin->end = 0; miniasm_exit(ctx); return 1; } else { bin->data = xmalloc(end - start); memcpy(bin->data, buf, end - start); bin->start = start; bin->end = end; miniasm_exit(ctx); return 0; } } /**************** Program file output ****************/ enum { OUTPUT_RAW = 1, /* raw binary file */ OUTPUT_PRG = 2, /* prg file */ OUTPUT_TXT = 4 /* text file */ }; #define CRLF "\r\n" /* Addresses and sizes of some common safe-RAM areas */ static const struct saferaminfo { long int start, size; const char* name; } new_saferam[] = { { 0xE315, 50, "OP1-OP5" }, { 0xE34F, 288+2, "tempMatrix" }, { 0xE46F, 288, "scratchMem" }, { 0xE46F+5, 288-5, "scratchMem+5" }, { 0xE69D, 138, "cmdShadow" }, { 0xE732, 131, "textShadow" }, { 0xE7C3, 768, "plotSScreen" }, { 0xEB55, 216, "real variables" }, { 0xEC2D, 870, "matrix variables" }, { 0xFCC7, 360, "equMem" }, { 0, 0, 0 } }, old_saferam[] = { { 0xE315, 50, "OP1-OP5" }, { 0xE34F, 768, "plotSScreen" }, { 0xE6E1, 216, "real variables" }, { 0xE7B9, 870, "matrix variables" }, { 0xEB1F, 288+2, "tempMatrix" }, { 0xEC3F, 288, "scratchMem" }, { 0xEC3F+5, 288-5, "scratchMem+5" }, { 0xF021, 138, "cmdShadow" }, { 0xF0C4, 131, "textShadow" }, { 0xFCC7, 360, "equMem" }, { 0, 0, 0 } }, common_saferam[] = { { 0xE315, 50, "OP1-OP5" }, { 0xE34F, 288+2, "new tempMatrix (old plotSScreen)" }, { 0xE46F, 288, "new scratchMem (old plotSScreen)" }, { 0xE46F+5, 288-5, "new scratchMem+5 (old plotSScreen)" }, { 0xE7C3, 768, "new plotSScreen (old matrix vars)" }, { 0xEB55, 216, "new real vars (old tempMatrix)" }, { 0xEC3F, 288, "old scratchMem (new matrix vars)" }, { 0xEC3F+5, 288-5, "old scratchMem+5 (new matrix vars)" }, { 0xFCC7, 360, "equMem" } }; static const char toptext[] = "This is an assembly program. To run this program, you will need to" CRLF "have the Unity kernel installed." CRLF CRLF "Note that '_' represents a space (ALPHA + 0), '@' represents theta" CRLF "(ALPHA + 3), and '#' represents pi (2nd + ^)." CRLF CRLF "Line breaks below are only shown for clarity; the entire block of code" CRLF "following '++' should be typed on one line." CRLF; static const char romwarningtext[] = CRLF "This program will ONLY work on calculators with the ROM version(s)" CRLF "indicated below. Running an assembly program on an incorrect ROM" CRLF "version will most likely crash your calculator. To find your ROM" CRLF "version, press 2nd + MATH (\"TEST\"), then press ALPHA + LN (\"S\")." CRLF "Press CLEAR to exit this screen." CRLF; static const unsigned char launchcode[4] = { 0xB9, 0xEC, 0x26, 0x26 }; static int save_output(const char* infilename, const char* outfilename, const char* progname, const char* version, binoutput* bin, int outmode, int firstver, int firstfmt) { FILE *outfile; char *fname, *p; char *pname = NULL; const struct saferaminfo *sri; unsigned char *packed; unsigned int packedlen; int column; unsigned int i; int n; if (outmode == OUTPUT_RAW) { fname = build_filename(infilename, outfilename, version, "bin"); outfile = open_output_file(fname, "wb"); xfree(fname); if (!outfile) return 1; n = fwrite(bin->data, 1, bin->end - bin->start, outfile); if (outfile != stdout) fclose(outfile); return (n != bin->end - bin->start); } if (bin->start < 0xC000 || bin->start > 0xFFFF) { if (firstfmt) fprintf(stderr, "%s <%s>: ERROR: program origin is not in RAM\n", infilename, version); return 1; } if (bin->end > 0xFFFE) { if (firstfmt) fprintf(stderr, "%s <%s>: ERROR: program is larger than available RAM\n", infilename, version); return 1; } if (firstfmt) { if (!strcmp(version, "ANY")) sri = common_saferam; else if (version[0] == '1' && version[1] == '.' && (version[2] == '0' || version[2] == '1')) sri = old_saferam; else sri = new_saferam; for (i = 0; sri[i].size; i++) { if (bin->start == sri[i].start && bin->end - bin->start + 2 > sri[i].size) { fprintf(stderr, "%s <%s>: WARNING: program overflows %s area\n", infilename, version, sri[i].name); fprintf(stderr, " (program = %ld bytes including checksum;" " safe RAM area = %ld bytes)\n", bin->end - bin->start + 2, sri[i].size); } } } pack_asm_data(bin->start, bin->data, bin->end - bin->start, &packed, &packedlen); if (outmode == OUTPUT_TXT) { fname = build_filename(infilename, outfilename, NULL, "81"); /* text file output uses explicit CRLF (so that .81 files can be easily read by noobs who use broken Windows text editors), so use binary mode */ outfile = open_output_file(fname, (firstver ? "wb" : "ab")); xfree(fname); if (!outfile) { xfree(packed); return 1; } if (firstver) fputs(toptext, outfile); if (!strcmp(version, "ANY")) { fprintf(outfile, CRLF "--- Start of %s (for any ROM version) ---" CRLF, progname ? progname : "program"); } else { if (firstver) fputs(romwarningtext, outfile); fprintf(outfile, (strchr(version, ',') ? CRLF "--- Start of %s (for ROM versions %s only) ---" CRLF : CRLF "--- Start of %s (for ROM version %s only) ---" CRLF), progname ? progname : "program", version); } fprintf(outfile, ":Pause" CRLF ":++"); column = 3; for (i = 0; i < packedlen; i++) { switch (packed[i]) { case 0x73: fputc('@', outfile); break; case 0x2C: fputc('"', outfile); break; case 0x99: fputc('#', outfile); break; case 0x56: fputc('_', outfile); break; case 0x1E: fputc(',', outfile); break; case 0x45: fputc('?', outfile); break; default: fputc('A' + packed[i] - 0x59, outfile); } column++; if (column == 16) { fputs(CRLF, outfile); column = 0; } } fputs(CRLF, outfile); if (strcmp(version, "ANY")) fprintf(outfile, "--- End of %s (for ROM version %s only) ---" CRLF, progname ? progname : "program", version); else fprintf(outfile, "--- End of %s (for any ROM version) ---" CRLF, progname ? progname : "program"); if (outfile != stdout) fclose(outfile); } else { fname = build_filename(infilename, outfilename, version, "prg"); outfile = open_output_file(fname, "wb"); xfree(fname); if (!outfile) { xfree(packed); return 1; } if (!progname && infilename && strcmp(infilename, "-")) { if ((p = strrchr(infilename, '.'))) progname = pname = xstrndup(infilename, p - infilename); else progname = pname = xstrdup(infilename); } else if (!progname) { progname = "ASM.PRGM"; } if (write_prg_file(outfile, progname, launchcode, 4, packed, packedlen)) { xfree(packed); xfree(pname); if (outfile != stdout) fclose(outfile); return 1; } xfree(pname); if (outfile != stdout) fclose(outfile); } xfree(packed); return 0; } static const char known_versions[] = "1.1K, 1.6K, 1.8K, 2.0V"; static void usage(const char* progname) { fprintf(stderr, "Usage: %s asmfile [-r] [-o outfile] [-l lstfile] [-x expfile]\n" " [-n name] [-v versions] [-I dir] [-D symbol[=value]] ...\n" "Options:\n" " -r: output raw binary file\n" " -o FILE: write output to FILE\n" " -l FILE: write assembly listing to FILE\n" " -x FILE: write exported symbols to FILE\n" " -n NAME: use NAME as program name\n" " -v VER,VER... assemble for specific ROM version(s)\n" " (use -v any for a ROM-independent program)\n" " -I DIRECTORY: search for header files in DIRECTORY\n" " -D SYM=VALUE: pre-define a symbol\n" "Known ROM versions: %s\n", progname, known_versions); } int main(int argc, char** argv) { const char* infilename = NULL; const char* outfilename = NULL; const char* lstfilename = NULL; const char* expfilename = NULL; const char* progname = NULL; const char* verstr = NULL; char** versions = NULL; int nversions = 0, autoversions; defsymbol* defs = NULL; int ndefs = 0; binoutput* bin; int outmodes = OUTPUT_PRG | OUTPUT_TXT; const char *p, *q; char* arg; int ok = 1, firstver, firstfmt; int i, j; char* vers; for (i = 1; i < argc; i++) { if (argv[i][0] != '-' || argv[i][1] == 0) infilename = argv[i]; else { if (argv[i][1] == 'o') { if (argv[i][2]) outfilename = &argv[i][2]; else if (++i < argc) outfilename = argv[i]; else { fprintf(stderr, "%s: -o: requires an argument\n", argv[0]); return 2; } } else if (argv[i][1] == 'l') { if (argv[i][2]) lstfilename = &argv[i][2]; else if (++i < argc) lstfilename = argv[i]; else { fprintf(stderr, "%s: -l: requires an argument\n", argv[0]); return 2; } } else if (argv[i][1] == 'x') { if (argv[i][2]) expfilename = &argv[i][2]; else if (++i < argc) expfilename = argv[i]; else { fprintf(stderr, "%s: -x: requires an argument\n", argv[0]); return 2; } } else if (argv[i][1] == 'n') { if (argv[i][2]) progname = &argv[i][2]; else if (++i < argc) progname = argv[i]; else { fprintf(stderr, "%s: -n: requires an argument\n", argv[0]); return 2; } } else if (argv[i][1] == 'v') { if (argv[i][2]) verstr = &argv[i][2]; else if (++i < argc) verstr = argv[i]; else { fprintf(stderr, "%s: -v: requires an argument\n", argv[0]); return 2; } } else if (argv[i][1] == 'I') { if (argv[i][2]) arg = &argv[i][2]; else if (++i < argc) arg = argv[i]; else { fprintf(stderr, "%s: -I: requires an argument\n", argv[0]); return 2; } num_search_dirs++; search_dirs = xrealloc(search_dirs, num_search_dirs * sizeof(char*)); search_dirs[num_search_dirs - 1] = arg; } else if (argv[i][1] == 'D') { if (argv[i][2]) arg = &argv[i][2]; else if (++i < argc) arg = argv[i]; else { fprintf(stderr, "%s: -D: requires an argument\n", argv[0]); return 2; } ndefs++; defs = xrealloc(defs, ndefs * sizeof(defsymbol)); defs[ndefs - 1].name = arg; defs[ndefs - 1].value = 1; if ((arg = strchr(arg, '='))) { *arg = 0; sscanf(arg + 1, "%li", &defs[ndefs - 1].value); } } else if (argv[i][1] == 'r') { outmodes = OUTPUT_RAW; } else { fprintf(stderr, "%s: unknown option %s\n", argv[0], argv[i]); usage(argv[0]); return 2; } } } if (outfilename && !strcmp(outfilename, "-")) outmodes &= ~OUTPUT_PRG; if (!infilename) { usage(argv[0]); return 2; } if (!verstr) { verstr = known_versions; autoversions = 1; } else { autoversions = 0; } q = verstr; while (*q) { while (*q == ' ') q++; versions = xrealloc(versions, ++nversions * sizeof(char*)); if ((p = strchr(q, ','))) { versions[nversions - 1] = xstrndup(q, p - q); q = p + 1; } else { versions[nversions - 1] = xstrdup(q); q = ""; } for (i = 0; versions[nversions - 1][i]; i++) if (versions[nversions - 1][i] >= 'a' && versions[nversions - 1][i] <= 'z') versions[nversions - 1][i] += 'A' - 'a'; } if (!nversions) { fprintf(stderr, "%s: no versions specified\n", argv[0]); return 2; } bin = xmalloc(nversions * sizeof(binoutput)); /* assemble the requested versions */ for (i = 0; i < nversions; i++) { if (assemble_program(infilename, lstfilename, expfilename, versions[i], ndefs, defs, &bin[i])) ok = 0; } /* merge matching versions */ for (j = 0; j < nversions; j++) { for (i = 0; i < j; i++) { if (bin[i].data && bin[j].data && bin[i].start == bin[j].start && bin[i].end == bin[j].end && !memcmp(bin[i].data, bin[j].data, bin[i].end - bin[i].start)) { xfree(bin[j].data); bin[j].data = NULL; vers = xstrconcat(versions[i], ", ", versions[j], (char*) NULL); xfree(versions[i]); versions[i] = vers; } } } if (autoversions && !strcmp(versions[0], known_versions)) { xfree(versions[0]); versions[0] = xstrdup("ANY"); } /* write output files */ firstver = 1; for (i = 0; i < nversions; i++) { if (bin[i].data) { firstfmt = 1; for (j = 1; j <= outmodes; j <<= 1) { if (outmodes & j) { if (save_output(infilename, outfilename, progname, versions[i], &bin[i], j, firstver, firstfmt)) { ok = 0; } firstfmt = 0; } } firstver = 0; } xfree(versions[i]); xfree(bin[i].data); } xfree(bin); xfree(versions); xfree(defs); xfree(search_dirs); for (i = 0; i < stdin_cache_nlines; i++) xfree(stdin_cache[i]); xfree(stdin_cache); return (ok ? 0 : 1); }