;       *** 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     ; <SI> = SHIFT INTO PROTOCOL MODE
SO:     EQU     0EH     ; <SO> = SHIFT OUT OF PROTOCOL MODE]
                       ; PROTOCOL MODE IMPLIES THAT <ESC> SEQUENCES
                       ; ARE NOT SENT TO CONSOLE BUT ARE USED TO CONTROL
                       ; THE UP/DOWN LOAD PROTOCOL
DC1:    EQU     11H     ; <DC1> CONTROL-Q: RESUME TRANSMISSION
DC2:    EQU     12H     ; <DC2> CONTROL-R: PRINTER ON
DC3:    EQU     13H     ; <DC3> CONTROL-S: STOP TRANSMISSION
DC4:    EQU     14H     ; <DC4> CONTROL-T: PRINTER OFF
KNAK:   EQU     15H     ; <NAK>
DLE:    EQU     10H     ; <DLE> (TRANSPARACY FLAG)
ESC:    EQU     1BH     ; ESCAPE
EOF:    EQU     1AH     ; ^Z (CP/M END OF FILE)
CR:     EQU     0DH     ; <RET>
LF:     EQU     0AH     ; <LF>
FF:     EQU     0CH     ; <FF>
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

; <ESC><I> 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:
;       <ESC><A>        cursor up
;       <ESC><B>        cursor down
;       <ESC><C>        cursor right
;       <ESC><D>        cursor left
;       <ESC><H>        cursor home (line 1, column 1)
;       <ESC><J>        erase to end of screen
;       <ESC><K>        erase to end of line
;       <ESC><j>        erase screen and home cursor (also <FF>)
;       <ESC><Y><L+31><C+31> position cursor to line L column C
; HC = Hard Copy
;   Implies following:
;       <DC2> (^R, 022 octal, 12 hex) enable printer; subsequent data
;                                     will be copied to local printer
;       <DC4> (^T, 024 octal, 14 hex) disable printer
;       <ESC><e>        disable terminal display
;       <ESC><f>        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 <SI> 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 <DLE>
       JZ      ISDLE           ; THEN PROCESS <DLE>
       CPI     SI              ; IF <SI>
       JZ      ISSI            ; THEN PROCESS <SI>
       CPI     SO              ; IF <SO>
       JZ      ISSO            ; THEN PROCESS <SO>
       CPI     ESC             ; IF <ESC>
       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 <FF>
       JZ      FFHZ19          ; THEN MAP IT TO <ESC><E>
       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 <DC2> (^R) RECEIVED FROM HOST OR CONSOLE

PRTON:  MVI     A,0FFH          ; SET PRINTER FLAG
       STA     PRTFLG
       JMP     TERM

; HERE IF <DC4> (^T) RECEIVED FROM HOST OR CONSOLE

PRTOFF: XRA     A               ; CLEAR PRINTER FLAG
       STA     PRTFLG
       JMP     TERM

; HERE IF <DLE> RECEIVED FROM HOST

ISDLE:  CALL    GETSIX          ; GET CHARACTER FOLLOWING <DLE>
       CALL    TERDSP          ; DISPLAY IT ON CONSOLE/PRINTER
       JMP     TERM

ISSO:   XRA     A       ; <SO> DISABLES PROTOCOL MODE
       if      shoxfr
       sta     shoflg
       endif
       LXI     SP,STACK        ; RESTORE STACK INCASE OF ABORT
ISSI:   STA     SIFLAG  ; <SI> ENABLES PROTOCOL MODE
       JMP     TERM

; HERE ON <ESC>

ISESC:  LDA     SIFLAG  ; IF <SI> 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 <ESC>
       CPI     'I'     ; IF <ESC><I>
       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 <ESC>
       CPI     'j'             ; <ESC><j> = <ESC><E>
       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 <ESC><L>
       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 <ESC><A>
       JNZ     TERM    ; THEN

; Initialize for data transmission using the CompuServe A-protocol
; The protocol begins with the following being sent from the host:
;       <ESC><A><SOH><U | D><A | B><FILESPEC><ETX><CKSUM>
; 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 <EOT> 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 <EOF> 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 <EOT> 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 FI
LE 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 <EOF>
       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 <NAK> 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      ; <RET> 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     ; <SOH> STARTS THE RECORD
       JZ      APRCV1
       CPI     ETX     ; <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 <EOT> 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 <DLE><DATA+40H)
       CALL    APPUTS
       MOV     A,M
       ORI     40H
ASND2A: CALL    APPUTS          ; TRANSMIT THE CHARACTER
       INX     H
       if      not shoxfr
       call    ctxfr           ; display '+' every 32 characters
       endif
       DCR     B
       JNZ     APSND2  ; BACK FOR MORE IF ANY

APSND3: MVI     A,ETX           ; TERMINATE THE TEXT PORTION
       CALL    APPUTS
       MOV     A,E             ; GET CHECKSUM
       CPI     20H             ; IF < 20H
       JNC     ASND3A          ; THEN
       MVI     A,DLE           ; SEND IT MASKED
       CALL    APPUTS
       MOV     A,E
       ORI     40H
ASND3A: CALL    APPUTS

ASND4A: MVI     C,30            ; ABOUT 4 SECONDS
ASND4C: LXI     D,2500H         ;
ASND4:  CALL    GETSIN          ; GET HOST'S REPLY
       JNZ     SND4B
       CALL    TRMGET
       CPI     ETX
       JZ      ABORT           ; ABORT THE OPERATION IF ^C TYPED
       DCX     D               ; DECREMENT INNER LOOP COUNT
       MOV     A,D
       ORA     E
       JNZ     ASND4
       DCR     C
       JNZ     ASND4C
       MVI     A,ETX           ; SEND EXTRA <ETX>
       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 <NAK>
       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 <NAK>
       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 <NAK> RECEIVED
       JZ      ISSO    ; THEN REVERT TO TERMINAL MODE
       RET             ; RETURN


GETCKS: CALL    GETSIX  ; GET NEXT SIO CHAR WITH CHECKSUMMING
       CPI     ETX     ; IF <ETX>
       RZ              ; THEN RETURN
       PUSH    PSW
       CPI     EOT
       JNZ     NOTEOT  ; THEN
       STA     APEOT   ; SET <EOT> SEEN FLAG
NOTEOT: CPI     DLE     ; IF <DLE>
       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, <HT> thru <RET>, 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 <SI> RECEIVED AND PROTOCOL ACTIVE
APEOT:  ds      1       ; NON ZERO IF <EOT> 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