        name msxapc
; File MSXAPC.ASM
; Last modification: 20 April 1986

; System dependent module for NEC APC
; ************ START MSXAPC.ASM ***********************************

; Kermit system dependent module for NEC Advanced Personal Computer (APC)
; Ron Blanford, University of Washington, August 1984

; Added global entry point vstat for use by Status in mssset.
; Also trimmed off trailing commas in publics. Joe R. Doupnik 12 March 1986
; Add global procedures ihosts and ihostr to handle host initialization
; when packets are to be sent or received by us,resp. 24 March 1986
; Add global procedure dtrlow (with discon in place of serhng) to force DTR
; and RTS low in response to Kermit command Hangup. [jrd]
; Add global procedure Dumpscr, called by Ter in file msster, to dump screen
;  to a file. Just does a beep for now. 13 April 1986 [jrd]
; Include modifications by Ron Blanford to Dobaud and Getbaud. 14 April 1986
; In proc Outchr add override of xon from chkxon sending routine.
;  This makes a hand typed Xoff supress the xon flow control character sent
;  automatically as the receiver buffer empties. 20 April 1986 [jrd]
;
;
; Modified to get key scan codes directly from fifo buffer in IO.SYS
; This version works with MS-DOS Versions 2.00 (000) and 2.11 (001); it
; should work with future MS-DOS revisions so long as NECIS maintains
; the configuration table in IO.SYS -  the thing most apt to fail is the
; key autorepeat because kbrepflg is furthest from a hook in config table.
; If this happens the equate for offset of kbrepflg will need adjusting.
; Ian Gibbons, University of Hawaii, 10/26/84

; Fixed incorrect timer command port assignment so that program will run
;   under MS-DOS 2.00 as well as 2.11.
; Added autodetermination of color/mono using crttype byte in IO.SYS; this
;   works only under DOS 2.11 (and future above ?)
; IG 10/28/84
; Added direct CRT I/O handling through BIOS in order to increase speed and
;  flexibility of help menus. It gives only an imitation window but it works
;  fairly well.
; IG 10/31/84
; Added simplified mode line data to the [Connecting to host...]" line which
;  has been moved here from MSTERM in order to have more system dependent
;  control.
; IG 11/4/84
; Make unredefined break/stop key act as a scroll/noscroll key in telnet by
;  sending alternately a ^S and a ^Q.
; IG 11/5/84
; By now code for this MSXAPC module could well be divided into separate
;  transmission and terminal modules, but I don't want to spend more time
;  on it. It's not as readable as it might be, but so far as I know
;  everything works.IG 11/6/84

        public  serini, serrst, clrbuf, outchr, coms, vts, vtstat, dodel
        public  ctlu, cmblnk, locate, lclini, lclrst, prtchr, dobaud
        public  discon, clearl, dodisk, getbaud, beep
        public  count, xofsnt, puthlp, putmod, clrmod, poscur
        public  sendbr, term, machnam, setktab, setkhlp, showkey
        public  ihosts, ihostr, dtrlow, dumpscr                 ; [jrd]
        include mssdef.h

false   equ     0
true    equ     1

BIOS    EQU     0DCH            ; NEC-APC BIOS interrupt call
fifoclr equ     6               ; clears keyboard fifo buffer
crtcmd  equ     7               ; Direct CRT I/O command call

; port assignments for 8251 serial controllers

;               Standard interface

mndata  equ     30H             ; Data port (read/write)
mnst1a  equ     32H             ; Status port (when read)
mncmda  equ     32H             ; Command port (when written)
mnst2a  equ     34H             ; Alternate status port (when read)
mnmska  equ     34H             ; Mask port (when written)
mntdca  equ     36H             ; Transmit disable port (write only)

;               Optional (H14) interface

mndatb  equ     31H             ; Data port (read/write)
mnst1b  equ     33H             ; Status port (when read)
mncmdb  equ     33H             ; Command port (when written)
mnst2b  equ     35H             ; Alternate status port (when read)
mnmskb  equ     35H             ; Mask port (when written)
mntdcb  equ     37H             ; Transmit disable port (write only)

; Status bits from mnst1

txrdy   equ     01H             ; Bit for output ready.
rxrdy   equ     02H             ; Bit for input ready.

; Command values for mncmd

ccmd    equ     37H             ; RTS & DTR high, RX & TX enabled, reset ERR
cbrk    equ     08H             ; break enabled
cmode   equ     40H             ; enable mode reset
mmode   equ     4EH             ; 16x rate, 8 data, no parity, 1 stop

; Mask values for mnmsk

txmsk   equ     01H             ; disables transmit ready interrupt
rxmsk   equ     02H             ; disables receive ready interrupt
tbemsk  equ     04H             ; disables transmit buffer empty interrupt


; port assignments for 8253 timers

;               Standard interface

tmdata  equ     2BH             ; data port
tmcmda  equ     2FH             ; command port  (Was 27H Ian 10/27/84)

;               Optional (H14) interface

tmdatb  equ     61H             ; data port
tmcmdb  equ     67H             ; command port

; values for tmcmd which select timer channel and mode

tmsela  equ     76H             ; Channel 1, mode 3 (standard port)
tmselb  equ     36H             ; Channel 0, mode 3 (optional port)

; Timer information for current port selection

tmrinfo struc
tmdat   dw      0               ; data port
tmcmd   dw      0               ; command port
tmsel   db      0               ; byte which selects channel and mode
tmrinfo ends


; port assignments for 8259 interrupt controllers

;               Standard interface

intcmda equ     20H             ; Command port (master controller)
intmska equ     22H             ; Mask port
ictmsk  equ     08H             ; Timer interrupt mask (to master)
icsmska equ     02H             ; Standard serial interrupt mask (to master)
icsvcta equ     11H             ; Interrupt vector for standard interface

;               Optional (H14) interface

; The interrupt request vector for the optional (H14) serial interface is
; jumper-selectable to any of vectors IR2, IR5, IR8, or IR12.  NEC recommends
; that IR8 be used, so that has been selected as the default here.  To use
; any of the other vectors, set the following conditionals appropriately.
; Only one of the following should be true:

IR2     equ     false           ; interrupt vector 2
IR5     equ     false           ; interrupt vector 5
IR8     equ     true            ; interrupt vector 8
IR12    equ     false           ; interrupt vector 12

        IF IR2
intcmdb equ     20H             ; Command port (master controller)
intmskb equ     22H             ; Mask port
icsmskb equ     04H             ; Interrupt mask
icsvctb equ     12H             ; Interrupt table index
        ENDIF

        IF IR5
intcmdb equ     20H             ; Command port (master controller)
intmskb equ     22H             ; Mask port
icsmskb equ     20H             ; Interrupt mask
icsvctb equ     15H             ; Interrupt table index
        ENDIF

        IF IR8
intcmdb equ     28H             ; Command port (slave controller)
intmskb equ     2AH             ; Mask port
icsmskb equ     02H             ; Interrupt mask
icsvctb equ     19H             ; Interrupt table index
        ENDIF

        IF IR12
intcmdb equ     28H             ; Command port (slave controller)
intmskb equ     2AH             ; Mask port
icsmskb equ     20H             ; Interrupt mask
icsvctb equ     1DH             ; Interrupt table index
        ENDIF

crtinfo struc           ; Structure for color/mono formatting info
nrmseq  db      esc,'[0m$'      ; Default
invseq  db      esc,'[7m$000'   ; Inverse (extra space for color)
bldseq  db      esc,'[17m$'     ; Bold video
nrmcrt  dw      8080H           ; Normal data for direct crt i/o
bldcrt  dw      9090H           ; Inverse data for direct crt i/o

crtinfo ends


icEOI   equ     20H             ; generic end of interrupt for intcmd
kbfifosiz equ   64              ; size of fifo buffer in IO.SYS


; miscellaneous constants

ctrlP   equ     10H             ; ^P.
ctrl_q  equ     11H             ; ^Q
ctrl_s  equ     13H             ; ^S
printkey equ    00FFH           ; Scan code of unshifted PRINT key.
brkstp  equ     0096H           ; Scan code of unshifted break/stop key.
mntrgh  equ     bufsiz*3/4      ; High XON/XOFF trigger = 3/4 of buffer full.

; external variables used:
; drives - # of disk drives on system
; flags - global flags as per flginfo structure defined in pcdefs
; trans - global transmission parameters, trinfo struct defined in pcdefs
; portval - pointer to current portinfo structure (currently either port1
;    or port2)
; port1, port2 - portinfo structures for the corresponding ports

; global variables defined in this module:
; xofsnt, xofrcv - tell whether we saw or sent an xoff.

datas   segment public 'datas'
        extrn   drives:byte,flags:byte, trans:byte
        extrn   portval:word, port1:byte, port2:byte, dmpname:byte ; [jrd]

machnam db      'NEC APC$'
nyimsg  db      cr,lf,'Not implemented$'
badbd   db      cr,lf,'Unimplemented baud rate$'
bdkscn  db      cr,lf,'Unable to install key translate table'
        db      cr,lf,'Possibly incompatible MS-DOS version',cr,lf
        db      cr,lf,'(MS-KERMIT can be used without keyboard translation'
        db      ' feature)',cr,lf,'$'
nokbtr  db      cr,lf,'No key translation table is installed$'
hngmsg  db      cr,lf,' The phone should have hungup.',cr,lf,'$' ; [jrd]
hnghlp  db      cr,lf,' The modem control lines DTR and RTS for the current'
        db      ' port are forced low (off)'
        db      cr,lf,' to hangup the phone. Normally, Kermit leaves them'
        db      ' high (on) when it exits.'
        db      cr,lf,'$'                                       ; [jrd]
rdbuf   db      80 dup (?)      ; temp buf [jrd]
prtmsg  db      cr,lf,'You cannot redefine the PRINT key$'
escmsg  db      cr,lf,'You cannot redefine the current CTRL-ESCAPE key$'
hngcfm  db      cr,lf,'Please confirm DISCONNECT command. (Y/N)  $'
dismsg  db      cr,lf,'Disconnecting for 3 seconds',cr,lf,'$'
rcnmsg  db      cr,lf,'Reconnected',cr,lf,'$'
cnmsg   db      cr,lf,'Connecting to host at '
cnmsgb  db      '     '
        db      ' baud on port '
cnmsgp  db      ' ',cr,lf,'(Type $'
cnmsg1  db      ' C  to return to PC)',cr,lf,lf,lf,'$'
crlf    db      cr,lf,'$'
delstr  db      BS,BS,' ',BS,'$'        ; Delete string.
clrlin  db      cr,'$'          ; Clear line (just the cr part).
ceolseq db      esc,'[K$'       ; Clear to end of line
cpseq   db      esc,'=rc'       ; rc replaced by row and column before display
clrseq  db      01EH,01AH,'$'   ; Home cursor and clear screen
lstpos  dw      0               ; column position for printer echoing

; Storage for color and mono formatting strings

nrmcol  db      esc,'[0m$'
invcol  db      esc,'[3;21m$'   ; Yellow with green overline
bldcol  db      esc,'[21m$'     ; Yellow
ncrtcol dw      8080H           ; Green
bcrtcol dw      0A0A0H          ; Yellow

;nrmmon db      esc,'[0m$'
;invmon db      esc,'[7m$000'   ; Inverse video
;bldmon db      esc,'[17m$'     ; Bold video
;ncrtmon        dw      8080H           ; Normal green
;bcrtmon dw     9090H           ; Inverse video

formdat crtinfo <>
formdtl db      $-formdat

nocur   db      esc,'[>5h$'     ; Disable cursor
recur   db      esc,'[>5l$'     ; Reenable cursor
storcur db      esc,'[s$'       ; Store cursor position
rstcur  db      esc,'[u$'       ; Restore cursor position
upcur   db      esc,'[A$'       ; Move cursor up one line
savcur  dw      0               ; storage for cursor position
msglns  db      0               ; No of lines in help message
rnoc    dw      0               ; Raw NOC for screen write

; Data area for direct CRT I/O

EVEN                            ; Force alignment to word boundary
dispadr db      2000 dup (0)    ; Storage for string data
attradr db      2000 dup (0)    ; Storage for attribute data

; Command block for direct CRT I/O

cmd     db      0               ; CRT command number
la      db      0               ; Line address   (0-24 binary)
ca      db      0               ; Column address (0-79 binary)
noc     dw      0               ; Number of chars (0-2000 binary)
dispptr dw      dispadr         ; Pointer to string data block
dispseg dw      datas           ;
attrptr dw      attradr         ; Pointer to attribute data block
attrseg dw      datas

ourarg  termarg <>
modem   mdminfo <mndata,mnst1a,mncmda,0,0,0,0>
timer   tmrinfo <tmdata,tmcmda,tmsela>

ourflgs db      0               ; Flags for telnet options
fprint  equ     80H             ;   echo screen output to printer
movcur  equ     40H             ;   cursor moved - needs resetting
kbtrflg equ     20H             ;   local flag showing if kb trans.is enabled
autorepflg equ  10H             ;   is this an autorepeat cycle
inited  equ     8H              ;   are we initiating first call to term
tlnxof  equ     4H              ;   have we sent a ^S in telnet with brk/stp
savscn  dw      0               ; save last key-in for auto repeat if needed
escscan dw      0               ; scan code for current ctrl-escape key

oldsera dw      ?               ; old serial handler for standard port
oldsega dw      ?               ; segment of above
oldmska db      ?               ; old interrupt controller mask
portina db      0               ; Has comm port been initialized.

oldserb dw      ?               ; old serial handler for optional port
oldsegb dw      ?               ; segment of same.
oldmskb db      ?               ; old interrupt controller mask
portinb db      0               ; Has comm port been initialized.

dosseg  dw      40H             ; Segment  to read IO.SYS
zero    dw      0               ; Use to load segment regs for low core

; Space for addresses in IO.SYS to be calculated at run time in 'lclini'

configptr equ   3H              ; Offset of pointer to base of config table
configbas dw    0               ; base address of config table in IO.SYS
fifobas dw      0               ; equ   0A62H for DOS 2.11

; offsets from configbas
statlnptr equ   0CH
kbinptr   equ   2CH             ; kbin addr = base of fifodata area (fifobas)
crtptr    equ   48H

; offsets from fifobas
kbin    equ     0                       ; fifobas
kbout   equ     1                       ; fifobas + 1
kbfifo  equ     2                       ; fifobas + 2
sfkeyflg equ    043H                    ; fifobas + 043H
kbrepflg equ    0D9H                    ; fifobas + 0D9H

xofsnt  db      0               ; Say if we sent an XOFF.
xofrcv  db      0               ; Say if we received an XOFF.

; variables for serial interrupt handler
source  db      bufsiz DUP (?)  ; Buffer for data from port.
srcpnt  dw      0               ; Pointer in buffer (DI).
count   dw      0               ; Number of chars in int buffer.
savesi  dw      0               ; Save SI register here.
        dw      80 DUP (?)      ; local stack for interrupt processing
mnstk   dw      ?
mnsp    dw      ?               ; remote stack info
mnsseg  dw      ?

shkbuf  db      300 dup (?)     ; room to display key definition
shkmsg  db      '  Scan code: '
shkmln  equ     $-shkmsg
shkms1  db      cr,lf,'  Definition: '
shkm1ln equ     $-shkms1

setktab db      24
        mkeyw   'BACKSPACE',9CH
        mkeyw   'F1',80H
        mkeyw   'F2',81H
        mkeyw   'F3',82H
        mkeyw   'F4',83H
        mkeyw   'F5',84H
        mkeyw   'F6',85H
        mkeyw   'F7',86H
        mkeyw   'F8',87H
        mkeyw   'F9',88H
        mkeyw   'F10',89H
        mkeyw   'F11',8AH
        mkeyw   'F12',8BH
        mkeyw   'F13',8CH
        mkeyw   'F14',8DH
        mkeyw   'F15',8EH
        mkeyw   'F16',8FH
        mkeyw   'F17',90H
        mkeyw   'F18',91H
        mkeyw   'F19',92H
        mkeyw   'F20',93H
        mkeyw   'F21',94H
        mkeyw   'F22',95H
        mkeyw   'SCAN',-1

setkhlp db      cr,lf,'Either keyname:  Backspace, F1, ...,F22',cr,lf
        db            '  or   "SCAN" followed by scan code'
        db      ' (given by SHOW KEY)',cr,lf,'$'

comptab db      7
        mkeyw   '1',1
        mkeyw   '2',0
        mkeyw   'COM1',1
        mkeyw   'COM2',0
        mkeyw   'H14',0
        mkeyw   'OPTIONAL',0
        mkeyw   'STANDARD',1

bddat   label   word
        dw      0D30H           ; 45.5 baud
        dw      0C00H           ; 50 baud
        dw      0800H           ; 75 baud
        dw      0574H           ; 110 baud
        dw      0476H           ; 134.5 baud
        dw      0400H           ; 150 baud
        dw      0200H           ; 300 baud
        dw      0100H           ; 600 baud
        dw      0080H           ; 1200 baud
        dw      0055H           ; 1800 baud
        dw      004DH           ; 2000 baud
        dw      0040H           ; 2400 baud
        dw      0020H           ; 4800 baud
        dw      0010H           ; 9600 baud
        dw      0008H           ; 19200 baud
        dw      0004H           ; 38400 baud (not tested - may not work)

; some static data for mode line

unkbaud db      ' Unk '                 ; must be 5 chars...
baudn   db      ' 45.5'                 ;               [g4 start]
        db      '  50 '
        db      '  75 '
        db      ' 110 '
        db      ' 135 '
        db      ' 150 '
        db      ' 300 '
        db      ' 600 '
        db      ' 1200'
        db      ' 1800'
        db      ' 2000'
        db      ' 2400'
        db      ' 4800'
        db      ' 9600'
        db      '19200'         ;               [g4 end]
baudnsiz  equ   15              ; # of baud rates known (tbl size / 5)

datas   ends


code    segment public 'code'
        extrn   comnd:near, dopar:near, escprt:near
        extrn   sleep:near                              ; [jrd]
        assume  cs:code,ds:datas

; local initialization routine, called by Kermit initialization.

LCLINI  PROC    NEAR
        cld
        mov flags.vtflg,0       ; turn off heath emulation
        push es
        mov es,dosseg
        mov bx,configptr
        mov bx,es:[bx]          ; Get offset of configuration table in IO.SYS
        mov configbas,bx        ; store it
        mov cl,fifoclr          ; Clear fifobuffer in case user has been
        int bios                ;  typing while program was loading
        mov bx,es:[bx].kbinptr  ; Should be address of kbin
        mov cl,es:[bx]          ; Confirm we're in right location
        cmp cl,byte ptr es:1[bx] ; by showing kbin = kbout
        jne lclin1              ; Fails for DOS 2.00 (no kbinptr)
        mov fifobas,bx          ; Kbin is base of fifo area
        or ourflgs,kbtrflg      ; Show we have a key tranlate table
        jmp short lclin2        ; Localization finished
lclin1: mov bx,configbas        ; DOS 2.00 does have pointer to status line
        mov bx,es:[bx].statlnptr ; Address of 'MS-DOS' in status line
        sub bx,6BH                      ; Work back to where kbin should be
        mov cl,es:[bx]                  ; Confirm we're in right location
        cmp cl,byte ptr es:1[bx]        ;  by showing kbin = kbout
        jnz lclin3              ; Nothing else to try - report failure
        mov fifobas,bx          ; We've made it!
        or  ourflgs,kbtrflg     ; Show we have a key tranlate table
        xor ax,ax               ; Set zero flag for mono format default
        jmp short lcli20
lclin2: mov bx,configbas
        mov bx,es:[bx].crtptr           ; Get address of crttype byte
        mov al,es:[bx]                  ; get the crttype byte
        test al,1                       ; Is it a color crt?
lcli20: mov dx,ds                       ; DOS 2.00 has no crt data and
        mov es,dx                       ;   and defaults to mono
        mov cl,formdtl                  ; Length of crt format block
        mov di,offset formdat           ; Where we want data
        jnz lcli21                      ; No, it's color
        jmp short lclin4
lcli21: mov si,offset nrmcol            ; Source of color data
        rep movsb                       ; Move data to working location
        jmp short lclin4
lclin3: mov dx,offset bdkscn
        call tmsg
        and ourflgs,not kbtrflg         ; Show no translate table enabled
lclin4: mov dx,offset formdat.nrmseq    ; set to our normal background color
        call tmsg
        pop es
        ret
LCLINI  ENDP

; Local reset routine, called upon exit from Kermit

LCLRST  PROC    NEAR
        ret
LCLRST  ENDP

; this is called by Kermit initialization.  It checks the
; number of disks on the system, sets the drives variable
; appropriately.  The only problem is that a value of two
; is returned for single drive systems to be consistent
; with the idea of the system having logical drives A and
; B.  Returns normally.

DODISK  PROC    NEAR
        mov ah,gcurdsk          ; current disk value to AL.
        int dos
        mov dl,al               ; put current disk in DL.
        mov ah,seldsk           ; select current disk.
        int dos                 ; get number of drives in AL.
        mov drives,al
        ret
DODISK  ENDP

; show the definition of a key.  The terminal argument block (which contains
; the address and length of the definition tables) is passed in ax.
; Returns a string to print in AX, length of same in CX.
; Returns normally.

; In this version, the complete untranslated key scan codes are obtained
; from the fifo buffer in IO.SYS. The fifo buffer pointers are then updated
; to show that the key has been read. Certain key scan codes which are
; intercepted in the kb interrupt routine give a blank or functional response
; to SHOW KEY  (eg FNC + CTRL + BREAK-STOP, PRINT, and CTRL + 0...9 )
; and these cannot be translated.

SHOWKEY PROC    NEAR
        push es
        test ourflgs,kbtrflg
        jnz showk0
        push ax                 ; Keep stack balanced
        mov bx,ax
        and [bx].flgs,not havtt ; reset flag
        mov dx,offset nokbtr    ; Inform no table installed and return
        jmp short shoerr

showk0: push ax                 ; save the terminal argument block
        mov al,trans.escchr     ; calculate scan code for ctrl-escape key
        add al,40H              ; uncontrolify escape char
        mov ah,2                ; control byte
        mov escscan,ax          ; save it
showk1: call inscan             ; get key-in scan code from IO.SYS
         jmp short showk1       ; Nothing there yet - keep trying
         nop
        call inckbo             ; increment kbout pointer
        cmp al,printkey         ; Is it the print key (any version) ?
        jnz show11
        mov dx,offset prtmsg    ; If so complain
        jmp short shoerr
show11: cmp al,byte ptr escscan ; Is it current ctrl-escape key ?
        jnz show12
        test ah,02              ; With ctrl + anything
        jz show12
        mov dx,offset escmsg    ; Then complain
        jmp short shoerr
show12: cld
        mov cx,ds
        mov es,cx
        push ax                 ; save scan code
        mov di,offset shkbuf    ; move 'Scan code' message to buffer
        mov si,offset shkmsg
        mov cx,shkmln
        rep movsb
        call nout               ; add scan code to buffer
        mov si,offset shkms1    ; move 'Definition' message to buffer
        mov cx,shkm1ln
        rep movsb
        pop ax                  ; retrieve scan code
        pop bx                  ; and terminal argument block
        mov cx,[bx].klen        ; length of translation table
        jcxz showk3             ; no table, key not defined
        push di
        mov di,[bx].ktab        ; get table address
        repne scasw             ; look for scan code
        mov si,di
        pop di
        jne showk3              ; not defined
        sub si,[bx].ktab        ; compute entry offset in table
        sub si,2
        add si,[bx].krpl        ; index to replacement
        mov si,[si]             ; get its address
        mov cl,[si]             ; get its length
        mov ch,0
        inc si
        rep movsb               ; transfer replacement to display buffer
showk3: mov ax,offset shkbuf    ; return address of buffer in ax
        mov cx,di               ; and length in cx
        sub cx,ax
        pop es
        ret
shoerr: call tmsg
        mov cx,0                ;
        pop ax                  ; get rid of junk
        pop es
        ret
SHOWKEY ENDP

; copy numeric value from AX to ASCII buffer indicated by DI.  DI is updated.

NOUT    PROC    NEAR
        mov dx,0                ; zero high word
        mov bx,10               ; divide
        div bx
        push dx                 ; save remainder digit
        or ax,ax                ; anything left?
        jz nout1                ; no, start output phase
        call nout
nout1:  pop ax                  ; retrieve a digit
        add al,'0'              ; make it ASCII
        stosb                   ; put it in buffer
        ret
NOUT    ENDP

; skip returns if no character available at port,
; otherwise returns with char in al, # of chars in buffer in dx.

PRTCHR  PROC    NEAR
        call chkxon             ; see if we have to xon the host.
        cmp count,0
        jnz prtch2
        jmp rskp                ; No data - check console.
prtch2: pushf                   ; save current interrupt value
        cli                     ; disable interrupts while manipulating pointers
        mov si,savesi
        lodsb                   ; get a byte
        cmp si,offset source + bufsiz   ; bigger than buffer?
        jb prtch1               ; no, keep going
        mov si,offset source    ; yes, wrap around
prtch1: dec count
        mov savesi,si
        mov dx,count            ; return # of chars in buffer
        popf                    ; restore original interrupt flag
        ret
PRTCHR  ENDP

; local routine to see if we have to transmit an xon

CHKXON  PROC    NEAR
        push bx
        mov bx,portval
        cmp [bx].floflg,0       ; doing flow control?
        je chkxo1               ; no, skip all this
        cmp xofsnt,false        ; have we sent an xoff?
        je chkxo1               ; no, forget it
        cmp count,mntrgh        ; below trigger?
        jae chkxo1              ; no, forget it
        mov ax,[bx].flowc       ; ah gets xon
        call outchr             ; send it
         nop                    ;  ignore failure
         nop
         nop
        mov xofsnt,false        ; remember we've sent an xon.
chkxo1: pop bx                  ; restore register
        ret                     ; and return
CHKXON  ENDP

; Put the char in AH to the serial port.  This assumes the
; port has been initialized.  Should honor xon/xoff.  Skip returns on
; success, returns normally if the character cannot be written.

OUTCHR  PROC    NEAR
        mov bp,portval
        cmp ds:[bp].floflg,0    ; Are we doing flow control.
        je outch2               ; No, just continue.
        sub cx,cx               ; clear counter
        cmp ah,byte ptr [bp].flowc      ; sending xoff? [jrd]
        jne outch1              ; ne = no
        mov xofsnt,false        ; supress xon from chkxon buffer routine
outch1: cmp xofrcv,true         ; Are we being held?
        jne outch2              ; No - it's OK to go on.
        loop outch1             ; held, try for a while
        mov xofrcv,false        ; timed out, force it off and fall thru.
outch2: push dx                 ; Save register.
        sub cx,cx
        mov al,ah               ; Parity routine works on AL.
        call dopar              ; Set parity appropriately.
        mov ah,al               ; Don't overwrite character with status.
        mov dx,modem.mdstat     ; port status register
outch3: in al,dx
        test al,txrdy           ; Transmitter ready?
        jnz outch4              ; Yes
        loop outch3
         jmp outch5             ; Timeout
outch4: mov al,ah               ; Now send it out
        mov dx,modem.mddat
        out dx,al
        pop dx
        jmp rskp
outch5: pop dx
        ret
OUTCHR  ENDP

; IHOSTS - Initialize the host by sending XON, or equivalent, and enter the
; cycle of clear input buffer, wait 1 second, test if buffer empty then exit
; else repeat cycle. Requires that the port be initialized before hand.
; Ihosts is used by the local send-file routine just after initializing
; the serial port.
; 22 March 1986 [jrd]

IHOSTS  PROC    NEAR
        push    ax              ; save the registers
        push    bx
        push    cx
        push    dx
        mov     bx,portval      ; port indicator
        mov     ax,[bx].flowc   ; put Go-ahead flow control char in ah
        call    outchr          ; send it (release Host's output queue)
         nop                    ; outchr can do skip return
         nop
         nop
ihosts1:call    clrbuf          ; clear out interrupt buffer
        mov     ax,1            ; sleep for 1 second
        call    sleep           ; procedure sleep is in msscom.asm
        call    prtchr          ; check for char at port
         jmp    ihosts1         ; have a char in al, repeat wait/read cycle
         nop                    ; prtchr does skip return on empty buffer
        pop     dx              ; empty buffer. we are done here.
        pop     cx
        pop     bx
        pop     ax
        ret
IHOSTS  ENDP

; IHOSTR - initialize the remote host for our reception of a file by
; sending the flow-on character (XON typically) to release any held
; data. Called by receive-file code just after initializing the serial
; port.         22 March 1986 [jrd]
IHOSTR  PROC    NEAR
        push    ax              ; save regs
        push    bx
        push    cx
        mov     bx,portval      ; port indicator
        mov     ax,[bx].flowc   ; put Go-ahead flow control char in ah
        call    outchr          ; send it (release Host's output queue)
         nop                    ; outchr can do skip return
         nop
         nop
        pop     cx
        pop     bx
        pop     ax
        ret
IHOSTR  ENDP

DTRLOW  PROC    NEAR            ; Global proc to Hangup the Phone by making
                                ; DTR and RTS low.
        mov ah,cmtxt            ; allow text to be able to display help
        mov bx,offset rdbuf     ; dummy buffer
        mov dx,offset hnghlp    ; help message
        call comnd              ; get a confirm
         jmp r
        call discon             ; try using preexisting routine
; [jrd] call serhng             ; drop DTR and RTS
; [jrd] mov ah,prstr            ; give a nice message
; [jrd] mov dx,offset hngmsg
; [jrd] int dos
        jmp rskp
DTRLOW  ENDP

; Hang up the Phone. Similar to SERRST except it just forces DTR and RTS low
; to terminate the connection. 29 March 1986 [jrd]
; Calling this twice without intervening calls to serini should be harmless.
; Returns normally.
; SERHNG is Not Yet Implemented. Use existing procedure discon below.



; Send a break out the current serial port.  Returns normally.

SENDBR  PROC    NEAR
        mov dx,modem.mdcom      ; send to command port
        mov al,cbrk+ccmd        ; add break to normal command
        out dx,al
        sub cx,cx               ; wait a while
sndbr1: loop sndbr1
        mov al,ccmd             ; restore normal command
        out dx,al
        ret                     ; and return.
SENDBR  ENDP

DISCON  PROC    NEAR
        mov dx,offset hngcfm
        call besure             ; Get confimation of command
         jmp short discn1
         nop
        mov dx,offset dismsg    ; Say what we're doing
        call tmsg
        mov al,CCMD
        xor al,2                ; Reset bit to drop DTR.
        mov dx,modem.mdcom
        out dx,al
        mov bx,05H              ; Set outer counter  5 X --> 3sec.
pause2: xor cx,cx
pause3: push bx                 ; Waste time for 600ms.
        pop bx
        loop pause3             ; Loop on inner loop.
        dec bx
        jnz pause2              ; Loop on outer loop.
        or al,2                 ; Set bit to enable DTR again.
        out dx,al
        mov dx,offset rcnmsg
        call tmsg
discn1: ret
DISCON  ENDP

BESURE  PROC    NEAR            ; Receives addr of prompt in DX.
        call tmsg
        mov ah,conin
        int dos
        and al,137Q             ; Convert to upper case if necessary.
        cmp al,'Y'
        jz besur1               ; We must return rskp for a 'Y'/'y'
        mov dx,offset crlf      ; For any other character input send a cr/lf.
        call tmsg
        ret                     ; And return.
besur1: jmp rskp
BESURE  ENDP


; Clear the input buffer. This throws away all the characters in the
; serial interrupt buffer.  This is particularly important when
; talking to servers, since NAKs can accumulate in the buffer.
; Returns normally.

CLRBUF  PROC    NEAR
        pushf                   ; save current interrupt value
        cli                     ; disable interrupts
        mov ax,offset source    ; reset pointers to beginning of buffer
        mov srcpnt,ax
        mov savesi,ax
        mov count,0
        popf                    ; restore original interrupt value
        ret
CLRBUF  ENDP

; Set the baud rate for the current port, based on the value in the
; portinfo structure.  On entry, previous value of baud rate is saved in AX.
; Returns normally.

DOBAUD  PROC    NEAR
        push ax                 ; save regs. [jrd]
        push bx
        push dx
        push bp
        mov bp,portval
        mov bx,ds:[bp].baud     ;make sure new value is valid
        cmp bx,BAUDSIZ          ; [RB] don't get stuck at 1200 baud
        jae dobd0               ; [RB]
        shl bx,1
        add bx,offset bddat
        cmp word ptr [bx],0FFH
        jne dobd1               ; [RB]
dobd0:  mov ds:[bp].baud,ax     ; [RB] replace bad rate with previous value
        mov dx,offset badbd
        jmp tmsg
dobd1:  mov dx,timer.tmcmd      ; [RB] timer command port
        mov al,timer.tmsel      ;select proper channel and mode
        out dx,al
        mov ax,[bx]             ;get timer initializer for this rate
        mov dx,timer.tmdat      ;timer data port
        out dx,al               ;output low byte
        mov al,ah
        out dx,al               ;output high byte
        pop bp                  ; [jrd]
        pop dx
        pop bx
        pop ax
        ret
DOBAUD  ENDP

; Get the current baud rate from the serial card and set it
; in the portinfo structure for the current port.  Returns normally.
; This is called any time the baud rate is to be displayed.

; We can't determine the baud rate on the APC, so just set it
; to the default of 1200 if the value is currently illegal.

GETBAUD PROC    NEAR
        push bx                 ; [jrd]
        push bp                 ; Additions by Ron Blanford 14 April 1986
        mov bp,portval
        mov bx,ds:[bp].baud
        cmp bx,BAUDSIZ
        jae getbd2
        shl bx,1
        add bx,offset bddat
        cmp word ptr [bx],0FFh
        jne getbd3
getbd2: mov ds:[bp].baud,B1200
getbd3: pop bp
        pop bx                  ; [jrd]
        ret
GETBAUD ENDP

; Set the mode for the current port.  This is part of the serial
; initialization routine.

DOMODE  PROC    NEAR
        mov dx,modem.mdcom      ;send 3 zeros to command port to reset chip
        mov al,0
        out dx,al
        mov al,0
        out dx,al
        mov al,0
        out dx,al
        mov al,cmode            ;enable mode setting
        out dx,al
        push ax                 ;allow 8251 time to reset
        pop ax
        push ax
        pop ax
        mov al,mmode            ;mode: 16x rate, 8 data, no parity, 1 stop
        out dx,al
        mov al,ccmd             ;RTS & DTR high, RX & TX enabled, reset errors
        out dx,al
        ret
DOMODE  ENDP

; Reassure user about connection to the host.  Tell him what escape
; sequence to use to return and the communications port and baud
; rate being used.   [19b]

DOMSG   PROC    NEAR
        push    ax                      ; save regs. [jrd]
        push    bx
        push    cx
        push    si
        mov     bx,offset ourarg        ; get argument block
        mov     al,[bx].baudb           ; get baud bits
        mov     si,offset unkbaud       ; Assume unknown baud.
        cmp     al,baudnsiz             ; too big?
        jnb     dmsg12                  ; yes, use default
        mov     cl,2                    ; each is 5 bytes long
        shl     al,cl                   ; 4 X
        add     al,[bx].baudb           ; make 4+1 = 5
        mov     ah,0
        add     ax,offset baudn
        mov     si,ax
dmsg12: mov     cx,5                    ; length of baud space
        mov     di,offset cnmsgb
        rep     movsb                   ; copy in baud rate
        mov     al,'1'
        cmp     ourarg.prt,1            ; One means port 1
        je      dmsg15                  ; yes, keep going
        mov     al,'2'                  ; Zero means port 2
dmsg15: mov     cnmsgp,al               ; fill in port number
        mov dx,offset cnmsg
        call tmsg
        call escprt                     ; in MSSET
        mov dx,offset cnmsg1
        call tmsg
        pop     si                      ; [jrd]
        pop     cx
        pop     bx
        pop     ax
        ret
DOMSG   ENDP

; set the current port.

COMS    PROC    NEAR
        mov dx,offset comptab   ;get port selection
        mov bx,0
        mov ah,cmkey
        call comnd
         jmp r
        push bx
        mov ah,cmcfm            ;get a confirmation
        call comnd
         jmp comx
         nop
        pop bx
        mov flags.comflg,bl     ;save port selection
        cmp flags.comflg,1
        jne coms2
        mov ax,offset port1     ;set to run on port 1
        mov portval,ax
        call resetb             ;reset port 2, if in use
        call inita              ;set up port 1
        ret
coms2:  mov ax,offset port2     ;set to run on port 2
        mov portval,ax
        call reseta             ;reset port 1, if in use
        call initb              ;set up port 2
        ret
comx:   pop bx
        ret
COMS    ENDP

; initialization for using serial port.  This routine performs
; any initialization necessary for using the serial port, including
; setting up interrupt routines, setting buffer pointers, etc.
; Doing this twice in a row should be harmless (this version checks
; a flag and returns if initialization has already been done).
; SERRST below should restore any interrupt vectors that this changes.
; Returns normally.

SERINI  PROC    NEAR
        cmp flags.comflg,1
        jne seri2
        call resetb
        call inita
        ret
seri2:  call reseta
        call initb
        ret
SERINI  ENDP

; Reset the serial port.  This is the opposite of serini.  Calling
; this twice without intervening calls to serini should be harmless.
; Returns normally.

SERRST  PROC    NEAR
        call reseta             ;reset port 1
        call resetb             ;reset port 2
        mov dx,offset recur     ; Reenable cursor display
        call tmsg
        and ourflgs,not inited  ; Reset init flag for term usage
        ret
SERRST  ENDP

; Local routine to initialize the standard serial port

INITA   PROC    NEAR
        cmp portina,1           ; Did we initialize port already? [21c]
        je inita0               ; Yes, so just leave. [21c]
        push es
        cli                     ; Disable interrupts
        mov ax,offset port1
        mov portval,ax
        xor ax,ax               ; Address low memory
        mov es,ax
        mov ax,es:[4*icsvcta]   ; save standard port interrupt vector
        mov oldsera,ax
        mov ax,es:[4*icsvcta+2]
        mov oldsega,ax
        mov ax,offset serint    ; point to our routine
        mov es:[4*icsvcta],ax   ; point at our serial routine
        mov es:[4*icsvcta+2],cs ; our segment
        mov dx,intmska          ; set up standard port...
        in al,dx
        mov oldmska,al          ; save old master controller mask

;       NEC recommends that the timer interrupt be disabled during interrupt-
;       driven serial I/O, but this disables the clock display and keyboard
;       repeat.  I have not had any problems leaving it enabled, so I will
;       leave it alone here.  If problems develop, uncomment the following
;       line to disable timer interrupts. -- RonB

;       or al,ictmsk            ; disable timer interrupt
        and al,not icsmska      ; enable serial interrupt at master controller
        out dx,al
        mov dx,mnmska           ; enable serial interrupt at port
        mov al,txmsk+tbemsk     ; disable tx and tbe interrupts (enable rx)
        out dx,al
        mov dx,mntdca           ; enable operation of serial port
        mov al,0
        out dx,al
        mov modem.mddat,mndata
        mov modem.mdstat,mnst1a
        mov modem.mdcom,mncmda
        mov timer.tmdat,tmdata
        mov timer.tmcmd,tmcmda
        mov timer.tmsel,tmsela
        call domode
        call dobaud
        mov portina,1           ; Remember port has been initialized.
        call clrbuf             ; Clear input buffer.
        sti                     ; Allow interrupts
        pop es
inita0: ret
INITA   ENDP

; Local routine to initialize the optional (H14) serial port

INITB   PROC    NEAR
        cmp portinb,1           ; Did we initialize port already? [21c]
        je initb0               ; Yes, so just leave. [21c]
        push es
        cli                     ; Disable interrupts
        mov ax,offset port2
        mov portval,ax
        xor ax,ax               ; Address low memory
        mov es,ax
        mov ax,es:[4*icsvctb]   ; save optional port interrupt vector
        mov oldserb,ax
        mov ax,es:[4*icsvctb+2]
        mov oldsegb,ax
        mov ax,offset serint    ; point to our routine
        mov es:[4*icsvctb],ax   ; point at our serial routine
        mov es:[4*icsvctb+2],cs ; our segment
        mov dx,intmskb          ; set up optional port...
        in al,dx
        mov oldmskb,al          ; save old master or slave controller mask
        and al,not icsmskb      ; enable serial interrupt at controller
        out dx,al
        mov dx,mnmskb           ; enable serial interrupt at port
        mov al,txmsk+tbemsk     ; disable tx and tbe interrupts (enable rx)
        out dx,al
        mov dx,mntdcb           ; enable operation of serial port
        mov al,0
        out dx,al
        mov modem.mdstat,mnst1b
        mov modem.mddat,mndatb
        mov modem.mdcom,mncmdb
        mov timer.tmdat,tmdatb
        mov timer.tmcmd,tmcmdb
        mov timer.tmsel,tmselb
        call domode
        call dobaud
        mov portinb,1           ; Remember port has been initialized.
        call clrbuf             ; Clear input buffer.
        sti                     ; Allow interrupts
        pop es
initb0: ret
INITB   ENDP

; Reset standard serial port

RESETA  PROC    NEAR
        cmp portina,0           ; Did we reset port already?
        je rsta0                ; Yes, so just leave.
        push es
        cli                     ; Disable interrupts
        xor ax,ax               ; Address low memory
        mov es,ax
        mov ax,oldsera          ; Restore interrupt vector
        mov es:[4*icsvcta],ax
        mov ax,oldsega
        mov es:[4*icsvcta+2],ax
        mov dx,intmska          ; restore old master controller mask
        mov al,oldmska
        out dx,al
        mov dx,mnmska           ; disable serial interrupts at port
        mov al,txmsk+rxmsk+tbemsk
        out dx,al
        mov portina,0           ; Remember port has been reset
        sti                     ; Allow interrupts
        pop es
rsta0:  ret
RESETA  ENDP

; Reset optional (H14) serial port

RESETB  PROC    NEAR
        cmp portinb,0           ; Did we reset port already?
        je rstb0                ; Yes, so just leave.
        push es
        cli                     ; Disable interrupts
        xor ax,ax               ; Address low memory
        mov es,ax
        mov ax,oldserb          ; Restore interrupt vector
        mov es:[4*icsvctb],ax
        mov ax,oldsegb
        mov es:[4*icsvctb+2],ax
        mov dx,intmskb          ; restore old slave controller mask
        mov al,oldmskb
        out dx,al
        mov dx,mnmskb           ; disable serial interrupts at port
        mov al,txmsk+rxmsk+tbemsk
        out dx,al
        mov portinb,0           ; Remember port has been reset
        sti                     ; Allow interrupts
        pop es
rstb0:  ret
RESETB  ENDP


; serial port interrupt routine.  This is not accessible outside this
; module, handles serial port receiver interrupts.

SERINT  PROC  NEAR
        push ds                 ; save these on remote stack
        push ax
        mov ax,seg datas        ; get our own data segment
        mov ds,ax
        mov mnsp,sp             ; save remote stack information
        mov mnsseg,ss
        mov sp,offset mnstk     ; switch to local stack
        mov ss,ax
        push es                 ; and save remaining registers
        push bp
        push di
        push si
        push dx
        push cx
        push bx
        mov es,ax
        call mnproc             ; process the interrupt
        mov al,icEOI
        cmp flags.comflg,1      ; If using standard port
        je intr1
        mov dx,intcmdb          ;    or H14 vectored to master
        cmp dx,intcmda
        je intr1                ;    only signal End of Interrupt to master,
        out dx,al               ; otherwise signal to both slave and master.
intr1:  mov dx,intcmda
        out dx,al
        pop bx                  ; restore registers from stack
        pop cx
        pop dx
        pop si
        pop di
        pop bp
        pop es
        mov ax,mnsseg           ; switch back to remote stack
        mov ss,ax
        mov ax,mnsp
        mov sp,ax
        pop ax
        pop ds
        iret

; handler for serial input

mnproc: cld
        mov di,srcpnt           ; get buffer pointer
        mov dx,modem.mdstat     ; is data available?
        in al,dx
        test al,rxrdy
        jz mnpro7
        mov dx,modem.mddat      ; read data
        in al,dx
        or al,al
        jz mnpro7               ; Ignore nulls.
        cmp al,7FH              ; Ignore rubouts, too.
        jz mnpro7
        mov ah,al
        and ah,7fH              ; only consider low-order 7 bits for flow ctl.
        mov bp,portval
        cmp ds:[bp].floflg,0    ; Doing flow control?
        je mnpro4               ; Nope.
        mov bx,ds:[bp].flowc    ; Flow control char (BH = XON, BL = XOFF).
        cmp ah,bl               ; Is it an XOFF?
        jne mnpro3              ; Nope, go on.
        mov xofrcv,true         ; Set the flag.
        jmp short mnpro7
mnpro3: cmp ah,bh               ; Get an XON?
        jne mnpro4              ; No, go on.
        mov xofrcv,false        ; Clear our flag.
        jmp mnpro7
mnpro4: stosb
        cmp di,offset source + bufsiz
        jb mnpro5               ; not past end...
        mov di,offset source    ; wrap buffer around
mnpro5: mov srcpnt,di           ; update ptr
        inc count
        cmp ds:[bp].floflg,0    ; Doing flow control?
        je mnpro7               ; No, just leave.
        cmp xofsnt,true         ; Have we sent an XOFF?
        je mnpro7               ; Yes.
        cmp count,mntrgh        ; Past the high trigger point?
        jbe mnpro7              ; No, we're within our limit.
        mov ah,bl               ; Get the XOFF.
        call outchr             ; Send it.
         nop                    ;   ignore failure.
         nop
         nop
        mov xofsnt,true         ; Remember we sent it.
mnpro7: ret

SERINT  ENDP

; Dumb terminal emulator.  Anyone wishing to enhance it is encouraged
; to do so.

TERM    PROC    NEAR
        push es
        test ourflgs,inited     ; Have we been here before
        jnz  term01             ; if so, skip this stuff
        or  ourflgs,inited      ; show we've been here
        test ourflgs,kbtrflg
        jnz term0
        mov bx,ax
        and [bx].flgs,not havtt ; If no table then reset flag
term0:  mov si,ax               ; save argument block locally
        mov di,offset ourarg
        mov ax,ds
        mov es,ax
        mov cx,size termarg
        rep movsb
        mov al,trans.escchr     ; Calculate scan code for cntrl-escape char
        add al,40H              ; Uncontollify escape char
        mov ah,02H              ; Control byte for escape scan code
        mov escscan,ax          ; save it
        call domsg              ; tell user how we're connecting.
term01: test ourflgs,movcur      ; Do we need to reset cursor position
        jz term1
        mov dx,offset cmd
        mov cx,savcur
        mov word ptr la,cx
        mov noc,0
        mov cmd,1               ; Bios  move cursor call
        mov cl,crtcmd
        int bios
        and ourflgs, not movcur
term1:  call prtchr             ; Serial port input processor
         jmp short term2        ;  ...have a char
         nop
        jmp term4               ; no char, continue
term2:  and al,7FH              ; only use ASCII in terminal mode
        push ax
        mov dl,al
        mov ah,conout
        int dos                 ; display char
        pop ax
        test ourarg.flgs,capt   ; are we capturing output?
        jz term3
        push ax
        call ourarg.captr
        pop ax
term3:  test ourflgs,fprint     ; are we echoing to printer?
        jz term4
        call lstchr

term4:  test ourflgs,kbtrflg
        jz term50
        call inscan
         jmp  short term1
         nop
        cmp al,printkey         ; All shifts of print key do special duty.
        jne term41
        cmp ah,0
        jne term40              ; but toggle printer only if unshifted print
        xor ourflgs,fprint      ; go toggle printer
term40: call inckbo             ; increment kbout pointer
        jmp term1
term41: cmp al,byte ptr escscan ; Is it current escape key
        jne  term42
        test ah,02              ;  with ctrl + anything ?
        jz  term42
        call inckbo
        jmp short termx         ; it's ctrl-escape key so just return
term42: call trnout             ; Returns rskp if char not sent.
         jmp short term1        ; Translation found and already sent.
         nop                    ; no translation so move char via DCONIO
        cmp ax,brkstp           ; is it unredefined/unshifted break/stop key
        jnz term50              ; if not, just continue
        mov ax,ctrl_s           ; get a ^S ready
        test ourflgs,tlnxof     ; have we already sent a ^S ?
        jz term43               ; no, we can just set flag and send the ^S
        mov ax,ctrl_q           ; yes, so now we need a ^Q
term43: xor ourflgs,tlnxof      ; change the flag in either case
        call sndhst             ; send our  ^S/ ^Q - IO.SYS 2.11 won't do it
        mov ah,dconio           ; do a dummy read to clear IO.SYS flush flag
        mov dl,0FFH
        int dos
        jmp term1               ; and go back for more
term50: mov ah,dconio           ; Keyboard input processor
        mov dl,0FFH
        int dos                 ; check console
        jnz term51
        jmp term1               ; no char, continue .Too far for rel jmp.
term51: cmp al,ourarg.escc      ; is it the escape char?
        je termx                ; allows use of unredef left arrow key to esc
        call sndhst             ; no translation, just send it out
;  Check if it is a function key that user has defined in MS-DOS
        mov es,dosseg
        mov bx,fifobas
        cmp es:byte ptr sfkeyflg[bx],0
        jnz term50              ; if so, go back to dconio for rest of chars
        jmp term1               ; and go back for more
termx:  pop es
        ret


; do appropriate translations on input key, and transmit
; if translation entry found it sends char(s) to sndhst and returns normally
; if no translation entry is found, returns rskp with unsent scan code in ax
;  so that CONIN in IO.SYS can do its translation if neessary.

trnout: test ourflgs,kbtrflg    ; is there a translation table?
        jz trnou3
        mov cx,ourarg.klen      ; get table length and origin
        mov di,ourarg.ktab
        push es
        mov bx,ds
        mov es,bx
        jcxz trnou3             ; Needed for case of table zero length
        repne scasw             ; look for key
        jne trnou3              ; if not found, return rskp
        sub di,ourarg.ktab      ; reset to offset of replacement
        sub di,2
        add di,ourarg.krpl
        mov si,[di]
        mov cl,[si]             ; get length of replacement
        mov ch,0
        jcxz trnou3             ; if length is zero, send nothing
        inc si
trnou1: lodsb                   ; get replacement character
        push si
        push cx
        call sndhst             ; send it to port
        pop cx
        pop si
        loop trnou1             ; continue until translation complete
trnou2: call inckbo             ; increment kbout pointer
        pop es
        ret                     ; return after translating and sending
trnou3: pop es
        jmp rskp                ; plain characters return rskp

; get key-in scan code from fifo buffer in IO.SYS
; if gets a key-in, skip returns with scan code in ax
; returns normally if no key-in

inscan: push es
        mov es,dosseg           ; Address IO.SYS segment with es
        and ourflgs,not autorepflg      ; Reset auto repeat flag
        mov bx,fifobas          ; Offset of fifobas in IO.SYS (from LCLINI)
insca1: mov al,es:[bx].kbout    ; Get value of kbin pointer
        cmp al,es:[bx].kbin     ; Compare value of kbout pointer
        jz  insca2              ; If equal, no key-in yet so exit
        sub ah,ah
        add bx,ax               ; Calculate address pointed to (-2)
        mov ax,es:2[bx]         ; Get scan code pointed to by kbout.
        xchg ah,al              ; Get control byte -> ah, data byte -> al.
        mov savscn,ax           ; Save scan data in case key repeat needed.
        pop es
        jmp rskp
insca2: mov bx,fifobas
        cmp byte ptr es:[bx].kbrepflg,0  ; Is it time to repeat last key-in
        jz inscax                       ; Nope so exit
        mov ax,savscn                   ; Get last key-in
        or ourflgs,autorepflg           ; Show we are on auto repeat cycle
        pop es                          ; And make it look like new
        jmp rskp
inscax: pop es
        ret

; increments kbout pointer (with reset to zero) and returns normally
;   (on auto repeat cycles resets kbrepflg and returns )

inckbo: push es
        mov es,dosseg
        mov bx,fifobas
        test ourflgs,autorepflg ; new key-in or autorepeat
        jnz inckb2
        mov cl,0                ; Update kbout pointer to fifo
        cli
        cmp byte ptr es:[bx].kbout,kbfifosiz-2          ; End of fifo buff ?
        je  inckb1              ; Yes, so start back at beginning
        mov cl,es:[bx].kbout    ; No, just update our place
        add cl,2
inckb1: mov es:[bx].kbout,cl    ; Write back new pointer value
        sti
        jmp short inckbx
inckb2: mov byte ptr es:[bx].kbrepflg,0 ; Reset autorepeat flag in IO.SYS
inckbx: pop es
        ret

; send character in AL to port, with possible local echo

sndhst: push ax
        mov ah,al
        call outchr             ; send char to port
         nop                    ;  ...don't care if it fails
         nop
         nop
        pop ax
        test ourarg.flgs,lclecho ; doing local echo?
        jz sndhs2
        mov dl,al
        mov ah,conout
        int dos                 ;  if so, display char
sndhs2: ret

; send character to printer.  The only special case is the tab, which must
; be expanded to spaces because MS-DOS doesn't.

lstchr: cmp al,tab
        jne lstch2
        mov ax,lstpos           ; current column position
        mov cx,8                ; # of spaces = 8 - (column % 8)
        div cl
        sub cl,ah
        add lstpos,cx           ; update the column position
        mov al,' '
lstch1: call lstch4             ; print all the spaces
        loop lstch1
        ret
lstch2: cmp al,cr               ; CR returns column count to zero
        jne lstch3
        mov lstpos,0
lstch3: cmp al,' '              ; only printable characters are counted
        jb lstch4
        cmp al,del
        je lstch4
        inc lstpos
lstch4: mov dl,al               ; print the character in any case
        mov ah,lstout
        int dos
        ret

TERM    ENDP

; Set heath emulation on/off.

VTS     PROC    NEAR
        mov dx,offset nyimsg
        jmp tmsg
VTS     ENDP

; Save the screen to a buffer and then append buffer to a disk file. [jrd]
; Default filename is Kermit.scn; actual file can be a device too. Filename
; is determined by mssset and is passed as pointer dmpname.

VTSTAT  PROC    NEAR            ; For Status display [jrd]
        ret                     ; no emulator status to display
VTSTAT  ENDP

; Save the screen to a buffer and then append buffer to a disk file. [jrd]
; Default filename is Kermit.scn; actual file can be a device too. Filename
; is determined by mssset and is passed as pointer dmpname.

DUMPSCR PROC    NEAR    ; Dumps screen contents to a file. Just Beeps here
        call beep       ; [jrd]
        ret
DUMPSCR ENDP


; Position the cursor according to contents of DX:
; DH contains row, DL contains column.  Returns normally.

POSCUR  PROC    NEAR
        push ax                 ; save regs. [jrd]
        push cx
        push si
        cmp dh,25               ; out of range just assumes high value
        jb poscu1
        mov dh,24
poscu1: cmp dl,80
        jb poscu2
        mov dl,79
poscu2: add dx,2020H            ; add offset for ADM cursor addressing
        mov cpseq+2,dh
        mov cpseq+3,dl
        mov si,offset cpseq     ; print sequence (ESC=rc)
        mov cx,4
posc1:  lodsb
        mov dl,al
        mov ah,conout
        int dos
        loop posc1
        pop si
        pop cx                  ; [jrd]
        pop ax
        ret
POSCUR  ENDP

; Locate; homes cursor position and disables its display. Returns normally.

LOCATE  PROC    NEAR
        mov dx,offset nocur     ; Disable cursor
        call tmsg
        mov dx,0                ; Go to top left corner of screen.
        jmp poscur
LOCATE  ENDP

; Delete a character from the terminal.  This works by printing
; backspaces and spaces.  Returns normally.

DODEL   PROC    NEAR
        mov dx,offset delstr    ; Erase weird character.
        jmp tmsg
DODEL   ENDP

; Move the cursor to the left margin, then clear to end of line.
; Returns normally.

CTLU    PROC    NEAR
        mov dx,offset clrlin    ; this just goes to left margin...
        call tmsg
        jmp clearl              ; now clear line
CTLU    ENDP

; Clear to the end of the current line.  Returns normally.

CLEARL  PROC    NEAR
        mov dx,offset ceolseq   ; clear sequence
        jmp tmsg
CLEARL  ENDP

; This routine blanks the screen and homes the cursor.  Returns normally.

CMBLNK  PROC    NEAR
        mov dx,offset clrseq    ; clear screen and home cursor sequence
        jmp tmsg
CMBLNK  ENDP

; write a line in inverse video at the bottom of the screen...
; the line is passed in dx, terminated by a $.  Returns normally.

PUTMOD  PROC    NEAR
        push dx                 ; preserve message
        mov dx,24*100H          ; line 24
        call poscur
        mov dx,offset formdat.invseq    ; put into inverse video
        call tmsg
        pop dx                  ; print the message
        call tmsg
        mov dx,offset formdat.nrmseq    ; normal video
        jmp tmsg                ; Jump to return
PUTMOD  ENDP

; clear the mode line written by putmod.  Returns normally.

CLRMOD  PROC    NEAR
        mov dx,24*100H
        call poscur
        mov dx,offset ceolseq
        jmp tmsg
CLRMOD  ENDP

; Put a help message in a box at the top of the screen.
; This one uses inverse video (or yellow if color)
; Pass the message in ax, terminated by a null.  Returns normally.

PUTHLP  PROC    NEAR
        cld
        mov dx,ax               ; Prepare to pass message to 'getnoc'
        call getnoc             ;
        mov rnoc,cx             ; This is unformatted NOC in message
        mov  cx,5               ; Calculate formatted area needed (in words)
        rol  bx,cl              ; BX is no of lf's.
        mov  NOC,bx
        mov cx,2
        ror bx,cl
        add NOC,bx              ; This is Lines X 40
        add NOC,80              ; Current line + one more
        mov cx,NOC
        mov si,dx               ; Source of message  given us
        mov di,attrptr          ; Pointer to screen attrib area
        mov ax,formdat.bldcrt   ; Need bold attribute
        rep stosw               ; Cover attribute area needed
        mov ax,formdat.nrmcrt   ; Rest of screen needs normal color
        mov cx,1000             ; Whole screen
        sub cx,NOC              ; Attribute area left to cover
        rep stosw               ; Do it
        mov cx,1000             ; Fill screen data area with null bytes
        mov di,dispptr          ; Pointer to screen data area
        xor ax,ax               ; This is source of null bytes
        rep stosw               ; Prepare clean screen data area
        mov cx,rnoc             ; No of unformatted data bytes
        mov di,dispptr          ; Destination is screen data area
pthlp0: push di                 ; Save start of current line
pthlp1: lodsb                   ; Load data bytes 1-by-1
        cmp al,cr               ; Is this one an eol?
        jz pthlp2               ; Yep - handle cr/lf's ourselves
        stosb                   ; No - move character
        loop  pthlp1            ; And go back for next
        jmp short pthlp3        ; Finished moving chars, so exit.
pthlp2: dec cx                  ; Account for cr and lf
        dec cx
        inc si                  ; Skip the lf
        pop di                  ; Get start of current line.
        add di,80               ; Adjust pointer to next line
        jmp short pthlp0        ; And go back for more
pthlp3: pop di
        mov noc,2000            ; We are going to write over whole screen
        mov cmd,2               ; Get cursor position before writing
        mov dx,offset cmd
        mov cl,crtcmd
        int bios                ; Get cursor call
        mov cx,word ptr la
        mov savcur,cx           ; Store cursor position
        mov la,24               ; Prepare to roll down 24 lines
        mov ca,0
        mov cmd,3               ; Screen roll down command
        mov dx,offset cmd
        mov cl,crtcmd
        int bios                ; Do it!
        mov la,0                ; Begin our msg on top line of screen
        mov cmd,1               ; String write command
        int bios                ; Write it.
        mov dx,offset upcur
        call tmsg
        mov dx,offset upcur     ; Put cursor up onto clean screen
        call tmsg
        or  ourflgs,movcur      ; Tell that we've moved the cursor
        ret
PUTHLP  ENDP

; Receives message pointer in DX, terminating character in CL
; Returns with message ptr in DX, NOC in CX, and number of lf's in BX.

GETNOC  PROC    NEAR
        cld
        mov di,dx
        mov al,cl                       ; Move terminator to AL
        mov cx,2000                     ; Longest acceptible message.
        repnz scasb                     ; Look for terminator
        jz gtnoc1
        xor cx,cx
        ret                             ; Error return
gtnoc1: sub di,dx                       ; Calculate NOC
        mov cx,di                       ; Move it to counter
        dec cx                          ; Discount terminator
        push cx                         ; Save NOC
        mov di,dx
        mov al,lf
        xor bx,bx
gtnoc2: repnz scasb                     ; Now count lf's
        jcxz gtnoc3                     ; If the counter ran out
        inc bx
        jmp short gtnoc2
gtnoc3: pop cx                          ; Recover NOC
        ret
GETNOC  ENDP



; Produce a short beep.  Returns normally.

BEEP    PROC    NEAR
        mov dl,bell
        mov ah,conout
        int dos
        ret
BEEP    ENDP

; Prints $-terminated message in dx, for local use only

TMSG    PROC    NEAR
        mov ah,prstr
        int dos
        ret
TMSG    ENDP

; Jumping to this location is like retskp.  It assumes the instruction
;   after the call is a jmp addr.

RSKP    PROC    NEAR
        pop bp
        add bp,3
        push bp
        ret
RSKP    ENDP

; Jumping here is the same as a ret.

R       PROC    NEAR
        ret
R       ENDP

code    ends
        end

; END OF MSXAPC.ASM
