diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | bas.c | 183 | ||||
-rw-r--r-- | bas.h | 80 | ||||
-rw-r--r-- | unprotbas.c | 272 |
4 files changed, 298 insertions, 241 deletions
@@ -49,6 +49,10 @@ RST2MAN=rst2man all: $(BINS) manpages symlinks subdirs +unprotbas: bas.o + +bas.o: bas.c bas.h + subdirs: for dir in $(SUBDIRS); do make -C $$dir COPT=$(COPT); done @@ -0,0 +1,183 @@ +/* bas.c - API for writing standalone programs that deal with + tokenized Atari 8-bit BASIC program. */ + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <time.h> + +#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; + +void die(const char *msg) { + fprintf(stderr, "%s: %s\n", self, msg); + exit(1); +} + +/* read entire file into memory */ +int readfile(void) { + int got = fread(program, 1, BUFSIZE - 1, input_file); + if(verbose) fprintf(stderr, "Read %d bytes.\n", got); + if(!feof(input_file)) + fprintf(stderr, "Warning: file is >64KB, way too big for a BASIC program.\n"); + else if(got > MAX_PROG_SIZE) + fprintf(stderr, "Warning: file is %d bytes, suspiciously large for a BASIC program.\n", got); + fclose(input_file); + if(got < MIN_PROG_SIZE) + die("File too short to be a BASIC program (truncated?)\n"); + return got; +} + +/* 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(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; + update_header(); + parse_header(); + filelen += offset; +} + +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); + } +} + +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) { + 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; +} @@ -0,0 +1,80 @@ +/* bas.h - API for writing standalone programs that deal with + tokenized Atari 8-bit BASIC program. */ + +/* maximum size of the program in memory. 64KB is actually way overkill. */ +#define BUFSIZE 65536 + +/* the difference between the VVTP and VNTP values in the file, and the + actual file positions of the variable names and values. */ +#define TBL_OFFSET 0xf2 + +/* minimum program size, for a program that has no variables and + only one line of code (the immediate line 32768, consisting only of + one token, which would be CSAVE). anything smaller than this, we + can't process. */ +#define MIN_PROG_SIZE 21 + +/* maximum practical size for a BASIC program. if a file exceeds this + size, we warn about it, but otherwise process it normally. + this value is derived by subtracting the default LOMEM without DOS + ($0700) from the start of the display list in GR.0 ($9c20, on a 48K Atari). + */ +#define MAX_PROG_SIZE 38176 + +/* maximum number of variables in the variable name and value tables. this + could be 128, but "ERROR- 4" still expands the tables. Entries >128 + don't have tokens, can't be referred to in code, but we'll preserve + them anyway. */ +#define MAXVARS 256 + +/* tokenized colon (statement separator) */ +#define TOK_COLON 0x14 + +/* variable types, bits 6-7 of byte 0 of each vvtable entry. */ +#define TYPE_SCALAR 0 +#define TYPE_ARRAY 1 +#define TYPE_STRING 2 + +/* BASIC 14-byte header values */ +extern unsigned short lomem; +extern unsigned short vntp; +extern unsigned short vntd; +extern unsigned short vvtp; +extern unsigned short stmtab; +extern unsigned short stmcur; +extern unsigned short starp; + +/* positions where various parts of the file start, + derived from the header vars above. */ +extern unsigned short codestart; +extern unsigned short code_end; +extern unsigned short vnstart; +extern unsigned short vvstart; +extern int filelen; + +/* name of executable, taken from argv[0] */ +extern const char *self; + +/* entire file gets read into memory (for now) */ +extern unsigned char program[BUFSIZE]; + +/* file handles */ +extern FILE *input_file; +extern FILE *output_file; + +extern int verbose; + +extern void set_self(const char *argv0); +extern void die(const char *msg); +extern int readfile(void); +extern unsigned short getword(int addr); +extern void setword(int addr, int value); +extern void dump_header_vars(void); +extern void parse_header(void); +extern void update_header(void); +extern void move_code(int offset); +extern void adjust_vntable_size(int oldsize, int newsize); +extern void invalid_args(const char *arg); +extern FILE *open_file(const char *name, const char *mode); +extern void open_input(const char *name); +extern void open_output(const char *name); diff --git a/unprotbas.c b/unprotbas.c index 3486ac4..8c5c14a 100644 --- a/unprotbas.c +++ b/unprotbas.c @@ -5,6 +5,8 @@ #include <ctype.h> #include <time.h> +#include "bas.h" + /* attempt to fix a "list-protected" Atari 8-bit BASIC program. we don't fully detokenize, so this won't fix truly corrupted files. @@ -16,43 +18,6 @@ or whatever), we "fix" that by making up new variable names. */ -/* maximum size of the program in memory. 64KB is actually way overkill. */ -#define BUFSIZE 65536 - -/* the difference between the VVTP and VNTP values in the file, and the - actual file positions of the variable names and values. */ -#define TBL_OFFSET 0xf2 - -/* minimum program size, for a program that has no variables and - only one line of code (the immediate line 32768, consisting only of - one token, which would be CSAVE). anything smaller than this, we - can't process. */ -#define MIN_PROG_SIZE 21 - -/* maximum practical size for a BASIC program. if a file exceeds this - size, we warn about it, but otherwise process it normally. - this value is derived by subtracting the default LOMEM without DOS - ($0700) from the start of the display list in GR.0 ($9c20, on a 48K Atari). - */ -#define MAX_PROG_SIZE 38176 - -/* maximum number of variables in the variable name and value tables. this - could be 128, but "ERROR- 4" still expands the tables. Entries >128 - don't have tokens, can't be referred to in code, but we'll preserve - them anyway. */ -#define MAXVARS 256 - -/* tokenized colon (statement separator) */ -#define TOK_COLON 0x14 - -/* variable types, bits 6-7 of byte 0 of each vvtable entry. */ -#define TYPE_SCALAR 0 -#define TYPE_ARRAY 1 -#define TYPE_STRING 2 - -/* entire file gets read into memory (for now) */ -unsigned char data[BUFSIZE]; - /* for the -p/-pc options: 32767 END */ unsigned char badcode[] = { 0xff, 0x7f, /* line number 32767 */ @@ -74,108 +39,19 @@ unsigned char varnames[BUFSIZE]; unsigned char *varmap[MAXVARS]; int varmap_count; -/* BASIC 14-byte header values */ -unsigned short lomem; -unsigned short vntp; -unsigned short vntd; -unsigned short vvtp; -unsigned short stmtab; -unsigned short stmcur; -unsigned short starp; - -/* positions where various parts of the file start, - derived from the header vars above. */ -unsigned short codestart; -unsigned short code_end; -unsigned short vnstart; -unsigned short vvstart; -int filelen; - -/* name of executable, taken from argv[0] */ -char *self; - /* these are set by the various command-line switches */ int keepvars = 0; int forcevars = 0; int keepgarbage = 1; int checkonly = 0; int was_protected = 0; -int verbose = 0; int readmap = 0; int writemap = 0; int protect_vars = 0; int protect_code = 0; -/* file handles */ -FILE *input_file = NULL; -FILE *output_file = NULL; char *output_filename = NULL; -void die(const char *msg) { - fprintf(stderr, "%s: %s\n", self, msg); - exit(1); -} - -/* read entire file into memory */ -int readfile(void) { - int got = fread(data, 1, BUFSIZE - 1, input_file); - if(verbose) fprintf(stderr, "Read %d bytes.\n", got); - if(!feof(input_file)) - fprintf(stderr, "Warning: file is >64KB, way too big for a BASIC program.\n"); - else if(got > MAX_PROG_SIZE) - fprintf(stderr, "Warning: file is %d bytes, suspiciously large for a BASIC program.\n", got); - fclose(input_file); - if(got < MIN_PROG_SIZE) - die("File too short to be a BASIC program (truncated?)\n"); - return got; -} - -/* get a 16-bit value from the file, in 6502 LSB/MSB order. */ -unsigned short getword(int addr) { - return data[addr] | (data[addr + 1] << 8); -} - -void setword(int addr, int value) { - data[addr] = value & 0xff; - data[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(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); -} - /* fixline() calculates & sets correct line length, by iterating over the statement(s) within the line. the last statement's offset will be the same as the line offset should have been, @@ -212,12 +88,12 @@ void update_header(void) { */ int fixline(int linepos) { /* +3 here to skip the line number + line length */ - int offset = data[linepos + 3]; + int offset = program[linepos + 3]; - while(data[linepos + offset - 1] == TOK_COLON) - offset = data[linepos + offset]; + while(program[linepos + offset - 1] == TOK_COLON) + offset = program[linepos + offset]; - data[linepos + 2] = offset; + program[linepos + 2] = offset; return offset; } @@ -236,7 +112,7 @@ int fixcode(void) { } lineno = tmpno; - offset = data[pos + 2]; + offset = program[pos + 2]; /* fprintf(stderr, "pos %d, line #%d, offset %d\n", pos, lineno, offset); */ if(offset < 6) { if(verbose) fprintf(stderr, "Found invalid offset %d (<6) at line %d, file offset $%04x.\n", offset, lineno, pos); @@ -255,12 +131,12 @@ int fixcode(void) { if(filelen > pos) { int i, same = 1; for(i = pos; i < filelen; i++) { - if(data[i] != data[pos]) same = 0; + if(program[i] != program[pos]) same = 0; } if(verbose) { fprintf(stderr, "Trailing garbage at EOF, %d bytes, ", filelen - pos); if(same) - fprintf(stderr, "all $%02x", data[pos]); + fprintf(stderr, "all $%02x", program[pos]); else fprintf(stderr, "maybe valid data"); fprintf(stderr, ", %s.\n", (keepgarbage ? "keeping" : "removing")); @@ -285,7 +161,7 @@ void breakcode(void) { if(tmpno == 32768) { break; } else { - offset = data[pos + 2]; + offset = program[pos + 2]; if(!offset) { fprintf(stderr, "%s: program already was code-protected.\n", self); exit(2); @@ -301,10 +177,10 @@ void breakcode(void) { /* pos is now the start of line 32768, move it up to make room for the new line */ offset = sizeof(badcode); - memmove(data + pos + offset, data + pos, filelen); + memmove(program + pos + offset, program + pos, filelen); /* insert new line */ - memmove(data + pos, badcode, offset); + memmove(program + pos, badcode, offset); if(verbose) fprintf(stderr, "Inserted line 32767 with invalid offset at file offset $%04x.\n", pos); @@ -317,29 +193,6 @@ void breakcode(void) { filelen += offset; } -/* 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 = data + vvstart + offset; - - if(dest < data || ((filelen + offset) > (BUFSIZE - 1))) { - die("Attempt to move memory out of range; corrupt header bytes?\n"); - } - - memmove(dest, data + vvstart, filelen); - - vntd += offset; - vvtp += offset; - stmtab += offset; - stmcur += offset; - starp += offset; - update_header(); - parse_header(); - filelen += offset; -} - /* Fixing the variables is a bit more work than it seems like it might be, because the last byte of the name has to match the type (inverse video "(" for numeric array, inverse "$" for @@ -378,7 +231,7 @@ int vntable_ok(void) { vp = vnstart + 1; bad = 1; while(vp < vvstart - 1) { - if(data[vp] != data[vnstart]) { + if(program[vp] != program[vnstart]) { bad = 0; break; } @@ -389,7 +242,7 @@ int vntable_ok(void) { /* 2nd pass: bad = 1 if there's any invalid character in the table. */ vp = vnstart; while(vp < vvstart) { - unsigned char c = data[vp]; + unsigned char c = program[vp]; /* treat a null byte as end-of-table, ignore any junk between it and VNTP. */ if(c == 0) break; @@ -423,15 +276,15 @@ int rebuild_vntable(int write) { while(vv < codestart) { unsigned char sigil = 0; /* type: scalar = 0, array = 1, string = 2 */ - unsigned char type = data[vv] >> 6; - /* fprintf(stderr, "%04x: %04x, %d\n", vv, data[vv], type); */ + unsigned char type = program[vv] >> 6; + /* fprintf(stderr, "%04x: %04x, %d\n", vv, program[vv], type); */ if(varnum == MAXVARS) { fprintf(stderr, "Warning: skipping variable numbers >=%d in value table.\n", MAXVARS); break; } - if(varnum != data[vv+1]) { + if(varnum != program[vv+1]) { fprintf(stderr, "Warning: variable #%d value is corrupt!\n", varnum); } @@ -445,13 +298,13 @@ int rebuild_vntable(int write) { } if(varname < 26) { - if(write) data[vp] = ('A' + varname); + if(write) program[vp] = ('A' + varname); size++; } else { varname -= 26; if(write) { - data[vp++] = 'A' + varname / 9; - data[vp] = '1' + varname % 9; + program[vp++] = 'A' + varname / 9; + program[vp] = '1' + varname % 9; } size += 2; } @@ -459,9 +312,9 @@ int rebuild_vntable(int write) { if(sigil) { size++; vp++; - if(write) data[vp++] = sigil; + if(write) program[vp++] = sigil; } else { - if(write) data[vp] |= 0x80; + if(write) program[vp] |= 0x80; vp++; } @@ -472,23 +325,12 @@ int rebuild_vntable(int write) { /* there's supposed to be a null byte at the end of the table, unless all 128 table slots are used... except really, there can be >=129 entries, and there's always a null byte. */ - if(write) data[vp] = 0; + if(write) program[vp] = 0; size++; return size; } -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); - } -} - int fixvars(void) { int old_vntable_size, new_vntable_size; @@ -515,8 +357,8 @@ void write_var_map(void) { die("Can't create map file for -w option."); } - for(vp = vnstart; (vp < vntd) && (data[vp] != 0); vp++) { - unsigned char c = data[vp]; + for(vp = vnstart; (vp < vntd) && (program[vp] != 0); vp++) { + unsigned char c = program[vp]; if(c < 0x80) { fputc(c, f); } else { @@ -564,7 +406,7 @@ void check_varname(const unsigned char *name, int line) { if(c == 0) c = name[0]; /* c now has the last char of the name, make sure it matches the variable type */ - type = data[vvstart + 8 * (line - 1)] >> 6; + type = program[vvstart + 8 * (line - 1)] >> 6; /* type: scalar = 0, array = 1, string = 2 */ if(type == TYPE_SCALAR) { if(c == '$') @@ -650,7 +492,7 @@ void apply_var_map(void) { i = vvstart - vnstart; adjust_vntable_size(i, newp); - memmove(data + vnstart, new_vntable, newp); + memmove(program + vnstart, new_vntable, newp); } void scramble_vars(void) { @@ -670,9 +512,9 @@ void scramble_vars(void) { for(i = vnstart; i < vvstart - 1; i++) if(varname_char == -1) - data[i] = (rand() >> 8) & 0xff; + program[i] = (rand() >> 8) & 0xff; else - data[i] = varname_char & 0xff; + program[i] = varname_char & 0xff; if(verbose) { i -= vnstart; @@ -705,62 +547,10 @@ void print_help(void) { fprintf(stderr, "Use - as a filename to read from stdin and/or write to stdout.\n"); } -void invalid_args(const char *arg) { - fprintf(stderr, "%s: Invalid argument '%s'.\n\n", self, arg); - print_help(); - 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) { - 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"); -} - void parse_args(int argc, char **argv) { - char *p; int xopt_used = 0; - self = *argv; - p = strrchr(self, '/'); - if(p) self = p + 1; + set_self(*argv); if(argc < 2) { print_help(); @@ -916,7 +706,7 @@ int main(int argc, char **argv) { /* we don't open the output file until all processing is done, to avoid leaving invalid output files if we exit on error. */ open_output(output_filename); - outbytes = fwrite(data, 1, filelen, output_file); + outbytes = fwrite(program, 1, filelen, output_file); fclose(output_file); if(verbose) fprintf(stderr, "Wrote %d bytes.\n", outbytes); |