From 60dd1bb5f3b3c95787043a9057d01d60740d85ce Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Sat, 18 May 2024 03:54:16 -0400 Subject: unprotbas: grow variable name table, if needed. --- unprotbas.1 | 8 ++--- unprotbas.c | 113 +++++++++++++++++++++++++++++++++++++++++++++------------- unprotbas.rst | 6 ++-- 3 files changed, 95 insertions(+), 32 deletions(-) diff --git a/unprotbas.1 b/unprotbas.1 index 92e6b66..3b8b832 100644 --- a/unprotbas.1 +++ b/unprotbas.1 @@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]] .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]] .in \\n[rst2man-indent\\n[rst2man-indent-level]]u .. -.TH "UNPROTBAS" 1 "2024-05-17" "0.2.1" "Urchlay's Atari 8-bit Tools" +.TH "UNPROTBAS" 1 "2024-05-18" "0.2.1" "Urchlay's Atari 8-bit Tools" .SH NAME unprotbas \- Unprotect LIST-protected Atari 8-bit BASIC programs .SH SYNOPSIS @@ -44,9 +44,9 @@ read from standard input. .sp \fBoutput\-file\fP will be the unprotected tokenized BASIC program. If it already exists, it will be overwritten. Use \fI\-\fP to write to standard -output, but \fB[TODO]\fP \fBunprotbas\fP will refuse to write to standard -output if it\(aqs a terminal (since tokenized BASIC is binary data and -may confuse the terminal). +output, but \fBunprotbas\fP will refuse to write to standard output if +it\(aqs a terminal (since tokenized BASIC is binary data and may confuse +the terminal). .SH OPTIONS .INDENT 0.0 .TP diff --git a/unprotbas.c b/unprotbas.c index 9c45fbb..70741e3 100644 --- a/unprotbas.c +++ b/unprotbas.c @@ -8,6 +8,7 @@ #include #include #include +#include /* attempt to fix a "list-protected" Atari 8-bit BASIC program. we don't fully detokenize, so this won't fix truly corrupted @@ -71,6 +72,46 @@ 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\n", lomem); + fprintf(stderr, "VNTP %04x\n", vntp); + fprintf(stderr, "VNTD %04x\n", vntd); + fprintf(stderr, "VVTP %04x\n", vvtp); + fprintf(stderr, "STMTAB %04x, codestart %04x\n", stmtab, codestart); + fprintf(stderr, "STMCUR %04x\n", stmcur); + fprintf(stderr, "STARP %04x\n", starp); + fprintf(stderr, "vvstart %04x\n", vvstart); +} + +void read_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 - STM_OFFSET - (vntp - 256); + vnstart = vntp - 256 + 14; + vvstart = vvtp - 256 + 14; + dump_header_vars(); +} + +void set_header_vars(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, @@ -157,6 +198,21 @@ int fixcode(void) { return result; } +/* sometimes the variable name table isn't large enough to hold + the generated variable names. move_code_up() makes more space, + by moving the rest of the program (including the variable value + table) up in memory. */ +void move_code_up(int offset) { + memmove(data + vvstart + offset, data + vvstart, filelen); + vvtp += offset; + stmtab += offset; + stmcur += offset; + starp += offset; + set_header_vars(); + read_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 @@ -171,9 +227,10 @@ int fixcode(void) { the file. We can find the actual table size in the file by subtracting VNTP - (start of variable name table) from VNTD (end of variable name table), - and if we run out of space for the generated names, something is - seriously off... + (start of variable name table) from VNTD (end of variable name table). + It's possible that the table size is too small for the generated + variable names, in which case we have to call move_code_up() to + make more room. The maximum number of variable names is 128. If all 128 vars are in use, the minimum table size is 230 (26 one-letter names, 102 2-letter @@ -214,7 +271,9 @@ int fixvars(void) { vp = vnstart; while(vp < vvstart) { unsigned char c = data[vp]; + /* fprintf(stderr, "%04x/%04x: %04x\n", vp, vvstart, c); + */ /* allow a null byte only at the end of the table! */ /* if(c == 0 && vp == vvstart - 1) break; */ @@ -237,7 +296,26 @@ int fixvars(void) { } if(!forcevars && !bad) return 0; + /* decide whether we have enough room. pretend every new variable name + is 3 bytes (really only true for the 10th and later strings and + arrays, but a little wasted space won't hurt anything). */ + { + int vntblsize = vvstart - vnstart; + int varcount = (codestart - vvstart) / 8; + int neededsize = varcount * 3 + 1; + int move_up_by; + + fprintf(stderr, "%d variables according to value table\n", varcount); + if(neededsize > vntblsize) { + move_up_by = neededsize - vntblsize; + fprintf(stderr, "need %d bytes for vntable, only have %d, moving up by %d to %04x\n", + neededsize, vntblsize, move_up_by, vvtp + move_up_by); + move_code_up(move_up_by); + } + } + vp = vnstart; + vv = vvstart; while(vv < codestart) { unsigned char sigil = 0; /* type: scalar = 0, array = 1, string = 2 */ @@ -259,8 +337,8 @@ int fixvars(void) { data[vp] = ('A' + varname); } else { varname -= 26; - data[vp++] = 'A' + (varname / 9); - data[vp] = ('1' + (varname % 9)); + data[vp++] = 'A' + ((varname - 26) / 9); + data[vp] = ('1' + ((varname - 26) % 9)); } if(sigil) { @@ -327,6 +405,10 @@ void open_input(const char *name) { void open_output(const char *name) { if(!name) { + if(isatty(fileno(stdout))) { + fprintf(stderr, "%s: refusing to write binary data to standard output\n", self); + exit(1); + } if(freopen(NULL, "wb", stdout)) { output_file = stdout; return; @@ -381,29 +463,10 @@ int main(int argc, char **argv) { parse_args(argc, argv); filelen = readfile(); - - lomem = getword(0); - vntp = getword(2); - vntd = getword(4); - vvtp = getword(6); - stmtab = getword(8); - stmcur = getword(10); - starp = getword(12); - codestart = stmtab - STM_OFFSET - (vntp - 256); - vnstart = vntp - 256 + 14; - vvstart = vvtp - 256 + 14; + read_header(); if(lomem) die("This doesn't look like an Atari BASIC program (no $0000 signature)"); - fprintf(stderr, "LOMEM %04x\n", lomem); - fprintf(stderr, "VNTP %04x\n", vntp); - fprintf(stderr, "VNTD %04x\n", vntd); - fprintf(stderr, "VVTP %04x\n", vvtp); - fprintf(stderr, "STMTAB %04x, codestart %04x\n", stmtab, codestart); - fprintf(stderr, "STMCUR %04x\n", stmcur); - fprintf(stderr, "STARP %04x\n", starp); - fprintf(stderr, "vvstart %04x\n", vvstart); - /* fprintf(stderr, "data at STMTAB (we hope):\n"); for(int i=codestart; i