#include #include #include #include #include "amsbtok.h" #define MIN_STD_TOK 0x80 /* END */ #define MAX_STD_TOK 0xf8 /* < */ #define MIN_EXT_TOK 0xa3 /* SGN */ #define MAX_EXT_TOK 0xc5 /* STACK */ #ifndef BUFSIZ #define BUFSIZ 4096 #endif #define EOL 0x9b #define MAX_LINENO 63999 /* a program bigger than this can't possibly fit into memory, even with cart-based AMSB2 and no DOS loaded. */ #define MAX_PROGLEN 30000 const char *self; char pipe_command[BUFSIZ + 1] = { "a8cat" }; int verbose = 0; int raw_output = 0; int need_pclose = 0; int bytes_read = 0; int unknowns = 0; FILE *infile; FILE *outfile; void set_self(const char *argv0) { char *p; self = argv0; p = strrchr(self, '/'); if(p) self = p + 1; } void die(const char *msg) { fprintf(stderr, "%s: %s\n", self, msg); exit(1); } unsigned char read_byte(void) { int c; c = fgetc(infile); if(c < 0) die("unexpected EOF, file truncated?"); bytes_read++; return (unsigned char)c; } int read_word(void) { int w; w = read_byte(); w |= (read_byte() << 8); return w; } /* return value is program length */ int read_header(void) { unsigned char b; int proglen; b = read_byte(); if(b) die("first byte not $00, not an AMSB file"); proglen = read_word(); if(proglen > MAX_PROGLEN) { fprintf(stderr, "%s: program too big (%d bytes), won't fit in Atari memory\n", self, proglen); } return proglen; } void unknown_token(int lineno, unsigned char byte, int ext) { unknowns++; fprintf(outfile, "", (ext ? "ff ": ""), byte); } int next_line(void) { int ptr, lineno, was_ff, in_string, last_lineno = -1; unsigned char byte; /* pointer to last token on the line, offset by whatever MEMLO happened to be when the file was SAVEd. 0 means this is the last line, otherwise we don't use its value. */ ptr = read_word(); if(!ptr) { if(verbose) fprintf(stderr, "end of program\n"); return -1; } lineno = read_word(); if(verbose) fprintf(stderr, "found line number %d\n", lineno); if(lineno <= last_lineno) { fprintf(stderr, "%s: line number out of order (%d <= %d)\n", self, lineno, last_lineno); } if(lineno > MAX_LINENO) { fprintf(stderr, "%s: line number out range (%d > %d)\n", self, lineno, MAX_LINENO); } last_lineno = lineno; /* note that AMSB always puts a space after the line number in LIST */ fprintf(outfile, "%d ", lineno); was_ff = 0; in_string = 0; /* walk and print the tokens. when we hit a null byte, we're done. */ while(1) { byte = read_byte(); if(in_string) { if(byte == 0x00 || byte == '|') { /* end of string */ in_string = 0; putc('"', outfile); /* if we read a null, that means the line ends with a string that's missing its closing double-quote. */ if(byte == 0x00) { break; } else { continue; } } putc(byte, outfile); } else if(byte == ':') { /* don't print the colon if the next token is a ! or ' for a comment */ unsigned char next = read_byte(); if( !(next == 0x9a || next == 0x9b) ) putc(byte, outfile); ungetc(next, infile); bytes_read--; } else if(byte == '"') { /* strings start but *don't end* with a double-quote */ in_string = 1; putc(byte, outfile); } else if(was_ff) { if(byte >= MIN_EXT_TOK && byte <= MAX_EXT_TOK) { fprintf(outfile, "%s", ext_tokens[byte - MIN_EXT_TOK]); } else { unknown_token(lineno, byte, 1); } was_ff = 0; } else if(byte == 0xff) { was_ff = 1; } else if(byte >= MIN_STD_TOK && byte <= MAX_STD_TOK) { fprintf(outfile, "%s", std_tokens[byte - MIN_STD_TOK]); } else if(byte >= 0x80) { unknown_token(lineno, byte, 0); } else { if(!byte) break; putc(byte, outfile); } } putc(EOL, outfile); return lineno; } void print_help(void) { printf("%s - detokenize Atari Microsoft BASIC files\n\n", self); printf("Usage: %s [-a] [-v] [-h] [-i] [-u] [-t] [-m] [-s] [file]\n", self); puts(" -a: raw ATASCII output"); puts(" -v: verbose"); puts(" -h: show this help"); puts(" -i -u -t -m -s: passed to a8cat (try 'a8cat -h')"); puts("file must be a tokenized (SAVEd) AMSB file. if not given, reads from stdin."); } void parse_args(int argc, char **argv) { char tmp[10]; int opt; while( (opt = getopt(argc, argv, "vaiutmsh")) != -1) { switch(opt) { case 'a': raw_output = 1; break; case 'h': print_help(); exit(0); case 'v': verbose = 1; break; case 'i': case 'u': case 't': case 'm': case 's': if(strlen(pipe_command) > (BUFSIZ - 10)) die("too many a8cat options"); sprintf(tmp, " -%c", opt); strcat(pipe_command, tmp); break; default: print_help(); exit(1); } } if(optind >= argc) { freopen(NULL, "rb", stdin); infile = stdin; } else { infile = fopen(argv[optind], "rb"); if(!infile) { fprintf(stderr, "%s: ", self); perror(argv[optind]); exit(1); } } } void open_output() { if(raw_output) { if(isatty(fileno(stdout))) die("refusing to write raw ATASCII to a terminal"); outfile = stdout; } else { if(verbose) fprintf(stderr, "using pipe for output: %s\n", pipe_command); outfile = popen(pipe_command, "w"); if(!outfile) { perror(pipe_command); exit(1); } need_pclose = 1; } } int main(int argc, char **argv) { int proglen, lineno, rv = 0; set_self(argv[0]); parse_args(argc, argv); open_output(); proglen = read_header(); if(verbose) fprintf(stderr, "proglen == %d (%04x)\n", proglen, proglen); while( (lineno = next_line()) != -1 ) if(verbose) fprintf(stderr, "line %d read OK\n", lineno); if(verbose) fprintf(stderr, "read %d bytes\n", bytes_read);; if(proglen == (bytes_read - 3)) { if(verbose) fprintf(stderr, "file size matches proglen\n"); } else { fprintf(stderr, "%s: trailing garbage at EOF\n", self); } if(unknowns) { fprintf(stderr, "%s: input has %d unknown tokens\n", self, unknowns);; rv = 2; } if(need_pclose) { if(pclose(outfile) != 0) { die("a8cat child process failed, do you have a8cat on your PATH?"); } } exit(rv); }