;			COMSEC	v1.3
; This program is similar in nature as the 'message service' for a
; SYSOP  on a BBS. The  main  point is, that it can be used in the
; command line in CP/M.  I was tired of having to re-enter the BBS
; just to  leave a  message  because the  SYSOP didn't  answer  on
; CHAT.

; Some of these  routines were  borrowed from CHAT.  The origional
; author of CHAT was: Roderick Hart...

; The caller simply enters the program  name or uses the option to
; immediately  start without instructions.  Using a ' D' after the
; name will directly enter it.

; It should also be noted that this program has  NO provisions for
; anything less than CP/M 2.x...

;###############################################
; Written by:
;(except where noted)
; Version 1.0
;		R. Kester
;		JAN 05 84

; Version 1.1
;	Minor changes, renumbered for me
; Version 1.2
;	Some more minor changes...
; Version 1.3
;	Re-did code so compatible with NUCHAT's..
;################################################

NO	EQU	0
YES	EQU	0FFH

STDCPM	EQU	YES		;Yes for 'standard' CP/M
ALTCPM	EQU	NO		;Yes for other type CP/M (TRS-80,etc)

	IF	STDCPM
BASE	EQU	0
	ENDIF

	IF	ALTCPM
BASE	EQU	4200H
	ENDIF

	ORG	BASE+100H

;Version 1.3
VER	EQU	13		;* Current version number

BDOS	EQU	BASE+5
FCB	EQU	5CH
OPEN	EQU	0FH
MAKE	EQU	16H
READ	EQU	14H
WRITE	EQU	15H
CLOSE	EQU	10H
SETDMA	EQU	1AH
USR	EQU	20H
DEFBUF	EQU	80H

CR	EQU	0DH
LF	EQU	0AH
BELL	EQU	07H
SPACE	EQU	20H
SECT	EQU	80H
DEL	EQU	7FH

ABORT	EQU	'A'-40H		;Abort program in message mode
FINIS	EQU	'C'-40H		;Quit and save file (message)
EOF	EQU	'Z'-40H		;End Of File
BACKUP	EQU	'H'-40H		;Baskspace

	JMP	START		;Bypass

; NOTE: When specifying the drive code, enter the number
; corrosponding to the drive.
; i.e.		0=current drive
;		1=dirve 'A'
;		2=drive 'B'.....etc.

; 'MEMLIM' = This allows that number (MEMLIM) of bytes to be added
; starting at BUFF.  BUFF is the area directly following this pro-
; gram where all received characters are stored, INCLUDING already
; existing messages (if any). i.e. If the value of MEMLIM were 50,
; then this program would only allow 50 bytes to be placed in mem-
; ory. It would then issue an error telling the user it is running
; low on memory and  automatically  'close up shop'.  It should be
; noted that,  even if it does enter the error condition, it still
; includes the LASTCALR information. So this number should be used
; as a reference only.

; I.E. 20,000 = 20,000 BYTE MESSAGE FILE.

;* * * * * *  USER MOD AREA * * * * * * *

;*    Message limit (see note above)	*
MEMLIM	EQU	20000

;*	Set YES for an RBBS system	*
;*        (use the LASTCALR file)	*
RBBS	EQU	YES

;*       # of characters per line	*
LIMIT	EQU	72

;*   How many repeatative characters	*
;*       before tagging an error?	*
TOMANY	EQU	LIMIT-8

;*    User area you want messages in	*
USER	EQU	10

;*  Drive for messages, put number here	*
DFDRV	EQU	1

;*       Drive with LASTCALR on it	*
CALLDR	EQU	1

;*        User area of LASTCALR		*
CALLU	EQU	0

;*    File name created for messages	*
;*         spaces ||||||||||| =11  	*
FNAME	DB DFDRV,'MESSAGE CPM'
;*         spaces ||||||||||| =11  	*

;	End of option selections	*
;****************************************

; From here on, you shouldn't need to modify anything else...

	IF	RBBS
DBUF	EQU	80H
BSIZE	EQU	80H
CALLERFCB:
	DB	CALLDR,'LASTCALR   ',0
	DS	23
	DB	0FFH
CALLERADR:DW	DBUF
CALLERSIZ:EQU	BSIZE
CALLERLEN:DW	BSIZE
CALLERPTR:DS	2
	ENDIF		;RBBS

START:
; Do the usual routine for the SP
	LXI	H,0
	DAD	SP
	SHLD	STACK
	LXI	SP,STACK

; Initialize direct CBIOS calls
	LHLD	1
	LXI	D,3
	DAD	D
	SHLD	CSTAT+1		;Con stat
	DAD	D
	SHLD	CIN+1		;Con in
	DAD	D
	SHLD	COUT+1		;Con out

; Get current user area and save it
	MVI	E,0FFH		;Code for GET
	MVI	C,USR
	CALL	BDOS		;Do it
	STA	OLDUSR		;Save it for return

;Get any potential options next
	LDA	DEFBUF+1
	ORA	A
	JZ	NNOP
	LDA	DEFBUF+2
	STA	OPT

NNOP:
	IF	RBBS
	XRA	A		;Zero A
	STA	CALLERFCB+12
	STA	CALLERFCB+32
	LXI	H,CALLERSIZ	;Get value
	SHLD	CALLERLEN
	SHLD	CALLERPTR
	MVI	E,CALLU		;Set area for LASTCALR
	MVI	C,USR
	CALL	BDOS
	LXI	D,CALLERFCB	;Point to filename
	MVI	C,OPEN
	CALL	BDOS
	CPI	YES		;Was it successful?
	JNZ	OPENOK		;Zero = No

	CALL	ILPRT
	DB BELL,CR,LF,LF
	DB 'ERROR --> LASTCALR file not found!...ABORTING'
	DB CR,LF,LF,0

	JMP	LEAVE

OPENOK:
	LXI	D,DEFBUF	;Point to default buffer
	MVI	C,SETDMA	;Make new DMA addr
	CALL	BDOS
	MVI	C,READ		;Read in file @DMA
	LXI	D,CALLERFCB
	CALL	BDOS
	ORI	0FFH		;Read OK?
	JNZ	ROK

	CALL	ILPRT
	DB BELL,CR,LF,LF
	DB 'ERROR -> Can''t read LASTCALR file!'
	DB CR,LF,LF,0
	JMP	LEAVE

ROK:
	CALL	VEIW		;Set up name
	MVI	M,'$'		;Mark end
	ENDIF		;RBBS

; Do sign-on
	CALL	ILPRT
	DB CR,LF,LF
	DB '            Computer Secretary v'
	DB VER/10+'0','.',VER MOD 10+'0'
	DB CR,LF,LF,0

; See if any requests are there
	LDA	OPT
	CPI	NO		;Any options?
	JZ	NONE		;No...
	CPI	'D'		;Direct entry?
	JZ	DIRECT		;We saw a 'D'

; Otherwise give brief instructions
NONE:
	CALL	ILPRT
	DB CR,LF
	DB 'When the  -:  prompt appears, you may  start entering'
	DB CR,LF
	DB 'your message. Hitting the RETURN key is not necessary'
	DB CR,LF
	DB 'for terminating lines. You may  ABORT  the process by'
	DB CR,LF
	DB 'entering a  ^A. Use  ^C for saving message.'
	DB CR,LF
	DB 'You may also make your life easier next time by:'
	DB CR,LF,LF
	DB 'A>progname D           <-- use a ''D'' for direct entry'
	DB CR,LF,LF
	DB 0

; First, move the FNAME into the FCB
DIRECT:
	MVI	B,12		;Number of bytes to move
	LXI	H,FCB		;The 'to' place
	LXI	D,FNAME		;The 'what to move' name
LOOP:
	LDAX	D		;Get the byte
	MOV	M,A		;Get the 'what' byte
	INX	H		;Bump the pointer
	INX	D		;Bump the 'getter'
	DCR	B		;Decrement the counter
	JNZ	LOOP		;If B<>0 then keep chuggin'
	CALL	CLRFCB		;Clear certain extensions

; And set the area for the messages...
	MVI	E,USER		;Get ready to set the
	MVI	C,USR		; user are desired
	CALL	BDOS		;Do it.
	LXI	D,FCB		;Point to the filename
	MVI	C,OPEN		;Get ready to open
	CALL	BDOS		;the file pointed by DE
	CPI	YES		;Was it successful?
	JZ	MAKEIT		;Zero = make it the first time

; Now read in the current contents...
	LXI	D,BUFF		;Point to buffer
RLOOP:
	MVI	C,SETDMA
	PUSH	D		;Save previous DMA addr.
	CALL	BDOS
	LXI	D,FCB		;Point to filename
	MVI	C,READ		;Read it in
	CALL	BDOS
	POP	D
	ORA	A		;Find out DIR code
	JNZ	FINISHED	;Zero = not finished
	LXI	H,80H		;Value of 1 sector
	DAD	D		;HL has new DMA addr.
	XCHG			;Now DE has
	JMP	RLOOP

CLRFCB:
	XRA	A		;Zero A
	STA	FCB+12
	STA	FCB+32
	RET

; We finished reading the file in to buffer
FINISHED:
	XCHG			;Get the last DMA for a double check
	SHLD	POINTR
	CALL	CLRFCB		;Clear the record info for writing
	CALL	SEARCH		;Find the EOF mark and cancel it.
				; and then reset the POINTR.
BEGIN:
	IF	RBBS
	CALL	FIRSTNM		;Get & print callers name
	ENDIF		;RBBS

	CALL	ILPRT
	DB BELL,CR,LF
	DB '      - ^A  aborts  -  ^C saves message'
	DB CR,LF,LF
	DB '-: '
	DB 0

READIT:
	CALL	TESTMEM		;Check memory limit
	CALL	CIN		;Get a byte typed by the user
	CPI	FINIS		;A ^C?
	JZ	QUIT		;Yes?, then tidy up
	CPI	ABORT		;Change their mind?
	JZ	STOP		;Yes?, then don't tidy up
	CPI	CR		;A return?
	JZ	CRLF		;Yes?, do the dirty work
	CPI	BACKUP		;A backspace?
	JZ	BACK		;Do what it requires
	CPI	DEL
	JZ	BACK
	CPI	' '		;A space?
	JC	READIT		;If it equals a value below, then loop
	CALL	PUTNMEM		;Slip it in memory
	PUSH	PSW		;Save 'A'
	MOV	C,A		;Swap it for output
	CALL	COUT		;Send it to them
	POP	B		;Get 'A' into 'B' now
	LDA	COUNT		;How far we gone on the screen?
	INR	A		;Bump it
	STA	COUNT		;Save it
	CPI	LIMIT		;Too many characters yet?
	JZ	CRLF		;Yep
	CPI	LIMIT-8		;Near the limit?
	JC	READIT		;Nope
	MOV	A,B		;Find out if we can
	CPI	' '		; help'm out and do a
	JNZ	READIT		; return for them...
CRLF:
	CALL	ILPRT		;...we could!
	DB CR,LF
	DB '-: '
	DB 0

	XRA	A		;Reset the counter
	STA	COUNT
	MVI	A,CR		;Load a RETURN
	CALL	PUTNMEM
	MVI	A,LF		;Load a LINE FEED
	CALL	PUTNMEM
	JMP	READIT		;Do it all again

BACK:
	LDA	COUNT		;Get the counter
	DCR	A		;Sub one for a backspace
	JM	READIT		;Already at 0?
	STA	COUNT		;Then save it

	CALL	ILPRT
	DB BACKUP,' ',BACKUP,0

	LHLD	POINTR		;Get pointer value
	MVI	A,L		;If it is already
	ORA	H		; a zero then
	JZ	READIT		; skip the rest
	DCX	H		;Sub one for backup
	SHLD	POINTR		;Save it
	JMP	READIT		;Go back and do some more

; Inline print routine using direct I/O
ILPRT:
	XTHL			;Swap SP/HL
ILPLP:
	MOV	C,M		;'C' = ->HL
	PUSH	H
	CALL	COUT		;Send it to the console
	POP	H
	INX	H		;Bump the char. pointer
	MOV	A,M		;'A' = ->(HL)
	ORA	A		;Is it a null?
	JNZ	ILPLP		;Nope, do some more
	XTHL			;Yep, swap HL/SP
	RET

	IF	RBBS
;Enter here to display callers name to CRT...
FIRSTNM:
	CALL	ILPRT
	DB 'Sorry I wasn''t around ',0

	LXI	H,DEFBUF	;Point to area
HAGA:
	MOV	A,M		;Get byte
	CPI	'$'		;See if end
	JZ	ALM		;Yes...
	PUSH	H		;Else, save HL
	MOV	C,A		;Get byte to send
	CALL	COUT		;Send it to CRT
	POP	H		;Get HL back
	INX	H		;Bump it
	JMP	HAGA		;Loop..

ALM:
	CALL	ILPRT
	DB '....',CR,LF,LF,0		;Send this for looks
	RET

;Enter this routine to set-up the name to be printed
;in the file, Replaces the comma with a space. Puts
;it the default buffer...
VEIW:
	LXI	H,DEFBUF	;Point to defualt buffer
DLOP:
	MOV	A,M		;Get a byte
	CPI	EOF		;End of file
	RZ			;Yes..or
	CPI	CR		; found a CR?
	RZ			;Yes...

ALOOP:
	CPI	','		;Then check for this
	JNZ	BLOP		;No...
	MVI	A,' '		;Then make it a space
BLOOP:
	MOV	M,A		;Put it in memory
BLOP:
	INX	H		;Bump pointer
	JMP	DLOP		;Loop...
	ENDIF		;RBBS

;Message for SYSOP if too many chars. in  arow.

TOMSG:	DB CR,LF,LF,'This person possibly tried to fool you!',CR,LF,'$'

;Enter here when we got too many of the same character in a row.
TOERR:
	CALL	ILPRT
	DB BELL,CR,LF,LF
	DB 'ERROR -> Too many similar characters, ABORTING!'
	DB CR,LF,LF,0

	LHLD	ORNPTR		;Get value before anything was entered
	SHLD	POINTR		;Make that the current value
	LXI	D,TOMSG		;Enter a msg. so SYSOP nows why
	CALL	PLOOP		; nothing was entered
QUIT:
	MVI	A,CR		;Put some area in for readibility
	CALL	PUTNMEM
	MVI	A,LF
	CALL	PUTNMEM
	CALL	PUTNMEM

	IF	NOT RBBS
	JMP	ALMOST
	ENDIF		;NOT RBBS

	IF	RBBS
	CALL	CALLGET		;Put name into file
	JMP	ALMOST

;Enter here to place callers name into file..
CALLGET:
	LXI	D,DEFBUF
HLOOP:
	LDAX	D		;Get byte
	CPI	'$'		;End?
	RZ			;Yes..
	PUSH	D		;Then save DE
	CALL	PUTNMEM		;Get byte=>DE put in file by (HL)
	POP	D		;Get DE back
	INX	D		;Bump it
	JMP	HLOOP		;Loop...
	ENDIF		;RBBS

; Call this routine each time we enter a byte into the buffer
; and keep track of twits...

PUTNMEM:
	STA	TEMP		;Save A for the following
	LHLD	POINTR		;Get current value
	MOV	B,A		;Save it
	MOV	M,A		;Slip in byte
	INX	H		;Bump the pointer
	SHLD	POINTR		;Save it
	LHLD	POINTR		;Get it back
	DCX	H		;Decrement it
	DCX	H		; again
	MOV	A,M		;Get byte
	CMP	B		;The same as B?
	JZ	SETNOT		;Yep..
	CPI	CR		; ?
	JZ	SETNOT		;Yep..
	CPI	LF		; ?
	JZ	SETNOT		;Yes?, do something about it
	XRA	A		;No?, then
	STA	MNYCNT		; reset count
	LDA	TEMP		;Get A back
	RET

;Enter here when we find the same character typed twice in a row.
;And exit if too many of them, and keep the caller's name.

SETNOT:
	LDA	MNYCNT		;Get count
	INR	A		;Bump it
	STA	MNYCNT		;Save new count
	CPI	TOMANY		;Too many of them?
	JZ	TOERR		;Yes?, then error exit
	LDA	TEMP		;Get A back
	RET

; Test memory limit... if we are there, then quit

TESTMEM:
	LHLD	MEMS		;The number not to exceed
	XCHG			;Swap
	LHLD	POINTR		;The number to compare to
	MOV	A,H		;Put MS part in A
	CMP	D
	RC			;Ok, if carry
	MOV	A,L		;Else do the same
	CMP	E
	RC			;Ok, if carry

;No carry so we are over exteneded...
	CALL	ILPRT		;Then print error message
	DB BELL,CR,LF,LF
	DB 'SORRY -> Ending things, running low on memory!'
	DB CR,LF
	DB 'Please try again another time...'
	DB CR,LF,LF,0

	JMP	QUIT		;Close up shop
	
MEMS:	DW	BUFF+MEMLIM	;Max. value not to exceed

; Put some sort of marking for the next message
; when being typed out.

; End of message delimmiter.
ENDING:	DB CR,LF,LF,'+ + + + + + + + + + + + + + + +',CR,LF,LF,'$'

ALMOST:
	LXI	D,ENDING	;Put the above line in the file
	CALL	PLOOP		; for readibility
	JMP	GONE

;Used elsewhere...
PLOOP:
	LDAX	D
	CPI	'$'
	RZ
	CALL	PUTNMEM
	INX	D
	JMP	PLOOP

GONE:
	MVI	A,EOF		;Get EOF mark
	CALL	PUTNMEM

; Change the user area for the message file
	MVI	E,USER
	MVI	C,USR
	CALL	BDOS
	LXI	D,BUFF		;Beginning of DMA
	PUSH	D		;Save it
WLOOP:
	POP	D		;Get previous push into DE
	PUSH	D		;Save on the stack
	MVI	C,SETDMA	;Set the DMA to
	CALL	BDOS		;the addr. in DE
	LXI	D,FCB		;Point to filename
	MVI	C,WRITE		;Write to it
	CALL	BDOS
	CPI	NO		;Successful?
	JNZ	WEXIT		;Zero = yes
	POP	H		;Get the past DMA addr.
	LXI	D,SECT		;One more sector
	DAD	D		; is added to the value
	PUSH	H		;Save the next DMA addr.
	MOV	A,H		;Get the high byte
	CMA			;1's compliment
	MOV	D,A		;Save that in D
	MOV	A,L		;Get the low byte
	CMA			;1's compliment
	MOV	E,A		;Save that in E
	INX	D		;= inverted current DMA addr.+1
	LHLD	POINTR		;Get # of bytes that were typed
	DAD	D		;Effectively -> NEW - CURRENT =
				; # of bytes left to write in HL
	MOV	A,H		;Get the MS value in A
	INR	A		;Bump it
	ANA	A		;Set any flags? (a -1?)
	JNZ	WLOOP		;No, then we have more to write.
	POP	H		;Clean the stack
	JMP	EXIT

WEXIT:
	CALL	ILPRT
	DB CR,LF,LF,BELL
	DB 'ERROR --> Can''t write file, ABORTING!'
	DB CR,LF,LF,0

	JMP	LEAVE		;Leave and do nothing

EXIT:
	LXI	D,FCB		;Point to filename
	MVI	C,CLOSE		;And close it
	CALL	BDOS
	CPI	YES		;Successful?
	JNZ	LEAVE		;Zero = No

	CALL	ILPRT
	DB CR,LF,LF,BELL
	DB 'ERROR --> Can''t close file, ABORTING!'
	DB CR,LF,LF,0

LEAVE:
	MVI	C,SETDMA	;Re-set the DMA
	LXI	D,DEFBUF	; so we don't
	CALL	BDOS		; mess up.

	LDA	OLDUSR		;Get origional
	MOV	E,A		; user area and
	MVI	C,USR		; return us to
	CALL	BDOS		; there.
	LHLD	STACK		;Get origional SP
	SPHL			; for 'soft' return
	RET

STOP:
	CALL	ILPRT
	DB CR,LF,LF
	DB '* * *  ABORTED! - Nothing saved  * * *'
	DB CR,LF,LF,0

	JMP	LEAVE

; Create the file
MAKEIT:
	CALL	ILPRT
	DB CR,LF
	DB 'Creating file...'
	DB CR,LF,LF,0

	LXI	D,FCB		;We had to create it new
	MVI	C,MAKE
	CALL	BDOS
	CPI	YES		;successful?
	LXI	H,BUFF		;If we goto BEGIN....
	SHLD	POINTR
	JNZ	BEGIN		;Zero = No

	CALL	ILPRT
	DB CR,LF,LF,BELL
	DB 'ERROR --> No directory space or trouble opening.'
	DB CR,LF,LF
	DB 'Please try again another time....'
	DB CR,LF,LF,0

	JMP	EXIT

;Search the current file and blank out the EOF mark...
SEARCH:
	LXI	D,BUFF		;Point to beginning
	LHLD	POINTR		;Get current position
SLOOP:
	LDAX	D		;Move byte into A
	CPI	EOF		;Was it the EOF?
	JZ	NULLIT		;Yep?, the zero it
	INX	D		;No?, then keep searching
	DCX	H		;Decrement the pointer
	MOV	A,H		;Find out if we have no
	ORA	L		; more positions
	JZ	NULLERR		;Just used for a double check
	JMP	SLOOP		;Else, check some more
NULLIT:
	XRA	A		;Zero A
	XCHG			;Get position in HL
	MOV	M,A		;Put a '0' there
	SHLD	POINTR		;Save the area where our new
	DCX	H		;Save for later if we
	SHLD	ORNPTR		; need it...
	RET			; buffer starts

; Enter here if we did not find an EOF mark in the available
; number of positions (double check)
NULLERR:
	CALL	ILPRT
	DB BELL,CR,LF,LF
	DB 'The validity of the file might be questioned'
	DB CR,LF
	DB 'Did NOT find the EOF, and should have!'
	DB CR,LF,LF,0
	RET

CSTAT:	JMP	$-$		;Set upon entry
CIN:	JMP	$-$		; "    "    "
COUT:	JMP	$-$		; "    "    "
COUNT:	DB	0
OPT:	DB	NO
TEMP:	DS	1
MNYCNT:	DS	1
OLDUSR:	DS	1
POINTR:	DS	2
ORNPTR:	DS	2

	DS	64		;32 level stack
STACK:
	DS	2		;Storge for incoming stack

BUFF	EQU	$		;Message buffer starts here

	END
