Jump to content





Convert Binary File to BASIC XL Data

Posted by kenjennings, 12 April 2015 · 690 views

Atari 8-bit BASIC XL utility lame programming
Sifting the heap of aged floppy disks has turned up an interesting pile of ... shtuff.   
 
Machine language routines can be a big help to Atari BASIC/BASIC XL programs boosting performance and providing nifty features not included by BASIC.  That is, provided the machine language routine can be brought into the BASIC program.  In this case the Atari BASIC language can be like an island fortress repelling all invading data, because the language makes it inconvenient to get a machine language routine into a safe place usable by the BASIC progam. 
 
Convenience for BASIC means representing the machine language routine as either DATA statements to be READ and POKE'd into memory, or as string assignments.   However, assembler output -- the machine language bytes -- does not come in a convenient format for BASIC.  A machine language program is usually stored in Atari's segmented load file format.  This is binary information including structure which embeds starting and ending addresses for blocks of memory data.
 
While the segmented file is clever and is the standard format for Atari machine language programs loaded and run from DOS, neither Atari BASIC nor BASIC XL have a facility to easily load the segmented file formats into memory.  A user could choose the DOS load option to load machine language utilities into memory, but this requires extra work on the part of users (who may be famous for forgetting to tie their shoe laces), and requires more thought and planning to design the machine language program to load into a location that will not be overwritten by the BASIC program when it loads and runs.
 
BASIC XL does have the BGET command which can load an arbitrary amount of binary data from a file into any specified location of memory.  The problem here is that this does exactly that -- it just loads data directly into memory.  It does not understand the segmented file format.  The BGET function is useful only if a machine language file is stripped of its segment information which is not smart to do if the file does not describe one contiguous block of memory.
 
So, how to easily get a file of machine language data turned into something useful for BASIC namely, DATA statements or string assignments?   Here is a BASIC XL program, BIN2DATA.BXL that processes binary/machine language file into more convenient data for OSS BASIC XL.
 
 
  Attached File  BIN2DATA.zip (11.94KB)
downloads: 85
 
 
What it does:
 
  • Accepts the input file name containing machine language or other binary data
  • Accepts the output file which will contain BASIC code that can be ENTER'd into BASIC XL and lightly modified for use.
  • Allows the user to choose to load the segmented file into the memory specified by the file.  Note that while this can load multiple segments it is intended for simple routines.  Multiple segments will be consolidated into one range from the minimum/starting address to the maximum/ending address.
  • Allows the user to choose to load the file directly into memory without reading any segment addresses.  In this case the user will be prompted to specify a starting address for loading the data.
  • Accepts the user's choice of output as DATA statements or string assignments.
  • In the case of DATA statements the user can choose if the data will be in Hex or Decimal.
  • The user may choose (within reason) the minimum and maximum number of values that will be output in a single line.
  • The user may choose the starting line number and line increment.
  • The program prints the BASIC statements to the screen and the output file at the same time providing the user visual feedback of the file output.
 
Note that for the string representation there are two binary values that cannot be directly expressed in the string.  The first is $02 which is the internal code for the the double quote character (ATASCII 34).  The other is value $DB which is the internal code for the end of line character (ATASCII 155).  The program substitutes a blank space for both of these values and remembers the position of the data in the  string.  After writing the string assignments the program will then output DATA statements listing the position of the problem characters within the string.  This is expressed using BASIC's string position assignment values where the first character is position (1), not (0).  It is up to the programmer to utilize the list to correctly update the data in the string.
 
 
Here are a few, lovely pictures of the program in operation:
 
 
Input entry of a program to output as a string:
 
Attached Image
 
Here is the actual string output.  This program has an embedded double quote character (binary $02) at position 8 in the string.
 
Attached Image
 
 
Input entry of a program to output as Data:
 
Attached Image
 
Here is the resulting output:
 
Attached Image
 
 
The Good The Bad And the Ugly...
 
The input handling is merely fairly decent, and not completely bullet resistant. When it asks for input from the user it pretty much needs actual input.  Hitting RETURN by itself is a reliable way to break the program.
 
The output is designed to fit the wider range of possibilities that BASIC XL permits.   Using the program's output for regular Atari BASIC requires some tweaks: the command statements need to be changed to all upper case (i.e. "DATA" not "Data"), and the Hex format for Data statements cannot be used. 
 
The quotes and end of line position lists are always generated in hexadecimal.  (Oops.  Well, BASIC XL, you know.)
 
The program has a lot of comments which could free up a lot of space when deleted. 
 
There are certainly redundant things going on in the code which could be optimized, and probably stupid, ugly misuse of TRAP.  (So, you have fun with that, OK?  Let me know how it goes.)
 
 
The BIN2DATA.BXL Program:
 
1000 Rem SAVE "H1:BIN2DATA.BXL"
1005 Rem
1010 Rem BY KEN JENNINGS
1015 Rem
1020 Rem READ A BINARY DOS OR MAC/65
1025 Rem OBJECT FILE INTO MEMORY THEN
1030 Rem OUTPUT THE BYTES AS BASIC
1035 Rem DATA STATEMENTS OR STRING
1040 Rem ASSIGNMENT.
1045 Rem
1050 Rem THE LOADER CAN HANDLE MULTI-
1055 Rem SEGMENT FILES, BUT THIS IS
1060 Rem ONLY TO COMPENSATE FOR
1065 Rem MAC/65s PROPENSITY TO MAKE
1070 Rem SEGMENTS WHEN NOT NEEDED.
1075 Rem
1080 Rem MULTIPLE SEGMENTS WILL BE
1085 Rem CONSOLIDATED TO ONE, ALL-
1090 Rem ENCOMPASSING START AND END
1095 Rem ADDRESS. THIS MEANS A  
1100 Rem PROGRAM WITH SEPARATED
1105 Rem SEGMENTS MAY HAVE TONS OF
1110 Rem USELESS MEMORY INCLUDED.
1115 Rem
1120 Rem THIS IS REALLY MEANT FOR
1125 Rem SHORT MACHINE LANGUAGE
1130 Rem ROUTINES THAT BASIC WILL CALL
1135 Rem VIA USR().
1140 Rem
1145 Rem FOR SAFETY THE PROGRAM WILL
1150 Rem LIMIT ADDRESSES TO PAGE 6,
1155 Rem OR ADDRESSES BEWEEN THE END
1160 Rem OF BASIC PROGRAM MEMORY AND
1165 Rem THE START OF THE CURRENT
1170 Rem DISPLAY LIST.
1175 Rem
1180 Fast
1185 Graphics 0:Poke 82,0:Poke 710,0
1190 ? :? "Convert DOS or MAC/65 binary/object"
1195 ? "file into BASIC Data or string."
1200 Dim I$(20),Infile$(12),Outfile$(12),File$(12)
1205 Dim H$(5),Hx$(32),W$(6)
1210 Maxqe=256:Ql=0:El=0
1215 Dim Qlist$(Maxqe,2),Elist$(Maxqe,2)
1220 W$="What?"
1225 Rem
1230 Rem FILENAMES
1235 Rem
1240 ? :Input "Enter file name to read: ",Infile$
1245 ? :Input "Enter file name to write: ",Outfile$
1250 Rem
1255 Rem PROCESS BIN FILE ADDRESS INFORMATION?
1260 Rem
1265 ? :Input "Ignore address structures (Y/N): ",I$
1270 Ignadd=Find("NnYy",I$(1,1),0)
1275 If Ignadd=0 Then ? W$:Goto 1265
1280 Ignadd=Int((Ignadd+1)/2)
1285   If Ignadd=2:Rem GET START ADDR
1290   Rem
1295   Rem STARTING ADDRESS IF LOADING
1300   Rem DIRECTLY. LIMIT THIS TO
1305   Rem SENSIBLY VALID ADDRESSES:
1310   Rem PAGE 6 AND RAM AFTER THE
1315   Rem PROGRAM IN MEMORY BEFORE
1320   Rem THE DISPLAY LIST
1325   Rem
1330   ? :Input "Enter starting address (hex): ",H$
1335   Gosub 2695:If Xaddr=0 Then ? W$:Goto 1330
1340   Ad=Xaddr:Gosub 2775:If Erradd>0 Then ? W$:Goto 1330
1345   Endif
1350 Rem
1355 Rem DUMP AS DATA STATEMENTS OR ATASCII STRING?
1360 Rem
1365 ? :Input "Output Data or String (D/S): ",I$
1370 Ds=Find("DdSs",I$(1,1),0)
1375 If Ds=0 Then ? W$:Goto 1365
1380 Ds=Int((Ds+1)/2)
1385 Hd=1:Rem A DEFAULT FOR QUOTE/EOL LISTS
1390   If Ds=1:Rem OUTPUT HEX OR DEC?
1395   Rem
1400   Rem DATA CAN BE HEX OR DECIMAL.
1405   Rem
1410   ? :Input "Output Hex or Decimal (H/D): ",I$
1415   Hd=Find("HhDd",I$(1,1),0)
1420   If Hd=0 Then ? W$:Goto 1410
1425   Hd=Int((Hd+1)/2)
1430   Endif
1435 Rem
1440 Rem STARTING LINE NUMBER. BASIC
1445 Rem ALLOWS LINES UP TO 32767, BUT
1450 Rem TO BE REALISTIC THE STARTING
1455 Rem LINE NUMBER SHOULD BE A LOT
1460 Rem LESS THAN 32K. SO, THIS IS
1465 Rem LIMITED TO 30000.
1470 Rem
1475 ? :Input "Starting line number: ",I$
1480 If Len(I$)<1 Then ? W$:Goto 1475
1485 If I$(1,1)<"0" Or I$(1,1)>"9" Then ? W$:Goto 1475
1490 Sline=Val(I$)
1495 If Sline>30000 Then ? W$:Goto 1475
1500 Rem
1505 Rem LINE INCREMENT
1510 Rem
1515 ? :Input "Line increment (1-50): ",I$
1520 If Len(I$)<1 Then ? W$:Goto 1515
1525 If I$(1,1)<"0" Or I$(1,1)>"9" Then ? W$:Goto 1515
1530 Iline=Val(I$)
1535 If Iline>50 Or Iline<1 Then ? W$:Goto 1515
1540 Rem
1545 Rem DATA ITEMS PER LINE. ALLOW
1550 Rem MORE DATA FOR STRINGS.
1555 Rem
1560 ? :? "Bytes per line (";
1565   If Ds=1:? "4";:Else :? "16";:Endif
1570 ? " to ";
1575   If Ds=1:? "24";:Else :? "96";:Endif
1580 Input "): ",I$
1585 If Len(I$)<1 Then ? W$:Goto 1560
1590 If I$(1,1)<"0" Or I$(1,1)>"9" Then ? W$:Goto 1560
1595 Bline=Val(I$)
1600 If (Ds=1 And (Bline<4 Or Bline>24)) Or (Ds=2 And (Bline<16 Or Bline>96)) Then ? W$:Goto 1560
1605 ?
1610 Rem
1615 Rem INIT THE READING VARS
1620 Rem
1625 Zstart=$ffff:Rem THE ABSOLUTE STARTING ADDRESS
1630 Zend=$00:Rem THE ABSOLUTE END ADDRESS
1635 Tstart=$ffff:Rem TEMPORARY START
1640 Tend=$00:Rem TEMPORARY END.
1645 Rem
1650 Rem OPEN THE FILE TO READ
1655 Rem
1660 File$=Infile$
1665 Trap 2970:Open #2,4,0,Infile$
1670 Rem
1675 Rem READING DATA
1680 Rem
1685 If Ignadd=2 Then Goto 1825:Rem STRAIGHT FILE READ
1690 Rem
1695 Rem READING SEGMENTED FILE  
1700 Rem
1705 Trap 3040
1710 Lb=-1:Hb=-1:Tstart=$ffff:Tend=$00
1715 Get #2,Lb:Get #2,Hb
1720 If Lb=$ff And Hb=$ff Then Goto 1710
1725 If Tstart=$ffff Then Tstart=Hb*256+Lb:Goto 1715
1730 Tend=Hb*256+Lb
1735 Ad=Tstart:Gosub 2770:Eads=Erradd
1740 Ad=Tend:Gosub 2770:Eade=Erradd
1745 Rem IF BAD ADDRESS THEN EXIT...
1750 If Eads>0 Or Eade>0 Then Goto 3120
1755 Rem REDETERMINE MIN/START, MAX/END ADDRESS
1760 If Tstart<Zstart Then Zstart=Tstart
1765 If Tend>Zend Then Zend=Tend
1770 Rem SEGMENT READ, POKE AT START
1775 Trap 3005
1780 Get #2,Ch
1785 Poke Tstart,Ch
1790 If Tstart=Tend Then Goto 1695:Rem BLOCK DONE
1795 Tstart=Tstart+1
1800 Goto 1775
1805 Rem
1810 Rem STRAIGHT FILE READ
1815 Rem UNKNOWN SIZE, POKE AT END
1820 Rem
1825 Tstart=Xaddr:Tend=Xaddr-1
1830 Trap 3085:Rem EXPECT EOF
1835 Get #2,Ch
1840 Tend=Tend+1
1845 Ad=Tend:Gosub 2775
1850 If Erradd>0 Then Goto 3120:Rem ABORT
1855 Poke Tend,Ch
1860 Goto 1835
1865 Zstart=Tstart:Zend=Tend
1870 Rem
1875 Rem IF DATA WAS READ, OUTPUT BYTES...
1880 Rem
1885 Close #2
1890   If Zstart>Zend:Rem NO ADDRESS RANGE
1895   ? :? "No addresses set from file"
1900   End
1905   Endif
1910 Rem
1915 Rem OUTPUT FILE...
1920 Rem
1925 File$=Outfile$
1930 Trap 2970:Open #2,8,0,Outfile$
1935 Trap 3215
1940 Rem
1945 Rem OUTPUT BYTES.
1950 Rem
1955 Sep=1:Byte=0
1960 Rem
1965 Rem DESCRIPTIVE COMMENT FOR THE FORGETFUL
1970 Rem
1975 ? Sline;" Rem ";Infile$
1980 ? #2;Sline;" Rem ";Infile$
1985 Sline=Sline+Iline
1990 Ch=Zend-Zstart+1
1995 ? Sline;" Rem Size  = ";
2000 ? #2;Sline;" Rem Size  = ";:Gosub 3150
2005 Sline=Sline+Iline
2010 Ch=Zstart
2015 ? :? Sline;" Rem Start = ";
2020 ? #2:? #2;Sline;" Rem Start = ";:Gosub 3150
2025 Sline=Sline+Iline
2030 Ch=Zend
2035 ? :? Sline;" Rem End   = ";
2040 ? #2:? #2;Sline;" Rem End   = ";:Gosub 3150
2045 Sline=Sline+Iline
2050 ? :? #2
2055 Rem
2060 Rem REAL DATA OUT...
2065 Rem
2070   While Zstart<=Zend
2075   Rem NEW LINE SEPARATION
2080     If Sep=1:Rem NEW LINE
2085     ? Sline;" ";
2090     ? #2;Sline;" ";
2095     Sline=Sline+Iline
2100       If Ds=1:Rem BASIC DATA
2105       ? "Data ";
2110       ? #2;"Data ";
2115       Else :Rem OUTPUT STRING
2120       ? "A$";
2125       ? #2;"A$";
2130         If Byte>0:Rem STRING CONTINUATION
2135         ? "(";Byte+1;")";
2140         ? #2;"(";Byte+1;")";
2145         Endif
2150       ? "=";Chr$(34);
2155       ? #2;"=";Chr$(34);
2160       Endif
2165     Endif
2170   Rem BETWEEN DATA
2175     If Sep=0 And Ds=1:Rem NOT END OF LINE
2180     ? ",";
2185     ? #2;",";
2190     Endif
2195   Rem NOW GET DATA TO OUTPUT
2200   Ch=Peek(Zstart)
2205   Rem DATA OR STRING?
2210     If Ds=1:Rem DATA
2215     Gosub 3150:Rem DEX OR DEC OUTPUT?
2220     Else :Rem STRING
2225     Gosub 2915:Rem CONVERT BYTE TO CHR$
2230     Gosub 2810:Rem CHECK FOR SUBSTITUTION
2235       If Subqe=1:Rem QUOTE OR EOL
2240       ? " ";
2245       ? #2;" ";
2250       Else :Rem NOT SUBSTITUTED
2255       ? Chr$(27);Chr$(Chs);:Rem CONVERTED FROM CH
2260       ? #2;Chr$(Chs);
2265       Endif
2270     Endif
2275   Rem COUNT THE BYTE OUTPUT
2280   Rem AND DETERMINE END OF LINE
2285   Byte=Byte+1:Sep=0
2290     If Byte/Bline=Int(Byte/Bline):Rem BREAK LINE
2295     Sep=1
2300     Endif
2305   Zstart=Zstart+1
2310     If Sep=1 Or Zstart>Zend:Rem END OF LINE OR END OF DATA
2315       If Ds=2:Rem END OF DATA FOR STRING
2320       ? Chr$($22)
2325       ? #2;Chr$($22)
2330       Else :Rem DATA
2335       ? :? #2
2340       Endif
2345     Endif
2350   Endwhile
2355 Rem
2360 Rem OUTPUT QUOTE AND EOL LISTS
2365 Rem
2370   If Ql>0:Rem OUTPUT QUOTE LIST
2375   ? :? Sline;" Rem Embedded quote list"
2380   ? #2:? #2;Sline;" Rem Embedded quote list"
2385   Sline=Sline+Iline
2390   Sep=1:Byte=0
2395   I=1
2400     While I<=Ql
2405       If Sep=1:Rem NEW LINE
2410       ? Sline;" Data ";
2415       ? #2;Sline;" Data ";
2420       Sline=Sline+Iline
2425       Endif
2430       If Sep=0:Rem BETWEEN DATA
2435       ? ",";
2440       ? #2;",";
2445       Endif
2450     Ch=Dpeek(Adr(Qlist$(I;)))
2455     Gosub 3150:Rem OUTPUT CH
2460     Sep=0
2465     If I&$fff8=I Then Sep=1
2470     I=I+1
2475     Endwhile
2480   Endif
2485 Rem
2490   If El>0:Rem OUTPUT EOL LIST
2495   ? :? Sline;" Rem Embedded EOL list"
2500   ? #2:? #2;Sline;" Rem Embedded EOL list"
2505   Sline=Sline+Iline
2510   Sep=1:Byte=0
2515   I=1
2520     While I<=El
2525       If Sep=1:Rem NEW LINE
2530       ? Sline;" Data ";
2535       ? #2;Sline;" Data ";
2540       Sline=Sline+Iline
2545       Endif
2550       If Sep=0:Rem BETWEEN DATA
2555       ? ",";
2560       ? #2;",";
2565       Endif
2570     Ch=Dpeek(Adr(Elist$(I;)))
2575     Gosub 3150:Rem OUTPUT CH
2580     Sep=0
2585     If I&$fff8=I Then Sep=1
2590     I=I+1
2595     Endwhile
2600   Endif
2605 ? :? #2
2610 Close #2:Trap 40000
2615 End
2620 Rem
2625 Rem REPORT LAST ERROR
2630 Rem
2635 ? :? "Error ";Err(0);" at line ";Err(1)
2640 Return
2645 Rem
2650 Rem REPORT LAST ADDRESSES
2655 Rem
2660 ? "Start: $";Hex$(Tstart);"/";Tstart
2665 ? "  End: $";Hex$(Tend);"/";Tend:?
2670 Return
2675 Rem
2680 Rem SUBROUTINE CONVERT HEX
2685 Rem STRING TO NUMERIC VALUE.
2690 Rem
2695 Hx$="0123456789ABCDEFabcdef"
2700 Xaddr=0
2705 If Len(H$)<1 Then Return
2710 If H$(1,1)="$" And Len(H$)>1 Then H$(1)=H$(2):Goto 2705
2715 If H$(1,1)="$" Then Return
2720   For X=1 To Len(H$)
2725   B=Find(Hx$,H$(X,X),0)
2730   If B=0 Then Xaddr=0:Return
2735   If B>16 Then B=B-6
2740   Xaddr=Xaddr*16+(B-1)
2745   Next X
2750 Return
2755 Rem
2760 Rem ADDRESS RANGE CHECK
2765 Rem
2770 Erradd=0
2775 If (Ad<Dpeek($0e) And (Ad<$0600 Or Ad>$06ff)) Or Ad>=Dpeek($0230) Then Erradd=1
2780 Return
2785 Rem
2790 Rem CANNOT EMBED QUOTE OR EOL
2795 Rem IN STRING, SO TRACK FOR
2800 Rem DATA OUTPUT LATER.
2805 Rem
2810 Subqe=0
2815   If Ch=2:Rem BYTE 2 = CHR$($22) = QUOTE
2820     If Ql>=Maxqe:Rem TOO MANY
2825     ? :? "Too many embedded quotes ($02=$22)."
2830     End
2835     Endif
2840   Ql=Ql+1:Subqe=1
2845   Dpoke Adr(Qlist$(Ql;)),Byte+1
2850   Endif
2855   If Ch=$db:Rem BYTE $DB = CHR$($9B) = EOL
2860     If El>=Maxqe:Rem TOO MANY
2865     ? :? "Too many embedded End Of Lines ($DB=$9B)."
2870     End
2875     Endif
2880   El=El+1:Subqe=1
2885   Dpoke Adr(Elist$(El;)),Byte+1
2890   Endif
2895 Return
2900 Rem
2905 Rem CONVERT CH BYTE TO CHR$
2910 Rem
2915 Chs=Ch:Chi=Ch&$7f
2920   If Chi>=$00 And Chi<=$3f:Rem ++ $20
2925   Chs=Chs+$20
2930   Endif
2935   If Chi>=$40 And Chi<=$5f:Rem -- $40
2940   Chs=Chs-$40
2945   Endif
2950 Return
2955 Rem
2960 Rem FILE OPEN ERROR
2965 Rem
2970 ? "Failed to open file: ";File$
2975 Gosub 2635
2980 End
2985 Rem
2990 Rem ERROR DURING SEGMENT DATA
2995 Rem IS NOT ALLOWED
3000 Rem
3005 ? :? "Error Reading Segment Data"
3010 Gosub 2635:Gosub 2660
3015 End
3020 Rem
3025 Rem ERROR DURING SEGMENT ADDRESSES
3030 Rem
3035 Rem EOF MAY MEAN END OF SEGMENT
3040 If Err(0)=136 Then Goto 1885
3045 Rem NOT EOF IS BAD(DER)
3050 ? :? "Error Reading Segment Header"
3055 Gosub 2635:Gosub 2660
3060 End
3065 Rem
3070 Rem ERROR DURING REGULAR FILE
3075 Rem
3080 Rem EOF MEANS END OF FILE
3085 If Err(0)=136 Then Goto 1865
3090 ? :? "Error reading data"
3095 Gosub 2635:Gosub 2660
3100 End
3105 Rem
3110 Rem ADDRESS RANGE ERROR
3115 Rem
3120 ? :? "Address Range Error During I/O":?
3125 Gosub 2660
3130 End
3135 Rem
3140 Rem OUTPUT CH AS HEX OR DEC
3145 Rem
3150   If Hd=1:Rem HEX OR DEC
3155   B$=Hex$(Ch)
3160   If Ch<=$ff Then B$=B$(3)
3165   ? "$";B$;
3170   ? #2;"$";B$;
3175   Else :Rem DEC
3180   ? Ch;
3185   ? #2;Ch;
3190   Endif
3195 Return
3200 Rem
3205 Rem FILE WRITE ERROR
3210 Rem
3215 ? "Failed to write file: ";File$
3220 Gosub 2635
3225 End