; *** CompuServe Information Service Executive for CP/M (R) ; (CP/M is a trademark of Digital Research) ; Copyright (C) 1980, 1981 CompuServe Incorporated ; Version 2.3 ; Written by: ; Russ Ranshaw false: equ 0 true: equ not false usebios: equ false ; true to use direct BIOS calls for ; console output ;*** Note: If you choose to use BDOS call for console ; write, AND have "shoxfr" true, you may not be able ; to do file transfers if your communication channel ; is operating at greater than 300 baud! shoxfr: equ true ; true to show data during file transfer sholcc: equ 09h ; lowest displayable control character for console shohcc: equ 0dh ; highest "" (used in PRODSP to map ctl characters) paglen: equ 60 ; printer page length other: equ false ; true if the computer is NOT one of the special ones hz89: equ false ; true if the computer is a Heath/Zenith 88, 89, 8 hz19: equ false ; Console is a Heath/Zenith -19 pmmi equ true ; true if PMMI modem board if other BBASE: EQU 0000H ; "PAGE 0" ADDRESS CTL EQU 03H ; CONTROL PORT SIO EQU 01H ; SIO PORT SIOIR EQU 40H ; SIO PORT DATA INPUT READY FLAG SIOTR EQU 80H ; SIO PORT TRANSMITTER READY FLAG hitrue: equ false ; SIO flags are "hi" (1) when true endif if hz89 bbase: equ 4200h ; "Page 0" Address - Set to 0 if you have 0 based CP/M ctl: equ 0ddh ; Line Control Register sio: equ 0d8h ; Receive/Transmit Data Register sioir: equ 01h ; Receive Data Ready flag siotr: equ 20h ; Transmitter Buffer Ready flag hitrue: equ true endif if pmmi bbase: equ 0h ; "Page 0" address basprt: equ 0c0h ; base i/o port address for pmmi board ctl: equ basprt ; primary control port sio: equ basprt+1 ; serial data port sioir: equ 02h ; data input ready flag siotr: equ 01h ; transmitter ready flag hitrue: equ true ; flags are high when true endif ; SPECIAL CHARACTERS FOR DATA TRANSMISSION PROTOCOL SOH: EQU 01H ; START OF TEXT ETX: EQU 03H ; END OF TEXT EOT: EQU 04H ; END OF TRANSMISSION ENQ: EQU 05H ; ^E, USED FOR PMMI EXIT W/O DISCONNECT SI: EQU 0FH ; = SHIFT INTO PROTOCOL MODE SO: EQU 0EH ; = SHIFT OUT OF PROTOCOL MODE] ; PROTOCOL MODE IMPLIES THAT SEQUENCES ; ARE NOT SENT TO CONSOLE BUT ARE USED TO CONTROL ; THE UP/DOWN LOAD PROTOCOL DC1: EQU 11H ; CONTROL-Q: RESUME TRANSMISSION DC2: EQU 12H ; CONTROL-R: PRINTER ON DC3: EQU 13H ; CONTROL-S: STOP TRANSMISSION DC4: EQU 14H ; CONTROL-T: PRINTER OFF KNAK: EQU 15H ; DLE: EQU 10H ; (TRANSPARACY FLAG) ESC: EQU 1BH ; ESCAPE EOF: EQU 1AH ; ^Z (CP/M END OF FILE) CR: EQU 0DH ; LF: EQU 0AH ; FF: EQU 0CH ; MON: EQU 18H ; ^X (RETURN TO CP/M) (& DISCONNECT PMMI) ; CP/M EQUATES BDOS: EQU BBASE+0005H ; MAIN ENTRY POINT FOR CP/M TFCB: EQU BBASE+005CH ; DEFAULT FILE CONTROL BLOCK TBUFF: EQU BBASE+0080H ; DEFAULT FILE BUFFER TBASE: EQU BBASE+0100H ; TRANSIENT BASE ; DEFINE OFFSETS INTO FILE CONTROL BLOCK (FCB) FCB$ET: EQU 0 ; ENTRY TYPE FCB$FN: EQU 1 ; FILE NAME (8 BYTES) FCB$FT: EQU 9 ; FILE TYPE (3 BYTES) FCB$RC: EQU 15 ; RECORD COUNT (CURRENT EXTENT) FCB$DM: EQU 16 ; DISK MAP FCB$NR: EQU 32 ; NEXT RECORD NUMBER TO READ OR WRITE ; BDOS FUNCTIONS: FN$SR: EQU 0 ; SYSTEM RESET FN$RC: EQU 1 ; READ CONSOLE FN$WC: EQU 2 ; WRITE CONSOLE FN$RR: EQU 3 ; READ READER FN$WP: EQU 4 ; WRITE PUNCH FN$WL: EQU 5 ; WRITE LIST FN$IS: EQU 7 ; INTERROGATE I/O STATUS FN$AS: EQU 8 ; ALTER I/O STATUS FN$PCB: EQU 9 ; PRINT CONSOLE BUFFER FN$RCB: EQU 10 ; READ CONSOLE BUFFER FN$CCS: EQU 11 ; CHECK CONSOLE STATUS FN$LDH: EQU 12 ; LIFT DISK HEAD FN$RDS: EQU 13 ; RESET DISK SYSTEM FN$SD: EQU 14 ; SELECT DISK FN$OPN: EQU 15 ; OPEN FILE FN$CLS: EQU 16 ; CLOSE FILE FN$SF: EQU 17 ; SEARCH FIRST FN$SN: EQU 18 ; SEARCH NEXT FN$DEL: EQU 19 ; DELETE FILE FN$RDR: EQU 20 ; READ DISK RECORD FN$WDR: EQU 21 ; WRITE DISK RECORD FN$CRE: EQU 22 ; CREATE FILE FN$REN: EQU 23 ; RENAME FILE FN$IL: EQU 24 ; INTERROGATE LOGIN FN$ID: EQU 25 ; INTERROGATE DISK FN$SDA: EQU 26 ; SET DMA ADDRESS FN$IA: EQU 27 ; INTERROGATE ALLOCATION ORG TBASE START: JMP START0 ; NORMAL START INISIO: JMP SIOINI ; INITIALIZE MODEM UART GETSIO: JMP SIOGET ; GET CHAR FROM MODEM UART PUTSIO: JMP SIOPUT ; PUT A CHAR TO MODEM UART ; response for this executive: if other SYSID: DB '#CPMTarbell,CC,HC,PA,PL',CR,00 endif if hz89 sysid: db '#CPMHeath/Zenith,CC,HC,PA,PL',cr,00 endif if pmmi sysid: db '#CPMPMMI,CC,HC,PA,PL',CR,00 endif ; CC = Cursor Control ; Implies following cursor controls: ; cursor up ; cursor down ; cursor right ; cursor left ; cursor home (line 1, column 1) ; erase to end of screen ; erase to end of line ; erase screen and home cursor (also ) ; position cursor to line L column C ; HC = Hard Copy ; Implies following: ; (^R, 022 octal, 12 hex) enable printer; subsequent data ; will be copied to local printer ; (^T, 024 octal, 14 hex) disable printer ; disable terminal display ; enable terminal display ; PA = A Protocol ; Implies file transfer capability using the CompuServe A protocol ; PL = Load Protocol ; Implies ability to load code segments under the CompuServe L protocol banner: db 'CompuServe CP/M (R) Executive Version 2.3',cr,lf if other db '**** Tarbell/Z-80 ****',cr,lf,0 endif if hz89 db '**** Heath/Zenith ****',cr,lf,0 endif if pmmi db '**** PMMI Modem ****',cr,lf db '^E exit WITHOUT disconnect, ^X exit WITH disconnect' db cr,lf,lf,'$' endif DB 'Copyright (C) 1980, 1981 CompuServe Incorporated',CR,LF DB CR,LF,'$' START0: LXI SP,STACK ; SET UP OUR OWN STACK MVI C,FN$ID ; GET CURRENT CP/M DISK DRIVE CALL BDOS STA CPMDEF LHLD BBASE+1 ; GET START OF BIOS XCHG LXI H,3 ; OFFSET TO CONSOLE CHECK DAD D SHLD TRMGET+1 ; STORE ADDRESS LXI H,6 ; OFFSET TO CONSOLE READ DAD D SHLD TERMRD+1 if usebios LXI H,9 ; OFFSET TO CONSOLE WRITE DAD D SHLD TERMWR+1 endif LXI H,2AH ; OFFSET TO LIST STATUS DAD D ; MUST BE IMPLEMENTED IN BIOS SHLD LSTST+1 ; FOR PRINT BUFFERING TO WORK XRA A ; DISABLE PRINTER OUTPUT STA PRTFLG STA SIFLAG ; NO SEEN if shoxfr sta shoflg ; Don't come up in "show transfer" endif ; mode! CALL INISIO ; INITIALIZE MODEM UART LXI D,BANNER ; ANNOUNCE OURSELVES MVI C,FN$PCB CALL BDOS ; TERMINAL EMULATOR LOOP TERM: CALL TRMGET JZ TSTSIN CPI MON ; IF USER WANTS TO RETURN TO CP/M IF NOT PMMI JZ BBASE ; THEN DO IT! ENDIF ;NOT PMMI IF PMMI JZ DISCON ; DISCONNECT MODEM ENDIF ;PMMI CPI ENQ ; IF ^E JZ BBASE ; THEN JUST EXIT CPI DC2 ; IF ^R JZ PRTON ; THEN TURN PRINTER ON CPI DC4 ; IF ^T JZ PRTOFF ; THEN TURN PRINTER OFF CALL PUTSIO ; ELSE PUT CHAR OUT TO SIO TSTSIN: CALL GETSIN ; ELSE GET A CHAR FROM SIO JZ TSTPRT CPI DLE ; IF JZ ISDLE ; THEN PROCESS CPI SI ; IF JZ ISSI ; THEN PROCESS CPI SO ; IF JZ ISSO ; THEN PROCESS CPI ESC ; IF JZ ISESC ; THEN GO PROCESS ESCAPE SEQUENCE CPI DC2 ; IF ^R JZ PRTON ; THEN TURN PRINTER ON CPI DC4 ; ELSE IF ^T JZ PRTOFF ; THEN TURN PRINTER OFF IF HZ19 ; IF RUNNING A H/Z-19 CONSOL CPI FF ; THEN IF CHAR IS JZ FFHZ19 ; THEN MAP IT TO ENDIF NOTSIM: CALL TERDSP ; DISPLAY/PRINT CHARACTER TSTPRT: LSTST: CALL 0000 ; MODIFIED TO LIST STATUS ORA A JZ TERM ; IF PRINTER IS BUSY ; THEN CONTINUE TO SERVICE THE ; KEYBOARD & MODEM AT HIGH POLLING RATE LHLD HEAD ; GET HEAD POINTER XCHG LHLD TAIL ; AND TAIL POINTER CALL DEHLCMP ; SEE IF THEY ARE EQUAL JZ TERM ; IF SO, BUFFER IS EMPTY, SO EXIT PUSH H ; SAVE TAIL POINTER MOV A,M ; GET THE CHAR ANI 7FH ; STRIP PARITY LXI H,LINCNT ; POINT TO LINE COUNTER CPI FF ; IF FF JZ NEWPAGE ; RESET LINE COUNTER CPI LF ; IF LF JNZ OUTP ; DECREMENT LINE COUNTER DCR M ; IF ROOM LEFT ON PAGE JNZ OUTP ; OUTPUT THE LF NEWPAGE MVI M,PAGLEN ; ELSE A FF MVI A,FF OUTP: MOV E,A MVI C,FN$WL ; CHAR TO LIST DEVICE CALL BDOS POP H ; RESTORE TAIL PTR INX H LDA BDOS+2 ; CHECK FOR MEMORY TOP DCR A CMP H JNC OUTP1 LXI H,PRTBUF ; WRAPAROUND OUTP1: SHLD TAIL ; UPDATE TAIL PTR JMP TERM DEHLCMP: ; TEST (DE)-(HL) COMPARISON MOV A,D CMP H RNZ MOV A,E CMP L RET ; GET A CHARACTER FROM LOCAL TERMINAL ; RETURN Z FLAG IF LOCAL NOT READY TRMGET: CALL 0000H ; **** MODIFIED ADDRESS!!! ANI 01 RZ ; RETURN IF NO LOCAL INPUT TERMRD: CALL 0000H ; **** MODIFIED ADDRESS!!! ANI 7FH ; RETURN WITH PARITY STRIPPED RET ; HERE IF (^R) RECEIVED FROM HOST OR CONSOLE PRTON: MVI A,0FFH ; SET PRINTER FLAG STA PRTFLG JMP TERM ; HERE IF (^T) RECEIVED FROM HOST OR CONSOLE PRTOFF: XRA A ; CLEAR PRINTER FLAG STA PRTFLG JMP TERM ; HERE IF RECEIVED FROM HOST ISDLE: CALL GETSIX ; GET CHARACTER FOLLOWING CALL TERDSP ; DISPLAY IT ON CONSOLE/PRINTER JMP TERM ISSO: XRA A ; DISABLES PROTOCOL MODE if shoxfr sta shoflg endif LXI SP,STACK ; RESTORE STACK INCASE OF ABORT ISSI: STA SIFLAG ; ENABLES PROTOCOL MODE JMP TERM ; HERE ON ISESC: LDA SIFLAG ; IF NOT RECEIVED ORA A MVI A,ESC IF NOT HZ19 JZ NOTSIM ; THEN JUST DISPLAY IT ENDIF IF HZ19 JZ CK1061 ; THEN CHECK FOR SPECIAL MAPPINGS ENDIF ISESCN: CALL GETSIX ; ELSE GET CHARACTER FROLLOWING CPI 'I' ; IF JNZ ESC0 ; THEN LXI H,SYSID ; SEND THE ID STRING TO HOST SNDID: MOV A,M ; GET NEXT ID BYTE ORA A ; IF NULL JZ TERM ; THEN FINISHED CALL PUTSIO ; ELSE SEND TO HOST INX H JMP SNDID IF HZ19 ; IF WE HAVE A H/Z-19 AS CONSOLE CK1061: CALL GETSIX ; GET CHAR FOLLOWING CPI 'j' ; = JNZ NT1061 FFHZ19: MVI A,'E' NT1061: PUSH PSW MVI A,ESC CALL TERDSP POP PSW JMP NOTSIM ENDIF ; H/Z-19 MAPPING ESC0: CPI 'L' ; IF JNZ ESC1 ; THEN MVI E,0 ; PERFORM SYSTEM LOAD FUNCTION CALL GETCKS ; GET BYTE COUNT MOV B,A CALL GETCKS ; GET LOW ADDRESS BYTE MOV L,A CALL GETCKS MOV H,A ; AND HIGH-ORDER ESCL0: CALL GETCKS ; GET NEXT DATA BYTE MOV M,A ; SAVE IT INX H ; BUMP MEMORY ADDRESS DCR B ; COUNT BYTES RECEIVED JNZ ESCL0 ; & LOOOP TILL ZERO MOV C,E ; SAVE CHECKSUM CALL GETCKS ; GET NEXT BYTE CMP C ; IF MATCH MVI A,'.' ; THEN SEND . JZ ESCL1 ; ELSE MVI A,'/' ; SEND / ESCL1: CALL PUTSIO if not shoxfr call viomrk ; display protocol mark endif JMP TERM if not shoxfr viomrk: push psw mvi a,cr call viodsp mvi a,lf call viodsp mvi a,32 sta xfrctr pop psw cpi '.' cnz viodsp ret ctxfr: push h push psw lxi h,xfrctr ; decrement count of xfr'd characters dcr m jnz ctxfr0 mvi m,32 mvi a,'+' ; display '+' every 32 bytes call viodsp ctxfr0: pop psw pop h ret endif ESC1: CPI 'A' ; IF JNZ TERM ; THEN ; Initialize for data transmission using the CompuServe A-protocol ; The protocol begins with the following being sent from the host: ; ; where: ; U = upload, D = download ; A = ASCII (file ends in 1Ah), B = binary ; FILESPEC = standard CP/M file specification, including optional drive ; CKSUM = checksum for the record MVI A,'0' ; INIT RECORD NUMBER STA APNXT CALL APRCVX ; GET COMMAND LINE FROM HOST LXI H,APBUF+2 ; POINT TO FILE SPEC FROM USER LXI D,TFCB ; POINT TO FILE CONTROL BLOCK MOV B,M ; GET POSSIBLE DISK DRIVE NAME INX H ; IF : NEXT MOV A,M CPI ':' JNZ NOCOL ; THEN INX H ; SKIP THE COLON MVI A,7 ; MASK OFF DRIVE NUMBER ANA B JMP FIRSTB NOCOL: DCX H ; POINT BACK TO FIRST FILE BYTE XRA A ; USE DEFAULT DRIVE NUMBER FIRSTB: STAX D ; STORE DRIVE NUMBER INX D ; POINT TO FIRST FILE NAME BYTE MVI B,8 ; MAX LENGTH OF NAME CALL NAAME ; GET FILE NAME MOV A,M ; GET NEXT BYTE CPI '.' ; IF . PRESENT JNZ EXT ; THEN INX H ; SKIP OVER IT EXT: MVI B,3 ; LENGTH OF EXTENSION CALL NAAME ; GET EXTENSION XRA A ; ZERO FILE EXTENT STAX D LDA TFCB ; SELECT THE DISK ORA A ; IF 0 THEN USE DEFAULT DISK JZ NODISK SUI 1 ; MAP A INTO 0, B INTO 1, ETC. MOV E,A MVI D,0 MVI C,FN$SD CALL BDOS NODISK: lda apbuf+1 ; store transfer type sta xfrtyp LDA APBUF ; CHECK DIRECTION CPI 'D' ; IF DOWN LOAD JNZ CHKUPL ; THEN lxi d,dnload mvi c,fn$pcb call bdos MVI C,FN$OPN ; IF THE FILE EXISTS CALL DSKOP CPI 0FFH JZ DLOKAY ; THEN LXI D,DLBOMB ; TELL THE USER ABOUT IT MVI C,FN$PCB CALL BDOS MVI C,FN$RC ; GET USER'S RESPONSE CALL BDOS ani 7fh PUSH PSW MVI A,CR CALL VIODSP MVI A,LF CALL VIODSP POP PSW CPI 'Y' ; IF NOT 'Y' JZ DLDEL CPI 'y' JNZ ABORT ; THEN ABORT THE DOWNLOAD ATTEMPT DLDEL: MVI C,FN$DEL ; ELSE DELETE THE FILE CALL DSKOP DLOKAY: CALL OPNOUT ; OPEN FOR OUTPUT if shoxfr mvi a,0ffh sta shoflg endif ; THE FOLLOWING LOOP DOES THE DOWNLOAD FUNCTION DL0: CALL APRCV ; GET NEXT LINE OF DATA JNZ DLEOT ; HANDLE END OF TRANSMISSION LXI H,APBUF ; POINT TO BUFFER DL1: MOV A,M ; GET NEXT BYTE INX H ; POINT TO NEXT BYTE CALL PUTBYT ; PUT IT INTO OUTPUT BUFFER DCR B ; COUNT THE BYTE JNZ DL1 JMP DL0 ; GET NEXT RECORD FROM HOST ; HERE WHEN THE HOST'S MESSAGE HAS BEEN RECEIVED DLEOT: lda xfrtyp ; if binary transfer cpi 'B' jz dleotb ; then don't insert ^Z MVI A,EOF ; PUT ^Z (END OF FILE MARK) CALL PUTBYT dleotb: MVI C,FN$WDR CALL DSKOP DLEOT0: MVI C,FN$CLS CALL DSKOP CALL RSTDEF MVI A,'.' ; TELL HOST WE GOT IT CALL PUTSIO if not shoxfr call viomrk endif if shoxfr xra a sta shoflg endif JMP TERM ; BACK TO TERMINAL MODE ; HERE IF NOT A DOWN LOAD - BETTER BE UP LOAD! CHKUPL: CPI 'U' JNZ ABORT ; SEND NAK TO HOST IF NOT . lxi d,upload mvi c,fn$pcb call bdos CALL OPNINP ; OPEN THE FILE FOR INPUT MVI A,'.' ; TELL HOST WE'RE READY TO SEND DATA CALL PUTSIO if not shoxfr call viomrk endif ; THE UPLOAD FUNCTION IS DONE IN THE FOLLOWING LOOP: CALL GETSIX ; GET HOST'S PROMPT CPI '.' ; ABORT IF NOT '.' JNZ ABORT if shoxfr mvi a,0ffh sta shoflg endif UPL1: MVI B,0 ; INIT COUNT LXI H,APBUF UPL2: CALL GETBYT ; GET DATA FROM FILE jp upl3 ; jump if occured MOV M,A ; THEN PUT INTO BUFFER INX H ; BUMP POINTER INR B ; AND BYTE COUNT JNZ UPL2 ; GET NEXT BYTE IF BUFFER NOT FILLED UPL4: MOV A,B ; SAVE COUNT STA APLEN CALL APSND ; SEND THE DATA JMP UPL1 ; GO DO NEXT LINE UPL3: MOV A,B ; WRITE FINAL DATA BLOCK IF THERE IS ONE STA APLEN ORA A CNZ APSND MVI A,0FFH ; SEND MESSAGE WITHOUT MASKING LXI H,EOTMSG CALL APSND0 CALL RSTDEF ; RESTORE CP/M'S DEFAULT DISK DRIVE if shoxfr xra a sta shoflg endif JMP TERM ; RETURN TO TERMINAL MODE EOTMSG: DB 1,EOT ;*** ; ROUTINE TO INTERFACE TO CP/M'S CONSOLE OUTPUT DRIVER viodsp: push b ; save register push d push h push psw if usebios mov c,a TERMWR: CALL 0000H ; ***** MODIFIED TO CONOT IN BIOS endif if not usebios mov e,a ; call BDOS to write char on console mvi c,fn$wc call bdos endif pop psw pop h pop d pop b RET ; ROUTINE TO DISPLAY C(A) ON CP/M CONSOLE AND PRINTER IF NECESSARY TERDSP: PUSH PSW ; SAVE C(A) CALL VIODSP ; DISPLAY ON CONSOLE POP PSW ; GET CHARACTER BACK MOV E,A ; SAVE CHARACTER LDA PRTFLG ; IF ^R RECEIVED ORA A RZ ; THEN ; STORE THE CHAR IN THE PRINTER BUFFER LHLD HEAD ; GET BUFFER PTR MOV M,E ; STORE THE CHAR INX H ; BUMP PTR LDA BDOS+2 ; CHECK FOR TOP OF MEMORY DCR A CMP H JNZ TERDS1 ; IF REACHED, WRAPAROUND LXI H,PRTBUF TERDS1: SHLD HEAD ; UPDATE PTR RET ; ROUTINE TO OPEN A FILE FOR OUTPUT OPNOUT: MVI C,FN$CRE ; CREATE FILE CALL DSKOP ; CALL CP/M CPI 0FFH ; IF OKAY JZ ERRCRE ; ERROR DURING CREATE (DIRECTOR FULL?) XRA A ; CLEAR NEXT RECORD COUNT STA TFCB+FCB$NR STA IBP ; INIT BUFFER POINTER RET ; ROUTINE TO OPEN FILE FOR INPUT OPNINP: MVI C,FN$OPN CALL DSKOP CPI 0FFH ; IF FILE NOT FOUND JZ ERROPN ; THEN ERROR MESSAGE TIME! XRA A STA TFCB+FCB$NR ; INIT TO FIRST RECORD MVI A,80H ; "EMPTY BUFFER" STA IBP RET ; ROUTINE TO PUT C(A) INTO DISK BUFFER PUTBYT: PUSH B ; SAVE REGS PUSH D PUSH H PUSH PSW ; SAVE BYTE LDA IBP ; IF BUFFER IS FULL CPI 80H JNZ PUT0 ; THEN MVI C,FN$WDR CALL DSKOP ORA A JNZ ERRWDR ; WRITE ERROR??? XRA A ; INIT IBP TO 0 PUT0: MOV E,A ; SAVE CUR BYTE POSITION MVI D,0 INR A ; BUMP POINTER STA IBP LXI H,TBUFF ; POINT TO BUFFER DAD D ; NOW POINT TO BYTE POP PSW ; GET BYTE MOV M,A ; STORE BYTE POP H ; RESTORE REGS POP D POP B RET ; ROUTINE TO GET NEXT BYTE FROM A DISK RECORD GETBYT: PUSH B ; SAVE REGS PUSH D PUSH H mvi b,0 ; assume not LDA IBP ; IF BUFFER IS EMPTY CPI 80H JNZ GET0 MVI C,FN$RDR CALL DSKOP mov b,a ; save return code (0 implies okay) XRA A ; RESET BYTE POINTER GET0: MOV E,A ; SAVE BYTE POS MVI D,0 INR A ; BUMP BYTE POS STA IBP LXI H,TBUFF DAD D lda xfrtyp ; if binary transfer cpi 'B' jz gtbtbn ; then don't check for ^Z MOV A,M ; GET THE BYTE cpi eof ; if ^Z jnz gtrstr ; then mvi b,1 ; we will exit with N cleared gtrstr: dcr b ; set N if NOT eof POP H ; RESTORE REGS POP D POP B RET gtbtbn: mov a,m ; get binary byte jmp gtrstr ; set N flag and exit ; FATAL CP/M ERROR CONDITIONS PRINT A LOCAL MESSAGE ; THEN SEND A TO HOST ERRCRE: LXI D,CREMSG JMP DFATAL ERROPN: LXI D,OPNMSG JMP DFATAL ERRWDR: LXI D,WDRMSG JMP DFATAL DFATAL: FATAL: MVI C,FN$PCB ; WRITE ERROR MESSAGE CALL BDOS ABORT: lxi d,abload ; tell user we are aborting mvi c,fn$pcb call bdos MVI C,FN$CLS CALL DSKOP CALL RSTDEF ; RESTORE DEFAULD DISK MVI A,KNAK CALL PUTSIO ; TELL HOST WE HAVE BOMBED JMP ISSO ; DISABLE PROTOCOL MODE ; HERE TO DO A CP/M DISK OPERATION; CALLED WITH DESIRED FUNCTION CODE IN C DSKOP: LXI D,TFCB CALL BDOS PUSH PSW ; SAVE RETURN CODE XRA A ; OUTPUT A NULL TO CONSOLE CALL VIODSP ; TO FLUSH DISK BUFFER POP PSW ; RESTORE DSK RETURN CODE RET ; ROUTINE TO RESTORE CP/M'S DEFAULT DISK DRIVE RSTDEF: LDA CPMDEF MOV E,A MVI D,0 MVI C,FN$SD CALL BDOS RET ; ROUTINE TO EXTRACT FILE NAME AND EXTENSION NAAME: MOV A,M ; GET NEXT BYTE CPI CR ; ENDS NAME JZ FILL ; FILL IF END OF STRING CPI '.' ; IF EXTENSION JZ FILL ; THEN FILL OUT WITH SPACES INX H ; SKIP THIS BYTE CPI 60H ; LOWER CASE A JC NAME1 ; JUMP IF NOT LOWER CASE SBI 20H ; CONVERT LOWER CASE TO UPPER NAME1: STAX D ; STORE BYTE IN FCB INX D DCR B ; COUNT THIS BYTE JNZ NAAME ; PROCESS NEXT IF MORET RET FILL: MVI A,' ' ; STORE A SPACE JMP NAME1 ; THIS ROUTINE RECEIVES A RECORD USING THE ASCII PROTOCOL APRCV: MVI A,'.' ; PROMPT REMOTE FOR NEXT RECORD CALL PUTSIO if not shoxfr call viomrk endif APRCVX: LDA APNXT ; BUMP EXPECTED RECORD NUMBER INR A CPI '9'+1 ; WRAP-AROUND JC APRCVY ; JUMP IF LEQ 9 MVI A,'0' APRCVY: STA APNXT if not shoxfr call viodsp endif APRCV0: CALL TRMGET ; GET LOCAL KEYBOARD INPUT CPI ETX ; IF ^C JZ ABORT ; THEN ABORT THE TRANSFER CALL GETSIX ; GET NEXT CHARACTER CPI SOH ; STARTS THE RECORD JZ APRCV1 CPI ETX ; BY ITSELF IS QUESTIONABLE JNZ APRCV0 MVI A,'/' ; SEND A LOGICAL NAK CALL PUTSIO if not shoxfr CALL VIODSP endif JMP APRCV0 APRCV1: MVI E,0 ; INIT CHECKSUM MOV B,E ; INIT BYTE COUNT MOV A,E ; CLEAR FLAG STA APEOT LXI H,APBUF CALL GETCKS ; GET SENDER'S RECORD NUMBER STA APCUR APRCV2: CALL GETCKS ; GET A CHECKSUMMED CHARACTER JZ APRCV3 MOV M,A ; PUT BYTE IN BUFFER INR B ; COUNT THIS BYTE INX H if not shoxfr call ctxfr ; display '+' every 32 bytes endif JMP APRCV2 APRCV3: MOV C,E ; SAVE CHECKSUM CALL GETCKS ; GET REMOTE'S CHECKSUM CMP C ; IF SAME JNZ APRCV4 ; THEN LDA APNXT ; CHECK RECORD COUNT MOV C,A LDA APCUR CMP C JNZ APRCV8 ; JUMP IF NOT MATCHED MOV A,B ; STORE BYTE COUNT STA APLEN LDA APEOT ; RETURN WITH EOT FLAG STATUS ORA A RET APRCV4: MVI A,'/' ; ELSE REQUEST RETRANSMISSION CALL PUTSIO if not shoxfr CALL VIODSP endif JMP APRCV0 APRCV8: JNC ABORT ; ABORT IF RCV GTR EXPECTED MVI A,'.' ; MUST HAVE RECEIVED A DUPLICATE RECORD CALL PUTSIO ; ACCEPT IT, AND TRY AGAIN if not shoxfr call viomrk endif JMP APRCV0 ; ROUTINE TO SEND A MESSAGE APSND: XRA A ; Flag for masking control characters LXI H,APLEN ; BUFFER ADDRESS: LENGTH FOLLOWED BY DTA APSND0: STA APFLG ; STORE MASK FLAG SHLD APADDR ; STORE BUFFER ADDRESS LDA APNXT ; BUMP NEXT RECORD COUNT INR A CPI '9'+1 JC ASND0A MVI A,'0' ASND0A: STA APNXT if not shoxfr call viodsp endif APSND1: MVI E,0 ; CLEAR CHECKSUM LHLD APADDR MOV B,M ; GET LENGTH INX H ; POINT TO DATA MVI A,SOH ; START THE MESSAGE CALL APPUTS LDA APNXT ; SEND RECORD NUMBER CALL DOCKS ; UPDATE CHECKSUM CALL APPUTS APSND2: MOV A,M ; GET NEXT DATA BYTE CALL DOCKS ;UPDATE CHECKSUM CPI 20H ; IF CONTROL CHARACTER JNC ASND2A ; THEN LDA APFLG ; IF MASKING CONTROL CHARACTERS ORA A MOV A,M ; GET BYTE AGAIN JNZ ASND2A ; THEN CPI 05H ; FOR EFFICIENCY, ONLY MASK THE BADDIES JC ASND2B ; MASK 00H 01H 02H 03H 04H ; NUL SOH STX ETX EOT CPI dle JZ ASND2B ; 10H DLE CPI knak JNZ ASND2A ; 15H NAK ASND2B: MVI A,DLE ; SEND CALL APPUTS JMP ASND4A snd4b: if shoxfr call viodsp endif if not shoxfr call viomrk endif CPI '.' RZ ; RETURN IF HOST GOT IT OKAY CPI '/' ; ELSE IF / JZ APSND1 ; THEN RETRANSMIT THE MESSAGE CPI KNAK ; ELSE IF JZ ISSO ; THEN ABORT JMP ASND4 ; ELSE KEEP WAITING APPUTS: PUSH PSW ; SAVE CHAR CALL GETSIN ; CHECK MODEM FIRST JZ APPUT4 ; THEN ani 7fh CPI KNAK ; IF WE RECEIVE A JZ ISSO ; THEN SHUT DOWN THE PROTOCOL CPI DC3 ; IF X-OFF JNZ APPUT4 ; THEN PUSH D ; DELAY A FEW SECONDS PUSH B MVI B,2 APPUT0: LXI D,8000H APPUT1: CALL GETSIN ; IF CHAR PRESENT JZ APPUT2 ; THEN ani 7fh CPI DC1 ; IF ^Q (XON) JZ APPUT3 ; THEN EXIT APPUT2: DCX D MOV A,D ORA E JNZ APPUT1 DCR B JNZ APPUT0 APPUT3: POP B ; RESTORE REGS AND RETURN POP D APPUT4: POP PSW ; GET CHAR CALL PUTSIO ; SEND CHAR RET ; ROUTINE TO GET A CHARACTER FROM UART WITH WAIT GETSIN: CALL GETSIO ; RETURN SIO CHAR WITH BIT 7 = 0 RZ ANI 7FH RET GETSIX: CALL GETSIO ; GET SIO CHAR OR WAIT JZ GETSIX ; WAIT FOR A CHARACTER CPI KNAK ; IF RECEIVED JZ ISSO ; THEN REVERT TO TERMINAL MODE RET ; RETURN GETCKS: CALL GETSIX ; GET NEXT SIO CHAR WITH CHECKSUMMING CPI ETX ; IF RZ ; THEN RETURN PUSH PSW CPI EOT JNZ NOTEOT ; THEN STA APEOT ; SET SEEN FLAG NOTEOT: CPI DLE ; IF JNZ GETCK0 ; THEN CALL GETSIX ; GET NEXT CHARACTER ANI 1FH ; MAKE CONTROL CHAR OF IT GETCK0: CALL DOCKS ; UPDATE CHECKSUM POP PSW ; RESTORE FLAGS MOV A,D ; RESTORE NEW CHAR RET ; RETURN DOCKS: MOV D,A ; SAVE BYTE MOV A,E ; GET OLD CHECKSUM RLC ; ROTATE ONE BIT LEFT ADD D ; ADD NEW BYTE ACI 0 ; ADD POSSIBLE CARRY MOV E,A ; REPLACE CHECKSUM WITH UPDATED ONE MOV A,D ; RESTORE NEW BYTE RET ; VARIOUS MESSAGE STRINGS proini: db cr,lf,'% CSEXEC - Initializing file transfer',cr,lf,'$' dnload: db cr,lf,'% CSEXEC - Beginning Download',cr,lf,'$' upload: db cr,lf,'% CSEXEC - Beginning Upload',cr,lf,'$' abload: db cr,lf,'? CSEXEC - Aborting file transfer',cr,lf,'$' DLBOMB: DB CR,LF,'% CSEXEC - That file already exists on your disk.',CR,LF DB 'Do you wish to replace it (Y or N) ? $' CREMSG: DB cr,lf,'? CSEXEC - Diskette is full!',CR,LF,'$' opnmsg: db Cr,lf,'? CSEXEC - That file is not on your diskette!',cr,lf,'$' wdrmsg: db cr,lf,'? CSEXEC - Your diskette is full!',cr,lf,'$' dmsg: db cr,lf,'++DISCONNECTED++',cr,lf,'$' ; I/O SUBROUTINES FOR SIO SIOGET: IN CTL ; GET MIO STATUS FLAGS ANI SIOIR ; ISOLATE INPUT READY FLAG if not hitrue XRI SIOIR ; INVERT IT endif RZ ; RETURN IF NOW 0 IN SIO ; ELSE GET SIO CHARACTER if shoxfr call prodsp endif RET ; AND RETURN (Z FLAG = 0) SIOPUT: PUSH PSW ; WRITE (A) TO SIO PUTSI1: IN CTL ; WAIT FOR FLAG TO = 0 ANI SIOTR if not hitrue JNZ PUTSI1 endif if hitrue jz putsi1 endif POP PSW OUT SIO if shoxfr if hz19 push psw mvi a,esc ; invert video for incoming characters call viodsp mvi a,'p' call viodsp pop psw push psw call prodsp mvi a,esc call viodsp mvi a,'q' ; return to normal video call viodsp pop psw endif if not hz19 call prodsp endif endif RET sioini: if hz89 mvi a,3 ; init uart to out sio+3 ; 8 data bits, 1 stop bit, no parity endif if pmmi mvi a,93 ; 8 data bits, 1 stop bit, no parity out basprt mvi a,52 ; 300 baud out basprt+2 mvi a,127 ; originate mode out basprt+3 endif ret if shoxfr prodsp: push psw ; save the character lda shoflg ; if in protocol ora a jz proter ; then pop psw push psw ani 7fh ; remove high-order bit cpi ' ' ; if this is a control char jnc proyes ; then lda xfrtyp ; if doing a binary transfer cpi 'B' jz pronot ; then "flag" all control characters pop psw ; else flag only funny ones push psw cpi sholcc ; is it a normal control character? jc pronot ; (ie, thru , 08h - 0Dh) cpi shohcc+1 jc proyes pronot: mvi a,'^' ; flag the control character call viodsp pop psw push psw adi 40h ; map char to letter proyes: call viodsp proter: pop psw ret endif if pmmi ; routine to disconnect pmmi modem discon: mvi a,03fh out basprt+3 xra a out basprt out basprt+2 lxi d,dmsg ; print disconnect msg mvi c,fn$pcb call bdos jmp bbase ; and exit endif ; RAM STORAGE AREA if shoxfr shoflg: ds 1 ; 1 if in file transfer protocol endif if not shoxfr xfrctr: ds 1 ; counter for displaying +'s endif CPMSTK: ds 2 ; SAVES CP/M'S STACK POINTER CPMDEF: ds 1 ; SAVES CP/M'S DEFAULT DISK DRIVE PRTFLG: ds 1 ; FF IF PRINTER ENABLED, 00 OTHERWISE SIFLAG: ds 1 ; NON-ZERO IMPLIES RECEIVED AND PROTOCOL ACTIVE APEOT: ds 1 ; NON ZERO IF SEEN IN GETCKS APFLG: ds 1 ; 00 IF MASKING CONTROL CHARACTERS, FF IF NOT xfrtyp: ds 1 ; 'A' if ASCII, 'B' if binary APADDR: ds 2 ; POINTER TO BUFFER APLEN: DB 0 ; LENGTH OF RECORD AS RECEIVED APBUF: DS 256 ; STORAGE FOR THE RECORD IBP: DS 1 ; BYTE POINTER APNXT: DS 1 ; EXPECTED RECORD NUMBER APCUR: DS 1 ; CURRENT (RECEIVED) RECORD NUMBER DS 256 ; STACK GOES HERE STACK: HEAD: DW PRTBUF TAIL: DW PRTBUF LINCNT: DB PAGLEN PRTBUF EQU $ END START