/* bas.c - API for writing standalone programs that deal with tokenized Atari 8-bit BASIC program. */ #include #include #include #include #include #include #include "bas.h" int verbose = 0; unsigned short lomem; unsigned short vntp; unsigned short vntd; unsigned short vvtp; unsigned short stmtab; unsigned short stmcur; unsigned short starp; unsigned short codestart; unsigned short code_end; unsigned short vnstart; unsigned short vvstart; int filelen; const char *self; unsigned char program[BUFSIZE]; FILE *input_file; FILE *output_file; char *output_filename = NULL; void die(const char *msg) { fprintf(stderr, "%s: %s\n", self, msg); exit(1); } void parse_general_args(int argc, char **argv, void (*helpfunc)()) { if(argc < 2) { (*helpfunc)(); exit(1); } if(strcmp(argv[1], "--help") == 0) { (*helpfunc)(); exit(0); } if(strcmp(argv[1], "--version") == 0) { printf("%s %s\n", self, VERSION); exit(0); } } /* read entire file into memory */ void readfile(void) { filelen = fread(program, 1, BUFSIZE - 1, input_file); if(verbose) fprintf(stderr, "Read %d bytes.\n", filelen); if(!feof(input_file)) fprintf(stderr, "Warning: file is >64KB, way too big for a BASIC program.\n"); else if(filelen > MAX_PROG_SIZE) fprintf(stderr, "Warning: file is %d bytes, suspiciously large for a BASIC program.\n", filelen); fclose(input_file); if(filelen < MIN_PROG_SIZE) die("File too short to be a BASIC program (truncated?)\n"); } int writefile(void) { int outbytes; outbytes = fwrite(program, 1, filelen, output_file); fclose(output_file); if(verbose) fprintf(stderr, "Wrote %d bytes.\n", outbytes); return outbytes; } /* get a 16-bit value from the file, in 6502 LSB/MSB order. */ unsigned short getword(int addr) { return program[addr] | (program[addr + 1] << 8); } void setword(int addr, int value) { program[addr] = value & 0xff; program[addr + 1] = value >> 8; } void dump_header_vars(void) { fprintf(stderr, "LOMEM $%04x VNTP $%04x VNTD $%04x VVTP $%04x\n", lomem, vntp, vntd, vvtp); fprintf(stderr, "STMTAB $%04x STMCUR $%04x STARP $%04x\n", stmtab, stmcur, starp); fprintf(stderr, "vnstart $%04x, vvstart $%04x, codestart $%04x, code_end $%04x\n", vnstart, vvstart, codestart, code_end); } void parse_header(void) { lomem = getword(0); vntp = getword(2); vntd = getword(4); vvtp = getword(6); stmtab = getword(8); stmcur = getword(10); starp = getword(12); codestart = stmtab - TBL_OFFSET - (vntp - 256); vnstart = vntp - TBL_OFFSET; vvstart = vvtp - TBL_OFFSET; code_end = starp - TBL_OFFSET; if(lomem) die("This doesn't look like an Atari BASIC program (no $0000 signature)."); if(filelen < code_end) { fprintf(stderr, "Warning: file is truncated: %d bytes, should be %d.\n", filelen, code_end); } if(verbose) dump_header_vars(); } void update_header(void) { setword(0, lomem); setword(2, vntp); setword(4, vntd); setword(6, vvtp); setword(8, stmtab); setword(10, stmcur); setword(12, starp); } /* sometimes the variable name table isn't large enough to hold the generated variable names. move_code() makes more space, by moving the rest of the program (including the variable value table) up in memory. */ void move_code(int offset) { unsigned char *dest = program + vvstart + offset; if(dest < program || ((filelen + offset) > (BUFSIZE - 1))) { die("Attempt to move memory out of range; corrupt header bytes?\n"); } memmove(dest, program + vvstart, filelen); vntd += offset; vvtp += offset; stmtab += offset; stmcur += offset; starp += offset; filelen += offset; update_header(); parse_header(); } void adjust_vntable_size(int oldsize, int newsize) { int move_by; if(oldsize != newsize) { move_by = newsize - oldsize; if(verbose) fprintf(stderr, "Need %d bytes for vntable, have %d, moving VVTP by %d to $%04x.\n", newsize, oldsize, move_by, vvtp + move_by); move_code(move_by); } } /* return true if the variable name table is OK */ int vntable_ok(void) { int vp, bad; if(vntp == vntd) { if(verbose) fprintf(stderr, "No variables.\n"); return 1; } /* first pass: bad = 1 if all the bytes in the table have the same value, no matter what it is. */ vp = vnstart + 1; bad = 1; while(vp < vvstart - 1) { if(program[vp] != program[vnstart]) { bad = 0; break; } vp++; } if(bad) return 0; /* 2nd pass: bad = 1 if there's any invalid character in the table. */ vp = vnstart; while(vp < vvstart) { unsigned char c = program[vp]; /* treat a null byte as end-of-table, ignore any junk between it and VNTP. */ if(c == 0) break; vp++; /* inverse $ or ( is OK */ if(c == 0xa4 || c == 0xa8) continue; /* numbers and letters are allowed, inverse or normal. */ c &= 0x7f; if(c >= 0x30 && c <= 0x39) continue; if(c >= 0x41 && c <= 0x5a) continue; bad++; break; } return !bad; } void invalid_args(const char *arg) { fprintf(stderr, "%s: Invalid argument '%s'.\n\n", self, arg); exit(1); } FILE *open_file(const char *name, const char *mode) { FILE *fp; if(!(fp = fopen(name, mode))) { perror(name); exit(1); } return fp; } void open_input(const char *name) { if(!name || strcmp(name, "-") == 0) { if(isatty(fileno(stdin))) { die("Can't read binary data from the terminal."); } if(freopen(NULL, "rb", stdin)) { input_file = stdin; return; } else { perror("stdin"); exit(1); } } input_file = open_file(name, "rb"); } void open_output(const char *name) { if(!name || (strcmp(name, "-") == 0)) { if(isatty(fileno(stdout))) { die("Refusing to write binary data to the terminal."); } if(freopen(NULL, "wb", stdout)) { output_file = stdout; return; } else { perror("stdout"); exit(1); } } output_file = open_file(name, "wb"); } extern void set_self(const char *argv0) { char *p; self = argv0; p = strrchr(self, '/'); if(p) self = p + 1; }