Stringing Atari Machine Language
Robert MartinsonsStoring Atari machine language in a string is a time-honored technique, but how do you get the ML into the string in the first place? This program does the job automatically, creating the necessary string and appending it to the BASIC program of your choice. It's easy and very fast.
A good way to enhance the performance of BASIC programs is to use machine language subroutines for tasks which either take too much time or consume too much memory. And one of the most popular places to store short ML routines is in an Atari BASIC string. Once the ML code is stored in a string, BASIC's ADR function can calculate the string's address, and the USR function can call it.
Short machine language routines can be dealt with by manually typing them into strings, but this can be somewhat tricky, since it usually involves typing strange-looking control characters. Another possibility is to use DATA statements which BASIC can READ under program control. Neither of these methods is attractive for large routines, however. Substantial ML programs are usually written with an editor/assembler, which produces a binary file as output. The problem, then, is how to convert the contents of a binary file into a string that BASIC can easily handle.
The routine that accompanies this article solves the problem of converting binary files into string form. It reads binary data from a disk or tape file, stores it in a series of strings through the editor's forced read mode, then deletes itself from memory. Type in the program lines listed below, then LIST the routine to disk or tape. Do not save the routine: It must be LISTed so that you can later ENTER it into memory without disturbing a program that's already present.
Stringing Along
To use the routine, first load the BASIC program to which you would like to add a machine language routine. Of course, the ML routine is one which normally resides in a binary file. (Note that the ML routine must be relocatable, since Atari BASIC strings can move around in memory while a program runs.) The BASIC program must not use any line numbers higher than 31499, since this routine itself uses the lines beginning at 31500. Next, ENTER the routine from disk. This brings it into memory without altering the BASIC program. To activate the routine, type GOTO 31500 and press RETURN.
The program begins by requesting the filename of your binary file. Be sure to include the correct device prefix in your response. For instance, to read the binary file CODE.BIN from disk, enter D:CODE.BIN at the prompt. At the next prompt, enter the name of the BASIC string which will hold your machine code. Limit the name to eight characters or fewer (if you enter too many characters, the routine automatically truncates the name). Answer the last prompt with the line number where you want the new ML strings to begin. When answering this prompt, you should take care not to start the new lines at a place which would overwrite existing lines. A safe rule of thumb is to allow ten line numbers for every 256 bytes of machine language.
At this stage, the routine begins reading the ML code into memory and converting it into strings. When the process is complete, the routine deletes itself, leaving your original program plus the strings that contain the machine language. Before you can resave the program, you must manually add a DIMension statement for the new string and add USR calls for the routine where needed. It's also a good idea to LIST the revised program to disk, type NEW, and ENTER it again, before saving it a final time. In this way you can clear out all the variables used by the deleted routine.
The Editor Does All The Work
For those who are interested, here is a short explanation of how the conversion routine works. All Atari binary files have a six-byte header, which contains the information shown in the table.
The conversion routine opens the binary file and executes a CIO (Central Input/Output) system call to bring in the first six bytes. It examines these and confirms that you have accessed a binary file, and then computes the file size by subtracting the starting address from the ending address. Next, a subroutine which dimensions a temporary string (TEMP$) is created and executed. For the sample header shown, the dimension of TEMP$ will be 841. TEMP$ becomes the input buffer for the next CIO call which reads in the remainder of the binary file.
Byte | Number | Number | Description |
---|---|---|---|
1 | 255 | FF | Identification |
2 | 255 | FF | code for binary load file |
3 | 0 | 00 | Starting address (LSB) |
4 | 10 | 0A | (MSB) |
5 | 72 | 4C | Ending address (LSB) |
6 | 13 | 0D | (MSB) |
A loop beginning at line 31610 now begins to break the data from TEMP$ into segments short enough to be stored in a BASIC line. Each new string will hold 90 bytes unless we find the ATASCII equivalent of a quotation mark (34) or carriage return (155). These values are handled separately to avoid confusing the screen editor.
The POKEs in the subsequent lines switch the editor into forced read mode, causing it to enter the new line just as if you'd typed it manually and pressed RETURN. Because the address of TEMP$ moves every time the editor enters a new line, its address is recomputed at the beginning of each loop. After the last byte of data has been packed into the new string, the conversion routine again uses forced read mode to delete itself from the finished program.
Chances are that you've been using a more manual method of embedding your assembly language routines into BASIC. If so, this routine should become a welcome part of your toolkit. Sit back and enjoy watching the screen editor do all the work. A final note: Every effort was made to keep the program as compact as possible. Therefore, no REMark statements are included and error trapping is held to a minimum.
String Atari Machine Language
For instructions on entering this listing, please refer to "COMPUTE!'s Guide to Typing In Programs" in this issue of COMPUTE!.
BD 31500 CLR:GRAPHICS 0:INDEX=1:LINENO=0:STRTLINE=45: DIM BUFFER$(8),FILNAM$(15),STRNAME$(8),A$(1):CIO=ADR("h"{P}LVd") GN 31510 ? "Enter filename for binary load file":INPUT FILNAM$ BO 31520 ? "Enter BASIC string name":INPUT STRNAME$ NL 31530 ? "Enter starting lineno for string":INPUT LINENO PK 31540 A=ADR(BUFFER$):OPEN #1,4,0,FILNAM$:POKE 850,7:B=INT(A/256): POKE 852,A-256*B:POKE 853,B:POKE 857,0 CO 31550 POKE 856,6:N=USR(CIO):IF PEEK(A)<>255 OR PEEK(A+1)<>255 THEN CLOSE #1: ? "ERROR: Not a binary file":STOP HC 31560 FILSIZ=(PEEK(A+4)+256*PEEK(A+5))-(PEEK(A+2)+256*PEEK(A+3))+1 PH 31570 GRAPHICS 0:POSITION 2,4:PRINT "31750 DIM TEMP$(";FILSIZ;"):RETURN" FK 31580 PRINT "CONT":POSITION 2,0:POKE 842,13:STOP FK 31590 POKE 842,12:GOSUB 31750:TEMP$(1)=" ":TEMP$(FILSIZ)=" ":TEMP$(2)=TEMP$: ADDRESS=ADR(TEMP$):B=INT(ADDRESS/256) OC 31600 POKE 852,ADDRESS-256*B:POKE 853,B:B=INT(FILSIZ/256):POKE 856,FILSIZ-256*B: POKE 857,B:N=USR(CIO):CLOSE #1 FB 31610 GRAPHICS 0:ADDRESS=ADR(TEMP$):POSITION 2,4:LINELIM=INDEX+89 OL 31620 IF LINELIM>FILSIZ THEN LINELIM=FILSIZ OM 31630 A$=TEMP$(INDEX,INDEX):IF A$=CHR$(34) OR A$=CHR$(155) THEN 31690 CA 31640 LINESTRT=INDEX:FOR INDEX=LINESTRT TO LINELIM CM 31650 A$=TEMP$(INDEX,INDEX):IF A$=CHR$(34) OR A$=CHR$(155) THEN LINEND=INDEX-1: GOTO 31670 PC 31660 NEXT INDEX:LINEND=LINELIM HK 31670 PRINT LINENO;" ";STRNAME$;"$(";LINESTRT;",";LINEND;")=";CHR$(34); FK 31680 FOR I=LINESTRT TO LINEND:? "{ESC}";TEMP$(I,I);:NEXT I:? CHR$(34): GOTO 31700 BM 31690 ? LINENO;" ";STRNAME$;"$(";INDEX;",";INDEX;")=CHR$(";ASC(A$);")": INDEX=INDEX+1 LB 31700 LINENO=LINENO+1:PRINT "CONT":POSITION 2,0:POKE 842,13:STOP GC 31710 POKE 842,12:IF LINELIM<FILSIZ THEN 31610 GP 31720 GRAPHICS 0:POSITION 2,4:FOR I=31490 TO 31650 STEP 10:? I:NEXT I: ? "CONT":POSITION 2,0:POKE 842,13:STOP FG 31730 POKE 842,12:GRAPHICS 0:P0SITION 2,4 OG 31740 FOR I=I TO 31750 STEP 10:? I:NEXT I:? "POKE 842,12":POSITION 2,0: POKE 842,13:STOP