Sunday, February 14, 2021

A method to relocate a BASIC program

On an Apple II with ROM BASIC, a BASIC program can run anywhere there is space in memory with the caveat that one step must be performed or the NEW command might fail. The first byte of the fresh location where BASIC will run must be set to 0. 

BASIC initially locates programs at $800. The decimal conversion of $800 is 2048.  To demonstrate the problem caused by the first byte, enter a fresh BASIC session. Issue the following commands:

] NEW
] LIST

] POKE 2048,255
] NEW
?SYNTAX ERROR 

] POKE 2048,0
] NEW

* no syntax error is encountered

] REM $0C00 IS 3072 IN DECIMAL
] POKE 3072,255
] REM $0C IS THE HIGH BYTE AND IS 12 IN DECIMAL
] POKE 104,12
] NEW
? SYNTAX ERROR

] POKE 3072,0
] NEW

* no syntax error is encountered


This is fantastic. A program can be located above the TEXT PAGE 2 ($0800-$0BFF) and use it for LORES graphics.  But, these steps must be performed each new session.

] REM START BASIC ABOVE GR PAGE 2 AT $0C00
] POKE 104,12
] POKE 3072,0

Could this be automated if loading from DOS or ProDOS? 
If yes, could it be automated per each program?

The short answer is yes & yes. The answer is provided in part by left over, or stale, data in memory.  I think of that state of data like dinosaur bones. That data is a sign of what happened in the past and may only be interesting to a few people. 

Retrieving that data in a BASIC program can be performed by using the PEEK command provided some awareness to where the data resides. If the data happens to be ASCII text, that data can be exposed to BASIC by using the POKE command to alter an already defined string.  I will demonstrate this later.

For my purpose, I will use the input buffer of ProDOS stored at $BCBD and the input buffer length at $BCBC.

Process Background
BASIC uses the zero page when operations are performed on variables.  There are dinosaur bones of BASIC variables found in the zero page. In particular, a region identified as scratch registers by Jon Relay's* very useful Apple II references.

Via trial and inspection, I discovered that scratch registers $85/$86 and $8C/$8D are affected when a new string is assigned the value of an old string.  In my example, N$ is the new string and O$ is the old string.

This assignment leaves dinosaur bones in the zero page, specifically the most recently used addresses of the strings.
N$ = O$

In this assignment, $85/86 is the location in memory of the old string and $8C/$8D is the location of the new string.

$85 = low address of old string
$86 = high address of old string

$8C = low address of new string
$8D = high address of new string

In the references* mentioned above, a keen reader will notice that the last used variable address is found in memory at $83/$84.  In the assignment operation N$ = "ascii text", $83/$84 is very much equal to $85/$86, and is the primary last used variable address and not one of the scratch registers.  
 
$83 = 131 in decimal
$84 = 132 in decimal

At first glance, this might appear to provide direct access to the string variable:
A = PEEK(131)+PEEK(132)*256

However, capturing the address of the numerical variable is not directly possible with a numerical variable. The address of the numerical variable receiving the value is assigned to $83/$84 and $85/$86 regardless of any operations on the right side of the assignment.  The value stored in the new variable is the address of itself, and not the anticipated address of the string.

Addresses $83 ... $86 will not keep value when capturing the last used variable address as a numerical variable. 

Lucky for us, the knowledge of how $8C/$8D store the last used variable addresses can help in this situation. 

Assigning a new string with the value of the old string, and even self assignment like O$ = O$ will suffice and keep a copy of the address of O$ in $8C/$8D. 

If the next operation is as follows, the address to the string is obtained:
A = PEEK(140)+PEEK(141)*256

Now for a word about how the string is stored.  The string payload, as in the ASCII text, is not located inline with the variable. The byte stored at A is the string length.  A+1 and A+2 are a pointer to the absolute locations of the string.  A+1 is the low byte and A+2 is the high byte of the string payload pointer.

We have the location of the string length and the pointer. With both these pieces of information, we can resize and repoint the string to the name of the program. 

This will be useful if we RUN THE.PROGRAM to get it loaded at our desired BASIC location.  If at first your LOAD the program, a RUN will not succeed.  

If the intent is to LOAD the program, this method is not needed as a the POKE statements on lines 10 and 20 can be issued before the LOAD.  This code that follows takes care of loading the BASIC program to the desired location.   

10 IF PEEK(104) <> 8 GOTO 50

20 POKE 12 * 256,0: POKE 104,12

30 A$ = “/”: A$ = A$: A = PEEK(140)+PEEK(141)*256: POKE A,PEEK(48316): POKE A+1,189:POKE A+2,188

40 NA$ = A$: PRINT CHR$(4);”LOAD”;NA$


Line 10 checks if the BASIC program location is at the default.  If it has already been altered, skip to program execution.
Line 20 clears the problematic first byte of the relocation address and then sets the high byte of the BASIC program location to 12 ($C00). 
Line 30 defines A$, performs the assignment that leaves dinosaur bones in $8C/$8D, stores the address from $8C/$8D in the variable A, pokes the value of 48316 ($BCBC) to the string, and closes by setting the high and low bytes of the ProDOS keyboard input buffer to A+1 and A+2.
Line 40 performs an assignment to copy the string, and invokes a ProDOS BASIC LOAD of the file.

There and done.

More on Variable Zero Page registers
In my experience, the last used variable address pointer in the zero page is only valid immediately after a variable has received an assignment.  Further instructions may clobber it.  Take this derivative of the code above which is not relocated:

30 A$ = “/”: A$ = A$: A = PEEK(140)+PEEK(141)*256: POKE A,PEEK(48316): POKE A+1,189:POKE A+2,188

40 NA$ = A$

50 PRINT PEEK(131)+PEEK(132)*256

60 GOTO 60


The output is 2163 from line 50, which in fact points to the original A$ having the updated paload pointer to $BCBD, but as of executing the GOTO in line 60, that value of decimal addresses 131/132 ($83/$84) are now $824, somewhere in the middle of the BASIC code.  The scratch registers at $85/$86 point to $881 and scratch registers $8C/$8D point to $009D.  No time is spent on $8C/$8D since the pointer destination fails to correspond to any string payload.  $881, however, points to the string region $95F7.  Inspection of the Start of string storage $6F/$70 yields $95F7, confirming a viable region in which BASIC is allowed to provision string payloads. The payload for the $NA assigned in line 40 and located in memory at $95F7 has the ASCII string RUN (CTRL-M) and substantiates that the variable assignment at line 40 successfully copied the data from the $BCBD text input buffer.


This hopefully stresses the importance of fetching the payload pointer immediately after performing an assignment that populates $8C/$8D.


Thanks for reading.