;                    PLINK.ASM  ver 6.4
;		      (revised 6/14/81)
;
;PLINK is a CP/M transient command which allows the user to
;PLINK currently supports two way transfer of text files between
;the CP/M disk and the remote computer. The following control
;codes may be initiated from the console keyboard:
;
;Control-E      Exit PLINK to CP/M "warm-boot".
;
;Control-T      Transmit ASCII file to remote system, asks for
;               drive (A, B, etc.) and filename.typ.
;
;Control-C      Aborts transmission of file to remote system.
;
;Control-Y      Switches between saving and ignoring
;		incoming ASCII data in RAM buffer,
;		for later transfer to disk.
;
;Control-W      Writes RAM buffer to disk, and asks for drive
;               and filename.typ.
;
;Del (delete)   Backspace when in command mode (e.g. ^T or ^W).
;
;Control-U      Aborts current line when in command mode.
;
;(Note: all other control codes are passed to modem output, and
;may be interpreted by the remote system as various control
;functions.)
;
;
;bdos entry point and function codes
;
base	equ	0	;<<-- set to offset of CP/M for your
			;system, standard systems are 0, some
			;'alternate' systems are 4200H
;
bdos	equ	base+5
resdsk	equ	13	;reset disk system
offc	equ	15	;open file
cffc	equ	16	;close file
dffc	equ	19	;delete file
rrfc	equ	20	;read record
wrfc	equ	21	;write record
mffc	equ	22	;make file
;
;TRS80 pickles and trout sio calls
;offset by -3 that is add 3 to all calls
;
setsio	equ	30h	;set up z80 sio
siotst	equ	33h	;read sio status
sioinp	equ	36h	;input a char
sioout	equ	39h	;output a char
;
;
;default fcb and field definitions
;
fcb	equ	base+5ch
fn	equ	1	;file name field (rel)
ft	equ	9	;file type field (rel)
ex	equ	12	;file extent field (rel)
nr	equ	32	;next record field (rel)
dbuf	equ	base+80h ;default disk buffer address
;
;ascii control characters
;
cr	equ	0dh	;carriage return
lf	equ	0ah	;line feed
del	equ	7fh	;delete (rubout)
bell	equ	07h	;bell signal
tab	equ	09h	;horizontal tab
xon	equ	11h	;x-on character
null	equ	00h	;null char
;
;the following "trigger" equate is set to "lf" (linefeed)
;by default. an optional trigger char may be passed via fcb1
;
; ie:  PLINK B		will set trigger to "bell"
;
;the following options are allowed
;
;	1. B = bell  07h
;	2. X = xon   11h
;	3. U = upload no trigger check at all
;
;any other ascii character may be passed through fcb1
;
;
trigger	equ	LF	;default value
;
;
;warning character for low memory
;
wrnsig	equ	BELL	;if you have one, put 'BELL' here
			;...else put '*' here.
;
;
;	**main program**
;
	org	base+100h
;
	jmp	4000h
	org	base+4000h
lnki:	lxi	sp,stack+64 ;create local stack
	lhld	base+1	;point to CP/M jmp table
	lxi	d,3	;get ready to add 3
	dad	d	;point to con status jmp
	shld	citcal+1 ;modify call adrs
	dad	d	;point to con in jmp
	shld	rccal+1	;modify call adrs
	dad	d	;point to con out jmp
	shld	wccal+1	;modify call adrs
	lda	fcb+1	;see if optional trigger char
	cpi	20h	;blank.. ?
	jz	skp	;..blank so use default "lf"
	cpi	'B'	;bell wanted
	jz	trgbel
	cpi	'X'	;xon wanted
	jz	trgxon
	cpi	'U'	;uploading no checking for trigger
	jz	trgupl
;
settrg	sta	overly+1 ;store the character as is then
	jmp	skp
;
trgbel	mvi	a,bell
	jmp	settrg
;
trgxon	mvi	a,xon
	jmp	settrg
;
trgupl	xra	a	 ;zero out jump
	sta	overl1+1 ;change check for c/r to null
	sta	overl2+1 ;and send linefeeds as well
	jmp	skp
;
skp:	equ	$
;
;
cont:
	xra	a	;clear char buffers
	sta	inch
	sta	outch
	sta	flag	;clear text save flag
	lxi	h,tbuf	;set ptr to tbuf
	shld	ptr
	lxi	h,0	;size = 0
	shld	size
	lxi	h,lnkims  ;print sign-on message
	call	wcs
;
;main loop
;
lnki3:	call	citest	;jump if no data from console
	jz	lnki4
	call	rcc	;else read console data
	cpi	20h
	cc	pcc	;call pcc if control char
	jc	lnki4	;jump if pcc handled char
	ori	80h	;else set valid data bit
	sta	inch	;and store in input char buffer
;
lnki4:	lda	outch	;jump if no data for console
	ora	a
	jp	lnki5
	ani	7fh	;else discard valid data bit
	call	wcc	;send char to console
	xra	a	;then clear output char buffer
	sta	outch
;
lnki5:	call	mitest	;jump if no data from modem
	jz	lnki6
	call	rmc2	;else read modem data
	call	save	;save char in text buffer if flag on
	ori	80h	;set data valid bit
	sta	outch	;store in output char buffer
;
lnki6:	call	motest	;jump if modem xmit buffer busy
	jnz	lnki7
	lda	inch	;jump if no data for modem
	ora	a
	jp	lnki7
	ani	7fh	;discard valid data bit
	call	wmc
;
	xra	a	;...then clear input char buffer
	sta	inch
;
lnki7:	jmp	lnki3	;end of main loop
;
lnkims:	db	cr,lf,'PLINK ver 6.4'
	db	cr,lf,cr,lf
	db	'[^T]ransmit, [^Y]ank, [^W]rite, [^E]xit, [^C]ancel'
	db	cr,lf,'Ready',cr,lf,lf,0
;
;pcc - process control character
;
pcc:	cpi	'E'-40h	;jump out if ctrl e
	jnz	pcc1
	push	h
	lxi	h,ays	;print 'are you sure'
	call	wcs
	pop	h
	call	rcc	;get answer
	call	wcc	;echo it
	ani	5fh	;make upper case
	cpi	'Y'	;yes?
	jz	pccex	;exit
	call	wccr	;crlf
	stc		;tell plink to ignore this character
;
;
pcc1:	cpi	'T'-40h	;jump if not control-t
	jnz	pcc2
	call	stf	;transmit text file to modem
	stc		;tell plink to ignore this character
	ret
;
pcc2:	cpi	'Y'-40h	;jump if not control-y
	jnz	pcc3
	lda	flag
	dcr	a	;was it zero?
	jnz	pcc2a	;yes
	sta	flag	;no, was 1, now 0
	lxi	h,pcmnix ;print ignore incoming stuff
	jmp	pcc2b
;
pcc2a:	mvi	a,1	;turn on text save flag
	sta	flag
	lxi	h,pccmr	;print 'saving incoming text in memory'
;
pcc2b:	call	wcs
	stc		;tell plink to ignore this character
	ret
;
pcc3:	cpi	'W'-40h	;jump if not control-W
	jnz	pcc4
	xra	a	;turn off text save flag
	sta	flag
	call	wtb	;write text buffer to disk
	stc
	ret
;
pcc4:	stc		;let plink handle all other cont. codes
	cmc
	ret
;
pccex:	lxi	h,disms	;print 'modem not disconnected'
	call	wcs
	jmp	base	;exit to warm boot
;
ays:	db	cr,lf,'Exit to CP/M - are you sure (Y or N)? ',0
;
disms:	db	cr,lf,'+++ Exit to CP/M +++',cr,lf,0
;
pccmr:	db	cr,lf,'Saving incoming text in memory',cr,lf,0
pcmnix:	db	cr,lf,'Ignoring incoming text',cr,lf,0
;
;stf - send text file (to modem)
;
stf:	call	gfn	;get name of disk file to send
	jc	stf6	;jump if file name error
	call	open	;try to open specified file
	cpi	255	;jump if file not found
	jz	stf7
;
stf1:	call	read	;read next record into dbuf
	cpi	1	;jump if end-of-file
	jz	stf5
	lxi	h,dbuf 	;point to disk buffer
	mvi	c,128
;
stf2:	mov	a,m	;fetch next char from dbuf
	inx	h
	cpi	'Z'-40h	;jump if end-of-file character
	jz	stf5
;
overl2	cpi	lf	;ignore line feeds
	jz	stf4
	call	wmc	;write character to modem
	call	wcc	;write character to console
;
overl1	cpi	cr	;jump if not carriage return
	jnz	stf4
;
stf3:	call	citest	;check console data ready
	jz	stf3a	;no data there
	call	rcc	;get console character
	cpi	'C'-40h	;control c aborts it
	jz	stf8
;
stf3a:	call	mitest	;wait for next modem character
	jz	stf3
	call	rmc2	;check modem for trigger char.
;
overly	cpi	trigger
	jnz	stf3
	call	wccr	;send crlf to console
;
stf4:	dcr	c	;loop thru rest of dbuf
	jnz	stf2
	jmp	stf1	;go get next record from disk
;
stf5:	lxi	h,stfsm	;print 'file send complete'
	call	wcs
	ret
;
stf6:	lxi	h,stfs1	;print 'file name error'
	call	wcs
	ret
;
stf7:	lxi	h,stfs2	;print 'file not found'
	call	wcs
	ret
;
stf8:	lxi	h,stfsa	;print 'file send aborted'
	call	wcs
	ret
;
stfsm:	db	'File send complete',cr,lf,0
stfs1:	db	'File name error or abort',cr,lf,0
stfs2:	db	'File not found',cr,lf,0
stfsa:	db	cr,lf,'File send aborted',cr,lf,0
;
;save - save char in text buffer if flag on
;
;  entry conditions
;     a - character to save
;
save:	push	psw
	lda	flag
	ora	a
	jnz	save1
	pop	psw
	ret
;
save1:	pop	psw
	cpi	del	;rubout (del) ?
	rz		;yes, ignore it
	cpi	20h	;test for control characters
	jnc	save2	;jump if not control char.
	cpi	cr	;allow cr to be saved
	jz	save2
	cpi	lf	;allow lf to be saved
	jz	save2
	cpi	tab	;allow tab to be saved
	jz	save2
	ret		;ignore all other control chars.
;
save2:	push	h
	lhld	size	;size = size + 1
	inx	h
	shld	size
	lhld	ptr
	mov	m,a
	inx	h
	shld	ptr
	push	psw
	lda	base+7	;get system size
	sui	1	;so we dont crash CP/M
	cmp	h	;are we out of room?
	jz	saveab	;yes, abort
	sui	4	;leave some room (1k)
	cmp	h
	mvi	a,wrnsig  ;signal console running out of space
	cc	wcc
	pop	psw
	pop	h
	ret
;
;saveab - ran out of room, issue message and flow
;	  through to disk save routine
;
savend:	db	bell,cr,lf,'Aborting - no room left',0
;
saveab:	lxi	sp,stack+64  ;reinitialize stack
	lxi	h,savend  ;print 'aborting - no room left'
	call	wcs
	lxi	h,lnki	;set up return address
	push	h	;leave it on the stack
;
;wtb - write text buffer to disk
;
wtb:	lhld	size	;jump if text buffer empty
	mov	a,l
	ora	h
	jz	wtb5
	mvi	c,resdsk ;reset in case read-only
	call	bdos
	call	gfn	;get file name
	jc	wtb6	;jump if file name error
	call	delt	;delete old file, if any
	call	make	;make new file
	lhld	size	;de = tbuf size
	xchg
	lxi	h,dbuf	;top of stack points to dbuf
	push	h
	lxi	h,tbuf	;hl points to tbuf
;
wtb1:	mvi	c,128	;disk buffer size
;
wtb2:	mov	a,m	;fetch next byte of tbuf
	inx	h
	xthl
	mov	m,a	;store in dbuf
	inx	h
	xthl
	dcx	d	;size = size - 1
	mov	a,d	;exit loop if size = 0
	ora	e
	jz	wtb3
	dcr	c	;loop until dbuf full
	jnz	wtb2
	call	write	;write full dbuf to disk
	xthl		;top of stack points to dbuf
	lxi	h,dbuf
	xthl
	jmp	wtb1	;loop until end of tbuf
;
wtb3:	pop	h	;hl points to current place in dbuf
;
wtb4:	mvi	m,'Z'-40h ;store eof code
	inx	h
	dcr	c	;loop thru rest of dbuf
	jnz	wtb4
	call	write	;write last sector to disk
	call	close	;clean up act and go home
	lxi	h,tbuf	;clear text buffer
	shld	ptr
	lxi	h,0
	shld	size
	lxi	h,wtbsm	;print 'buffer saved on disk'
	call	wcs
	ret
;
wtb5:	lxi	h,wtbs1	;print 'text buffer empty'
	call	wcs
	ret
;
wtb6:	lxi	h,wtbs2	;print 'file name error'
	call	wcs
	ret
;
wtbsm:	db	cr,lf,'Buffer saved on disk',cr,lf
	db	'Memory save cancelled',cr,lf,0
wtbs1:	db	'Text buffer empty',cr,lf,0
wtbs2:	db	'File name error or abort',cr,lf,0
;
;wcs - write console string
;
;  entry conditions
;     hl - points to string (term by zero byte)
;
wcs:	mov	a,m
	inx	h
	ora	a
	rz
	call	wcc
	jmp	wcs
;
;wccr - write console carriage return (and line feed)
;
wccr:	mvi	a,cr
	call	wcc
	mvi	a,lf
;
;wcc - write console character
;
;  entry conditions:
;     a - character to write
;
wcc:	push	psw
	push	b
	push	d
	push	h
	mov	c,a	;get character for cbios
wccal:	call	$-$	;modified by init.
	pop	h
	pop	d
	pop	b
	pop	psw
	ret
;
;rcs - read console string (with echo)
;
;  exit conditions
;     b - number of characters read (<255)
;    hl - points to last char stored (cr)
;
rcs:	lxi	h,ibuf
	mvi	b,0
;
rcs1:	call	rcc	;read next char from console
	cpi	del	;jump if not del
	jnz	rcs2
	inr	b	;ignore del if ibuf already empty
	dcr	b
	jz	rcs1
	dcx	h	;else discard last char
	mov	a,m	;echo discarded char to console
	call	wcc
	dcr	b	;decrement count
	jmp	rcs1	;	and loop
;
rcs2:	cpi	'U'-40h	;jump if not control u
	jnz	rcs3
	call	wccr	;else abort current line
	jmp	rcs	;	and start over
;
rcs3:	call	wcc	;echo char to console
	mov	m,a	;store char in ibuf
	inr	b	;increment count
	cpi	cr	;jump if carriage return
	jz	rcs4
	inx	h	;else advance pointer
	jmp	rcs1	;	and loop
;
rcs4:	mvi	a,lf	;issue line feed and return
	call	wcc
	ret
;
;rcc - read console character
;
;  exit conditions
;     a - character read
;
rcc:	push	b
	push	d
	push	h
rccal:	call	$-$	;modified by init.
	pop	h
	pop	d
	pop	b
	ret
;
;wmc - write modem character
;
;  entry conditions
;     a - character to write
;
;
wmc:
	ani	7fh
	sta	xyz
	di
	out	0
	mvi	a,0
	sta	0ef08h
	lda	xyz
	sta	2a01h
	out	1
	mvi	a,1
	sta	0ef08h
	ei
	ret
;
;rmc - read modem character
;
;  exit conditions:
;     a - character read
	di
	out	0
	mvi	a,0
	sta	0ef08h
rmc:
	lda	2a00h
	ani	01h
	cpi	00h
	jz	rmc
	out 1
	mvi	a,1
	sta	0ef08h
	ei
rmc2:	di
	out	0
	mvi	a,0
	sta	0ef08h
	lda	2a01h
	sta	xyz
	out	1
	mvi	a,1
	sta	0ef08h
	ei
	lda	xyz
	ani	7fh
	ret
;
;
;
;gfn - get file name
;
gfn:	lxi	h,gfnsd	;print 'which drive?'
	call	wcs
	call	rcc	;get answer from console
	call	wcc	;echo it to console
	ani	5fh	;make upper case
	cpi	'C'-40h	;^C means abort
	jz	gfn6
	sui	'A'-1
	jc	gfn	;require alphabetic
	jz	gfn
	cpi	17	;allow 16 drives (as in CP/M 2.x)
	jnc	gfn
	sta	fcb
;
gfnb:	lxi	h,gfns1	;print 'filename? '
	call	wcs
	call	rcs	;read response into ibuf
	lxi	h,fcb+fn  ;blank fill fn and ft fields
	mvi	c,11
;
gfn1:	mvi	m,' '
	inx	h
	dcr	c
	jnz	gfn1
	lxi	h,ibuf	;point to input buffer
	lxi	d,fcb+fn  ;scan off fn field
	mvi	c,9
;
gfn2:	mov	a,m	;fetch next char from ibuf
	inx	h
	cpi	61h	;if lc, convert to uc
	jc	gfn2a
	sui	20h
;
gfn2a:	cpi	cr	;jump if end of line
	jz	gfn5
	cpi	'.'	;jump if end of name
	jz	gfn3
	stax	d	;else store char in fn field
	inx	d
	dcr	c	;loop if 8 or less chars so far
	jnz	gfn2
	jmp	gfn6	;else take error exit
;
gfn3:	lxi	d,fcb+ft  ;scan off ft field
	mvi	c,4
;
gfn4:	mov	a,m	;fetch next char from ibuf
	inx	h
	cpi	61h	;if lc, convert to uc
	jc	gfn4a
	sui	20h
;
gfn4a:	cpi	cr	;jump if end of line
	jz	gfn5
	stax	d	;else store char in ft field
	inx	d
	dcr	c	;loop if 3 or less chars so far
	jnz	gfn4
	jmp	gfn6	;else take error exit
;
gfn5:	xra	a
	sta	fcb+ex	;set extent number to zero
	sta	fcb+nr	;set record number to zero
	stc		;clear error flag and return
	cmc
	ret
;
gfn6:	stc		;set error flag and return
	ret
;
gfnsd:	db	cr,lf,'Which drive? ',0
gfns1:	db	cr,lf,'Filename? ',0
;
;open - open disk file
;
open:	push	h
	push	d
	push	b
	lxi	d,fcb
	mvi	c,offc
	call	bdos
	pop	b
	pop	d
	pop	h
	ret
;
;read - read record from disk file
;
read:	push	h
	push	d
	push	b
	lxi	d,fcb
	mvi	c,rrfc
	call	bdos
	pop	b
	pop	d
	pop	h
	ret
;
;close - close disk file
;
close:	push	h
	push	d
	push	b
	lxi	d,fcb
	mvi	c,cffc
	call	bdos
	pop	b
	pop	d
	pop	h
	ret
;
;delt - delete disk file
;
delt:	push	h
	push	d
	push	b
	lxi	d,fcb
	mvi	c,dffc
	call	bdos
	pop	b
	pop	d
	pop	h
	ret
;
;write - write record to disk
;
write:	push	h
	push	d
	push	b
	lxi	d,fcb
	mvi	c,wrfc
	call	bdos
	pop	b
	pop	d
	pop	h
	ret
;
;make - make new disk file
;
make:	push	h
	push	d
	push	b
	lxi	d,fcb
	mvi	c,mffc
	call	bdos
	pop	b
	pop	d
	pop	h
	ret
;
;citest - check console input status
;
citest:	push	b
	push	d
	push	h
citcal:	call	$-$	;modified by init.
	ora	a	;set zero flag
	pop	h
	pop	d
	pop	b
	ret		;zero flag carries answer
;
;mitest - check modem input status
mitest:	di
	out	0
	mvi	a,0
	sta	0ef08h
	lda	2a00h
	sta	xyz
	out	1
	mvi	a,1
	sta	0ef08h
	ei
	lda	xyz
	ani	01h
	cpi	00h
	jnz	mitst1
	ora	a
	jmp	mitst2
mitst1	cma
	ora	a
mitst2	ret
;
;
;motest - check modem output status
;
motest:
	di
	out	0
	mvi	a,0
	sta	0ef08h
	lda	2a00h
	sta	xyz
	out	1
	mvi	a,1
	sta	0ef08h
	ei
	lda	xyz
	xri	02h
	ani	02h
	mvi	a,0
	jz	motst1	;zero flag carries answer
	cma
;
motst1:	ora	a	;set zero flag if ready
	ret
;
;data area
;
xyz:	db	00
inch:	ds	1	;input char buffer (to cyber)
outch:	ds	1	;output char buffer (from ciber)
stack:	ds	80	;local stack
ibuf:	ds	256	;input buffer
;
;text buffer
;
flag:	ds	1	;text save flag
ptr:	ds	2	;text buffer pointer
size:	ds	2	;text buffer size
tbuf:	equ	$	;start of text buffer
;
	end
