	.Z80
        PAGE 70

;       RX.M
;       receives files on the ELZET80 via console.

;       11 Mar 1988     A.Herrmann
;       		adapted from RX.Z80 on the FX180

;       RX signals that it is ready to receive by sending 
;       an ACK char (0FH).
;       First, RX expects to receive the file name, a string 
;       of up to 12 (8+'.'+3) chars followed by an EOT char.
;       Note: No disk name in file name!
;       The program is aborted when an empty file name is received.
;       RX acknowledges a non-empty file name with an ACK.

;       Next, a 2-byte integer denoting the file length in 
;       128-byte records is expected (low byte, then high byte).
;       Again, RX acknowledges a non-zero file length with ACK.

;       Then, the signalled number of records are received.
;       Each record is acknowledged with an ACK char.
;       The incoming bytes are stored in memory and usually
;       written to disk after all bytes have been received.
;       However, if the file length exceeds the buffer size,
;       the input buffer is flushed to disk when it becomes full.

;       This whole procedure is repeated until an empty file name
;       is received.
; NOTE: An existing file with the same name as the file that is
;       being received is overwritten without warning!

HDR     EQU 0           ;No header message

RX::    JP START

;--------------- Constants and Variables ---------

BDOS    EQU 5           ;BDOS ENTRY ADDRESS
FBASE   EQU 6           ;FDOS BASE ADDRESS

CONSTA  EQU 6           ;CONSOLE STATUS PORT
CONDAT  EQU 4           ;CONSOLE DATA PORT

TEMPTY  EQU 2           ;TRANSMIT BUFFER EMPTY BIT
RAVAIL  EQU 0           ;RECEIVE CHAR AVAILABLE BIT

ACK     EQU 0FH         ;Ack char
EOT     EQU 7FH         ;End Of Transmission

RECSIZ  EQU 128         ;CP/M RECORD SIZE

BUFSTA: DW 0            ;BUFFER START
BUFEND: DW 0            ;BUFFER END
DATEND: DW 0            ;DATA END

INPBUF: DS 16,0         ;INPUT STRING BUFFER

STRING: DS 5,0          ;OUTPUT STRING BUFFER
CRLF:   DB 0DH,0AH,'$'

FCB:    DS 36           ;FILE CONTROL BLOCK
FCBLEN  EQU $-FCB

FILSIZ: DW 0            ;FILE SIZE IN BYTES

ISOPEN: DS 0            ;FLAG NON-ZERO IFF FILE IS OPEN

;--------------- Output Strings ------------------

        IF HDR

HEADER: DB 0CH
        DB 'RX.M - Version 1.0   11 Mar 88  (C) A.Herrmann'
        DB 0DH,0AH,0AH,'$'

BUFMSG: DB 'Buffer Length = '
        DB '$'

RCVMSG: DB 0DH,0AH,0AH
        DB 'Ready to receive...'
        DB '$'

        ENDIF

;--------------- Start of program ------------------

START::
        IF HDR
        LD DE,HEADER    ;Header message
        CALL PRINT
        ENDIF

        LD HL,PRGEND    ;Find buffer start
        LD L,0
        INC H
        LD (BUFSTA),HL
        EX DE,HL

        LD HL,(FBASE)   ;Find buffer end
        LD L,0
        DEC HL
        LD (BUFEND),HL  ;BUFEND -> last char in buffer

        IF HDR
        SCF             ;Calculate buffer size
        CCF
        SBC HL,DE
        INC HL
        CALL CONVRT     ;Convert HL into string
        LD DE,BUFMSG
        CALL PRINT      ;and print buffer size
        LD DE,STRING
        CALL PRINT
        LD DE,RCVMSG    ;Ready to receive-message
        CALL PRINT
        ENDIF


;--------------- Main loop ------------------------

RXLOOP:
        CALL SNDACK     ;Signal that ready to receive
        CALL RXNAME     ;receive file name
        JP Z,EXIT       ;If empty file name, exit.
        CALL SNDACK
        CALL RXSIZE     ;receive file size
        CALL SNDACK
        CALL RXDATA     ;receive file data
        CALL SNDACK
        JP RXLOOP       ;Continue with next file

;--------------- End of Main loop ------------------------

EXIT:
        JP 0            ;WARMBOOT

;--------------- Major Subroutines ------------------------

RXNAME:
        LD B,15         ;Receive file name
        LD HL,INPBUF    ;as FILENAME.EXT<EOT>

RXNAM1: PUSH HL
        PUSH BC
        CALL GETCH      ;Get next char from console
        POP BC
        POP HL
        CP EOT
        JR Z,RXNAM2     ;Until EOT
        LD (HL),A
        INC HL
        DJNZ RXNAM1     ;or 15 chars

RXNAM2: XOR A
        LD (HL),A
        LD DE,INPBUF
        SBC HL,DE

        RET

RXSIZE:                 ;Receive file size (2-byte integer)
        CALL GETCH      ;Low Byte first
        LD L,A
        PUSH HL
        CALL GETCH      ;High byte last
        POP HL
        LD H,A
        LD (FILSIZ),HL

        XOR A
        LD (ISOPEN),A   ;File is not yet open
BLDFCB:                 ;Build File Control Block 
        LD HL,INPBUF
        LD DE,FCB
        CALL BF

        RET

RXDATA:                 ;Receive file data 
        LD HL,(BUFSTA)  ;HL = buffer start
RXDAT1:
        PUSH HL
        LD HL,(FILSIZ)
        LD A,L          ;More bytes to come?
        OR H
        POP HL
        JP Z,DONE

        LD BC,RECSIZ
RXDAT2:                 ;Get next record
        PUSH BC
        PUSH HL
        CALL GETCH      ;Get next char from console
        POP HL
        POP BC
        LD (HL),A       ; and store it away
        INC HL
        EX DE,HL        ;HL -> beyond End of Buffer?
        LD HL,(BUFEND)
        XOR A           ;Clear carry flag
        SBC HL,DE
        EX DE,HL
        CALL C,FLUSHB

        DEC BC
        LD A,B          ;Record complete?
        OR C
        JP NZ,RXDAT2

        PUSH HL         ;Yes, decrement record counter
        LD HL,(FILSIZ)
        DEC HL
        LD (FILSIZ),HL
        CALL SNDACK     ;Acknowledge record
        POP HL
        JP RXDAT1

FLUSHB:                 ;flush data buffer to disk
        PUSH BC         ;Save in-record counter (should be 0 now)
        CALL WRTDSK
        LD A,1          ;File is now open
        LD (ISOPEN),A
        LD HL,(BUFSTA)  ;HL = buffer start
        POP BC
        RET

DONE:
        LD A,L          ;Fill record (i.e.,
        AND 07FH        ; up to ..7FH or ..FFH)
        JR Z,DONE1
        XOR A           ;with 0's
        LD (HL),A       ;(Actually, this shouldn't
        INC HL          ;be necessary.)
        JR DONE
DONE1:
        CALL WRTDSK
        CALL CLOSEF     ;Close file

        RET

;--------------- Write file to disk --------------

WRTDSK:                 ;Write buffer contents to disk
        DEC HL          ;Save end of actual data
        LD (DATEND),HL

        LD A,(ISOPEN)   ;File already open (after flush)?
        OR A
        JR NZ,WRTBUF

        LD C,19         ;Delete file if already exists
        LD DE,FCB
        CALL BDOS

        LD C,22         ;Open file
        LD DE,FCB
        CALL BDOS
WRTBUF:
        LD DE,(BUFSTA)  ;First write address
        LD HL,(DATEND); ;Last write address
        XOR A
        SBC HL,DE       ;Calculate buffer length
        RET Z           ;Buffer empty?

WLOOP:                  ;Loop writing to disk
        PUSH DE
        LD C,26         ;Set DMA to DE
        CALL BDOS
        LD DE,FCB
        LD C,21         ;Write record
        CALL BDOS
        POP DE
        OR A            ;Error?
        JR NZ,DSKERR

        LD HL,RECSIZ    ;No,
        ADD HL,DE       ;go to next record
        EX DE,HL
        LD HL,(DATEND)  ;End of data reached?
        SCF
        SBC HL,DE
        JR NC,WLOOP
        RET

CLOSEF:                 ;Close file
        LD C,16
        LD DE,FCB
        CALL BDOS
        CP 0FFH
        JR Z,DSKERR
        RET

DSKERR:
        JP EXIT

;--------------- SUBROUTINEN ----------------------

GETCH::                 ;Get char from console port
        IN A,(CONSTA)
        BIT RAVAIL,A
        JR NZ,GETCH1    ;Wait until character available
        CALL CHKKEY
        JR GETCH
GETCH1:
        IN A,(CONDAT)
        RET             ;Return char in A

SNDACK::                        ;Send acknowledge char via console
        LD E,ACK
        LD C,6
        CALL BDOS
        RET

CHKKEY:                 ;Check keyboard, abort on CTRL/C
        LD C,6
        LD E,0FFH
        CALL BDOS
        CP 03
        JP Z,EXIT
        RET


PRINT:                  ;String output to console
        LD C,9
        CALL BDOS
        RET

CONVRT:                 ;Convert integer in HL
        LD DE,STRING    ; into 5-digit string
        LD BC,5

        EX DE,HL
        ADD HL,BC
        DEC HL
        EX DE,HL
        LD B,C

        PUSH DE         ;HL >= A000H ?
        EX DE,HL
        LD HL,0A000H
        SCF
        SBC HL,DE
        JR NC,CONVR0

        EX DE,HL        ;Yes, subtract A000H = 40960 
        LD DE,0A000H
        CCF
        SBC HL,DE
        CALL DIV10      ; and divide only the difference
        POP DE
        CALL RCODE      ;Encode the remainder
        DEC B           ;lest one digit
        PUSH DE
        LD DE,1000H     ;add 1000H = 4096 to the quotient
        ADD HL,DE
        POP DE
        JR CONVR1       ; and continue as normally

CONVR0: EX DE,HL
        POP DE

CONVR1: CALL DIV10      ;Divide HL by 10
        CALL RCODE      ;Encode remainder
        DJNZ CONVR1     ;all digits generated?
        RET

DIV10:                  ;divides HL by 10
        PUSH BC
        PUSH DE
        LD B,12         ;Dividend has 12+4 bits
        LD D,0A0H       ;Divisor (=10) in upper half byte of D
        LD E,0
DIVBIT: XOR A           ;Clear carry flag
        ADD HL,HL       ;Shift HL 1 bit left
        PUSH AF         ;Sace carry flag
        XOR A           ;Clear carry flag
        SBC HL,DE       ;DE fits in HL?
        INC HL          ;Let's assume so
        JR NC,DIVCNT    ;If not,
        POP AF          ; and if no carry before,
        JR C,DIVNXT
        ADD HL,DE       ; subtract and
        DEC HL          ; decrement quotient
        JR DIVNXT
DIVCNT: POP AF
DIVNXT: DJNZ DIVBIT
        POP DE
        POP BC
        RET

RCODE:                  ;encodes the remainder in HL
        LD A,H          ;shift remainder from upper half byte
        RR A            ;into lower half byte
        RR A
        RR A
        RR A
        AND 0FH         ;Mask it
        OR 30H          ;Encode it as ASCII char
        LD (DE),A       ;Store it
        LD A,H          ;New quotient is in L and
        AND 0FH         ;lower half byte of H
        LD H,A
        DEC DE
        RET


;BF - Build File Control Block
; formats a 00H-byte-terminated string (presumed to be a filename)
; into an FCB, setting the disk and file name and type and clearing
; the remainder of the FCB to 0's.

; Entry parameters:
;       DE -> File Control Block (36 Bytes)
;       HL -> File name string (00H-byte terminated)

; Exit parameters
;       The built File Control Block

; Calling sequence
;       LD DE,FCB
;       LD HL,FILENAME
;       CALL BF

; This subroutine was taken from Andy Johnson-Laird's
; "The Programmer's CP/M Handbook", pp.100 (vi. for more
; comments)

BF:
        INC HL
        LD A,(HL)
        DEC HL
        CP ':'
        JP NZ,BF$ND
        LD A,(HL)
        AND 00011111B
        INC HL
        INC HL
        JP BF$SD

BF$ND:
        XOR A
BF$SD:
        LD (DE),A
        INC DE
        LD C,8
        CALL BF$GT

        CP '.'
        JP NZ,BF$NT
        INC HL

BF$NT:
        LD C,3
        CALL BF$GT

        LD B,0
        LD C,24
        CALL BF$FT
        RET

BF$GT:
        LD A,(HL)
        OR A
        JP Z,BF$SFT
        CP '*'
        JP Z,BF$QFT
        CP '.'

        JP Z,BF$SFT
        LD (DE),A

        INC DE
        INC HL
        DEC C
        JP NZ,BF$GT

BF$SKIP:
        LD A,(HL)
        OR A
        RET Z
        CP '.'
        RET Z
        INC HL
        JP BF$SKIP

BF$SFT:
        LD B,' '
        JP BF$FT

BF$QFT:
        LD B,'?'
        CALL BF$FT
        JP BF$SKIP

BF$FT:
        PUSH AF
        LD A,B

BF$FTL:
        LD (DE),A
        INC DE
        DEC C
        JP NZ, BF$FTL
        POP AF
        RET


PRGEND  EQU $

        END
