diff options
| -rw-r--r-- | listamsb.1 | 31 | ||||
| -rw-r--r-- | listamsb.c | 105 | ||||
| -rw-r--r-- | listamsb.rst | 21 | 
3 files changed, 132 insertions, 25 deletions
@@ -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 "LISTAMSB" 1 "2025-02-26" "0.2.1" "Urchlay's Atari 8-bit Tools" +.TH "LISTAMSB" 1 "2025-02-27" "0.2.1" "Urchlay's Atari 8-bit Tools"  .SH NAME  listamsb \- List the source of a tokenized Atari Microsoft BASIC program  .SH SYNOPSIS @@ -50,6 +50,10 @@ is the same for both versions, so there\(aqs no need to specify it.  \fBinput\-file\fP must be a tokenized AMSB program. If no \fBinput\-file\fP is given,  input is read from stdin.  .sp +Programs created with \fISAVE "filename" LOCK\fP are autodetected, +and will be listed normally. It\(aqs also possible to convert a LOCKed +program to an unencrypted one, with the \fB\-l\fP option. +.sp  Output is to stdout. Use shell redirection to save the output to a file.  .SH OPTIONS  .INDENT 0.0 @@ -71,6 +75,25 @@ If the start line number is omitted (e.g. \fB,100\fP), it will be treated as  number is omitted (e.g. \fB100,\fP), it means "list until the end of the program".  \fB\-r,\fP is equivalent to not using the \fB\-r\fP option at all.  .TP +.B \fB\-l\fP +"Lock" or "unlock" the program. Locked programs are created with +\fISAVE "filename" LOCK\fP\&. The "encryption" is reversible: locking and +unlocking are the same operation. The output will be the unlocked tokenized +program (rather than a listing), and you must +use redirection to save it to a file. Example: +.INDENT 7.0 +.INDENT 3.5 +.sp +.nf +.ft C +listamsb \-u LOCKED.AMB > UNLOCKED.AMB +.ft P +.fi +.UNINDENT +.UNINDENT +.sp +None of the other options have any effect with \fB\-u\fP\&. +.TP  .B \fB\-v\fP  Verbose output, on stderr.  .TP @@ -94,10 +117,10 @@ generally mean the input isn\(aqt actually an tokenized Atari Microsoft  BASIC file.  .INDENT 0.0  .IP \(bu 2 -not an AMSB file: first byte not $00 +not an AMSB file: first byte not $00 or $01  .sp -Pretty self\-explanatory: if the file doesn\(aqt begin with a \fI$00\fP byte, -it\(aqs not a tokenized AMSB file. +Pretty self\-explanatory: if the file doesn\(aqt begin with a \fI$00\fP or +\fI$01\fP byte, it\(aqs not a tokenized AMSB file.  .IP \(bu 2  not an AMSB file: too big (\fIN\fP bytes), won\(aqt fit in Atari memory  .sp @@ -57,6 +57,10 @@  	it would overlap the GR.0 display list or the ROMs at $c000 */  #define MAX_PTR 0xbc1f +/* SAVE "filename" LOCK does 'encryption' by subtracting every byte +   from this (except the 3-byte header) */ +#define UNLOCK_KEY 0x54 +  const char *self;  char pipe_command[BUFSIZ + 1] = { "a8cat" }; @@ -66,8 +70,9 @@ int raw_output =  0;           /* -a */  int check_only =  0;           /* -c */  int startline =   0;           /* -r */  int endline   =   MAX_LINENO;  /* -r */ +int unlock_mode = 0;           /* -u */ - +int locked =      0;  int need_pclose = 0;  int bytes_read = 0;  int warnings = 0; @@ -104,6 +109,25 @@ unsigned char read_byte(void) {  	return (unsigned char)c;  } +/* "decrypt" a byte from a "SAVE x LOCK" program. */ +unsigned char unlock_byte(unsigned char b) { +	return ((UNLOCK_KEY - b) & 0xff); +} + +/* the "encryption" is the same (process is reversible) */ +#define lock_byte(x) unlock_byte(x) + +unsigned char read_prog_byte(void) { +	unsigned char b = read_byte(); +	return locked ? unlock_byte(b) : b; +} + +void unread_prog_byte(unsigned char b) { +	if(locked) b = lock_byte(b); +	ungetc(b, infile); +	bytes_read--; +} +  int read_word(void) {  	int w; @@ -113,11 +137,23 @@ int read_word(void) {  	return w;  } +int read_prog_word(void) { +	int w; + +	w = read_prog_byte(); +	w |= (read_prog_byte() << 8); + +	return w; +} +  void read_header(void) { -	unsigned char b; +	/* $00 for plain, $01 for SAVE with LOCK */ +	locked = read_byte(); +	if(locked > 1) die2("not an AMSB file: first byte not $00 or $01"); -	b = read_byte(); -	if(b) die2("not an AMSB file: first byte not $00"); +	if(verbose && locked) { +		fprintf(stderr, "program is locked, decrypting\n"); +	}  	proglen = read_word(); @@ -159,14 +195,14 @@ int next_line(void) {  	/* 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. */ -	ptr = read_word(); +	ptr = read_prog_word();  	if(!ptr) {  		if(verbose)  			fprintf(stderr, "end of program\n");  		return 0;  	} -	lineno = read_word(); +	lineno = read_prog_word();  	if(verbose)  		fprintf(stderr, "found line %d, offset %d, end-of-line %d\n", lineno, offset, ptr); @@ -212,7 +248,7 @@ int next_line(void) {  	/* walk and print the tokens. when we hit a null byte, we're done. */  	while(1) { -		byte = read_byte(); +		byte = read_prog_byte();  		if(in_string) {  			if(byte == 0x00 || byte == '|') { @@ -231,11 +267,10 @@ int next_line(void) {  			if(printing) 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(); +			unsigned char next = read_prog_byte();  			if( !(next == TOK_SQUOTE || next == TOK_BANG) )  				if(printing) putc(byte, outfile); -			ungetc(next, infile); -			bytes_read--; +			unread_prog_byte(next);  		} else if(byte == '"') {  			/* strings start but *don't end* with a double-quote */  			in_string = 1; @@ -300,12 +335,37 @@ int next_line(void) {  	return 1;  } +/* when this gets called, input and output are open, read_header() +   has already run. "locking" and "unlocking" are the same +   transform, so this function does both. */ +void unlock_program(void) { +	int c; + +	fprintf(stderr, "%s: program is %slocked, output will be %slocked\n", +		self, locked ? "" : "un", locked ? "un" : ""); + +	/* 3-byte header: 0 for unlocked, 1 for locked */ +	fputc(!locked, outfile); +	/* LSB of program length (not encrypted) */ +	fputc(proglen & 0xff, outfile); +	/* MSB */ +	fputc((proglen >> 8) & 0xff, outfile); + +	/* rest of file, including trailing nulls, is transformed */ +	while( (c = fgetc(infile)) >= 0) +		fputc(unlock_byte(c & 0xff), outfile); +	fclose(outfile); + +	exit(0); +} +  void print_help(void) {  	printf("%s v" VERSION " - detokenize Atari Microsoft BASIC files\n", self);  	puts("By B. Watson <urchlay@slackware.uk>, released under the WTFPL"); -	printf("Usage: %s [-a] [-v] [-h] [-i] [-u] [-t] [-m] [-s] [-r *start,end*] [file]\n", self); +	printf("Usage: %s [[-l] | [-a] [-v] [-h] [-i] [-u] [-t] [-m] [-s] [-r *start,end*]] [file]\n", self);  	puts("  -a: raw ATASCII output");  	puts("  -c: check only (no listing)"); +	puts("  -l: lock or unlock program");  	puts("  -v: verbose");  	puts("  -r: only list lines numbered from *start* to *end*");  	puts("  --help, -h: print this help and exit"); @@ -364,12 +424,13 @@ void parse_args(int argc, char **argv) {  		}  	} -	while( (opt = getopt(argc, argv, "r:cvaiutmsh")) != -1) { +	while( (opt = getopt(argc, argv, "lr:cvaiutmsh")) != -1) {  		switch(opt) { -			case 'c': check_only = 1; break; -			case 'a': raw_output = 1; break; +			case 'l': unlock_mode = 1; break; +			case 'c': check_only  = 1; break; +			case 'a': raw_output  = 1; break; +			case 'v': verbose =     1; break;  			case 'h': print_help(); exit(0); -			case 'v': verbose = 1; break;  			case 'r': get_line_range(optarg); break;  			case 'i':  			case 'u': @@ -413,9 +474,12 @@ void open_output() {  		}  		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"); +	} else if(raw_output || unlock_mode) { +		if(isatty(fileno(stdout))) { +			fprintf(stderr, "%s: refusing to write %s to a terminal\n", +				self, (unlock_mode ? "tokenized BASIC" : "raw ATASCII")); +			exit(1); +		}  		outfile = stdout;  		if(verbose)  			fprintf(stderr, "using stdout for output\n"); @@ -442,6 +506,11 @@ int main(int argc, char **argv) {  	read_header(); +	if(unlock_mode) { +		unlock_program(); +		exit(0); +	} +  	while(next_line())  		linecount++; diff --git a/listamsb.rst b/listamsb.rst index b95bd99..b23d6a4 100644 --- a/listamsb.rst +++ b/listamsb.rst @@ -31,6 +31,10 @@ is the same for both versions, so there's no need to specify it.  **input-file** must be a tokenized AMSB program. If no **input-file** is given,  input is read from stdin. +Programs created with *SAVE "filename" LOCK* are autodetected, +and will be listed normally. It's also possible to convert a LOCKed +program to an unencrypted one, with the **-l** option. +  Output is to stdout. Use shell redirection to save the output to a file.  OPTIONS @@ -53,6 +57,17 @@ OPTIONS    number is omitted (e.g. **100,**), it means "list until the end of the program".    **-r,** is equivalent to not using the **-r** option at all. +**-l** +  "Lock" or "unlock" the program. Locked programs are created with +  *SAVE "filename" LOCK*. The "encryption" is reversible: locking and +  unlocking are the same operation. The output will be the unlocked tokenized +  program (rather than a listing), and you must +  use redirection to save it to a file. Example:: + +    listamsb -u LOCKED.AMB > UNLOCKED.AMB + +  None of the other options have any effect with **-u**. +  **-v**    Verbose output, on stderr. @@ -78,10 +93,10 @@ These are errors in the program header at the start of the file. They  generally mean the input isn't actually an tokenized Atari Microsoft  BASIC file. -- not an AMSB file: first byte not $00 +- not an AMSB file: first byte not $00 or $01 -  Pretty self-explanatory: if the file doesn't begin with a *$00* byte, -  it's not a tokenized AMSB file. +  Pretty self-explanatory: if the file doesn't begin with a *$00* or +  *$01* byte, it's not a tokenized AMSB file.  - not an AMSB file: too big (*N* bytes), won't fit in Atari memory  | 
