;CPM+ADD4.ASM Some useful CP/M+ addresses, etc. ; ; Jerry Levy ; (215) 657-0898 ; voice - evenings ; ; This will only work for CP/M Plus implementations. It will work ; on any CP/M Plus system. ; ; ; This all began when I needed a few addresses to install BYE5. ; Because I was working with three different bios's on my ; Apple ][ (ALS CP/M Card bios's 3.01b2, 3.01c2, and the Public ; Domain CP-PLUG bios 3.02b), I decided to write a short program. ; It just got out of hand... ; ; ALS, for the ALS CP/M Card on Apple ][, added two special bios ; functions in v. 3.01b1, a third in 3.01b2, and a fourth in 3.01c2. ; CP-PLUG, also for the ALS CP/M Card, added three of those. As ; these new bios functions (#'s 33-36) are non-standard, I've used ; an ALSCARD equate to control whether ALS's names for them are ; printed out as part of the bios jump table. The equate changes ; nothing else. ; ; Where I could in the output produced, I tried to spell things as ; they are spelled in DRI's "CP/M Plus System Guide". ; ; In v.3 I corrected an error in the v.2 dump routine that obviates ; my (erroneously) perceived need to do one of the dumps from a ; buffer. In v.3 I kept dump-buffering on only as an option controlled ; by the new equate, BFDMP. To select dump-buffering set BFDMP EQU YES. ; You should not need to do that for any of the memory areas we are ; dumping, so best to leave it NO. ; ; ; Feel free to modify and add special stuff for other implementations, ; but please note below that you've done it in the version log. ; Isolate hardware-specific stuff by using equates as I've done for ; the ALS CPM Card, indicate your name and date. Most recent change ; last. If you can, leave a copy on George Peace's Fog #10 RCP/M ; (717-657-8699) and also leave me a note there. That will allow me ; to keep track of and then to distribute changes. ; ; I've seen address programs for CP/M 2.2 but they don't quite ; cut it for CP/M Plus. ; -JL ;------------------- ; FIX FOR BUG IN CPM+ADD2: ; ; If you prefer CPM+ADD2 to this revision, make the following change in ; CPM+ADD2.ASM and reassemble. It corrects an error in the dump routine. ; This fixes the bug that made me think I had to buffer for one of the ; dumps. If you didn't change the program at all, the bug just laid there, ; but if you made changes, it could bite. ; ; To make the fix in CPM+ADD2, locate the dump2: label, then a bit further on ; ; find... and change to... ; ; jnz cont jnz contin ; mov a,e mov a,e ; cmp l cmp l ; jz done jz done ; cont: inx h contin: inx h ; ; This change can also be made with SID; see CPM+ADD2.FIX for details. ; ; Note also that @MXTPA, one of the items which CPM+ADD2 outputs, is only the ; entry to BDOS in the situation where no RSX's are active, otherwise it is ; the entry to the lowest RSX. In v.3 (CPM+ADD3) I have altered the @MXTPA ; description in the output to signify this. ; -JL ;------------------- ; 28-Feb-87 Instead of dumping the regular Disk Parameter Headers ; v 4 commencing at XDPH+0, we output the full Extended DPH's, ; reaching back to XDPH-10 for the start of the dump. ; ; Distributed CPM+ADD4.UPD and revised .LBR ; ; Possible bugs/limitations for all versions of CPM+ADD ; -------------------------------------------------------------- ; 1. Since CPM+ADD loads and runs from the TPA bank (Bank1), ; some possibility exists that the program will output code ; in Bank1 in a few situations where the true bank of the ; address is Bank0 (example: when the Disk Parameter Block ; or Disk Parameter Header may be DSEG'ed in the bios). This ; won't always be a problem, even when code of interest is ; DSEG'ed, but be forewarned about what to track down if ; output doesn't make sense. ; ; 2. Another problem occasionally encountered is that the ; location of the common memory boundary which this program ; gets from the CP/M Plus System Control Block isn't always ; to be believed. I suspect that when it is not correct it ; is because hardware or firmware in a particular system sets ; the common memory independently. Seems to be the case with ; the ALS CP/M Card (for Apple ][ and //e). ; -JL ; ; 13-Sep-86 Extended length of SCB, after reading CPM3SCB.DOC in Jim ; v 3 Lopushinsky's CPM3SCB.LBR. Changed format of dumps and ; fixed a bug in the dump routine. Added more things, including ; version printout, TPA size, information on RSX's that are ; active, and a dump of system page zero. Lots of little ; changes. Several free dumps of your choice can be set ; (Wild-Card Dump option). ; ; 15-Dec-85 Added more addresses/values, a few dumps, other changes. ; v 2 -JL ; ; 22-Nov-85 Created. ALS-CP/M Card-specific bios functions named as ALS ; v 1 names them if ALSCARD EQU YES. ; -JL ;------------------ no equ 0 yes equ not no ;------------------ ; EQUATES YOU CAN CHANGE ; (see Wild-Card Dumps, below, for other optionals) BFDMP EQU NO ;Leave NO unless dumps go wild on you. YES means ; memory that is dumped is transferred to buffer and ; dumped from there rather than from the original ; location. If you select this YES, AND you also ; have dumps longer than 256 bytes, increase BFSZ, ; below. BFSZ EQU 256 ;Only if BFDMP is selected YES. Sets buffer size. ; 256 is OK for BFDMP EQU YES and present dumps ALSCARD EQU NO ;Leave NO, unless you have Apple ][ with ALS CP/M Card. ; This only attaches ALS's labels to four special ; bios functions added by ALS. There are no other ; alterations introduced by how you set this equate. ; ; The next 4 are defaults for chars used in the RSX name when RSX prefix info ; is presented. Change if your console/printer cannot handle them. They are ; used in place of unprintable chars in printing out the rsx name field. Ctrl ; chars can send your printer or console off to the country and we therefore ; replace unprintables by a suitable combination of printables. ; ; An example of how such names can arise in the RSX name field is the case ; when the operating system or Loader processes multiple commands that are ; entered at the command line prompt, each separated by an exclamation point. ; An RSX is created with the name field uninitialized. The name field thus ; contains whatever adventitious characters were in memory where the name ; field happens to have been placed. ; ; These 4 characters can be patched with SID.COM. ; The patch locations are: ; 0103h (carat) ; 0104h (sedilla) ; 0105h (quote) ; 0106h (rubout) ; ; The rubout char is ascii 07fh and because some O.S.'s replace it with a ; backspace, the uploaded version uses 7dh as the "rubout" char. If your ; system will accept 7fh as a printable, 7fh is more appropriate because it ; will not be confused with the 7dh or fdh characters themselves. ; rubout equ 7dh ;'}', in place of 7fh. If 7fh prints, use 7fh ; ; ; The other 3 are prefix chars used as follows: ; ; ;(No prefix signifies an ordinary, printable character) carat equ 5eh ;^ prefix a control char with hi bit not set sedilla equ 7eh ;~ prefix a non-control char with hi bit set quote equ 22h ;" prefix a control char with hi bit set ; ;------------- ; ; OTHER EQUATES boot equ 0000 bdos equ 0005 ; conin equ 01 Šconout equ 02 prstr equ 09 vers equ 12 ;Cp/m version call scblock equ 49 ;Get/set scb dirbios equ 50 ;Direct bios calls under cp/m 3 ; bell equ 07 lf equ 10 cr equ 13 ; seldsk equ 09 ;Bios function to select drive ;-------------- ; org 0100h ; jmp begin ; ;-------------- car: db carat ;Here is where we patch with SID sed: db sedilla quot: db quote rub: db rubout ;-------------- ; org 0120h - 8 db ' 0120H->' ;Some help for patchers ; ;------------------------------------ ; WILD-CARD DUMPS ; You can specify your own additional dumps. ;---- wc$dmp: db 0 ;The enabler byte ; 0feh = we don't do this one but may do next ; 00h = we don't do any more wild-card dumps ; 0ffh (or anything besides 00h and 0feh) ; = we do the dump ; dw 0000 ;Number of bytes to dump ; dw 0000 ;Dump data which starts at this address ; dw 0000 ;Format so first byte in dump is at this ; "address". Usually the same as the start ; address in the dw above. ; ; If dealing with a table where you want to ; show offsets from the start address (study ; the second SCB dump when CPM+ADD3.COM is ; run), set this to 0000. ; ; ...or, as you wish, set to anything useful ; to help you understand the data you dump. ;---- db 0 ;Enabler byte of second wild-card dump dw 0000 ; etc. dw 0000 dw 0000 ;---- db 0 ;Third w-c dump. Add as many more as you wish dw 0000 dw 0000 dw 0000 ;---- wc$end: db 0 ;Terminator, leave as null byte ; ; ; The Wild-Card dump parameters above are all patchable with SID. We quit ; the Wild-Card dump subroutine with the first null enabler byte we ; encounter. ; ; Patching Locations ; ----------------------------------------------- ; # of Bytes Dump Addr Dump Index ; Enabler ---------- ---------- ---------- ; byte lsb msb lsb msb lsb msb ; ------- ---- ---- ---- ---- ---- ---- ; First Dump 0120h 0121h 0122h 0123h 0124h 0125h 0126h ; Next, 0127h 0128h 0129h 012Ah 012Bh 012Ch 012Dh <- address ; etc. above + 7 ;------- db '<-END PATCH AREA ' ;Warning for patchers ; hello: db cr,lf,'SOME USEFUL CPM+ ADDRESSES AND REGISTER CONTENTS',cr,lf db 'CPM+ADD v.4 28-Feb-87 Jerry Levy',cr,lf,cr,lf,'$' pausemsg: db cr,lf,'Control-S stops scrolling, Control-Q resumes.',cr,lf db 'Press any other key to continue...',cr,lf,'$' ; begin: lxi d,hello ;Sign on call print ; call ver ;Get/check/store cp/m version. Sets carry flag jc back ; if less than ver. 3.0 and we exit program ; lxi d,pausemsg call print mvi c,conin ;Take time to read "hello" call bdos ; xra a sta stg$fl ;Set flag so store ascii output in texts call bios$ad ;Bios addr's, convert to ascii, place in text call scb$locns ;Addresses and contents via SCB accesses call misc$locns ;Others ; lxi d,archt ;Output system architecture call print ; call rsxmap ; lxi d,text ;Output table of addresses, etc. call print ; call dumps ;Dumps DPH's, DPB's, SCB, Zero Page, etc. ; back: ret ; ; End of main program ;---------------------- ; Š; ver: mvi c,vers ;Get bdos id and version number call bdos shld versn mov a,h jnz notcpm3 ;H is zero for CP/M; if non-zero, we will exit mov a,l ;Check version cpi 30h cnc vstore jc notcpm3 ;If less than 30h (30h=ver 3.0) we will exit ; vstore: push psw ;Present version-byte as nybble1/period/nybble2 lda versn lxi d,vstg+14 ;Offset from label by 14 call hexout lxi d,vstg+15 call hexout mvi a,'.' sta vstg+15 pop psw ret ; versn: db 0 ;Cp/m version ; notcpm3:push psw lxi d,xcpm3 ;Tell us not running cpm-plus call print pop psw ret ;Return and exit ; xcpm3: db bell,cr,lf db 'Exiting. This program only functions under CP/M Plus',cr,lf db ' (CP/M versions 3.0 and higher).',cr,lf,'$' ;------------------------- ; ; Bios addresses of jumps in the jump table, and of bios routines themselves ; bios$ad:mvi a,0h sta stg$fl ;Set flag so addresses get stored in ram lhld 0001h ;Bios pointer to warm boot address in jmp tbl dcx h ! dcx h ! dcx h ;Back-up to cboot lxi d,biosbas+13 ;Where to put ascii address of jmp tbl entry call hlout ;Translate hex biosbase address to ascii lxi d,cboot+13 ;Also put same address here next1: call hlout ;Cboot or next jmp address to ascii push h ;Preserving HL, advance DE to next storage addr lxi h,8 dad d xchg pop h push h push d ;...and save it inx h ;Point to address of routine call into$hl pop d call hlout pop h inx h ! inx h !inx h ;Advance to where next jump instr (0c3h) is mov a,m cpi 0c3h ;Is it really a jmp instruction? rnz ;We're done if it isn't push h lxi h,19 ;Advance DE by 19 to do next line dad d xchg pop h jmp next1 ; ; Other addresses or memory contents ; misc$locns: ; ; Device table address ; lhld 0001h lxi d,3*19 ;Offset to devtbl jump dad d ;HL points to devtbl jump in bios jmp table inx h call into$hl ;Get what HL points to into HL mov a,m cpi 21h ;First instruction should be an LXI H,addr jnz next2 inx h ;+1 to address of device table (@ctbl) call into$hl ;Get what HL points to into HL lxi d,chrtbl+13 ;Storage call hlout ;...as 4 ascii digits next2: ; ; Drive table address ; lhld 0001h lxi d,3*21 ;Offset to drvtbl jump dad d inx h call into$hl mov a,m cpi 21h ;First instruction should be an LXI H,addr jnz next3 inx h call into$hl ;Get what HL points to into HL lxi d,drtbl+13 ;Storage call hlout next3: ; ; Address of location where selmem stores/accesses current_bank_byte ; lhld 0001h lxi d,3*26 ;Offset to selmem jump dad d inx h call into$hl mov a,m cpi 32h ;First instruction should be an STA addr jnz next4 ; inx h call into$hl lxi d,curbnk+13 ;Storage of current_bank_byte call hlout ; as 4 ascii digits ret next4: ; ; If you want to add more routines to the train, put them here. ; ret ; ;-------- ; into$hl:push d ;Get what HL points to into HL mov e,m ;Transfer lo byte into E inx h ; and hi byte into D mov d,m xchg pop d ret ;-------- ; ; Selected SCB contents ; scb$locns: mvi a,06 ;SCB+06h, a user-definable byte sta scbpb call scbget lxi d,usr1+15 ;Offset from label by 15 for byte call hexout ; mvi a,07 ;SCB+07h, a user-definable byte sta scbpb call scbget lxi d,usr2+15 call hexout ; mvi a,08 ;SCB+08h, a user-definable byte sta scbpb call scbget lxi d,usr3+15 call hexout ; mvi a,09 ;SCB+09h, user-definable byte sta scbpb call scbget lxi d,usr4+15 call hexout ; mvi a,22h ;SCB+22h, CONIN redirection flags sta scbpb call scbget lxi d,cinrx+13 ;Store here after conversion to ascii call hlout ;Convert to ascii ; mvi a,24h ;SCB+24h, CONOUT redirection flags sta scbpb call scbget lxi d,coutrx+13 call hlout ; mvi a,26h ;SCB+26h, AUXIN redirection flags sta scbpb call scbget lxi d,ainrx+13 call hlout ; mvi a,28h ;SCB+28h, AUXOUT redirection flags sta scbpb call scbget lxi d,aoutrx+13 call hlout ; mvi a,2ah ;SCB+2ah, LSTOUT redirection flags sta scbpb call scbget lxi d,loutrx+13 call hlout ; mvi a,3ah ;SCB+3ah, for SCB base address sta scbpb call scbget lxi d,scbbas+13 shld scb$ad call hlout ; mvi a,58h ;SCB+58h, for 16-bit word, days since 1-jan-78 sta scbpb call scbget lxi d,days+13 ;Put address here after translation call hlout ; mvi a,5ah ;SCB+5ah, for bcd hour at SCB+5ah sta scbpb call scbget lxi d,hr+15 ;Offset from label by 15 vs. 13 (byte vs. word) call hexout ; mvi a,5bh ;...bcd minute sta scbpb call scbget lxi d,min+15 call hexout ; mvi a,5ch ;...bcd second sta scbpb call scbget lxi d,sec+15 call hexout ; mvi a,5dh ;...common memory address sta scbpb call scbget lxi d,commem+21 call hlout ; mvi a,62h ;...entry to lowest RSX sta scbpb call scbget shld rsx06 ;Lowest RSX, entry address lxi d,toptpa+13 call hlout dcx h ;Entry address minus 1 = last byte of avail TPA lxi d,avail+29 call hlout ;Store hex... lxi d,availd+28 call dec ;...and decimal lhld rsx06 mvi l,0 shld rsx00 ;Lowest RSX, base address lhld rsx06 dcr h ;Avail TPA size lxi d,avail+38 call hlout lxi d,availd+37 call dec lxi d,availd+45 ;Where to put... call cal$k2 ;...size in K's ; lhld scb$ad ;...top of user TPA mvi l,98h ;is at SCB page start + 98h call into$hl shld bdos06 ;BDOS entry address dcx h lxi d,syst+29 call hlout lxi d,systd+28 call dec lhld bdos06 mvi l,0 shld bdos00 ;BDOS base address lhld bdos06 dcr h lxi d,syst+38 call hlout lxi d,systd+37 call dec lxi d,systd+45 ;Where to put... call cal$k2 ;...size in K's ; ; Compare BDOS entry point and lowest RSX entry point. If same, there are ; no active RSX's, and ret with 0 in rsx$fl:. If not same, ret with 0ffh in ; rsx$fl. ; xra a ! sta rsx$fl ;RSX flag, change to 0ffh if RSX's are present lhld rsx06 ;Retrieve lowest RSX entry address lda bdos06+1 ;Compare to system entry address cmp h jnz z$lsb ;Addresses difft? say so. If the same... lda bdos06 ;...go check msb's cmp l rz ;Addresses same, no RSX's, return z$lsb: mvi a,0ffh ! sta rsx$fl ;Different. Put 0ffhh in rsx$fl ret ; rsx$fl: db 0 ;---------- ; scb$ad: dw 0 ;Storage of DRI's SCB_BASE address ; scbget: mvi c,49 ;Get SCB values lxi d,scbpb call bdos ret ; scbpb: db 0 ;Offset within SCB db 0,0,0,0 dw 0 ;-------- ; xbios: mvi c,50 ;Direct bios call lxi d,biospb call bdos ret ; ; Parameter block for CP/M 3 direct bios calls ; biospb: db 0 ;Bios function areg: db 0 ;Input reg values bcreg: dw 0 dereg: dw 0 hlreg: dw 0 ;-------- ; ; Dumps ; dumps: ; ; DPH's and DPB's of all valid drives ; lxi d,dphdpb ;Print title call print ; ; DPH first ; drives: mvi h,16 ;16 drives, A: thru P: = 0 through 15 mvi l,0 ;First drive to test for shld bcreg drvs: lhld bcreg mov a,l cmp h jz fin ;Done all? mov a,l ;No adi 41h ;Convert drive number to ascii sta drvdph+1 ;Store in heading texts sta drvdpb+1 mvi a,seldsk ;Make direct bios call to seldsk sta biospb ;Store bios function number in biosp call xbios ;Do the call. HL either is DPH, or 0 if no drv shld dph$ad push h lhld bcreg ;Get ready for next drive inx h shld bcreg pop h xra a ;Is returned HL zero? cmp h jnz exists ;HL non-zero, so dph address is in HL cmp l jz drvs ;If returns HL=0, no such drive exists: call crlf lxi d,drvdph call print lxi d,-10 ;Back up to XDPH-10 dad d lxi d,35 ;Dump 35 bytes call addrx xra a ! sta asci$fl ;Set flag so hex-dump only, i.e., no ascii dump ; IF BFDMP call buf$dump ELSE call dump ENDIF ; ; Get DPB and dump it for same drive. ; call crlf lxi d,drvdpb ;Title of dump call print lhld dph$ad lxi d,12 ;Locate DPB address in DPH dad d call into$hl ;Put it into HL lxi d,17 ;Dump 17 bytes call addrx xra a ! sta asci$fl ;Set flag so hex-dump only, i.e., no ascii dump ; IF BFDMP call buf$dump ELSE call dump ENDIF ; jmp drvs ;Do DPH and DPB thing for next drive fin: ;End up here when no more drives to check ; ; Dump system control block ; mvi a,0ffh sta stg$fl ;Set so addresses output to console, not get ; stored in ram call crlf ! call crlf lxi d,scbdmp1 ;Main title of SCB dump call print call crlf lxi d,scbdmp2 ;Sub-title + Lopushinsky's scb base address call print lhld scb$ad ;DRI's documentation: SCB_BASE lxi d,152-100 ;Actual SCB starts 52 bytes earlier than DRI's ; documentation says. Refer Jim Lopushinsky's ; CPM3SCB.DOC in CPM3SCB.LBR call subtr ;Subtract DE from HL and leave result in HL push h ;Save Lopushinsky's address call addresh ; and output it call crlf lxi d,scbdmp3 ;Note about DRI SCB_BASE call print lhld scb$ad call addresh ;Output DRI's SCB base address call crlf ! call crlf lxi d,scbdmp4 ;Another sub-title call print pop h ;Lopushinsky's address back lxi d,152 ;Dump all 152 bytes of it call addrx xra a ! sta asci$fl ;Set flag so hex-dump only, i.e., no ascii dump ; IF BFDMP call buf$dump ELSE call dump ENDIF call crlf mvi a,0 sta stg$fl ;Reset storage flag to set results in text ; ; Dump DRI-documented portion of SCB referencing addresses to SCB_BASE ; call crlf lxi d,scbdmp5 call print lhld scb$ad ;SCB_BASE + 0h lxi d,100 ;Dump 100 bytes push h ! lxi h,0 ! call addrx ! pop h ;Ref. to SCB_BASE (offset=0) xra a ! sta asci$fl ;Set flag so hex dump only ; IF BFDMP call buf$dump ELSE call dump ENDIF call crlf ; ; Dump system zero page ; call crlf lxi d,z$page call print lxi h,0000 ;Address of what we want to dump lxi d,256 ;Dump 256 bytes call addrx mvi a,0ffh ! sta asci$fl ;Set flag so hex + ascii dump ; IF BFDMP call buf$dump ELSE call dump ENDIF call crlf ; ; Wild-Card Dump ; lxi d,wc$dmp ;Start from this pointer wcd: xchg ! lxi d,wc$dmp1 ! lxi b,7 ;Move Wild-Card dump parms db 0edh,0b0h ; with z80 ldir xchg ;DE now points to next parm set at wc$dmp + 7 xra a ! sta wc$end ;In case we accid. patched it to anything else, ; restore to a '0' so terminator is in place call do$we$quit ;Sets zero flag if we should quit jz no$wcd call do$we$dmp ;Set zero if bytes to dump are 0000h or if ; wc$dmp byte is 0feh cnz wcdump jmp wcd ;Do until a 00h enabler byte kicks us out ; wcdump: push d call crlf lxi d,wchdg ;Print Heading call wchdg$ad call print lhld wc$bts1 xchg ;Bytes to dump now in DE lhld wc$ad1 ;Dump from address pointed to by HL push h ! lhld wc$offs ! call addrx ! pop h mvi a,0ffh ! sta asci$fl ;Set flag so hex + ascii dump ; IF BFDMP call buf$dump ELSE call dump ENDIF no$wcd: call crlf pop d ; ret ; asci$fl:db 0ffh ;0ffh enables, 00h disables ascii dump dph$ad: dw 0 ; wc$dmp1:ds 1 wc$bts1:ds 2 wc$ad1: ds 2 wc$offs:ds 2 ; do$we$quit: lda wc$dmp1 ;Get the enabler byte cpi 0 ; if 0h we're done ret ; do$we$dmp: lda wc$dmp1 cpi 0feh ;If byte is 0feh, we bypass rz lhld wc$bts1 ;...or if bytes are zero we bypass mov a,h cpi 0 rnz mov a,l cpi 0 ret ; wchdg$ad: ;Fill in # of bytes in wchdg: xra a ! sta stg$fl ;Set so hlout: stores result in ram push d lhld wc$bts1 lxi d,wchdgd call dec lxi d,wchdgh call hlout pop d mvi a,0ffh ! sta stg$fl ret ; dmpad: dw 0 ;For buffered dumps, the original dump location dmpad$0:dw 0 ;dmpad: contents with last nybble zeroed lst$byt:db 0 ;Last byte of dmpad: contents (of start addr) lst$byt2: db 0 ;Store same thing twice lstlst: db 0 ;Last byte of address of last byte of dump blk c$lstlst: db 0 ;Complement of lstlst contents ;-------- ; ; Some address/byte storage and manipulation ; addrx: push h ! push d shld dmpad ;Store address which is in HL mov a,l sta lst$byt ! sta lst$byt2 ;Store last byte of dmpad ani 11110000b ;Zero last nybble of L mov l,a shld dmpad$0 ;Store address with zeroed last nybble (in HL) lhld dmpad ;Get original address back into HL dcx d dad d ;Calculated last byte address (in HL) mov a,l sta lstlst ;Store last byte of address of final dump byte cma sta c$lstlst ;Store complement of that pop d ! pop h ;Start addr of block to be dumped is back in HL ret ;---------- ; dphdpb: db cr,lf,' DPH''s and DPB''s FOR VALID DRIVES','$' drvdph: db ' : drive Extended Disk Parameter Header. Starts at XDPH-10' db cr,lf db 'and runs for 35 (23h) bytes. XDPH-10 (XDPH-0Ah) is ','$' drvdpb: db ' : drive Disk Parameter Block, 17 (11h) bytes from ','$' scbdmp1:db 'SYSTEM CONTROL BLOCK',cr,lf db ' 152 (98h) bytes total of which only the last 100 (64h) ' db 'bytes',cr,lf db ' are documented by DRI',cr,lf db cr,lf db ' [Consult CPM3SCB.DOC (CPM3SCB.LBR) for more info]',cr,lf,'$' scbdmp2:db ' Real SCB starting address (refer to CPM3SCB.DOC): ','$' scbdmp3:db ' SCB_BASE (starting address as documented by DRI): ','$' scbdmp4:db 'Dump of full SCB starting at ','$' scbdmp5:db 'DRI-Documented SCB, addresses are offset.',cr,lf db 'Reference: SCB_BASE+0h is ','$' z$page: db 'System Page Zero, 256 (100h) bytes from ','$' wchdg: db cr,lf,'Start address below and dump format location of',cr,lf db 'first byte may differ if dump index has been offset.',cr,lf db 'Wild-Card Dump: ' wchdgd: db ' (' wchdgh: db ' h) byte(s) from ','$' ; cols1: db ' 0 1 2 3 4 5 6 7 8 9 A B C D E F',cr,lf db ' -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --',cr,lf db '$' ; cols2: db ' 0 1 2 3 4 5 6 7 8 9 A B C D E F' db ' 0123456789ABCDEF',cr,lf db ' -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --' db ' ----------------',cr,lf db '$' ; ;dump: routine: ; ; Dumps memory starting at address in HL. Dumps number of bytes in DE. ; Maximum bytes per line set by what is in B. Starts each line with ; address of first byte in the line. ; ;buf$dump: routine: ; ; Transfers from original location to a buffer, dumps from that buffer, ; but address labels in the formatted dump properly reference original dump ; location. Unless you add your own dumps and they go haywire, you should ; call dump: rather than buf$dump: for actual dumps. ; ; BFDMP equate selects which of these dump procedures is used. ; buf$dump: ;On entry, bytes to move in DE push d mov b,d ! mov c,e ;For Z80 ldir, move BC bytes from HL to DE lxi d,buffer db 0edh,0b0h ;Z80 ldir instruction pop d lxi h,buffer ;Where what we're dumping is now ; ; Fall into dump: ; dump: mvi b,16 mvi a,0ffh sta stg$fl ;Set so outputs to console, not stores in ram push b push h dcx d dad d xchg ;Calculated last byte address (in DE) pop h call stor$ln1 ;Buffer bytes we're dumping for poss. ascii dmp push h ! lhld dmpad ! call addresh ! pop h call cols ;Column headings push h ! lhld dmpad$0 ! call address ! pop h lda lst$byt ! call blanks ;Output blanks for all pre-dump-block- ; address locations in the formatted ; dump dump2: mov a,m ;First/next byte call prt ;Output to console mov a,d ;Are we done? cmp h jnz contin ;Msb's different, so continue mov a,e cmp l jz done ;Msb's and lsb's the same so we're done contin: inx h ;Not done, address of next byte push h lhld dmpad$0 ;Advance count for stored reference address, inx h ; so dumps from buffer can reference the shld dmpad$0 ; real location, not that of the buffer pop h dcr b ;Are we at end of line? xra a cmp b jnz cont ;No pop b ;Yes, reset B counter push b call spaces lda asci$fl ! cpi 0 cnz typ$out ;Do ascii dump if flag non-zero call crlf push h ! lhld dmpad$0 ! call address ! pop h ;Start next line with ; address of first byte call stor$ln2 cont: jmp dump2 ;Keep going done: lda c$lstlst ! call blanks ;Do this many (in A) trailing spaces call spaces lda asci$fl ! cpi 0 cnz typ$out2 ;...do last ascii dump mvi a,0 ;Restore flag to ram storage mode of output #'s sta stg$fl call crlf pop b ret ; ln$buf: ds 16 ;Temp storage of dump line db '$' ;String terminator prt: push psw ! call space ! pop psw ;First do space as separator call hexout ;Output byte in A as two ascii chars ret ; address: ;Output address of start of dump and each line call hlout mvi a,':' ;Output ':' call prchar ret ; addresh: ;Output address of start of dump and each line call hlout mvi a,'h' ;Terminated by 'h' call prchar ret ; ; Stor$ln routines prepare line we're dumping for possible ascii dumping. ; ; Stor$ln1: is for the very first dump line. It moves line from ram to the ; ln$buf: buffer, aligned as we format our dump. That is, the byte at address ; ???0h is first and at address ???Fh is last. From within stor$ln1: we call ; the lead$fil: routine which overwrites a blank (space char) over each entry ; in ln$buf: that precedes the actual start of what we say we're dumping. ; ; Stor$ln2: is for all other lines; alignment with our dump format is ; controlled by routines in or called up by dump2: ; ; Typ$out: handles outputting of all ascii dump lines except the last, which ; typ$out2: takes care of. ; stor$ln1: push b ! push d ! push h lhld dmpad$0 lxi d,ln$buf lxi b,16 db 0edh,0b0h ;Z80 ldir instruction call lead$fil ;Overwrite with spaces all entries that ; correspond to locations preceding the ; real start address pop h ! pop d ! pop b ret ; stor$ln2: push b ! push d ! push h lxi d,ln$buf lxi b,16 db 0edh,0b0h ;Z80 ldir instruction pop h ! pop d ! pop b ret ; typ$out: ;Do this for all dump lines except final one push h ! push d ! push b call asc$ln ;Filter and output line pop b ! pop d ! pop h ret ; typ$out2: ;Only do for final ascii-line in dump push h ! push d ! push b call tail$fil ;Overwrite post-dump entries call asc$ln ;Filter and output line pop b ! pop d ! pop h ret ; lead$fil: ;Fill leading blanks in first ascii line lxi h,ln$buf lda lst$byt2 ani 00001111b ;Zero first nybble rz ;Leave if what's left is zero inr a mov b,a lf2: dcr b xra a ! cmp b rz ;Done mvi a,' ' ! mov m,a inx h jmp lf2 ; tail$fil: lxi h,lnbuf+15 ;Fill trailing blanks in last ascii line lda c$lstlst ani 00001111b rz ;Leave inr a mov b,a tf2: dcr b xra a ! cmp b rz ;Done mvi a,' ' ! mov m,a dcx h jmp tf2 ; asc$ln: mvi b,16 ;Filter the ascii line and output it lxi h,ln$buf al2: mov a,m ;Get first/next byte call filter ;Zero all 7th-bits, convert unprintables to '.' mov m,a ;Re-store it inx h ;Next byte dcr b ;Decr. counter mov a,b cpi 0 ;Done? jnz al2 ;No lxi d,ln$buf ;Yes, output line call print ret ; cols: call crlf ;Column hdg for dump, either cols1: or cols2: push d lxi d,cols1 lda asci$fl cpi 0 jz col lxi d,cols2 col: call print pop d ret ; ; Output blanks for all pre-dump-start-address locations in the formatted dump. ; Preserves HL, DE. On entry, HL points to ln$buf address. Blank: inserts ; one space per location for the ascii equiv line. Blanks: inserts 3 spaces ; for each pre-start-address or post-finish-address location for the hex dump. ; ; On entry to blanks:, reg A has number of blank entries (@ 3 spaces per each) ; we need to do. A has either the contents of lst$byt (for pre-dump blanking) ; or the contents of c$lstlst (for trailing blanks). The blanks: routine ; advances entries/dump-line by however many blank entries we do. ; blank: lda lst$byt ;Last byte of address we want to dump ani 00001111b ;Zero first nybble, then... jz no$blnk1 ;...if lsnybble is zero, we have none to insert mov c,a ;C is our counter, contains lsnybble of dump ; address spc1: call space ;Print one space dcr c ;Count down spaces we enter dcr b ;Decrement entries-per-line counter mov a,c ;Do as many times as lsnybble of dump address cpi 0 ;Done? jnz spc1 ;Nope, so repeat no$blnk1: xra a ! sta lst$byt ;Zero this 'cause we only do once per dump ret ; and back to caller ; blanks: ;On entry, Reg A has number of blank entries ; (3 spaces per each) we want to enter. ani 00001111b ;zero first nybble, then... push h ! push d jz no$blnk2 ;...if lsnybble is zero, we have none to insert mov d,a ;D is our counter, contains lsnybble of dump ; address spc2: call spaces ;Print three spaces dcr d lhld dmpad$0 ;Get/increment/re-store reference address inx h shld dmpad$0 dcr b ;Also decrement entries-per-line counter mov a,d ;Do as many times as lsnybble of dump address cpi 0 ;Done? jnz spc2 ;Nope, so repeat no$blnk2: pop d ! pop h ;Restore DE, HL ret ; and exit ; ; Cancel any hi 7th-bits and substitute a printable ascii char for control ; chars and for delete chars (7fh and ffh). We use period as the printable. ; filter: ani 01111111b ;Cancel hi-bit cpi 07fh ;Is it the rubout char? jz per mvi c,20h ;All ascii below 20h are control chars cmp c rnc per: mvi a,'.' ;Replace any control char or 7fh/ffh with '.' ret ;----------- ; ; ; RSX map ; rsxmap: xra a sta stg$fl ;hlout routine will store results in text call savpfx ;Recopy prefix stencil to storage lda rsx$fl ! cpi 0 ;Are rsx's present? cz no$rsx ;Tell us if not rz ;Ret if not call notes ;Print rsx prefix notes if RSX's are present lhld rsx00 ;Get... rsxmp: shld rsx00 ;Save RSX address call starts ;Print where our next RSX starts call prfx ;Fill in prefix call pr$rsx ; and print it out lda rsxldr ;Are we done? Get loader flag cpi 0ffh ;Done if 0ffh cz newpfx ;Refresh prefix template before we leave rz ; lhld rsxnxt ;Not done, get address of entry to next rsx xra a ! mov l,a ;Zero L jmp rsxmp ;Start over for next RSX ; notes: lda car ! sta car2+9 lda sed ! sta sed2+9 ! sta sed3 lda quot ! sta quot2+9 lda rub ! sta rub2 ! sta rub3 lxi d,rsxnote ;Notes about RSX prefix we print out call print ret ; starts: lhld rsx00 lxi d,st$add call hlout lxi d,strtad call print ret ; no$rsx: lxi d,nrsx call print ret nrsx: db cr,lf,'NO RSX''s ARE CURRENTLY ACTIVE.',cr,lf,'$' ; strtad: db 'RSX starting at ' st$add: db ' h',cr,lf db '---------------------',cr,lf,'$' ; ; Get and fill in all 27 bytes of the rsx prefix ; prfx: call newpfx ;Renew prefix: block by overwriting it ; with a virgin copy lxi d,prefix+15 call getbyt ! call punct call getbyt ! call punct call getbyt ! call punct call getbyt ! call punct call getbyt ! call punct call getbyt ! mvi a,'h' ! stax d ; lxi d,stjmp+15 mov a,m ! sta rsxnj ;Save opcode for jmp, possibly to check later call getbyt ! mvi a,'h' ! stax d ; lxi d,start+15 push h ! call into$hl ! shld rsxstrt ! pop h ;RSX start-exec address call getwrd ! mvi a,'h' ! stax d ; lxi d,nxtjmp+15 mov a,m ! sta rsxnj ;Save opcode for jmp, possibly to check later call getbyt ! mvi a,'h' ! stax d ; lxi d,nxtadd+15 push h ! call into$hl ! shld rsxnxt ! pop h ;Save entry to next rsx call getwrd ! mvi a,'h' ! stax d ; lxi d,prev+15 push h ! call into$hl ! shld rsxprv ! pop h ;Save addr, prev module call getwrd ! mvi a,'h' ! stax d ; lxi d,remove+15 mov a,m ! sta rsxrmv ;Save remove flag of the rsx call getbyt ! mvi a,'h' ! stax d ; lxi d,bnkdfl+15 mov a,m ! sta rsxbnk ;Save banked flag call getbyt ! mvi a,'h' ! stax d ; lxi d,rsxnm+15 call filtname ;Get, filter, re-store RSX name ; lxi d,ldr+15 mov a,m ! sta rsxldr ;Save loader flag of the rsx call getbyt ! mvi a,'h' ! stax d ; lxi d,resvd+15 mov a,m ! sta rsxrsv ;Save first reserved byte call getbyt ! mvi a,'h' ! stax d mov a,m ! sta rsxrsv+1 ;Save second reserved byte call getbyt ! mvi a,'h' ! stax d ; ret ;We've filled in the prefix template ;---------------- ; savpfx: lxi h,prefix ;Copy BC bytes from HL to DE lxi d,pfxstg lxi b,pfxend-prefix db 0edh,0b0h ;Z80 ldir instruction ret ; newpfx: push h ! push psw lxi h,pfxstg ;Copy BC bytes from HL to DE lxi d,prefix lxi b,pfxend-prefix db 0edh,0b0h ;Z80 ldir instruction pop psw ! pop h ret ; pr$rsx: lxi d,prefix ;Print out the RSX prefix we created call print ret ; getbyt: mov a,m ;Get byte call ldg$0 ;Put a "0" in front of it if needed call hexout ;Translate hex byte in A to ascii inx h ;Increment source pointer. The dest. ptr ret ; increments inside the hexout: routine ; getwrd: push h call into$hl call lead$0 ;Put a "0" in front of it if needed call hlout ;Translate hex word in HL to ascii pop h inx h ! inx h ;Increment source pointer inx d ! inx d ! inx d ! inx d ;...and destination pointer ret ; punct: mvi a,'h' ;Punctuate with 'h,' stax d inx d ;Increment dest. pointer mvi a,',' stax d inx d ret ; lead$0: mov a,h ;Hi byte into A, then fall into ldg$zero ; ldg$0: ;Ouput leading zero's if needed cpi 0a0h jc lz ;If less than 0a0h, no "0" needed mvi a,'0' ;= or >, so write "0"... stax d ;...here inx d ;Increment ascii-destination pointer lz: mov a,m ;restore byte to A ret ; filtname: mvi a,27h ;Apostrophe to start stax d ! inx d mvi b,0 filtn: mov a,m ;Get... call filtnm ;...and filter... stax d inx h ! inx d ! inr b mov a,b cpi 8 ;...the 8-character rsxname jz endflt jmp filtn ret endflt: mvi a,27h ;Final apostrophe stax d ret ; filtnm: ani 01111111b cmp m jnz hbitset ;Hi-bit was set mvi c,20h cmp c jnc rubber ;Ordinary char, check for rubout adi 40h ;Non-hi-bit-set ctrl char, convert to printable push psw lda car ;Send preceeding sign (^ is default) stax d ! inx d pop psw ret ;Ret to send printable hbitset: ;Here if hi-bit (now zeroed) was originally set mvi c,20h ;Was it a control char with hi-bit set? cmp c jnc notctl ;No adi 40h ;Yes, convert control to ascii printable push psw lda quot ;Send preceeding sign (" is default) stax d ! inx d pop psw ret ;Ret to send printable notctl: push psw lda sed ;Send preceeding sign (~ is default) stax d ! inx d pop psw rubber: cpi 7fh ;Is it a rubout char? rnz ;No so ret to send printable lda rub ;Yes replace or restore it as the case may be ret ;Let routine send rsxnote:db cr,lf db 'RSX''s ARE ACTIVE. They are identified below in the order ' db 'in which calls are',cr,lf db 'passed to them (lowest RSX in memory -> highest). Format ' db 'is that of a source-',cr,lf db 'code RSX prefix data structure. Note that serial number, ' db 'next-module entry,',cr,lf db 'previous-module address and loader flag, fields which ' db 'programmer does not',cr,lf db 'initialize, have been initialized by LOADER.',cr,lf db cr,lf,'Other Notes:',cr,lf db ' Remove flag: 0FFh means LOADER removes RSX, 00h means it ' db 'doesn''t.',cr,lf db ' Banked flag: 0FFh means RSX only loads on non-banked ' db 'systems.',cr,lf db ' Loader flag: 00h, except 0FFh for oldest (top) RSX, which ' db 'is usually LOADER.',cr,lf,cr,lf db ' Control chars and chars with hi-bits may appear in the ' db '.RSX name field. If',cr,lf db ' any such are found we so indicate in rsxname field by use ' db 'of prefix chars:',cr,lf db ' No prefix: a printable char hi bit NOT SET',cr,lf car2: db ' ^ prefix: a CTRL char hi bit NOT SET',cr,lf sed2: db ' ~ prefix: a printable char hi bit SET',cr,lf quot2: db ' " prefix: a CTRL char hi bit SET',cr,lf,cr,lf db ' Any rub/del chars (07fh and 0ffh) in the rsxname field' db cr,lf,' are printed as ' rub2: db 0 db ' and ' sed3: db 0 rub3: db 0,', respectively.',cr,lf,cr,lf,'$' ; ; We fill this block in, renewing it each time prfx: routine is run by ; overwriting it with a fresh copy located at prefix0:. Renew routine does ; the overwriting. ; prefix: db 'serial: db ',cr,lf stjmp: db ' db ; Should be 0C3h (jmp opcode)',cr,lf start: db 'start: dw ',cr,lf nxtjmp: db ' db ; Usually 0C3h (jmp)',cr,lf nxtadd: db 'next: dw ',cr,lf prev: db 'prev: dw ',cr,lf remove: db 'remove: db ',cr,lf bnkdfl: db 'banked: db ',cr,lf rsxnm: db 'rsxname:db ',cr,lf ldr: db 'loader: db ',cr,lf resvd: db 'resrvd: db ',cr,lf db cr,lf,cr,lf,'$' pfxend: dw $ pfxstg: ds pfxend-prefix ;For saved copy of prefix template ; ; Storage locations for rsxmap: data ; bdos06: dw 0 ;Bdos entry point bdos00: dw 0 ;Base of system BDOS rsx06: dw 0 ;Entry, lowest RSX rsx00: dw 0 ;Lowest RSX base address rsxstj: db 0 ;Opcode, should be 0c3h, for checking later rsxstrt:dw 0 ;Start of RSX (beginning of program) rsxnj: db 0 ;Opcode, should be 0c3h, for checking later rsxnxt: dw 0 ;Next module entry address rsxprv: dw 0 ;Previous module rsxrmv: db 0 ;Remove flag rsxbnk: db 0 ;Banked/non-banked load flag rsxldr: db 0 ;Remove (by LOADER) flag rsxrsv: db 0,0 ;The RSX reserved bytes ;------------- ; ; Convert 2-byte hex contents of HL to 4 ascii digits. Store ; the ascii output at address pointed to by DE if stg$fl contents ; are 0, or output to console if stg$fl is non-zero. ; hlout: push h ! push d ! push b ! push psw ;Save regs mov a,h ;Do H (hi nybble) first call hexout ;Hex to ascii for contents of a, then output mov a,l ;Now do L call hexout pop psw ! pop b ! pop d ! pop h ;Restore regs ret ; stg$fl: db 0 ; ; Hex to ascii for contents of A ; hexout: push psw ;Save it rrc ;Hi nybble into lo rrc rrc rrc call nybble ;Print it pop psw ;Restore, fall into lo nybble: ani 0fh ;Zap any garbage in hi nybble adi 90h ;Hex to ascii, from old Intel library daa aci 40h daa call store call type ret ; store: push psw ;Store in memory location pointed to by DE lda stg$fl ;...if stg$fl is zero cpi 0 jnz nostg ;If non-zero, output to console, don't store it pop psw stax d ;Store inx d ;Advance to next storage address ret nostg: pop psw ret ; type: push psw ;Output char in A to console lda stg$fl cpi 0 jz notype ;If zero, we store in memory, don't output it pop psw call prchar ret notype: pop psw ret ;-------------- ; ; Hexadecimal to decimal conversion routine. Hex number is in HL with ; msb in h, decimal result will be stored in registers pointed to by DE ; dec: push h ;Save regs push b push d mvi c,0 ;Zero counter for how many dec digits xchg shld wrtit2+1 ;Fill in first mem address we write digit to lxi h,powers ;Look-up table, hex values of powers of ten shld main2+1 ;Fill in address of 1st table entry we use... xchg ; main: push h main2: lhld 0000 ;...here (currently used entry in look-up tbl) mvi a,0 ;Are we past the end of the look-up table? cmp l ;i.e., are we at the '0' xchg pop h jz zblank ;Therefore done converting mvi b,0 calcmp: call comp ;We keep subtracting until remainder is less ; than power of ten being subtracted (the ; comp: subroutine returns with zero flag set. jz wrtit ; We also count the number of subtractions. cnz subtr inr b jmp calcmp ; wrtit: mov a,b ;Write dec digit adi 30h wrtit2: sta 0000 ;...to mem address pgm writes here inr c ;Count how many digits we write push h lhld main2+1 inx h ;Increment pointer, next power in look-up tbl inx h shld main2+1 ;Sneak it into the main: routine lhld wrtit2+1 inx h shld wrtit2+1 pop h jmp main ; ; Compare contents of de and hl. ; Return with 0ffh in a if HL>=DE ; 0 if HL< DE ; comp: mov a,d cmp h jc hl$ge$de jnz hl$l$de mov a,e cmp l jc hl$ge$de jnz hl$l$de hl$ge$de: ;If HL>=DE mvi a,0ffh cpi 0 ret ;Ret with zero flag clear hl$l$de: ;If HLnone, hashing ' db 'supported)',cr,lf db ' (FFFE->none, no hashing)',cr,lf curbnk: db '@CBNK = storage of current_memory_bank',cr,lf scbbas: db 'SCB_BASE+0h = address of System Control Block, at ' db 'SCB_BASE+3Ah',cr,lf,cr,lf db ' SELECTED REGISTER CONTENTS',cr,lf db 'User Defined Bytes:',cr,lf usr1: db ' USER1 at SCB_BASE+06h',cr,lf usr2: db ' USER2 at SCB_BASE+07h',cr,lf usr3: db ' USER3 at SCB_BASE+08h',cr,lf usr4: db ' USER4 at SCB_BASE+09h',cr,lf,cr,lf db 'I/O Redirection Flags (16-bit words):',cr,lf cinrx: db ' for CONIN at SCB_BASE+22h',cr,lf coutrx: db ' for CONOUT at SCB_BASE+24h',cr,lf ainrx: db ' for AUXIN at SCB_BASE+26h',cr,lf aoutrx: db ' for AUXOUT at SCB_BASE+28h',cr,lf loutrx: db ' for LSTOUT at SCB_BASE+2Ah',cr,lf,cr,lf days: db '@DATE at SCB_BASE+58h, 16-bit integer, ' db 'days since 01-Jan-78',cr,lf hr: db '@HOUR at SCB_BASE+5Ah, BCD hours',cr,lf min: db '@MIN at SCB_BASE+5Bh, BCD minutes',cr,lf sec: db '@SECOND at SCB_BASE+5Ch, BCD seconds',cr,lf,cr,lf db ' CPM+ BIOS ADDRESSES',cr,lf db 'Jump Table Entries and Subroutines',cr,lf db ' Jump Sub- ',cr,lf db ' Table routine',cr,lf db ' ------ --------',cr,lf cboot: db ' 0 CBOOT ',cr,lf db ' 1 WBOOT ',cr,lf db ' 2 CONST ',cr,lf db ' 3 CONIN ',cr,lf db ' 4 CONOUT ',cr,lf db ' 5 LIST ',cr,lf db ' 6 AUXOUT ',cr,lf db ' 7 AUXIN ',cr,lf db ' 8 HOME ',cr,lf db ' 9 SELDSK ',cr,lf db '10 SETTRK ',cr,lf db '11 SETSEC ',cr,lf db '12 SETDMA ',cr,lf db '13 READ ',cr,lf db '14 WRITE ',cr,lf db '15 LISTST ',cr,lf db '16 SECTRN ',cr,lf db '17 CONOST ',cr,lf db '18 AUXIST ',cr,lf db '19 AUXOST ',cr,lf db '20 DEVTBL ',cr,lf db '21 DEVINI ',cr,lf db '22 DRVTBL ',cr,lf db '23 MULTIO ',cr,lf db '24 FLUSH ',cr,lf db '25 MOVE ',cr,lf db '26 TIME ',cr,lf db '27 SELMEM ',cr,lf db '28 SETBNK ',cr,lf db '29 XMOVE ',cr,lf db '30 USERF ',cr,lf db '31 RESERV1 ',cr,lf db '32 RESERV2 ',cr,lf ; IF ALSCARD db '33 APREAD ',cr,lf db '34 APWRITE ',cr,lf db '35 CAPPLE xxxx xxxx',cr,lf ;xxxx if not supported db '36 FORMAT xxxx xxxx',cr,lf ENDIF ; db ' $ ',cr,lf ;Extras if needed db ' $ ',cr,lf db ' $ ',cr,lf db ' $ ',cr,lf db ' $ ',cr,lf db ' $ ',cr,lf db ' $ ',cr,lf,'$' ; ; IF BFDMP buffer: ds BFSZ ELSE buffer: dw $ ENDIF ; end