;                       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