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); | 
