1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
|
.\" Man page generated from reStructuredText.
.
.
.nr rst2man-indent-level 0
.
.de1 rstReportMargin
\\$1 \\n[an-margin]
level \\n[rst2man-indent-level]
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
-
\\n[rst2man-indent0]
\\n[rst2man-indent1]
\\n[rst2man-indent2]
..
.de1 INDENT
.\" .rstReportMargin pre:
. RS \\$1
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
. nr rst2man-indent-level +1
.\" .rstReportMargin post:
..
.de UNINDENT
. RE
.\" indent \\n[an-margin]
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
.nr rst2man-indent-level -1
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.TH "UNPROTBAS" 1 "2024-07-21" "0.2.1" "Urchlay's Atari 8-bit Tools"
.SH NAME
unprotbas \- Unprotect LIST-protected Atari 8-bit BASIC programs
.SH SYNOPSIS
.sp
unprotbas [\fB\-v\fP] [\fB\-f\fP] [\fB\-n\fP] [\fB\-g\fP] [\fB\-c\fP] [\fB\-r\fP | \fB\-w\fP] \fBinput\-file\fP \fBoutput\-file\fP
.SH DESCRIPTION
.sp
\fBunprotbas\fP modifies a tokenized LIST\-protected Atari 8\-bit BASIC
program, creating a new non\-protected copy. See \fBDETAILS\fP, below,
to understand how the protection and unprotection works.
.sp
\fBinput\-file\fP must be a tokenized (SAVEd) Atari BASIC program. Use
\fI\-\fP to read from standard input, but \fBunprotbas\fP will refuse to
read from standard input if it\(aqs a terminal.
.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 \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
.sp
Options may appear in any order. The first non\-option argument is used
for \fBinput\-file\fP; the second is \fBoutput\-file\fP\&. A third non\-option
argument is an error.
.sp
Option bundling is not supported, use e.g. \fB\-v \-f\fP, not \fB\-vf\fP\&.
To use filenames beginning with \fI\-\fP, write them as \fI\&./\-file\fP, or they
will be treated as options.
.SS Unprotection Options
.INDENT 0.0
.TP
.B \fB\-f\fP
Force the variable name table to be rebuilt, even if it looks OK.
This option cannot be combined with \fB\-n\fP\&.
.TP
.B \fB\-n\fP
Don\(aqt rebuild the variable table (only fix the line pointers, if
needed). This option cannot be combined with \fB\-f\fP\&.
.TP
.B \fB\-g\fP
Remove any "garbage" data from the end of the file. By default,
it\(aqs left as\-is, in case it\(aqs actually data used by the program.
.TP
.B \fB\-c\fP
Check only. Does a dry run. Loads the program, unprotects it in
memory, but doesn\(aqt write the result anywhere. In this mode, there
is no \fBoutput\-file\fP\&.
.TP
.B \fB\-w\fP
Write the variable names to \fBvarnames.txt\fP, one per line.
This can be edited, and later used with \fB\-r\fP to set the variable names
to something sensible rather than A, B, C, etc. For an unprotected
program, you can use \fB\-n\fP to write the existing names rather than
generating new ones. See \fBVARIABLE NAMES\fP, below. If \fBvarnames.txt\fP
already exists, it will be overwritten.
.TP
.B \fB\-r\fP
Read variable names from \fBvarnames.txt\fP, and use them instead of
generating the names. See \fBVARIABLE NAMES\fP, below.
.UNINDENT
.SS General Options
.INDENT 0.0
.TP
.B \fB\-\-help\fP
Print usage message and exit.
.TP
.B \fB\-\-version\fP
Print version number and exit.
.TP
.B \fB\-v\fP
Verbose operation. When displaying a number in verbose mode, it will
be prefixed with \fI$\fP if it\(aqs in hex, or no prefix for decimal.
.UNINDENT
.SH EXIT STATUS
.INDENT 0.0
.TP
.B 0
\fBinput\-file\fP was protected, unprotection was successful.
.TP
.B 1
I/O error, or \fBinput\-file\fP isn\(aqt a valid BASIC program.
.TP
.B 2
\fBinput\-file\fP is already an unprotected BASIC program.
.UNINDENT
.SH DETAILS
.sp
In the Atari BASIC world, it\(aqs possible to create a SAVEd (tokenized)
program that can be RUN from disk (\fBRUN "D:FILE.BAS"\fP) but if
it\(aqs LOADed, it will either crash the BASIC interpreter, or LIST
as gibberish. This is known as LIST\-protection. Such programs are
generally released to the world in protected form; the author
privately keeps an unprotected copy so he can modify it. In
later days, collections such as the Holmes Archive contain many
LIST\-protected programs, for which the unprotected version was never
released.
.sp
One example of LIST\-protection, taken from \fIMapping the Atari\fP (the
\fBSTMCUR\fP entry in the memory map) looks like:
.INDENT 0.0
.INDENT 3.5
.sp
.nf
.ft C
32000 FOR VARI=PEEK(130)+PEEK(131)*256 TO PEEK(132)+PEEK(133)*256:POKE VARI,155:NEXT VARI
32100 POKE PEEK(138)+PEEK(139)*256+2,0:SAVE "D:filename":NEW
.ft P
.fi
.UNINDENT
.UNINDENT
.sp
To use, add the 2 lines of code to your program, then execute them
with \fBGOTO 32000\fP in immediate mode.
.sp
This illustrates both types of protection, which can be (and usually
are) applied to the same program:
.INDENT 0.0
.TP
.B Variable name table scrambling
BASIC has specific rules on what are and aren\(aqt considered legal
variable names, which are enforced by the tokenization process,
at program entry time. However, it doesn\(aqt use the variable names
at runtime, when the tokenized file is interpreted.
.sp
Replacing the variable names with binary gibberish will render the
program LIST\-proof, either replacing every variable name with the
same control character, or causing LIST to display a long string of
binary garbage for each variable name... but the program will still
RUN correctly. Note that the original variable names are \fIgone\fP,
and cannot be recovered.
.sp
Line 32000 in the example above does this job, replacing every
variable name with the EOL character (155).
.sp
\fBunprotbas\fP detects a scrambled variable name table, and builds
a new one that\(aqs valid. However, since there are no real variable
names in the program, the recovery process just invents new ones,
named A through Z, A1 through A9, B1 through B9, etc, etc. It\(aqll
require human intelligence to figure out what each variable is for,
since the names are meaningless. See \fBVARIABLE NAMES\fP, below.
.sp
The \fBoutput\-file\fP may not be the exact size that the
\fBinput\-file\fP was. Some types of variable\-name scrambling shrink
the variable name table to the minimum size (one byte per name), so
the rebuilt table will be larger. Other types of scrambling leave
the variable name table at its original size, but \fBunprotbas\fP
generates only one\- and two\-character variable names, so the rebuilt
table might be smaller.
.sp
The program \fBPROTECT.BAS\fP, found on Disk 2 of the Holmes Archive,
creates protected BASIC programs that only use variable name
scrambling.
.sp
\fBprotbas\fP(1) also does variable name scrambling.
.TP
.B Bad next\-line pointer
Every line of tokenized BASIC contains a line length byte, which
BASIC uses as a pointer to the next line of code. Before executing
an immediate mode command, BASIC iterates over every line of code in
the program, using the next\-line pointers, in order to delete any
existing line 32768 (the previous immediate mode command). If any
line\(aqs pointer is set to zero, that means it points to itself.
.sp
When BASIC tries to traverse a line of code that points to itself as
"next" line, it will get stuck in an infinite loop. This not only
prevents LIST, it actually prevents any immediate mode command:
after LOADing such a file, \fInothing\fP will work (even pressing RESET
won\(aqt get you out of it). The only way to use such a program is to
use the RUN command with a filename, and if the program ever exits
(due to END, STOP, an error, Break key, or even System Reset), BASIC
will get stuck again.
.sp
This doesn\(aqt \fIhave\fP to be done with the last line in the program,
though it normally is. The "poisoned" line can never be executed (or
BASIC will lock up), but it could be followed by more lines of code
(which also could never be executed).
.sp
Line 32100 in the example above does this job, taking advantage of
the STMCUR pointer used by BASIC, which holds the address of the
line of tokenized code currently being executed.
.sp
Each statement in the line also has a statement\-length byte. For
lines with only one statement, its value is the same as the line
length. For lines with multiple statements (separated by \fI:\fP), it\(aqs
a pointer to the next statement, counting from the start of the
current line. For the last statement on a line, it\(aqs a pointer to
the next line of code, meaning it\(aqs identical to the line length.
.sp
\fBunprotbas\fP fixes bad line lengths by setting the line length to
the statement length of the last statement. No information is lost
by doing this.
.sp
The program \fBUNPROTEC\fP, from the \fIPirate\(aqs Treasure Chest\fP, can
fix bad pointers in protected programs, though it doesn\(aqt do
anything about variable name scrambling.
.sp
\fBprotbas\fP also does this type of protection.
.UNINDENT
.sp
One more thing \fBunprotbas\fP can do is remove extra data from the end
of the file. It\(aqs possible for BASIC files to contain extra data that
occurs after the end of the program. Such data might be:
.INDENT 0.0
.IP \(bu 2
Pre\-defined strings and/or arrays, saved with the program by
modifying the STARP pointer.
.IP \(bu 2
Arbitrary binary data used by the program at runtime, such as
machine language routines, or fonts.
.IP \(bu 2
Zero bytes, caused by SAVEing the program with revision B BASIC. Every
time a program is LOADed, edited (or not) and then SAVEd again, 16
bytes of extra (garbage) data gets added to the program. To avoid
this, don\(aqt use revision B (use rev C if possible, A otherwise).
.IP \(bu 2
Garbage added by some system previously used to store or transmit
the file. CP/M systems might add an EOF (^Z) character. Dumb
file transfer software might pad the file up to its block size.
.UNINDENT
.sp
Normally, such "garbage" doesn\(aqt hurt anything. BASIC ignores it. Or
it normally does... if you suspect it\(aqs causing a problem, you can
remove it with the \fB\-g\fP option. If removing the "garbage" causes the
program to fail to run, it wasn\(aqt garbage! \fBunprotbas\fP doesn\(aqt
remove extra data by default, to be on the safe side.
.SH VARIABLE NAMES
.sp
If variable name scrambling was used, the original variable names no
longer exist. \fBunprotbas\fP will generate them, according to these rules:
.INDENT 0.0
.INDENT 3.5
The first 26 numeric variables will be called \fIA\fP through \fIZ\fP\&. Further
numeric variables will be \fIA1\fP through \fIA9\fP, \fIB1\fP through \fIB9\fP, etc.
.sp
The first 26 string variables will be \fIA$\fP to \fIZ$\fP, then \fIA1$\fP to
\fIA9$\fP, \fIB1$\fP to \fIB9$\fP, etc.
.sp
The first 26 array variables will be \fIA(\fP to \fIZ(\fP, then \fIA1(\fP to
\fIA9(\fP, \fIB1(\fP to \fIB9(\fP, etc.
.UNINDENT
.UNINDENT
.sp
Note that array variables have only the \fI(\fP as part of the name. The
closing \fI)\fP is "cosmetic" and not part of the actual name.
.sp
To properly reverse\-engineer the protected program, it\(aqs necessary to assign
meaningful variable names. \fBunprotbas\fP isn\(aqt smart enough to do this for you,
but it can semi\-automate the process.
.sp
First, run \fBunprotbas\fP with the \fB\-w\fP option. This will create a
file called \fBvarnames.txt\fP, containing the generated variable names.
These are in order, one line per variable name, ending with \fI$\fP for strings
and the \fI(\fP for arrays.
.sp
Load the unprotected program on the Atari and LIST it (or use \fBchkbas\fP to get a
listing), and edit \fBvarnames.txt\fP in a text editor.
.sp
As you figure out what each variable\(aqs purpose is, change its name
in the text file. When editing the file:
.INDENT 0.0
.IP \(bu 2
Don\(aqt add or delete any lines.
.IP \(bu 2
Don\(aqt get rid of the \fI$\fP or \fI(\fP at the end of any line.
.IP \(bu 2
You may enter the names in lowercase (\fBunprotbas\fP will convert them to uppercase).
.IP \(bu 2
Remember to follow the rules for BASIC variable names:
The first character must be a letter, other characters must be a letter
or a number, and only the last character can be \fI$\fP or \fI(\fP\&.
.IP \(bu 2
No duplicates of the same type are allowed (you can have \fIFOO\fP and \fIFOO$\fP,
but not two numerics called \fIFOO\fP).
.UNINDENT
.sp
When you\(aqre finished, re\-run \fBunprotbas\fP, this time with the \fB\-r\fP
option. If all is well, the unprotected program will use your variable
names, rather than generating new ones. If you broke the rules, you
should get an informative error message explaining what and where the
problem is.
.sp
This process can also be used for regular unprotected programs. Use
\fB\-n \-w\fP the first time, to save the existing variable names to
\fBvarnames.txt\fP rather than generating new ones.
.SH NOTES
.sp
Atari BASIC has a limit of 128 variables in a program. It\(aqs actually
possible for the variable name table to contain up to 256 variables,
though the 129th and further ones won\(aqt be usable in the program. The
variable value table can hold more than 256 values, though the
variable numbers wrap around once they pass 255. The attempt to add
variables past the 128th causes BASIC to respond with \fIERROR\- 4\fP, but
the variable does get added to the tables. \fBunprotbas\fP will preserve
these extra (useless) entries in the tables.
.sp
If there more than 256 entries in the value table, you will see
"Warning: skipping variable numbers >=256 in value table". This is
a pathological case, and shouldn\(aqt happen with programs that aren\(aqt
deliberately crafted to test this behaviour.
.SH COPYRIGHT
.sp
WTFPL. See \fI\%http://www.wtfpl.net/txt/copying/\fP for details.
.SH AUTHOR
.INDENT 0.0
.IP B. 3
Watson <\fI\%urchlay@slackware.uk\fP>; Urchlay on irc.libera.chat \fI##atari\fP\&.
.UNINDENT
.SH SEE ALSO
.sp
\fBa8cat\fP(1),
\fBa8eol\fP(1),
\fBa8xd\fP(1),
\fBatr2xfd\fP(1),
\fBatrsize\fP(1),
\fBaxe\fP(1),
\fBbas2aplus\fP(1),
\fBblob2c\fP(1),
\fBblob2xex\fP(1),
\fBcart2xex\fP(1),
\fBcxrefbas\fP(1),
\fBdasm2atasm\fP(1),
\fBdiffbas\fP(1),
\fBdumpbas\fP(1),
\fBf2toxex\fP(1),
\fBfenders\fP(1),
\fBlistbas\fP(1),
\fBprotbas\fP(1),
\fBrenumbas\fP(1),
\fBrom2cart\fP(1),
\fBunmac65\fP(1),
\fBunprotbas\fP(1),
\fBvxrefbas\fP(1),
\fBwhichbas\fP(1),
\fBxex1to2\fP(1),
\fBxexamine\fP(1),
\fBxexcat\fP(1),
\fBxexsplit\fP(1),
\fBxfd2atr\fP(1),
\fBxex\fP(5),
\fBatascii\fP(7).
.sp
Any good Atari 8\-bit book: \fIDe Re Atari\fP, \fIThe Atari BASIC Reference
Manual\fP, the \fIOS Users\(aq Guide\fP, \fIMapping the Atari\fP, etc.
.\" Generated by docutils manpage writer.
.
|