Loading Binary DOS Files From BASIC
Robert E. Alleger
You can load binary (machine language) files from DOS with selection "L." Here's a machine language program that lets you do it from BASIC.
Introduction
Several months ago, my friend Doug came to me and said, "Hey Bob, I want to show you the nifty menu program I wrote." After he demonstrated his program, I said, "Big deal! You came all the way over here to show me this?" He replied, "Not exactly. There's one slight problem that I don't know how to solve. My menu will not load machine language programs. I thought that you might be able to help me." After some arm twisting, I agreed to write a routine that would allow a BASIC program to load machine language disk files.
DOS 2 LOAD File Format
Before I begin with a step-by-step breakdown of LOADIT, it might be helpful to define the format of a DOS 2 binary load file (see Figure 1). A binary load file begins with a two-byte header id of $FF $FF ($xx indicates hexadecimal numbers), followed by a two-byte start address, a two-byte end address, and the program data (object code).
For programs with multiple ORGs, this pattern may repeat over and over again, beginning with the start address. If the file was created by using the DOS copy ('C') command to append two or more files together, then the pattern may repeat beginning with the header id.
Figure 1.
: $FF $FF : start address : end address : object code : …
On With The Show
LOADIT is designed to be called from BASIC via the USR function. It is ORG'd for page six (1536-1791) so that it is relatively safe, as long as the machine language program that it runs does not load into this area.
Referring to Program 1 (LOADIT. ASM) line numbers 0440-0580 (INIT) is the initialization routine. It calls a subroutine that CLOSEs IOCB (I/O Control Block) number one (in case it was already open), retrieves the address of the file-spec from BASIC, and then OPENs the specified file.
Lines 0620-0810 (RDHDR) read the first two bytes of a block of object code from the input file. If both bytes are an $FF (header id), then the program loops back to get the next two. Together, these bytes form the address into which to start loading the object code. An end of file error ($88) at this point indicates that the whole file has been loaded, and therefore execution branches to the DONE routine.
Lines 0850-0930 (CONT) read the next two bytes, which form the ending address for the current block of object code. Any error returned in the Y-register by CIO at this point either indicates that the file is bad (i.e., "File Number Mismatch," etc.) or that the file is not in binary load format (see Figure 1).
Lines 0970-1040 (HDROK) check to see if this is the first block of object code that has been read from this file. If it is, then the address of the first instruction is used as the default run address, in case none is specified. In assembly language, the run address is specified by storing the address of the entry point to your program in locations $02E0-02E1.
Lines 1080-1270 (RDBLOK) read the object code from the file and store it in memory, from start address through end address. The number of bytes to be read is calculated by taking the ending address, subtracting the start address, and adding one. The only non-fatal error code that CIO could return at this point is $03, which indicates that an end of file error will be encountered on the next read.
After loading the entire block of object code, the program loops back to the RDHDR routine.
Lines 1340-1350 (DONE) are executed on an end of file error in the RDHDR routine. The input file is CLOSEd and execution is transferred to the loaded program via the vector at address $02E0-02E1.
Does It Really Work?
I will use a very simple, no frills menu program (see Program 2) to demonstrate that LOADIT really does work. Although LOADIT might be useful in other applications, I chose this menu because it illustrates the most common usage.
The subroutine starting at line number 5000 is responsible for placing LOADIT at its proper location in memory. A FOR/NEXT loop reads the decimal equivalent of the machine language instructions and POKEs them into page six of memory.
LOADIT is only called when a machine language program is chosen from the screen menu. I chose to indicate a machine language load-and-go file by using the file extension ".CMD" (from my old TRS-80 days). Of course you can use anything you like, just change line number 480.
LOADIT is called by the statement in line number 550. The parameters specified in the USR function are LOADIT's starting address (1536 = $0600) and the address of the string variable which contains a complete file-spec (i.e., Dn:name.ext).
BASIC should never fall through to line number 570, because LOADIT only returns to BASIC if an error is encountered.
Did I Really Type All Those DATA Statements?
In case you were wondering, I did not type in the DATA statements on line numbers 5001-5008. Instead, I used a handy BASIC utility program that I wrote called DATAGEN (see Program 3). It is included as a bonus.
DATAGEN reads a binary load format file, such as LOADIT.OBJ, and produces a file that can be appended to your BASIC program with the ENTER command (see page 25 of the ATARI "BASIC REFERENCE MANUAL"). This file will contain one FOR/NEXT loop and a number of DATA statements for each block of object code in the file.
Upon startup, DATAGEN requests a complete file-spec for the input and output files. It also asks for the starting line number that will be used to begin numbering the subroutine that is being written to the output file.
And That's Not All, Folks
Program 4 is another utility program which can be used to find out the load parameters of a binary load format file. DPSLOAD reads a binary file and displays the starting and ending address of each block of object code, the auto run, and init addresses, if present (see pages 41-44 of the Atari Disk Operating System II Reference Manual).
The input requested by DSPLOAD is a complete file-spec of the input file. The information will be displayed on the screen when each block of object code is encountered.
To some, it may seem that DATAGEN and DSPLOAD have nothing to do with my intended topic, but I hope that they prove to be educational as well as useful aids to programming.
PROGRAM 1. Loading Binary DOS Files From BASIC.
10 .TITLE "LOADIT 1.1 02/24/82"
20 ;Author : Robert E. Alleger
30 ;
40 ;This program allows a BASIC
50 ;program to LOAD a machine
60 ;language program and execute it.
70 ;
80 ;
90 ;
0100 IOCB1 = 1 * 16 ;IOCB #1 (D : )
0110 ;IOCB (8 * 16 bytes)
0120 ICHID = $0340 ;handler ID
0130 ICDNO = ICHID + 1 ;device #
0140 ICCOM = ICDNO + 1 ;command
0150 ICSTA = ICCOM + 1 ;status
0160 ICBAL = ICSTA + 1 ;buffer address
0170 ICPTL = ICBAL + 2 ;PUT routine addr - 1
0180 ICBLL = ICPTL + 2 ;buffer length
0190 ICAX1 = ICBLL + 2 ;AUX 1
0200 ICAX2 = ICAX1 + 1 ;AUX 2
0210 ICAX3 = ICAX2 + 1 ;AUX 3
0220 ICAX4 = ICAX3 + 1 ;AUX 4
0230 ICAX5 = ICAX4 + 1 ;AUX 5
0240 ICAX6 = ICAX5 + 1 ;AUX 6
0250 ;
0260 CIO = $E456 ;CIO entry point
0270 ENR = $03 ;EOF on next read
0280 EOF = $88 ;EOF status
0290 OPEN = $03 ;OPEN command
0300 GETCHR = $07 ;GET CHARACTERS command
0310 CLOSE = $0C ;CLOSE command
0320 OREAD = $04 ;OPEN direction = READ
0330 ;
0340 RUNLOC = $02E0 ;auto run vector
0350 FREE0 = $00CB ;free 0 page RAM (to $00D1)
0360 HEADER = FREE0 ;block header buffer
0370 FLAB = HEADER + 4 ;1st block flag
0380 ;
0390 ;
0400 * = $0600
0410 ;
0420 ;Initialization
0430 ;
0440 INIT LDX #IOCB1
0450 JSR CLOSEIT ;in case IOCB was in use
0460 STX FLAG
0470 PLA ;get rid of # of args on stack
0480 PLA ;MSB of file spec location
0490 STA ICBAL + 1, X
0500 PLA ;LSB
0510 STA ICBAL, X
0520 LDA #OREAD
0530 STA ICAX1, X
0540 LDA #OPEN
0550 STA ICCOM, X
0560 JSR CIO ;open file
0570 BPL RDHDR
0580 JMP ERROR ;file not found
0590 ;
0600 ;Read header id or start address
0610 ;
0620 RDHDR LDA #HEADER&255
0630 STA ICBAL, X
0640 LDA #HEADER/256
0650 STA ICBAL + 1, X
0660 LDA #2
0670 STA ICBLL, X
0680 LDA #0
0690 STA ICBLL + 1, X
0700 LDA #GETCHR
0710 STA ICCOM, X
0720 JSR CIO ;get id or start addr
0730 BPL CHKID
0740 CPY #EOF ;end of file?
0741 BEQ DONE ; - yes
0742 BNE ERROR ; - no, bad file
0750 ;
0760 CHKID LDA #$FF
0770 CMP HEADER
0780 BNE CONT
0800 CMP HEADER + 1
0810 BEQ RDHDR ;ignore $FF, $FF id code
0820 ;
0830 ;Read end address
0840 ;
0850 CONT LDA #HEADER + 2&255
0860 STA ICBAL, X
0870 LDA #HEADER + 2/256
0880 STA ICBAL + 1, X
0890 JSR CIO ;get end address
0900 BPL HDROK
0930 BMI ERROR ;bad file
0940 ;
0950 ;Store program start address
0960 ;
0970 HDROK LDA FLAG
0980 BEQ RDBLOK ;skip if not 1st block
0990 LDA HEADER ;use start of program
1000 STA RUNLOC ; as default run adr
1010 LDA HEADER + 1
1020 STA RUNLOC + 1
1030 LDA #0
1040 STA FLAG ;clear 1st block flag
1050 ;
1060 ;Read a block of object code
1070 ;
1080 RDBLOK LDA HEADER ;load address
1090 STA ICBAL, X
1100 LDA HEADER + 1
1110 STA ICBAL + 1, X
1120 LDA HEADER + 2 ;end address
1130 SEC
1140 SBC HEADER ;length = end - start
1150 STA ICBLL, X
1160 LDA HEADER + 3
1170 SBC HEADER + 1
1180 STA ICBLL + 1, X
1190 INC ICBLL, X ;adjust length by 1
1200 BNE * + 5
1210 INC ICBLL + 1, X
1220 ;
1230 JSR CIO ;read block
1240 BPL RDHDR ;get next block
1250 CPY #ENR
1260 BEQ RDHDR ;this is also OK
1270 JMP ERROR ;bed file
1280 ;
1290 ;Subroutines follow
1300 ;
1310 ; **********************
1320 ;Start selected program
1330 ; **********************
1340 DONE JSR CLOSEIT
1350 JMP (RUNLOC) ;start program
1360 ; **************************
1370 ;Return error code to BASIC
1380 ; **************************
1390 ERROR TYA
1400 STA 212 ;tell BASIC what's wrong
1410 LDA #0
1420 STA 213
1430 ;now fall through to CLOSEIT
1440 ;then return to BASIC
1450 ; **************
1460 ;Close the IOCB
1470 ; **************
1480 CLOSEIT LDA #CLOSE
1490 STA ICCOM, X
1500 JSR CIO ;close file
1510 RTS
PROGRAM 2. Loading Binary DOS Files From BASIC.
10 REM LOADIT demo menu
20 REM by Robert E. Alleger
30 DIM LINE$(13), DIR$(12 * 64), DRIVE$(3)
40 REM * Initialization *
50 GRAPHICS 0 : POKE 752, 1
60 DRIVE$ = "D1 :"
70 PRINT, "MENU for Drive ";DRIVE$ : PRINT
80 GOSUB 5000 : REM store LOADIT.OBJ
90 LINE$ = DRIVE$ : LINE$(4) = " * . * "
100 DIR$(1, 1) = " " : DIR$(12 * 64) = " "
110 DIR$(2) = DIR$
120 CLOSE #1 : OPEN #1, 6, 0, LINE$
130 TRAP 380 : ENTRY = 1 : LINEFLAG = 1
140 REM * Read the directory *
150 FOR FILENUMBER = 1 TO 64
160 INPUT #1, LINE$
170 IF LINE$(2, 2) < > " " THEN 380
180 PD = ENTRY
190 REM * Scan file name *
200 FOR PS = 3 TO 10
210 IF LINE$(PS, PS) = " " THEN 240
220 DIR$(PD, PD) = LINE$(PS, PS)
230 PD = PD + 1 : NEXT PS
240 REM * Check for extension *
250 IF LINE$(11, 11) = " " THEN 320 : REM no extension
260 DIR$(PD, PD) = "." : REM append dot
270 PD = PD + 1
280 REM * Scan file extension *
290 FOR PS = 11 TO 13
300 DIR$(PD, PD) = LINE$(PS, PS)
310 PD = PD + 1 : NEXT PS
320 REM * Display file name.ext *
330 IF LINEFLAG = 3 THEN PRINT : LINEFLAG = 1
340 IF FILENUMBER < 10 THEN PRINT " ";
350 PRINT FILENUMBER;" ";DIR$(ENTRY, ENTRY + 11); " ";
360 LINEFLAG = LINEFLAG + 1
370 ENTRY = ENTRY + 12 : NEXT FILENUMBER
380 REM * Choose one *
390 PRINT : PRINT "Enter number of file to load : ";
400 TRAP 390 : INPUT N
410 IF N < 1 OR N > FILENUMBER - 1 THEN 390
420 LINE$ = DRIVE$
430 LINE$(4) = DIR$(N * 12 - 11, N * 12)
440 GRAPHICS 0 : POSITION 2, 10
450 PRINT "{6 SPACES}LOADING ";LINE$
460 REM * See if machine language *
470 FOR PS = 4 TO 12
480 IF LINE$(PS, PS + 3) = ".CMD" THEN 540
490 NEXT PS
500 REM * Load BASIC program *
510 TRAP 530
520 RUN LINE *
530 ERROR = PEEK(195) : GOTO 560
540 REM * Load M.L. program *
550 ERROR = USR(1536, ADR(LINE$))
560 REM * Shouldn't be here! *
570 PRINT "ERROR #";ERROR;" encountered during load"
580 END
5000 FOR A = 1536 TO 1717 : READ B : POKE A, B : NEXT A
5001 DATA 162, 16, 32, 173, 6, 134, 207, 104, 104, 157, 69, 3, 104, 157, 68, 3, 169, 4, 157, 74, 3, 169, 3, 157, 66
5002 DATA 3, 32, 86, 228, 16, 3, 76, 166, 6, 169, 203, 157, 68, 3, 169, 0, 157, 69, 3, 169, 2, 157, 72, 3, 169
5003 DATA 0, 157, 73, 3, 169, 7, 157, 66, 3, 32, 86, 228, 16, 6, 192, 136, 240, 92, 208, 96, 169, 255, 197, 203, 208
5004 DATA 4, 197, 204, 240, 210, 169, 205, 157, 68, 3, 169, 0, 157, 69, 3, 32, 86, 228, 16, 2, 48, 69, 165, 207, 240
5005 DATA 14, 165, 203, 141, 224, 2, 165, 204, 141, 225, 2, 169, 0, 133, 207, 165, 203, 157, 68, 3, 165, 204, 157, 69, 3
5006 DATA 165, 205, 56, 229, 203, 157, 72, 3, 165, 206, 229, 204, 157, 73, 3, 254, 72, 3, 208, 3, 254, 73, 3, 32, 86
5007 DATA 228, 16, 137, 192, 3, 240, 133, 76, 166, 6, 32, 173, 6, 108, 224, 2, 152, 133, 212, 169, 0, 133, 213, 169, 12
5008 DATA 157, 66, 3, 32, 86, 228, 96
5009 RETURN
PROGRAM 3. Loading Binary DOS Files From BASIC.
10 REM DATAGEN
20 REM Translates DOS LOAD files
30 REM to BASIC DATA statements
40 REM (C) 1981 by Robert E. Alleger
50 REM * Initialization *
60 DIM FI$(15), FO$(15)
70 ERRSAV = 195 : MAX = 25
80 REM * ask for file specs *
90 GRAPHICS 0
100 PRINT, "DATAGEN" : PRINT
110 TRAP 120
120 PRINT "Input file spec : ";
130 INPUT FI$
140 TRAP 150
150 PRINT "Output file spec : ";
160 INPUT FO$
170 TRAP 610 : CLOSE #1 : CLOSE #2
180 OPEN #1, 4, 0, FI$
190 OPEN #2, 8, 0, FO$
200 REM * Get header ID (2 bytes) *
210 GET #1, B1 : GET #1, B2
220 IF (B1 = 132 AND B2 = 152) OR (B1 = 255 AND B2 = 255) THEN 260
230 PRINT "{BELL}Not LOAD format" : GOTO 620
240 REM * Ask for starting line *
250 TRAP 260
260 PRINT "Starting line number : ";
270 INPUT LNBR
280 REM * Get START & END addresses *
290 TRAP 560 : REM trap normal EOF
300 GET #1, B1 : GET #1, B2
310 ADRSTART = B1 + B2 * 256
320 IF ADRSTART = 65535 THEN 300
330 GET #1, B1 : GET #1, B2
340 ADREND = B1 + B2 * 256
350 TRAP 540 : REM trap premature EOF
360 REM * Build FOR/NEXT loop *
370 PRINT #2; STR$(LNBR);
380 PRINT #2; "FOR A = ";STR$(ADRSTART); " TO ";STR$(ADREND);
390 PRINT #2; " : READ B : POKE A, B : NEXT A"
400 A = ADRSTART - 1
410 REM * Build DATA statements *
420 LNBR = LNBR + 1
430 IF A + 1 > ADREND THEN 520
440 IF LNBR > 32765 THEN PRINT "{BELL}Line number too large" : GOTO 620
450 PRINT #2; STR$(LNBR); "DATA ";
460 FOR N = 1 TO MAX : A = A + 1
470 IF A > ADREND THEN 520
480 IF N > 1 THEN PRINT #2; ",";
490 GET #1, B1 : PRINT #2; STR$(B1);
500 IF N = MAX THEN PRINT #2
510 NEXT N : GOTO 410
520 PRINT #2 : LNBR = LNBR + 1
530 GOTO 290
540 IF PEEK(ERRSAV) = 136 THEN PRINT "Premature EOF on input file" : GOTO 620
550 GOTO 610
560 REM * Error TRAP *
570 IF PEEK(ERRSAV)< >136 THEN 610
580 PRINT #2; STR$(LNBR); "RETURN"
590 PRINT " < < < DONE > > > "
600 GOTO 620
610 PRINT : PRINT "{BELL} ERROR #";PEEK(ERRSAV)
620 CLOSE #1 : CLOSE #2 : TRAP 65535
PROGRAM 4. Loading Binary DOS Files From BASIC.
10 REM DSPLOAD
20 REM Display DOS LOAD
30 REM format information
40 REM (C) 1981 by Robert E. Alleger
50 REM * Initialization *
60 DIM F$(15), HEX$(4)
70 GRAPHICS 0
80 PRINT, "DSPLOAD" : PRINT : PRINT
90 PRINT "This program will print information"
100 PRINT "for DOS LOAD format files."
110 TRAP 110 : CLOSE #1
120 PRINT : PRINT "File spec :";
130 INPUT F$
140 OPEN #1, 4, 0, F$
150 PRINT "{CLEAR}", "LOAD Display" : PRINT : PRINT
160 PRINT "File name", F$
170 PRINT "Format",,
180 REM * GET header ID *
190 TRAP 530
200 GET #1, B1 : GET #1, B2
210 IF B1 = 132 AND B2 = 9 THEN PRINT "DOS 1 LOAD" : GOTO 240
220 IF B1 = 255 AND B2 = 255 THEN PRINT "DOS 2 LOAD" : GOTO 240
230 PRINT "{BELL} Not LOAD format {DOWN}" : GOTO 730
240 REM * Get START & END Addresses *
250 TRAP 590 : GET #1, B1
260 TRAP 530 : GET #1, B2
270 NBR = B1 + B2 * 256
280 IF NBR = 65535 THEN 310
290 ADRSTART = NBR : GOSUB 460
300 PRINT "Start - End Address ";HEX$; " - ";
310 GET #1, B1 : GET #1, B2
320 NBR = B1 + B2 * 256
330 ADREND = NBR : GOSUB 460
340 PRINT HEX$
350 IF ADREND < ADRSTART THEN 710
360 REM * Read LOAD file *
370 TRAP 560
380 FOR N = ADRSTART TO ADREND
390 GET #1, B1 : BYTES = BYTES + 1
400 IF N = 736 THEN ADRAUTOL = B1
410 IF N = 737 THEN ADRAUTOH = B1
420 IF N = 738 THEN ADRINITL = B1
430 IF N = 739 THEN ADRINITH = B1
440 NEXT N
450 GOTO 240 : REM get next LOAD block
460 REM * Convert decimal to hex *
470 I = 4 : HEX$ = "0000"
480 T = NBR : NBR = INT(NBR/16) : T = T - NBR * 16
490 IF T < 10 THEN HEX$(I, I) = STR$(T) : GOTO 510
500 HEX$(I, I) = CHR$(T + 55)
510 IF NBR< >0 THEN I = I - 1 : GOTO 480
520 RETURN
530 REM * ERROR #1 *
540 PRINT : PRINT "{BELL} Premature EOF while reading HEADER"
550 GOTO 730
560 REM * ERROR #2 *
570 PRINT : PRINT "{BELL} Premature EOF while reading DATA"
580 GOTO 730
590 REM * ERROR #3 *
600 IF ADRAUTOL = 0 AND ADRAUTOH = 0 THEN 640
610 NBR = ADRAUTOL + ADRAUTOH * 256
620 GOSUB 460
630 PRINT "Auto Run Address", HEX$
640 IF ADRINITL = 0 AND ADRINITH = 0 THEN 680
650 NBR = ADRINITL + ADRINITH * 256
660 GOSUB 460
670 PRINT "Init Address", HEX$
680 PRINT "Program size", INT(BYTES/1024 * 100)/100; "K BYTES"
690 PRINT : PRINT "{BELL}---EOF---"
700 GOTO 730
710 REM * ERROR #4 *
720 PRINT : PRINT "{BELL} END Address less than START Address"
730 REM * Exit *
740 TRAP 65535 : CLOSE #1 : END
Return to Table of Contents | Previous Section | Next Section