#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 /* a program whose header has a length less than this can't be a real AMSB program. The minimum size is what you get if you SAVE when there's no program in memory (right after boot or a NEW). The minimum size for a program that actually contains code seems to be 5 (for 10 PRINT) */ #define MIN_PROGLEN 2 const char *self; char pipe_command[BUFSIZ + 1] = { "a8cat" }; int verbose = 0; /* -v */ int raw_output = 0; /* -a */ int check_only = 0; /* -c */ 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("not an AMSB file: first byte not $00"); proglen = read_word(); if(verbose) fprintf(stderr, "proglen == %d (%04x)\n", proglen, proglen); if(proglen > MAX_PROGLEN) { fprintf(stderr, "%s: not an AMSB file: too big (%d bytes), won't fit in Atari memory\n", self, proglen); exit(1); } if(proglen < MIN_PROGLEN) { fprintf(stderr, "%s: not an AMSB file: program size too small (%d). Atari BASIC file?\n", self, proglen); exit(1); } return proglen; } void unknown_token(int lineno, unsigned char byte, int ext) { unknowns++; fprintf(outfile, "", (ext ? "ff ": ""), byte); } int next_line(void) { static int last_lineno = -1; int ptr, lineno, was_ff, in_string; 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; if(byte < 0x20) { /* ATASCII graphics outside of a string */ fprintf(stderr, "%s: line %d has character %d outside of a string, " "maybe not an AMSB file?\n", self, lineno, byte); } putc(byte, outfile); } } putc(EOL, outfile); return lineno; } void print_help(void) { printf("%s v" VERSION " - detokenize Atari Microsoft BASIC files\n", self); puts("By B. Watson , released under the WTFPL"); printf("Usage: %s [-a] [-v] [-h] [-i] [-u] [-t] [-m] [-s] [file]\n", self); puts(" -a: raw ATASCII output"); puts(" -c: check only (no listing)"); 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, "cvaiutmsh")) != -1) { switch(opt) { case 'c': check_only = 1; break; 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) { if(isatty(fileno(stdin))) { fprintf(stderr, "%s: can't read binary data from a terminal\n", self); print_help(); exit(1); } 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(check_only) { outfile = freopen("/dev/null", "wb", stdout); if(!outfile) { fprintf(stderr, "%s: ", self); perror("/dev/null"); exit(1); } if(verbose) fprintf(stderr, "using /dev/null for output (check_only)\n"); } else if(raw_output) { if(isatty(fileno(stdout))) die("refusing to write raw ATASCII to a terminal"); outfile = stdout; if(verbose) fprintf(stderr, "using stdout for output\n"); } 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(); while( (lineno = next_line()) != -1 ) /* nop */ ; 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: actual program size doesn't match program size in header\n", self); } if(fgetc(infile) != EOF) { fprintf(stderr, "%s: trailing garbage at end of file\n", self); } if(unknowns) { fprintf(stderr, "%s: file has %d unknown tokens\n", self, unknowns);; rv = 2; } if(need_pclose) { int got = pclose(outfile); if(verbose) fprintf(stderr, "return value from pipe is %d\n", got); if(got != 0) { die("a8cat child process failed, do you have a8cat on your PATH?"); } } exit(rv); }