COMPUTE! #30 / NOVEMBER 1982 / PAGE 209
You can customize your Atari BASIC by adding new commands to the language itself. To demonstrate how to do it, the program below adds five DOS commands to BASIC — including a directory command. There are two versions of the same program. Program I is a BASIC loader. You type it in normally and it will create a machine language program for you from the information in the DATA statements. Program 2 is a disassembly of the same routine. It shows how the machine language works and is useful to programmers who know machine language or want to learn more about it. It’s not necessary, however, to understand Program 2 in order to make good use of Program 1.

The Atari Wedge:
Adding Commands To
Atari BASIC

Charles Brannon
Editorial Assistant

A letter was published recently in COMPUTE!’s “Ask The Readers” column, regretting the need for “this POKE or that POKE” to accomplish various tasks. The required solution is an “expanded command set.” An enticing prospect, adding commands to a language, and a seemingly impossible one, too.

Atari BASIC, like most microcomputer BASICS, is “burned” into nonvolatile ROM memory. The machine language routines to list, save, edit, and run your program cannot be altered or “patched” in any way. (However, on a 48K Atari, you can copy the BASIC cartridge to disk as a binary file, modify it with a “machine language monitor,” and load it into the top of memory where it will act almost as a ROM cartridge.)

The most common (and easiest to implement) extension of a language is the addition of “immediate mode” commands. These direct commands, which are not usually executed in a program, but from the keyboard, include RUN, SAVE, LIST, NEW, DOS, etc. Thanks to Atari’s modular Operating System (OS), we can easily add this type of command.

An Overview Of Atari’s Operating System

To understand how the Atari Wedge works, we’ll have to delve into the mysterious 10K ROM. If you just want to use the program and aren’t concerned about the technical details, feel free to skip ahead. The Operating System (OS) of a computer is responsible for all input and output to and from disk, cassette, printer, and keyboard. It can also perform such chores as memory management and screen display. On many microcomputers, the OS does not exist as a separate entity, but is incorporated into the BASIC interpreter.

The Atari, on the other hand, is the first microcomputer with a general-purpose “plug-in” operating system. This goes hand in hand with the use of program and game cartridges. All programs running on an Atari use a common set of routines, from floating point arithmetic to high-resolution graphics routines such as PLOT, DRAWTO, and FILL.

A Mini-language

So, instead of BASIC providing a marginal operating system (which on many machines is a maze of machine language calls, requiring incompatible register setup and initialization), we have a BASIC cartridge which uses “universal” OS routines. A good OS simulates a mini-language. It provides documented, unchanging (between various revisions), unified subroutines with full parameter passing and error-checking.

Furthermore, a good OS is extensible. All the major routines and subroutines are accessed indirectly, through pointers. That is why the Atari is so flexible. If you want to change the personality of your computer, just change one of the vectors of a given routine to point to your machine language routine. Your program can then pass on control to the default program.

A Flexible Computer

This indirection is visible throughout the Atari. At the low end is color indirection, where you can change the color of anything drawn to another color merely by changing one color register. The default character set pointer can be changed to point to a user-designed character set. The system interrupt routines and display list interrupts are all fully accessible via a table of pointers. The BREAK key can be masked; the keyboard scan routine can be modified or bypassed; exotic periphetals can be serviced. And all input/output devices are user-definable, from the keyboard to the disk drive.

A notable peculiarity of the Atari is that not just the disk drive or printer, but also the TV screen and keyboard, are considered “peripherals.” You don’t print a character to the screen on the Atari; you send a character or buffer to the Editor device.

Chain Of Command

Through the hierarchy of a subset of the OS, the CIO (Central Input/Output), BASIC politely requests a line of input from screen and keyboard. After BASIC makes this request, control is passed to CIO, which calls the Editor. The Editor lets the user enter a “line” of text (which can be up to three screen lines long). The user can use cursor controls to edit the line or to move the cursor anywhere on the screen to edit another line.

When RETURN is pressed, the line the cursor is on is placed into a buffer (block of memory). Next, CIO gives this information to the calling routine via another buffer. The CIO is designed to be easy to use from machine language. If you think it sounds complicated, imagine performing all these tasks without an operating system!

Driving A Wedge

      ┌──────────────────┐
      │ Operating System │
      └────────┬─────────┘
               ∨
            ┌─────┐
            │ CIO │
            └──┬──┘
               ∨
       ┌─────────────────┐
       │  Handler Table  │
       └───────┬─────────┘
  ┌───┬───┬───┬┴──┬───┬───┬───┬─────────>
  │   │   │   │   │   │   │   │     up to
  │   │   │   │   ├─┐ │   │   │     34
  ▼   ▼   ▼   ▼   ▼ │ ▼   ▼   ▼     names
  D   P   C   S   E │ K   R   User
  i   r   a   c ┌>d │ b   S
  s   i   s   r │ i │ d   2
  k   n   s   e │ t │     3
      t   e   e │ o │     2
      e   t   n │ r ∨
      r   t     │  ┌───────┐
                └──┤ Wedge │
                   └───────┘

Figure 1. Wedging into a Vector

We don’t have to modify BASIC at all. We just “wedge” our way into the Editor device, “E:”. As intimated, even the “system” devices such as “E:” or “D:”, the disk “driver,” can be replaced. Usually, however, you don’t want to replace a vectored routine; you just want to insert an additional task. In this case, you point the vector to your routine, which performs the little extra task and then calls the main routine. This “bypass” explains the term wedge.

The Handler table contains the names of all the devices. If you wanted to, you could change the name of the cassette device (C:) to another character, such as T: (for Tape), by finding the “C” in the table and changing it to a “T”. Along with each name, the Handler table includes an address that points to another table of addresses that point to all the functions of that particular device, this is multi-level indirection. There is even a vector that points to a list of vectors!

We want to modify the Editor, so we change the first vector to point to our list of vectors. All we really need to do is change one of the vectors in the Editor’s list of vectors, the “Get Character” address. Since this list is in ROM, at $E400, we need to copy this 16-byte table to RAM, modify it, and re-point the Handler table to our RAM version of the Editor Handler table.

A Monitor Monarchy

Now that we’ve got the Operating System calling our routine instead of the Editor in ROM, we’ve got total control of almost all console input/output. The Get Character routine, instead of Calling E:, asks us for an ASCII character, presumably from the screen and keyboard. We comply by calling the default routine in ROM.

This seems rather roundabout, doesn’t it? But we reserve the right to monitor all characters returned to the Operating system, and hence, BASIC. We get to examine every line of input before that line is returned to BASIC, where any strange new commands would be scorned with an error message.

So, we just catch the carriage return code and leisurely examine the input buffer, located at $0580. All we have to do is compare it against a table of commands, and, if we find a match, execute the command. If not, we just return the line to CIO (and CIO gives it back to BASIC) on the assumption that it’s either a blank line, a BASIC command, or a syntax error. Sounds simple, but such a “parsing” routine is quite a headache to code and understand.

A REMarkable Solution

After we’ve intercepted and executed the line, how do we prevent a syntax error when we return the line to BASIC? (And since we’ve “cut in,” we have to follow protocol and return something.) One solution would be to erase the buffer by filling it with spaces. An easier trick would be to change the first character of the line to a period, e.g., “SCRATCH D:TEMP” would become “.CRATCH D:TEMP”. Since BASIC interprets a leading period as an abbreviation for “REM” (don’t ask me why, it’s just a lucky fluke), BASIC conveniently ignores the command and returns READY (which it wouldn’t if we merely blanked out the line).

The parser routine makes it easy for you to add commands. Just place the name of each command, followed by a zero, and the address where you want control to be transferred after the command is recognized, in COMTBL (COMmand TaBLe, see Program 2). The length of the line is found in LENGTH, and the second character after the command is returned in PARMS (since this is where any parameters would be).

command   xxx,yyy,zzz
          ↑          ↑
         PARAMS     LENGTH

Figure 2.

Note that the length is one character past the end of the string, assuming you number from zero. Your command processor can find the command string in LBUFF ($0580).

Theoretically, this technique can be used to add commands to any language environment. You only have to find a way to make the language processor ignore commands when you return the line (such as blanking it out). Of course, the commands themselves are usually language-specific.

Copious Commands

Now the way is open to add a plethora of BASIC utility commands. Of course, these will have to be written in machine language and interfaced with the Wedge. I’ve included the resident DOS commands LOCK, UNLOCK, RENAME, and SCRATCH, as well as DIR to print the directory.

You can study the assembly listing (Program 2). If you have an assembler, try typing it in and modifying it. It contains a wealth of techniques and information, such as pattern matching, indirect subroutine calls, making a routine “RESET-proof,” using CIO for input/output from machine language, long branching, modular programming, calling BASIC’s ERROR routine, even “pressing” SYSTEM RESET from within a program.

Using Wedge 1.0

A machine language program can be hard to even enter into the Atari without an assembler. Program 1 will write the machine language to disk in the form of an “AUTORUN.SYS” file. Save this program so you can write copies to any disk. When you boot this disk, the AUTORUN file will automatically load and initialize the Wedge. You can use the Wedge’s “console DOS” directly, without waiting for the disk utility package (DUP.SYS) to load in, and without losing any programs in memory.

Commands provided are DIR (lists the directory of drive one), LOCK, UNLOCK, SCRATCH (delete), and RENAME. Remember to include the D: (or D2: for drive two, if you have one) in the filename with all the commands except DIR. With RENAME, use the convention RENAME D:oldname,newname.

The Wedge is “persistent”; in other words, it re-initializes itself when you press SYSTEM RESET, so it’s kind of hard to get rid of it. An additional command, KILL, removes the Wedge. You can bring back the Wedge with: PRINT USR(7936).

These commands are just a start. Many others are possible: RENUMBER. FIND, AUTO line number, UPDATE (removes unused variables from the variable name table), and more. If you come up with a useful BASIC utility in machine language, send it to COMPUTE! to be incorporated into a future version of the Wedge.

Talking Back

We’ve managed to intercept BASIC at the command level. In future issues, we’ll go into how you can tell BASIC what to do from machine language. We’ll even try to pursue that elusive aim — actually adding commands to a running program.

Program 1: BASIC Loader

100 REM WEDGE BASIC LOADER
110 GRAPHICS 0:? "Insert a DOS 2.0S diskette"
120 ? "with DOS.SYS in drive 1."
130 ? :? "Press RETURN when you have done this."
140 IF PEEK(764)<>12 THEN 140
150 POKE 764,255
160 ? :? "Now writing the Medge AUTORUN.SYS file"
170 TRAP 190
180 OPEN #1,8,0,"D:AUTORUN.SYS":TRAP 40000:GOTO 200
190 CLOSE #1:? :? "Can't open AUTORUN.SYS for write.":END
200 PUT #1,255:PUT #1,255:REM $FFFF HEADER
210 PUT #1,0:PUT #l,31:REM $1F00 START
220 PUT #1,74:PUT #1,33:REM $214A END
230 FOR I=7936 TO 8522+6: REM INCLUDE 6-BYTE AUTORUN
240 READ A:TRAP 310:PUT #1,A;TRAP 40000
250 CKSUM=CKSUM+A
260 NEXT I
270 IF CKSUM<>60435 THEN ? "{BELL}Bad number in DATA statements.":ERR=1
280 CLOSE #1
290 IF  NOT ERR THEN ? :? "DATA ok, write successful."
300 END
310 ? :? "Error-";PEEK(195);" when attempting disk write.":CLOSE #1:END
320 REM
330 REM Following is the decimal
340 REMO equilvalent of Wedge 1.0
350 REM Must be typed in perfectly
360 REM In order to function.
370 REM
7936 DATA 104,165,12,141,37,31
7942 DATA 165,13,141,38,31,169
7948 DATA 36,133,12,169,31,133
7954 DATA 13,32,4 3,31,32,92
7960 DATA 31,169,75,141,231,2
7966 DATA 169,33,14 1,232,2,96
7972 DATA 32,64,21,32,11,31
7978 DATA 96,169,80,141,68,3
7984 DATA 169,31,14 1,69,3,169
7990 DATA 0,141,73,3,169,12
7996 DATA 141,72,3,169,11,141
8002 DATA 66,3,162,0,32,86
8008 DATA 228,152,48,1,96,76
8014 DATA 55,33,65,116,97,114
8020 DATA 105,32,87,101,100,103
8026 DATA 101,155,160,0,185,26
8032 DATA 3,201,69,240,7,200
8038 DATA 200,192,34,208,243,96
8044 DATA 200,169,165,153,26,3
8050 DATA 200,169,31,153,26,3
8056 DATA 162,0,189,0,228,157
8062 DATA 165,31,232,224,16,208
8068 DATA 245,169,184,141,169,31
8074 DATA 169,31,141,170,31,24
8080 DATA 173,4,228,105,1,141
8086 DATA 186,31,173,5,228,105
8092 DATA 0,141,187,31,169,0
8098 DATA 133,203,96,251,243,51
8104 DATA 24 6,184,31,163,246,51
8110 DATA 246,60,24 6,76,228,243
8116 DATA 56,1,1,125,32,32
8122 DATA 62,24 6,8,201,155,240
8128 DATA 4,230,203,40,96,140
8134 DATA 181,31,142,182,31,165
8140 DATA 203,240,86,169,51,133
8146 DATA 205,169,32,133,206,160
8152 DATA 0,177,205,217,120,5
8156 DATA 208,12,200,177,205,240
8164 DATA 40,196,203,208,240,76
8170 DATA 37,32,201,255,240,53
8176 DATA 160,0,177,205,240,9
8182 DATA 230,205,144,2,230,206
8188 DATA 76,242,31,24,165,205
8194 DATA 105,3,133,205,144,2
8200 DATA 230,206,76,215,31,200
8206 DATA 132,204,177,205,141,183
8212 DATA 31,200,177,205,141,184
8210 DATA 31,108,183,31,160,0
8224 DATA 169,46,153,128,5,169
8230 DATA 0,133,203,169,155,172
8236 DATA 101,31,174,182,31,40
8242 DATA 96,68,73,82,0,125
8248 DATA 32,83,67,82,65,84
8254 DATA 67,72,0,22,33,76
8260 DATA 79,67,75,0,27,33
8266 DATA 85,78,76,79,67,75
8272 DATA 0,32,33,82,69,78
8278 DATA 65,77,69,0,37,33
8284 DATA 75,73,76,76,0,42
8290 DATA 33,255,155,50,54,32
8296 DATA 70,82,69,69,32,83
8302 DATA 69,67,84,79,82,83
8308 DATA 155,155,0,0,68,58
8314 DATA 42,46,42,162,80,169
8320 DATA 12,157,66,3,32,86
8326 DATA 228,162,80,169,3,157
8332 DATA 66,3,169,6,157,74
8338 DATA 3,169,120,157,68,3
8344 DATA 169,32,157,69,3,32
8350 DATA 86,228,152,16,3,76
8356 DATA 55,33,162,80,169,5
8362 DATA 157,66,3,169,100,157
8368 DATA 68,3,141,68,3,169
8374 DATA 32,157,69,3,141,69
8380 DATA 3,169,20,157,72,3
8386 DATA 141,72,3,32,86,228
8392 DATA 152,48,13,169,9,141
8398 DATA 66,3,162,0,32,86
8404 DATA 228,76,166,32,162,80
8410 DATA 169,12,157,66,3,32
8416 DATA 86,228,76,30,32,162
8422 DATA 80,157,66,3,169,0
8428 DATA 157,73,3,164,203,153
8434 DATA 128,5,56,152,229,204
8440 DATA 157,72,3,24,169,128
8446 DATA 101,204,157,68,3,169
8452 DATA 5,105,0,157,69,3
8458 DATA 32,86,228,152,16,3
8464 DATA 76,55,33,76,30,32
8470 DATA 169,33,76,229,32,169
8476 DATA 35,76,229,32,169,36
8482 DATA 76,229,32,169,32,76
8488 DATA 229,32,173,37,31,133
8494 DATA 12,173,38,31,133,13
8500 DATA 76,116,228,72,162,80
8506 DATA 169,12,157,66,3,32
8512 DATA 86,228,104,162,255,154
8518 DATA 133,185,76,64,185
9000 REM DATA FOR AUTORUN ADDRESS
9010 DATA 224,2,225,2,1,31
9020 REM END OF DATA STATEMENTS

Program 2: Wedge Disassembly

0100 ; The Atari Wedge
0110 ;
0120            *=$1F00
0130 ICCOM    =$0342
0140 ICBADR   =$0344
0150 ICBLEN   =$034B
0160 ICAUX1   =$034A
0170 COPN     =$03
0180 CPTXTR   =$09
0190 CGTXTR   =$05
0200 CPBINR   =$0B
0210 CCLOSE   =$0C
0220 CIO      =$E456
0230 OPDIR    =$06
0240 HATABS   =$031A
0250 LBUFF    =$0580
0260 LENGTH   =$CB
0270 MEMLO    =$02E7
0280 PARMS    =$CC
0290 COM      =$CD
0300 DOSINIT  =$0C
0310 ENTRY    PLA   ;For BASIC initialization
0320 ; Make wedge "RESET-proof"
0330 INIT
0340          LDA DOSINIT     ;Save DOS
0350          STA REINIT+1    ;initialization
0360          LDA DOSINIT+1   ;inside the REINIT
0370          STA REINIT+2    ;JSR call
0380 ;
0390 INIT2    LDA #REINIT&255 ;Replace DOS init
0400          STA DOSINIT     ;with Wedge
0410          LDA #REINIT/256 ;init
0420          STA DOSINIT+1
0430          JSR MSG ;Print message
0440          JSR ECHANGE     ; hookup ne
0450          LDA #ENDWEDGE&255 ; Bump up
0460          STA MEMLO
0470          LDA #ENDWEDGE/256 ;low memory pointer
0480          STA MEMLO+1
0490          RTS
0500 ;
0510 REINIT   JSR XXXX ;XXXX is filled in with DOSINIT
0520          JSR INIT2
0530 XXXX     RTS
0540 ;
0550 ; Print "welcome" message
0560 ;
0570 MSG      LDA #WMSG&255 ; Store address of
0580          STA ICBADR     ; message
0590          LDA #WMSG/256
0600          STA ICBADR+1
0610          LDA #0         ;Set length
0620          STA ICBLEN+1
0630          LDA #12
0640          STA ICBLEN
0650          LDA #CPBINR    ; Ignore carriage-returns
0660          STA ICCOM
0670          LDX #0         ;File O, the editor
0680          JSR CIO        ;Call CIO to print it
0690          TYA
0700          BMI ERR        ;If no error, return
0710          RTS
0720 ERR      JMP ERROR
0730 ;
0740 WMSG     .BYTE "Atari Wedge", 155
0760 ;
0770 ; Following replaces the old E:
0780 ;
0790 ECHANGE  LDY #0         ; Search for E:
0800 ELOOP    LDA HATABS,Y   ; in handler table
0810          CMP #'E
0820          BEQ EFOUND     ; Found end?
0830          INY            ; no, next entry
0840          INY
0850          CPY #34        ; end of table?
0860          BNE ELOOP
0870          RTS            ; return
0880 ;
0890 ; Store new handler table address
0900 ;
0910 EFOUND   INY
0920          LDA #WEDGETAB&255
0930          STA HATABS,Y
0940          INY
0950          LDA #WEDGETAB/256
0960          STA HATABS,Y
0970 ; Transfer Editor table to Wedge table
0980          LDX #0
0990 XFER     LDA $E400,X
1000          STA WEDGETAB,X
1010          INX
1020          CPX #16
1030          BNE XFER
1040 ; Patch in MYINPUT routine
1050          LDA #MYINPUT-1&255
1060          STA WEDGETAB+4
1070          LDA #MYINPUT-1/256
1080          STA WEDGETAB+5
1090          CLC
1100          LDA $E404      ; Set character address
1110          ADC #1         ; Actual address is +1
1120          STA MYINPUT+1  ; Egads !
1130          LDA $E405      ; Self-modifying code !
1140          ADC #0         ;(Accept any carry )
1150          STA MYINPUT+2
1160          LDA #0
1170          STA LENGTH     ;Clear length initially
1180          RTS
1190 ;
1200 ; Wedge handler address table
1210 WEDGETAB *=*+16
1220 YSAVE    *=*+1          ;Used to save Y register
1230 XSAVE    *=*+1          ;Ditto for X
1240 JUMPADR  *=*+2          ;used for indirect JMP
1250 MYINPUT
1260 ; The $F63E address is actually placed here by above code
1270 ; to permit this routine to run on the Revision B OS
1280 ; (where it wouldn't be $F63E)
1290          JSR $F63E      ;Get a character from E:
1300          PHP
1310          CMP #155       ;End of line? (CR)
1320          BEQ ENDLINE    ;Yes, complete line ready
1330          INC LENGTH
1340          PLP
1350          RTS            ;No, let CIO have the character
1360 ENDLINE
1370          STY YSAVE ;Save Y for CIO
1380          STX XSAVE
1390          LDA LENGTH
1400          BEQ RETURN.LINE
1410 LOOKUP
1420          LDA #COMTBL&255 ; Set up indirect pointer for
1430          STA COM
1440          LDA #COMTBL/256 ;command table
1450          STA COM+1
1460 NEXTCOM  LDY #0
1470 COMPLOOP
1480          LDA (COM),Y    ;Compare command against line buffer
1490          CMP LBUFF,Y    ;Okay so far?
1500          BNE NOTSAME    ;no match
1510          INY
1520          LDA (COM),Y    ;is next character null?
1530          BEQ COMPOUND   ;yes, command found
1540          CPY LENGTH     ;exceeded limits?
1550          BNE COMPLOOP   ;if not, continue comparison
1560          JMP RETURN.LINE ;give line to language
1570 NOTSAME  CMP #255       ; End of table?
1580          BEQ RETURN.LINE
1590          LDY #0         ; No, skip over command
1600 FINDEND  LDA (COM),Y
1610          BEQ ENDCOM     ;Hit the zero yet?
1620          INC COM        ;No, next character
1630          BCC NOINC1
1640          INC COM+1
1650 NOINC1   JMP FINDEND   ;continue until null byte found
1660 ENDCOM   CLC           ;Add 3 to skip over null byte
1670          LDA COM       ;and JMP address
1680          ADC #3
1690          STA COM
1700          BCC NOINC2    ;Check for carry
1710          INC COM+1
1720 NOINC2   JMP NEXTCOM
1730 COMPOUND
1740          INY
1750          STY PARMS     ;Y is index into parameters
1760          LDA (COM),Y    ;Load JUMPADR with command address
1770          STA JUMPADR
1780          INY
1790          LDA (COM),Y
1800          STA JUMPADR+1
1810          JMP (JUMPADR)  ;Execute!
1820 EXIT     LDY #0         ;Commands return here
1830          LDA #'.        ;Change first character to
1840          STA LBUFF,Y    ;"." or REM
1850 ;                       Allows BASIC to ignore line
1860 RETURN.LINE
1870          LDA #0
1880          STA LENGTH
1890          LDA #155       ;Return EOL to CIO
1900          LDY YSAVE      ;Restore Y
1910          LDX XSAVE      ;and X
1920          PLP            ;and processor status
1930          RTS            ;That's it
1940 COMTBL
1950 ; Wedge commands and command table
1960 ; Format is:
1970 ; .BYTE "COMMAND" ,0
1980 ; .WORD COMMAND,ADDRESS
1990 ; End of table is
2000 ; .BYTE 255
2010          .BYTE "DIR",0
2020          .WORD DIR
2030          .BYTE "SCRATCH",0
2040          .WORD SCRATCH
2050          .BYTE "LOCK",0
2060          .WORD LOCK
2070          .BYTE "UNLOCK",0
2080          .WORD UNLOCK
2090          .BYTE "RENAME",0
2100          .WORD RENAME
2110          .BYTE "KILL",0
2120          .WORD KILL
2130          .BYTE 255
2140 ;
2150 DIRBUF   *=*+20
2160 DIRNAME  .BYTE "D:*.*"
2170 ;
2180 ; Start of commands:
2190 ;
2200 DIR
2210          LDX #$50       ; IOCB#5
2220          LDA #CCLOSE
2230          STA ICCOM,X
2240          JSR CIO  ;CLOSE#5
2250 ; OPEN#5,6,0,"D:*.*"
2260          LDX #$50       ;channel #5
2270          LDA #COPN      ;open command
2280          STA ICCOM,X
2290          LDA #OPDIR     ;special "directory" command
2300          STA ICAUX1,X
2310          LDA #DIRNAME&255 ;filename (wildcard)
2320          STA ICBADR,X
2330          LDA #DIRNAME/256
2340          STA ICBADR+1,X
2350          JSR CIO        ;set it up!
2360          TYA
2370          BPL NOERR1
2380          JMP ERROR
2390 ; Print a line to the Editor
2400 NOERR1
2410 NEXT     LDX #$50       ;#5
2420          LDA #CGTXTR    ;Get a line
2430          STA ICCOM,X
2440          LDA #DIRBUF&255 ;Put it into the buffer
2450          STA ICBADR,X
2460          STA ICBADR
2470          LDA #DIRBUF/256
2480          STA ICBADR+1,X
2490          STA ICBADR+1
2500          LDA #20        ;Maximum length is 20
2510          STA ICBLEN,X   ;(actually 17)
2520          STA ICBLEN
2530          JSR CIO
2540          TYA            ;Check for end of file
2550          BMI ENDIR      ;On error, finished directory
2560 NOERR2   LDA #CPTXTR    ;Put text record (print a line)
2570          STA ICCOM
2580          LDX #0         ;Channel 0 is open to the Editor
2590          JSR CIO
2600          JMP NEXT       ;Read next line
2610 ;
2620 ENDIR    LDX #$50       ;CLOSE#5
2630          LDA #CCLOSE
2640          STA ICCOM,X
2650          JSR CIO
2660          JMP EXIT
2670 ;End of directory routine
2680 ;
2690 ;Following routine is used by lock
2700 ;unlock, scratch, and rename
2710 ;Filename buffer is in LBUFF
2720 ;e.g. LOCK D:TEMP
2730 ; this     ^   portion is used
2740 ; to tell CIO the filename.
2750 CALLCIO
2760          LDX #$50       ;Use file 5 (XID n, #5, etc. )
2770          STA ICCOM,X    ;Store command
2780          LDA #0         ;Clear MSB
2790          STA ICBLEN+1,X ;of length
2800          LDY LENGTH
2810          STA LBUFF,Y
2820          SEC            ;Get length
2830          TYA            ;of filename
2840          SBC PARMS      ;(skip over command name)
2850          STA ICBLEN,X
2860          CLC
2870          LDA #LBUFF&255 ;PARMS is start of parameters,
2880          ADC PARMS      ;the space in LBUFF
2890          STA ICBADR,X   ;after the command
2900          LDA #LBUFF/256
2910          ADC #0         ;Catch any carry
2920          STA ICBADR+1,X
2930          JSR CIO ; Do the job
2940          TYA
2950          BPL NOERR3
2960          JMP ERROR
2970 NOERR3   JMP EXIT
2980 ;
2990 SCRATCH  LDA #33
3000          JMP CALLCIO
3010 LOCK     LDA #35
3020          JMP CALLCIO
3030 UNLOCK   LDA #36
3040          JMP CALLCIO
3050 RENAME   LDA #32
3060          JMP CALLCIO
3070 ;
3080 ;Remove Wedge
3090 ;
3100 KILL     LDA REINIT+1   ;Restore old DOS
3110          STA DOSINIT    ;vector
3120          LDA REINIT+2
3130          STA DOSINIT+1
3140          JMP $E474      ;"Press" SYSTEM RESET
3150 ;
3160 ;
3170 ; End of current wedge
3180 ; (Although more commands can be added.)
3190 ; See future issues of COMPUTE!
3200 ;
3210 ERROR    PHA        ;Save error code
3220          LDX #$50   ;close files
3230          LDA #CCLOSE
3240          STA ICCOM,X
3250          JSR CIO
3260          PLA        ;retrieve error code
3270          LDX #$FF   ;reset stack
3280          TXS
3290          STA $B9    ;tell BASIC the error code
3300          JMP $B940  ;call the ERROR routine
3310 ; in the BASIC cartridge
3320 ;
3330 ENDWEDGE
3340 ; Autorun
3350 ;
3360          *=$02E0
3370          .WORD INIT
3380 ;
3390          .END