Programming Tips

Unlisting

In some circumstances you may wish to allow people to run your program, but not to list it - to hide your programming technique or to protect a password or other sensitive data within the program. Unfortunately a determined programmer can break any software safeguards if he has total control of the machine - as with ATOM - but it is possible to make it difficult enough to discourage the merely curious, or those with little time to spare.

The basic technique is to insert those ASCII codes that turn off the printer and VDU into REM statements so that they have no effect when the program is run, but prevent the later lines from being shown when the LIST command is given. Thus, if you have a completed, tested program ready for added protection type;

1 REM CTRL-L CTRL-U CTRL-C RETURN

(do it carefully, the screen will not show anything after the REM statement).

Then press BREAK, and enter OLD to restore the VDU to normal use. You should now be able to run the program as before, but not list it out on the VDU or printer.

Machine Language to BASIC

Although it is easy enough to call a machine language routine from a BASIC program by using the LINK command, there are occasions when it would be useful if a short BASIC routine could be called from a machine language program - for example to generate a random number with BASIC's RND function. It can be done if the RAM locations 5 & 6 are loaded with the address of the start of the BASIC routine (low byte in 5, high byte in 6), then a JSR #C2F2 is executed. At the end of the BASIC routine, a suitable LINK instruction will return control to the remainder of the machine language routine.

Autostart

If a BASIC program is saved on tape by using the COS command

*SAVE "FILENAME" START END CE86

Where START is the text space start address (2900 or 8200), and END is the (hex) address of the end of the program (obtained by typing P.&TOP), then you will subsequently be able to LOAD the program, and RUN it as normally, but the command

*RUN "FILENAME"

will fetch the program from tape, and run it automatically as soon as it has loaded.

Chaining

It is possible for a BASIC program to load and run another program from tape - the original program being deleted from RAM in the process - if the second program is saved as an 'autostart' program.

Thus, in the example given below, two programs are entered and saved on tape, the second in autostart mode, then the first one is loaded and run. Program 1 then automatically loads and runs the second program.

10 REM PROG 1
20 PRINT "THIS IS PROGRAM 1"'
30 *RUN "PROG 2"
>
>&TOP
>*SAVE "PROG 1" 2900 293F CE86
RECORD TAPE
>
>NEW
>10 REM PROG 2
>20 PRINT "THIS IS PROGRAM 2"'
>30 END
>
>P.&TOP
     2935>
>*SAVE "PROG 2" 2900 2935 CE86 
RECORD TAPE
>*RUN 'PROG 1' 
PLAY TAPE
THIS IS PROGRAM 1 
PLAY TAPE 
THIS IS PROGRAM 2
>

FOR - - NEXT/ DO - - UNTIL CRASHES

Repeatedly branching out of a FOR ... NEXT loop (or a DO ... UNTIL loop) can cause a program to 'crash', giving error code 18 or 111. For example, the two programs given below should both print out indefinitely, but they both crash after 11 *'s.

10 REM
20 X=1
30 DO
40 X=X+1
50 IF X=5 GOTO 70
60 UNTIL X=0
70 PRINT '*'
80 GOTO 10
RUN
ERROR 18 LINE	30
10 REM START
20 FOR I-1 TO 10
30 IF I=5 GOTO 50
40 NEXT I
50	PRINT '*'
60 GOTO 10
>RUN
***********
ERROR 111 LINE	20

The reason is that on entering a FOR ... NEXT loop (or a DO UNTIL loop), ATOM BASIC puts details of the loop onto a stack, and pulls them from the stack when it finally exits via the NEXT or UNTIL statement. However, jumping out of the middle of the loop leaves the data still on the stack, which can eventually fill up and cause the program to crash.

In the case of the DO … UNTIL loop, there is no way around the problem, so programs should avoid having to branch out of DO UNTIL loops. FOR … NEXT loops, however, will not cause a crash if an outer FOR ... NEXT loop (which is not jumped out of) can be included so that the NEXT of the outer loop is reached before the stack has filled. Thus in the example given below, the addition of the loop involving lines 10 and 60 prevents the overall program from crashing. The reason for this recovery is that the data relating to the outer loop is put onto the stack first, so on encountering the outer NEXT statement in line 60, ATOM BASIC cleans-up the stack, removing the irrelevant data left from the inner loop. For a practical example, see the program "SCRAMBLE", where lines 410,600 prevent the inner loop between lines 520 and 540 from crashing the program after several moves.

10 FOR K=1 TO 30
20 FOR 1=1 TO 10
30 IF 1=5 GOTO 50
4O NEXT I
50 PRINT '*'
60 NEXT K
70 END
>RUN
******************************>

Reading a Single Keystroke

In some cases it would be convenient if the program could read a single keystroke, without the user having to press the Return key. The following example program does this by using a machine language routine to wait for a keystroke then place the appropriate ASCII code in location 80 before returning to the BASIC program which then displays the ASCII code.

1 REM KEYPRESS DEMO
10 DIM P(-1)
20[JSR #FFE6;STA #80;RTS;]
100 PRINT"PRESS A KEY; "'
110 LINK TOP
120 PRINT" CODE =    "?#80
130 GOTO 100

As it stands, the routine will display the character corresponding to the key pressed. To prevent this 'echo' change line 20 to;

20[JSR #FFE3;STA #80;RTS;]

Preventing Escapes

Atomic Theory and Practice suggests that to prevent operation of the ESC key from halting a program, you use ?#B000=10. This is because, when the program is running, the low four bits of the 8255 port B000 are normally set to 0 to allow the BASIC interpreter to examine the state of the ESC key by simply looking at bit 5 of B001; setting B000 to 10 means that B001 bit 5 no longer is connected to any of the keys.

Unfortunately, if the program requires an INPUT from the user, then each time a character is read from the keyboard, the keyboard scan routine resets the low four bits of B000 to zero. To overcome 'this, and so provide a more foolproof method of disabling the ESC Key, we should also modify the OSRDCH routine so that B000 is set to 10 after each character has been read. This can be done by placing a routine such as that listed below at the start of the program;

10 P=#80;p.$21
20[LDA @0; STA#B000;JSR #FE94
30 CMP @#1B;BNE P+4;LDA @#20
40 PHA;LDA @10;STA #B000;PLA;RTS;]
50 P.$6;?#20A=#80;?#20B=0;?#B000=10

Lines 20-40 are a machine language routine which is substituted for OSRDCH by changing the vector at 20A & 20B. Line 20 reads a character from the keyboard, using the (undocumented) COS routine at FE94, line 30 then checks to see if it is ESC, and if so changes the code to that for 'space', line 40 resets B000 to 10. As well as changing the vector, line 50 also sets B000 to 10 before anything else happens. Note that certain control codes (e.g. that generated by PRINT $6) or a CLEAR will reset the low four bits of B000 to zero.

Packing

If you have to store a lot of alphanumeric data in RAM or on tape, and if you can do without the inverse (lower case) characters and the control codes, then it is possible to increase the number of characters that can be stored in a given area by converting each ASCII character to a 6-bit code, then packing 4 characters into 3 bytes.

The demonstration program below use lines 100-180 to pack the 16-character string $A into the 12 byte vector P(), then lines 400-480 reverse the process to restore the original form of $A.

10 REM PACKING DEMO
20 DIM A(16),P(11)
30 $A='ABCDEF0123156789'
100 Y=A
110 FOR X=P TO (P+11) STEP 3
120 Z=0
130 FOR Q=0 TO 3;Z=Z*#40+Y?Q-#20
140 NEXT Q
150 FOR Q=0 TO 2;X?Q=Z%256;Z=Z/256
160 NEXT Q
170 Y=Y+4
180 NEXT X
200 @=0;P.$12$A''"PACKS TO (HEX)"'
210 FOR X=P TO P+11;P.&?X" ";NEXT X
300 $A=" ";REM DESTROY THE EVIDENCE
400 Y=A
410 FOR X=P TO (P+11) STEP 5
420 Z=0
430 FOR Q=2 TO 0 STEP -1;Z=Z*256+X?Q
410 NEXT P
450 FOR Q=3 TO 0 STEP 1;Y?Q=Z%#40+#20;Z=Z/#40
460 NEXT Q
470 Y=Y+4
480 NEXT X
500 P. '' 'AND UNPACKS TO ; " ''
510 END

If a control code such as CR is essential, then it could be sub-stituted for one of the less-used alphanumerics such as ASCII codes 5B-5F.

A similar technique can he used to pack two BCD digits into a single byte for data storage applications.

Keyboard Time-out

In some applications (as for example the "Sums Tester" program given elsewhere in this book), the computer should be able to keep track of how long it has waited for a response from the keyboard, and take alternative action if it has waited too long.

This can be done by modifying ATOM's normal keyboard input routine so that a counter is incremented at regular intervals when it is waiting for a key to be pressed, and abandoning the wait via a BRK instruction when the counter has reached a pre-determined number.

As example of such a routine is given below.

10 REM KB TIMEOUT EXAMPLE
100 P=#B4
102[\ NEW KEYIN ROUTINE
104 PHP
106 CLD
108 STX #E4
110 STY #E5
112 BIT #B002
114 BVC #94	REPT?
116 JSR #210	SCAN KB
118 BCC #8A	BR IF KEYPRESS
120 JSR #FB8A	CHECK SHFT,CTRL
122 JSR #21C	WAIT FOR
124 BCS #97	KEYPRESS
126 JMP #FEA7	TO OP SYSTEM
129]
130 P=#21C
132[\ SCAN & COUNT SUBROUTINE
134 JSR #FE66	WAIT FOR FLYBACK
136 INC #80	INCREMENT
138 BNE #228
140 INC #81
142 BNE #228	TIMER
144 BRK
146 JMP #FE71	SCAN KB & RTS
148]
200 REM LINK IN NEW ROUTINE
210 ?#20A=#84;?*20B=0
300 REM TEST ROUTINE
310 !#80=-600
320 INPUT STOP
330 PRINT !#80
340 GOTO 310

Lines 100-128 assemble to give a machine language routine in the free RAM locations 80-9E, which is used to replace the first part of ATOM's normal input routine. It waits until all keys (except perhaps the SHIFT, CTRL or REPT keys) have been released (lines 114,118), then waits for a new key to be pressed (lines 122,124) before jumping to the second part of ATOM's normal keyboard input routine at FEA7. While waiting, it uses a subroutine (lines 132, 146) which assembles into the free RAM locations 21C-22A to increment the contents of locations 80,81 and scan the keyboard every 1/60 second. Locations 80,81 are to be preset to a negative number at the start of a program, and the routine times out by executing the BRK instruction when the count reaches zero. Note that locations 82 & 83 are left free so that the counter can be preset with a normal ATOM BASIC 4-byte word. The longest timeout that can be obtained is 18 minutes, obtained by pre-setting the count to -65535.

As an example of using the timeout facility, lines 300-340 are a simple loop which first presets the count to -600 (10 seconds), then waits for an input. If the Return key is pressed to signal the end of the input before the 10 seconds has elapsed, the program will display the remaining count then loop back for another input. If the time limit is exceeded, the program simply crashes;- in practice it would be better to add a BRK error handler as described in sections 18.8 and 23.10 of 'Atomic Theory and Practice', or as in the 'Sums Tester' program in this book.

It should be noted that the ATOM Operating System routines used by this program are not documented by Acorn, and so may not be valid if they introduce an updated ROM.

ASSEMBLER LISTING OF MACHINE CODE ROUTINES

102 0084		\ NEW KEYIN ROUTINE
104 0084 08		PHP
106 0085 D8		CLD
108 0086 86 E4		STX #E4
110 0088 84 E5		STY #E5
112 008A 2C 02 B0	BIT #B002
114 008D 50 05		BVC #94		REPT?
116 008F 20 1C 02	JSR #21C	SCAN KB
118 0092 90 F6		BCC #8A		BR IF KEYPRESS
120 0094 20 8A FB	JSR #FB8A	CHECK SHIFT,CTRL
122 0097 20 10 02	JSR #21C	WAIT FOR
124 009A B0 FB		BCS #97		KEYPRESS
126 009C 4C A7 FE	JMP #FEA7	TO OP SYSTEM
132 021C		\ SCAN & COUNT SUBROUTINE
134 021C 20 66 FE 	JSR #FE66	WAIT FOR FLYBACK
136 021F E6 80		INC #80		INCREMENT
138 0221 D0 05		BNE #228
140 0223 E6 81		INC #81
142 0225 D0 01		BNE #228	TIMER
144 0227 00		BRK
146 0228 4C 71 FE	JMP #FE71	SCAN KB & RTS