aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Watson <urchlay@slackware.uk>2025-02-27 02:11:46 -0500
committerB. Watson <urchlay@slackware.uk>2025-02-27 02:11:46 -0500
commit63dc39eae5df00607a531f86841191aa30da00db (patch)
treef0ab22ba4cb60a2c1d5cc8c0b71bd30cb7a37898
parent4ecf4de44ae2bb81b1972cb8b0a5ccbe27ef9e61 (diff)
downloadbw-atari8-tools-63dc39eae5df00607a531f86841191aa30da00db.tar.gz
listamsb: support "locked" programs.
-rw-r--r--listamsb.131
-rw-r--r--listamsb.c105
-rw-r--r--listamsb.rst21
3 files changed, 132 insertions, 25 deletions
diff --git a/listamsb.1 b/listamsb.1
index 112febd..cc12fb1 100644
--- a/listamsb.1
+++ b/listamsb.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 "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
diff --git a/listamsb.c b/listamsb.c
index 207c3bb..6737243 100644
--- a/listamsb.c
+++ b/listamsb.c
@@ -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