        NAME    msssen
; File MSSSEN.ASM
; Edit history:
; Last edit: 1 Jan 1988
; 1 Jan 1988 version 2.30
; 26 Dec 1987 clean ups [jrd]
; 8 Oct 1987 Ensure error pkts use 1 byte chksum at init stage. [jrd]
; 21 Sept 1987 Add error return to doenc for filenames too long for pkt. [jrd]
; 6 July 1987 Fix bug of 9 Dec below for case of short S/I packets. [jrd]
; 27 June 1987 Correct statistics gathering for last file. [jrd]
; 7 June 1987 Return DOS errlev of 1 if sending fails. [jrd]
; 24 May 1987 Add Delay seconds before starting local Send file. [jrd]
; 7 May 1987 Add statistics gathering functions. [jrd]
; 9 Dec 1986 Version 2.29a
; 9 Dec 1986 Add final check on packet length in Spar to avoid sending
; packets longer than the local user wishes. [jrd]
; 5 Sept 1986 minor cleanup in spar regular pkt size calculation. [jrd]
; 14 August 1986 Fix Send/Receive EOL, padding, pad-char [rjd]
; 3 August 86 modify for long packets, fix 8 bit quoting negotiations. [jrd]
; 26 May 1986 Revise code to permit serial display. [jrd]
;  Also, remove case conversion of "as" filename being sent. [rjd]
; [2.29] code frozen on 6 May 1986 [jrd]

        public  spar, rpar, error, error1, nout, send, flags, trans, pack
        public  dodec, doenc, curchk, inichk, packlen, send11, dtrans
        include mssdef.h

spmin   equ     20              ; Minimum packet size.
spmax   equ     94              ; Maximum packet size.

datas   segment public 'datas'
        extrn   buff:byte, data:byte, filbuf:byte, fsta:word
        extrn   decbuf:byte, chrcnt:word, bufpnt:word, errlev:byte
        extrn   rptq:byte, origr:byte, rptct:byte, rptval:byte
        extrn   diskio:byte, maxtry:byte, imxtry:byte, portval:word

flags   flginfo <>
trans   trinfo  <>
dtrans  trinfo  <>                      ; default trans info
pack    pktinfo <>
crlf    db      cr,lf,'$'
ender   db      bell,bell,'$'
erms14  db      '?Unable to receive an acknowledgment from the host$'
erms15  db      '?Unable to find file$'
erms20  db      'Unable to send init packet$'
erms21  db      'Unable to send file header$'
erms22  db      'Unable to send data$'
erms23  db      'Unable to send end-of-file packet$'
erms24  db      'Unable to send break packet$'
infms2  db      cr,'             Sending: In progress',cr,lf,'$'
infms3  db      'Completed',cr,lf,'$'
infms4  db      'Failed',cr,lf,'$'
infms6  db      'Interrupted$'
remmsg1 db      'Kermit-MS: File not found$'
filhlp  db      ' A filename (possibly wild) $'
filmsg  db      ' Local Source File  or a carriage return $'
remfnm  db      ' Remote Destination File: $'
lclfnm  db      ' Local Source File: $'

curchk  db      0               ; Use to store checksum length.
inichk  db      1               ; Original or set checksum length.
siz     dw      ?               ; Size of data from gtchr.
difsiz  dw      0               ; Size of new exported file name.
sendas  dw      50 dup(0)       ; Buffer for file name.
temp    dw      0
temp4   dw      0
asmsg   db      '  as  $'
filopn  db      0               ; Says if disk file is open.
ninefive dw     95              ; constant word for long packets
datas   ends

code    segment public 'code'
        extrn serini:near, serrst:near, comnd:near, init:near
        extrn spack:near, rpack:near, gtnfil:near, gtchr:near
        extrn getfil:near, clrfln:near, nppos:near, rprpos:near
        extrn erpos:near, rtpos:near, cxmsg:near, stpos:near, decout:near
        extrn encode:near, nulref:near, decode:near, nulr:near
        extrn errpack:near, updrtr:near, clrmod:near, prompt:near
        extrn prtfn:near, strcpy:near, strlen:near, pktsize:near
        extrn pcwait:near, ihosts:near, begtim:near, endtim:near

        assume  cs:code, ds:datas

;       This routine sets up the data for init packet (either the
;       Send_init or ACK packet).
; trans.rxxx are items we are prepared to receive
RPAR    PROC    NEAR
        mov     ah,trans.rpsiz          ; Get the receive packet size.
        add     ah,' '                  ; Add a space to make it printable.
        mov     [bx],ah                 ; Put it in the packet.
        mov     ah,trans.rtime          ; Get the receive packet time out.
        add     ah,' '                  ; Add a space.
        mov     1[bx],ah                ; Put it in the packet.
        mov     ah,trans.rpad           ; Get the number of padding chars.
        add     ah,' '
        mov     2[bx],ah                ; Put it in the packet.
        mov     ah,trans.rpadch         ; Get the padding char.
        add     ah,40h                  ; Uncontrol it.
        and     ah,7FH
        mov     3[bx],ah                ; Put it in the packet.
        mov     ah,trans.reol           ; Get the EOL char.
        add     ah,' '
        mov     4[bx],ah                ; Put it in the packet.
        mov     ah,trans.rquote         ; Get the quote char.
        mov     5[bx],ah                ; Put it in the packet.
        mov     ah,trans.ebquot         ; Get 8-bit quote char.
        mov     6[bx],ah                ; Add it to the packet.
        mov     ah,trans.chklen         ; Length of checksum.
        add     ah,48                   ; Make into a real digit.
        mov     7[bx],ah
        mov     ah,rptq                 ; Repeat quote char.
        cmp     ah,0                    ; Null means no.
        jne     rpar0
        mov     ah,' '                  ; Send a blank instead.
rpar0:  mov     8[bx],ah
                                        ; begin long packet changes
        mov     ah,02h                  ; CAPAS, bit1 = can do long packets
        add     ah,20h                  ; apply tochar() to byte
        mov     9[bx],ah                ; add to packet
                                        ; additional CAPAS go in here
        mov     ah,20h          ; WINDO field, null applied through tochar()
        mov     10[bx],ah               ; put into packet

        push    ax                      ; save some regs
        push    dx
        mov     ax,trans.rlongp     ; long packet length which we can receive
        xor     dx,dx                   ; clear extended part for division
        div     ninefive                ; divide by 95. quo = ax, rem = dx
        add     al,20h                  ; apply tochar() to quotient
        mov     11[bx],al               ; add to packet
        add     dl,20h                  ; apply tochar() to remainder
        mov     12[bx],dl               ; add to packet
        pop     dx                      ; restore regs
        pop     ax
        mov     ah,13                   ; 13 bytes of data
        ret
RPAR    ENDP

; This routine reads in all the send init packet information.
; Enter with BX/ packet address, AX/ packet length
; This could probably be done much more legibly if it were table
; driven, but I'm afraid to touch it...
;
; dtrans.xxx are the default parameters if the other side says nothing.
; trans.sxxx are the active negotiated parameters we will use for sending.
SPAR    PROC    NEAR
        mov     temp4,ax                ; Save the number of arguments
        cmp     ax,1
        jge     spara                   ; ge = want more than bare minimum
        mov     ah,dspsiz             ; Data not supplied by host, use default
        jmp     short sparc
spara:  mov     ah,[bx]                 ; Get the max packet size.
        sub     ah,' '                  ; Subtract a space.
        cmp     ah,spmin                ; Can't be below the minimum.
        jge     sparb
        mov     ah,spmin
        jmp     short sparc
sparb:  cmp     ah,spmax                ; Or above the maximum.
        jle     sparc
        mov     ah,spmax
sparc:  mov     trans.spsiz,ah          ; Save it.
        mov     ax,temp4
        mov     ah,dtrans.stime         ; pick up default stime
        cmp     al,2                    ; Fewer than two pieces?
        jl      spar02                  ; yes, use default
spar0:  cmp     ah,dstime               ; Is current value the default?
        jne     spar02                  ; No, assume changed by user.
        mov     ah,1[bx]                ; Get the timeout value.
        sub     ah,' '                  ; Subtract a space.
        cmp     ah,0
        ja      spar01                  ; Must be non-negative.
        mov     ah,0
spar01: cmp     ah,trans.rtime          ; Same as other side's timeout.
        jne     spar02
        add     ah,5                    ; If so, make it a little different.
spar02: mov     trans.stime,ah          ; Save it.
        mov     ax,temp4
        mov     ah,dtrans.spad          ; get default send padding
        cmp     al,3                    ; Fewer than three pieces?
        jl      spar11                  ; yes, use default
spar1:  mov     ah,2[bx]                ; Get the number of padding chars.
        sub     ah,' '
        cmp     ah,0
        jge     spar11                  ; Must be non-negative.
        mov     ah,0
spar11: mov     trans.spad,ah
        mov     ax,temp4
        mov     ah,dtrans.spadch        ; pick up default send pad character
        cmp     al,4                    ; Fewer than four pieces?
        jl      spar21
spar2:  mov     ah,3[bx]                ; Get the padding char.
        add     ah,40h                  ; Re-controlify it.
        and     ah,7FH
        cmp     ah,del                  ; Delete?
        je      spar21                  ; Yes, then it's OK.
        cmp     ah,0
        jge     spar20
        mov     ah,0                    ; Below zero is no good.
        jmp     spar21                  ; Use zero (null).
spar20: cmp     ah,31                   ; Is it a control char?
        jle     spar21                  ; Yes, then OK.
        mov     ah,0                    ; No, use null.
spar21: mov     trans.spadch,ah
        mov     ax,temp4
        mov     ah,dtrans.seol          ; get default send eol char
        cmp     al,5                    ; Fewer than five pieces?
        jl      spar31                  ; yes, use default
spar3:  mov     ah,4[bx]                ; Get the EOL char.
        sub     ah,' '
        cmp     ah,0
        jge     spar30                  ; Cannot be negative.
        mov     ah,cr                 ; If so, use default of carriage return
        jmp     spar31
spar30: cmp     ah,31                   ; Is it a control char?
        jle     spar31                  ; Yes, then use it.
        mov     ah,cr                   ; Else, use the default.
spar31: mov     trans.seol,ah
        mov     ax,temp4
        mov     ah,dtrans.squote        ; send quote
        cmp     al,6                    ; Fewer than six pieces?
        jl      spar41
spar4:  mov     ah,5[bx]                ; Get the quote char.
        cmp     ah,' '                  ; Less than a space?
        jge     spar40
        mov     ah,dsquot               ; Yes, use default.
        jmp     spar41
spar40: cmp     ah,7eh                  ; Must also be less than a tilde.
        jle     spar41
        mov     ah,dsquot               ; Else, use default.
spar41: mov     trans.squote,ah
        cmp     al,7                    ; Fewer than seven pieces?
        jge     spar5
        mov     trans.ebquot,'Y'      ; Data not supplied by host, use default
        jmp     short spar51
spar5:  mov     ah,6[bx]                ; Get other sides 8-bit quote request
        call    doquo                   ; And set quote char.
spar51: cmp     al,8                    ; Fewer than eight pieces?
        jge     spar6
        mov     trans.chklen,1
        jmp     short spar61
spar6:  mov     ah,inichk
        mov     trans.chklen,ah         ;Checksum length we really want to use
        mov     ah,7[bx]                ; Get other sides checksum length.
        call    dochk                   ; Determine what size to use.
spar61: cmp     al,9                    ; Fewer than nine pieces?
        jge     spar7
        mov     rptq,0
        jmp     short spar71
spar7:  mov     ah,8[bx]                ; Get other sides repeat count prefix.
        mov     ch,drpt
        mov     rptq,0
        call    dorpt
;;; begin long packet changes - update this for strict ELP protocol
spar71: mov     ah,0                    ; get default operating capabilities
        cmp     al,10                   ; 10 or more pieces?
        jl      spar81                  ; l = no
        mov     ah,9[bx]                ; get capas bitmap from other side
        sub     ah,20h                  ; apply unchar()
        call    decapa                  ; negotiate them back into ah
spar81: mov     trans.capas,ah          ; store result in active byte

        mov     ah,0                    ; setup default window size
        cmp     al,11                   ; 11 or more pieces?
        jl      spar9                   ; l = no, use default
        mov     ah,10[bx]               ; get other side's window size
        sub     ah,20h                  ; apply unchar()
        call    dewind                  ; negotiate window size back into ah
spar9:  mov     trans.windo,ah          ; store it
                                        ; decode window info
        push    cx                      ; save a reg
        xor     ch,ch
        mov     cl,trans.spsiz          ; normal packet size
        mov     trans.slongp,cx         ; assume not using long packets
        pop     cx                      ; restore reg
        cmp     al,13           ; 13 or more pieces (long packet needs two)?
        jae     spar9d                  ; ae = more to look at
        mov     ax,trans.slongp ; put above size in ax for final checks
        push    ax
        jmp     spar9a          ; do final checks (they want longer than us)
spar9d: test    trans.capas,2   ; do they have long packet capability?
        jz      sparx           ; z = no, skip following lp length fields.
        push    ax                      ; save al
        xor     ah,ah
        mov     al,11[bx]               ; long packet length, high order byte
        sub     al,20h                  ; apply unchar()
        mul     ninefive                ; times 95 to dx(hi),ax(lo)
        mov     trans.slongp,ax         ; store that much
        xor     ah,ah
        mov     al,12[bx]               ; long packet length, low order byte
        sub     al,20h                  ; apply unchar()
        add     ax,trans.slongp         ; plus high order part
        mov     trans.slongp,ax         ; store it
        or      ax,ax           ; if result is zero then use regular packets
        jnz     spar9a                  ; non-zero, use what they want
        mov     ah,0
        mov     al,trans.spsiz          ; default to regular packet size
        mov     trans.slongp,ax ;  and ignore the CAPAS bit (no def 500 bytes)
spar9a: cmp     ax,trans.slong          ; longer than we want to do?
        jbe     spar9b                  ; be = no
        mov     ax,trans.slong          ; limit to our longest sending size
        mov     trans.slongp,ax         ; and use it
spar9b: cmp     ax,94                   ; shorter than normal packet too?
        ja      spar9c                  ; a = no
        mov     trans.spsiz,al          ; update normal packet size, again.
spar9c: pop     ax                      ; recover al
sparx:  ret
SPAR    ENDP

; Set 8-bit quote character based on my capabilities and the other
; Kermit's request.

DOQUO   PROC    NEAR
        cmp     trans.ebquot,'N'        ; Can I do 8-bit quoting at all?
        je      dq3                     ; No - so forget it.
        cmp     trans.ebquot,'Y'        ; Can I do it if requested?
        jne     dq0                     ; No - it's a must that I do it.
        mov     trans.ebquot,ah         ; Do whatever he wants.
        jmp     dq1
dq0:    cmp     ah,'Y'                  ; I need quoting - can he do it?
        je      dq1                     ; Yes - then all is settled.
        cmp     ah,'N'                  ; No - then don't quote.
        je      dq3
        cmp     ah,trans.ebquot         ; Both need quoting - chars must match
        jne     dq3
dq1:    mov     ah,trans.ebquot
        cmp     ah,'Y'                  ; If Y or N, don't validate prefix.
        je      dq2
        cmp     ah,'N'
        je      dq2
        call    prechk                  ; Is it in range 33-62, 96-126?
         mov    ah,'Y'                  ; Failed, don't do quoting.
         nop
        cmp     ah,trans.rquote         ; Same prefix?
        je      dq3                     ; Not allowed, so don't do quoting.
        cmp     ah,trans.squote         ; Same prefix here?
        je      dq3                     ; This is illegal too.
        mov     trans.ebquot,ah         ; Remember what we decided on.
dq2:    ret
dq3:    mov     trans.ebquot,'N'        ; Quoting will not be done.
        ret
DOQUO   ENDP

; Check if prefix in AH is in the proper range: 33-62, 96-126.
; RSKP if so else RETURN.
prechk: cmp     ah,33
        jge     prec0                   ; It's above 33.
        ret
prec0:  cmp     ah,62
        jg      prec1
        jmp     rskp                    ; And below 62.  OK.
prec1:  cmp     ah,96
        jge     prec2                   ; It's above 96.
        ret
prec2:  cmp     ah,126
        jg      prec3
        jmp     rskp                    ; And below 126.  OK.
prec3:  ret

; Set checksum length.
dochk:  cmp     ah,'1'                  ; Must be 1, 2, or 3.
        jl      doc1
        cmp     ah,'3'
        jle     doc2
doc1:   mov     ah,'1'
doc2:   sub     ah,48                   ; Don't want it printable.
        mov     trans.chklen,ah         ; other side's request is do-able here
        cmp     ah,trans.chklen         ; Do we want the same thing?
        je      dochk0                  ; Yes, then we're done.
        mov     trans.chklen,1          ; No, use single character checksum.
dochk0: ret                             ; Just return for now.

; Set repeat count quote character.  The one used must be different than
; the control and eight-bit quote characters.  Also, both sides must
; use the same character.
dorpt:  call    prechk                  ; Is it in the valid range?
         mov    ah,0                    ; No, don't use their value.
         nop
        cmp     ah,trans.squote         ; Same as the control quote char?
        je      dorpt0                  ; Yes, that's illegal, no repeats.
        cmp     ah,trans.rquote         ; How about this one?
        je      dorpt0                  ; No good.
        cmp     ah,trans.ebquot         ; Same as eight bit quote char?
        je      dorpt0                  ; Yes, that's illegal too, no repeats.
        cmp     ah,ch                   ; Are we planning to use same char?
        jne     dorpt0                  ; No, that's no good either.
        mov     rptq,ch                 ; Use repeat quote char now.
dorpt0: ret
                                        ; negotiate capas byte in ah
decapa: ret                             ; nothing for now

                                        ; negotiate window size in ah
dewind: xor     ah,ah                   ; no windowing at our end
        ret

;       Send command

SEND    PROC    NEAR
        mov     difsiz,0                ; Assume we'll use original filename.
        mov     byte ptr sendas,0       ; clear sendas name (in case none)
        mov     ah,cmfile               ; get an input file spec
        mov     dx,offset diskio.string ; address of filename string
        mov     bx,offset filmsg        ; Text of help message.
        call    comnd
         jmp    r                       ;  Give up on bad parse.
        cmp     flags.cxzflg,0          ; ^X, ^Z, ^C typed?
        je      send0                   ; e = no, continue
        or      errlev,1                ; say send failed
        jmp     rskp                    ; yes, quit
send0:  cmp     ah,0                    ; any text given?
        ja      send0c                  ; a = yes

        mov     dx,offset lclfnm        ; prompt for local filename
        call    prompt
        mov     dx,offset diskio.string ; reload destination of user's text
        mov     bx,offset filhlp        ; help file
        mov     ah,cmfile               ; allow paths
        call    comnd                   ; try again for a local filename
         jmp    r
        cmp     flags.cxzflg,0          ; ^X, ^Z, ^C typed?
        je      send0a                  ; e = no, continue
        or      errlev,1                ; say send failed
        jmp     rskp                    ; yes, quit
send0a: cmp     ah,0                    ; user's byte count
        jne     send0b                  ; something was typed
        jmp     r                   ; else return (gives "not confirmed" msg)

send0b: mov     dx,offset remfnm        ; ask for remote name first
        call    prompt

send0c: mov     bx,offset sendas     ; See if want to send file under dif name
        mov     dx,offset filhlp        ; In case user needs help.
        mov     ah,cmtxt                ; allow embedded white space
        call    comnd
         jmp    r
        cmp     flags.cxzflg,0          ; ^X, ^Z, ^C typed?
        je      send1                   ; e = no, continue
        or      errlev,1                ; say send failed
        jmp     rskp                    ; yes, quit
send1:  mov     al,ah                   ; store count of user's chars.
        mov     ah,0
        mov     difsiz,ax               ; Remember length of new name.
        mov     bx,offset sendas        ; look at string again.
        push    es
        push    di
        push    si
        mov     ax,ds                   ; use segment 'datas' for es:
        mov     es,ax
        mov     si,bx           ; look at start of string, remove whitespace
send1c: cmp     byte ptr [si],0         ; at terminator?
        je      send1d                  ; e = yes
        cmp     byte ptr [si],' '       ; text (greater than space)?
        ja      send1d                  ; a = yes.
        inc     si                      ; look at next char
        jmp     send1c                  ; look some more
send1d: cmp     bx,si                   ; did we find leading whitespace?
        je      send1e                  ; e = no
        mov     di,bx                   ; place to copy chars
        call    strcpy                  ;   from ds:si to ds:di
send1e: mov     dx,bx                   ; address of string
        call    strlen                  ; get its new length (returned in cx)
        mov     difsiz,cx               ; store it
        pop     si
        pop     di
        pop     es
        mov     ah,trans.sdelay         ; seconds to delay before sending
        shl     ah,1                    ; times 4*256 to get millisec
        shl     ah,1                    ;  for pcwait
        mov     al,1                    ; set low byte to 1 for no delay case
        call    pcwait                  ; wait number of millisec in ax
        mov     flags.xflg,0            ; Reset flag for normal file send[mtd]
        mov     flags.cxzflg,0          ; clear interrupt flag too.
        mov     bx,offset diskio.string
        cmp     byte ptr [bx],'#'       ; Is first char a replacement for '?'?
        jne     send1f                  ; ne = no
        mov     byte ptr [bx],'?'       ; yes. Change '#' for '?'
send1f: mov     bx,offset sendas
        cmp     byte ptr [bx],'#'       ; Is first char a replacement for '?'?
        jne     snd11a
        mov     byte ptr [bx],'?'       ; yes. Change '#' for '?'
        jmp     snd11a
                                ; SEND11 is an entry point for REMote cmds.
SEND11: mov     flags.nmoflg,0          ; Reset flags from fn parsing.
        mov     difsiz,0                ; clear any old 'sendas' filespec
snd11a: mov     ah,setdma               ; set dta address
        mov     dx,offset diskio.dta
        int     dos
        mov     ah,first2               ; search for first
        mov     cx,0                    ; consider only regular files
        mov     dx,offset diskio.string ; full filename, inc paths
        int     dos
        pushf                           ; save flags
        push    dx
        mov     ah,setdma               ; restore dta to offset buff
        mov     dx,offset buff
        int     dos
        pop     dx
        popf                            ; restore flags
        jnc     send16                  ; carry reset = file found
        cmp     pack.state,'R'          ; was this from a remote GET?
        jne     sen11a                  ; no, print error and continue
        mov     bx,offset remmsg1       ; else get error message
        mov     ah,trans.chklen
        mov     curchk,ah               ; Store checksum length we want to use
        mov     trans.chklen,1          ; Send init checksum is always 1 char.
        call    errpack                 ; go complain
        mov     ah,curchk
        mov     trans.chklen,ah         ; Checksum length we want to use.
        jmp     abort                   ; and abort this
sen11a: mov     ah,prstr
        mov     dx,offset crlf
        int     dos
        mov     ah,prstr
        mov     dx,offset erms15        ; '?Unable to find file'
        int     dos
        or      errlev,1                ; set DOS error level
        mov     ax,1            ; tell statistics this was a send operation
        call    endtim                  ; stop statistics counter
        jmp     rskp                    ; pretend successful completion

send16: call    serini                  ; Initialize serial port
        mov     ax,1                    ; say this is a send operation
        call    endtim                  ; get tod for end of transfer
        call    begtim                  ; get tod for start of transfer
        mov     pack.pktnum,0           ; Set the packet number to zero.
        mov     pack.numtry,0           ; Set the number of tries to zero.
        mov     pack.numpkt,0           ; Set the number of packets to zero.
        mov     pack.numrtr,0           ; Set the number of retries to zero.
        mov     pack.state,'S'          ; Set the state to receive initiate.
        call    ihosts                  ; initialize the host (clear NAKs)
        call    init            ; Clear the line and initialize the buffers.
        test    flags.remflg,dquiet+dserial ; quiet or serial display mode?
        jnz     send2                   ; nz = yes, suppress 0 retry msg
        call    stpos                   ; Print status of file transfer.
        mov     ah,prstr                ; Be informative.
        mov     dx,offset infms2
        int     dos
send18: test    flags.remflg,dquiet+dserial ; quiet or serial display mode?
        jnz     send2                   ; nz = yes, suppress 0 retry msg
        call    rtpos                   ; Position cursor.
        mov     ax,0                    ; set retry counts to zero
        call    nout                    ; Write the number of retries.
send2:  call    nppos                   ; Number of packets sent.
        mov     ax,pack.numpkt
        call    nout                    ; Write the packet number.
        cmp     pack.state,'D'          ; Are we in the data send state?
        jne     send3                   ; ne = no
        call    sdata                   ; send data
        jmp     send2

send3:  cmp     pack.state,'F'          ; Are we in the file send state?
        jne     send4
        call    sfile                   ; Call send file.
        jmp     send2
send4:  cmp     pack.state,'Z'          ; Are we in the EOF state?
        jne     send5
        call    seof
        jmp     send2
send5:  cmp     pack.state,'S'          ; Are we in the send initiate state?
        jne     send6
        call    sinit
        jmp     send2
send6:  cmp     pack.state,'B'          ; Are we in the eot state?
        jne     send7
        call    seot
        jmp     send2
                                        ; Completion processor section
send7:  push    ax
        mov     ax,1            ; tell statistics this was a send operation
        call    endtim                  ; stop statistics counter
        call    serrst                  ; Reset serial port.
        pop     ax
        mov     dx,offset infms3        ; Completed message
        cmp     pack.state,'C'          ; Are we in the send complete state?
        je      send8                   ; e = yes, else failure
        mov     dx,offset infms4        ; Failed message
        or      errlev,1                ; say send failed

send8:  cmp     flags.cxzflg,0          ; completed normally?
        jne     send8b                  ; ne = no, don't bother with this
        or      errlev,1                ; say send failed
send8b: test    flags.remflg,dquiet     ; quiet display mode?
        jnz     send8f                  ; nz = yes, no printing.
        test    flags.remflg,dserial    ; serial display mode?
        jnz     send8c                  ; nz = yes, skip positioning
        push    dx
        call    stpos
        pop     dx
send8c: mov     ah,prstr
        cmp     flags.cxzflg,0          ; Completed or interrupted?
        je      send8d                  ; e = no interruption
        mov     dx,offset infms6        ; Say transfer was interrupted.
send8d: int     dos
        cmp     flags.belflg,0          ; Bell desired?
        je      send8e                  ; e = no
        mov     dx,offset ender         ; Ring them bells.
        int     dos
send8e: test    flags.remflg,dserial    ; serial display mode?
        jnz     send8f                  ; nz = yes, no cursor positioning
        call    clrmod
        call    rprpos
send8f: jmp     rskp
SEND    ENDP

;       Send routines

;       Send initiate
SINIT   PROC    NEAR
        mov     dl,imxtry
        cmp     pack.numtry,dl  ; Have we reached the maximum number of tries?
        jl      sinit2                  ; l = no
        test    flags.remflg,dquiet     ; quiet display mode?
        jnz     sinit1                  ; nz = yes. Don't write to screen.
        call    erpos
        mov     dx,offset erms14
        mov     ah,prstr
        int     dos                     ; Print an error message.
sinit1: mov     ah,trans.chklen
        mov     curchk,ah               ; Store checksum length we want to use
        mov     trans.chklen,1          ; Send init checksum is always 1 char.
        mov     bx,offset erms20
        call    errpack                 ; Send error packet just in case.
        mov     ah,curchk
        mov     trans.chklen,ah         ; Checksum length we want to use.
        jmp     abort                   ; Change the state to abort.
sinit2: inc     pack.numtry             ; Save the updated number of tries.
        mov     bx,offset data          ; Get a pointer to our data block.
        call    rpar                    ; Set up the parameter information.
        xchg    ah,al
        mov     ah,dtrans.seol          ; restore default end-of-line char
        mov     trans.seol,ah
        mov     ah,0
        mov     pack.argbk1,ax          ; Save the number of arguments.
        mov     ax,pack.numpkt          ; Get the packet number.
        mov     pack.argblk,ax
        mov     trans.ebquot,'Y'        ; say we can do 8 bit quoting
        push    bx
        mov     bx,portval
        cmp     [bx].parflg,parnon      ; using parity=none locally?
        pop     bx
        je      sinit2a                 ; e = no parity
        mov     trans.ebquot,dqbin      ; def 8 bit quot, needed with parity
sinit2a:mov     ah,trans.chklen
        mov     curchk,ah               ; Store checksum length we want to use
        mov     trans.chklen,1          ; Send init checksum is always 1 char.
        call    pktsize                 ; report packet size
        mov     ah,'S'                  ; Send initiate packet.
        call    spack                   ; Send the packet.
         jmp    abort
        call    rpack                   ; Get a packet.
         jmp    sini23          ; Trashed packet don't change state, retry.
        push    ax
        mov     ah,curchk
        mov     trans.chklen,ah         ; Checksum length we want to use.
        pop     ax
        call    acknak                  ; was it ok?
        cmp     al,0                    ; maybe an ack?
        je      sini22                  ; yes, go handle it
        cmp     al,1                    ; maybe a nak?
        jne     sinit4                  ; no, check for error or something
        ret                             ; else just return and try again
sini22: mov     ax,pack.argbk1
        mov     bx,offset data          ; point to data for spar
        call    spar                    ; Read in the data.
        call    packlen                 ; Get max send packet size.
        mov     pack.numtry,0           ; Reset the number of tries.
        mov     pack.state,'F'          ; Set the state to file send.
        call    getfil                  ; Open the file.
         jmp    abort                   ;  Something is wrong, die.
        mov     filopn,1                ; Disk file is open.
        ret
sini23: mov     ah,curchk               ; Restore desired checksum length.
        mov     trans.chklen,ah
        call    updrtr                  ; Update retry counter.
        ret                             ; And retry.
sinit4: cmp     ah,'E'                  ; Is it an error packet.
        jne     sinit5
        call    error
sinit5: jmp     abort
SINIT   ENDP

;       Send file header

SFILE   PROC    NEAR
        mov     dl,maxtry
        cmp     pack.numtry,dl  ; Have we reached the maximum number of tries?
        jl      sfile1                  ; l = no
        test    flags.remflg,dquiet     ; quiet display mode?
        jnz     sfile0                  ; nz = yes. Don't write to screen
        call    erpos
        mov     dx,offset erms14
        mov     ah,prstr
        int     dos                     ; Print an error message.
sfile0: mov     bx,offset erms21
        call    errpack                 ; Send error packet just in case.
        jmp     abort                   ; Change the state to abort.
sfile1: inc     pack.numtry             ; Increment it.
        mov     flags.cxzflg,0          ; Clear ^X,^Z flag.
        mov     si,offset diskio.fname  ;addr of asciiz filename without paths
        mov     di,offset data          ; destination
        call    strcpy                  ; copy filename there
        push    dx
        mov     dx,offset data
        call    strlen                  ; get length (w/o terminator) into cx
        pop     dx
        mov     ch,0
        test    flags.remflg,dquiet     ; quiet display mode?
        jnz     sfil13                  ; nz = yes, no printing.
        call    prtfn                   ; print filename in data
sfil13: call    newfn           ; show possible new filename, put length in cx
        call    doenc                   ; Do encoding; length is in cx.
        mov     ax,pack.pktnum          ; Get the packet number.
        mov     pack.argblk,ax
        mov     ah,'F'                  ; File header packet.
        cmp     flags.xflg,0            ; remote display requested?
        je      sfl13y                  ; e = no
        mov     ah,'X'          ; use X rather than F packet for remote
sfl13y: call    pktsize                 ; report packet size
        call    spack                   ; Send the packet.
         jmp    abort
        call    rpack                   ; Get a packet.
         jmp    tryagn          ; Trashed packet don't change state, retry.
        call    dodec                   ; Do all decoding.
        call    acknak                  ; see what they had to say
        cmp     al,0                    ; ack'd ok?
        je      sfil14                  ; yes, on to next state
        cmp     al,1                    ; maybe a nak?
        jne     sfile3                  ; no, check for error
        ret                             ; if nak, just return and try again

sfil14: mov     flags.filflg,0FFH       ; Indicate file buffer empty.
        call    gtchr
         jmp    sfil16                  ; Error go see if its EOF.
         nop
        jmp     sfil17                  ; Got the chars, proceed.
sfil16: cmp     ah,0FFH                 ; Is it EOF?
        je      sfl161
        jmp     abort                   ; If not give up.

sfl161: mov     ah,'Z'                  ; Set the state to EOF.
        mov     pack.state,ah
        ret

sfil17: mov     siz,ax
        mov     pack.state,'D'          ; Set the state to data send.
        ret

sfile3: cmp     ah,'E'                  ; Is it an error packet.
        jne     sfile4
        call    error
sfile4: jmp     abort
SFILE   ENDP


;       Send data

SDATA   PROC    NEAR
        cmp     flags.cxzflg,0          ; Have we seen ^X or ^Z?
        je      sdata2                  ; Nope, just continue.
        cmp     flags.cxzflg,'C'        ; Stop it all?
        jne     sdata1                  ; It was a ^X or ^Z.
        mov     pack.state,'A'          ; It was a ^C -- abort
        ret
sdata1: mov     pack.state,'Z'          ; Else, abort sending the file.
        ret
sdata2: mov     dl,maxtry
        cmp     pack.numtry,dl  ; Have we reached the maximum number of tries?
        jl      sdata3                  ; l = no
        test    flags.remflg,dquiet     ; quiet display mode?
        jnz     sdat2a                  ; nz = yes. Don't write to screen
        call    erpos
        mov     dx,offset erms14
        mov     ah,prstr
        int     dos                     ; Print an error message.
sdat2a: mov     bx,offset erms22
        call    errpack                 ; Send error packet just in case.
        jmp     abort                   ; Change the state to abort.
sdata3: inc     pack.numtry             ; Increment it.
        mov     cx,siz                  ; number to transfer
        mov     si,offset filbuf        ; source of characters
        mov     di,offset data          ; destination
        push    es                      ; save es
        push    ds
        pop     es                      ; make es:di point to datas segment
        cld
        rep     movsb                   ; just copy data
        pop     es                      ; restore reg
        mov     ax,siz                  ; this is how many were moved
sdata7: mov     pack.argbk1,ax
        mov     ax,pack.pktnum          ; Get the packet number.
        mov     pack.argblk,ax
        call    pktsize                 ; report packet size
        mov     ah,'D'                  ; Data packet.
        call    spack                   ; Send the packet.
         jmp    tryagn          ; if can't send it, retry before giving up
        call    rpack                   ; Get a packet.
         jmp    tryagn          ; Trashed packet don't change state, retry.
        call    dodec                   ; Do all decoding.
        call    acknak          ; see if ack or nak, check packet number
        cmp     al,0                    ; 0 => ack ok, go on
        je      sdat11
        cmp     al,1            ; 1 => nak, retry count incremented, try again
        jne     sdat15                  ; else look for other packet types...
        ret                             ; else return

sdat11: cmp     pack.argbk1,1           ; any data?
        jne     sdat23
        mov     bl,data                 ; get 1st byte
        cmp     bl,'X'                  ; someone typed control X?
        je      sdat24                  ; e = yes.
        cmp     bl,'Z'          ; Control Z? Corrects earlier proto error
        jne     sdat23                  ; not X or Z, just keep going
sdat24: mov     flags.cxzflg,bl         ; set flag appropriately
        mov     pack.state,'Z'          ; simulate eof
        ret                             ; and return

sdat23: call    gtchr
         jmp    sdat12                  ; Error go see if its EOF.
         nop                            ; make  three bytes
        mov     siz,ax                  ; Save the size of the data gotten.
        ret

sdat12: cmp     ah,0FFH                 ; Is it EOF?
        je      sdat13
        jmp     abort                   ; If not give up.

sdat13: mov     pack.state,'Z'          ; Set the state to EOF.
        ret

sdat15: cmp     ah,'E'                  ; Is it an error packet.
        jne     sdat16
        call    error
sdat16: jmp     abort
SDATA   ENDP

; check the current packet for an ack or nak and handle it from any of
; the send states.  Returns: 0 if an ack received with the correct expected
; packet number, or if a nak received with the NEXT packet number (the
; packet number is incremented, retry count reset); 1 if a nak or ack
; with a bad packet number is received, retry count is updated and displayed.
; Finally, 2 is returned if anything else is seen.
;

ACKNAK  PROC    NEAR
        cmp     ah,'Y'                  ; ack packet?
        jne     ackna1                  ; no, keep going
        mov     bx,pack.pktnum
        cmp     bx,pack.argblk          ; is it what we were expecting?
        jne     ackna2                  ; no, update retries and punt
                                        ; packet ok, increment packet number
ackna0: mov     bx,pack.pktnum          ; reload packet number (!!!)
        inc     bx
        and     bx,03fh                 ; increment packet number
        mov     pack.pktnum,bx          ; store back
        inc     pack.numpkt             ; increment # of packets
        mov     pack.numtry,0
        mov     al,0                    ; ack'd ok
        ret
                                        ; not a 'Y'...
ackna1: cmp     ah,'N'                  ; a nak?
        je      ackna5                  ; yes, go on
        mov     al,2
        ret                             ; unknown packet type
ackna5: mov     bx,pack.pktnum
        inc     bx
        and     bx,3fh
        cmp     bx,pack.argblk          ; maybe a nak for pktnum+1?
        je      ackna0                  ; yes, treat as ack
                                        ; nak or bad ack, update retry stuff
ackna2: call    rtpos                   ; Position cursor.
        inc     pack.numrtr             ; Increment the number of retries
        mov     ax,pack.numrtr
        call    nout                    ; Write the number of retries.
        mov     al,1                    ; nak code
        inc     fsta.nakrcnt            ; count received NAK for statistics
        ret                             ; and return
ACKNAK  ENDP

;       Send EOF

SEOF    PROC    NEAR
        mov     dl,maxtry
        cmp     pack.numtry,dl  ; Have we reached the maximum number of tries?
        jl      seof1                   ; l = no
        test    flags.remflg,dquiet; quiet display mode?
        jnz     seof0                   ; nz = yes. Don't write to screen
        call    erpos                   ; Position cursor.
        mov     dx,offset erms14
        mov     ah,prstr
        int     dos                     ; Print an error message.
seof0:  mov     bx,offset erms23
        call    errpack                 ; Send error packet just in case.
        jmp     abort                   ; Change the state to abort.
seof1:  inc     pack.numtry             ; Increment it.
        mov     ax,pack.pktnum          ; Get the packet number.
        mov     pack.argblk,ax
        mov     pack.argbk1,0           ; No data.
        cmp     flags.cxzflg,0          ; Seen a ^X or ^Z?
        je      seof11                  ; Nope, send normal EOF packet.
        mov     data,'D'                ; Use "D" for discard.
        mov     pack.argbk1,1           ; Set data size to 1.
seof11: mov     cx,pack.argbk1          ; Put size in CX.
        call    doenc                   ; Encode the packet.
        call    pktsize                 ; report packet size
        mov     ah,'Z'                  ; EOF packet.
        call    spack                   ; Send the packet.
         jmp    abort
        call    rpack                   ; Get a packet.
         jmp    tryagn          ;  Trashed packet don't change state, retry.
        call    dodec                   ; Do decoding.
        call    acknak                  ; see what they had to say
        cmp     al,0                    ; ack?
        je      seof12                  ; yes, go close file and proceed
        cmp     al,1                    ; maybe a nak?
        jne     seof3                   ; no, check for error packet
        ret                             ; if nak, just return...

seof12: mov     ah,close2               ; DOS 2.0 close file
        push    bx
        mov     bx,diskio.handle        ; file handle
        int     dos
        pop     bx
        mov     ax,1            ; tell statistics this was a send operation
        call    endtim                  ; get tod of end of file transfer
        call    GTNFIL                  ; Get the next file.
         jmp    seof13                  ;  No more.
         nop                            ; make three bytes
        call    begtim                  ; start statistics counter

        mov     pack.state,'F'          ; Set the state to file send.
        cmp     flags.cxzflg,'X'        ; Control-X seen?
        jne     seof14
        call    cxmsg                   ; Clear out the interrupt msg.
seof14: mov     flags.cxzflg,0          ; Reset the flag.
        ret
seof13: mov     pack.state,'B'          ; Set the state to EOT.
        mov     filopn,0                ; No files open.
        mov     difsiz,0                ; clear original filename
        mov     byte ptr sendas,0       ; clear sendas name
        ret
seof3:  cmp     ah,'E'                  ; Is it an error packet?
        jne     seof4
        call    error
seof4:  jmp     abort
SEOF    ENDP


; Send EOT

SEOT    PROC    NEAR
        mov     dl,maxtry
        cmp     pack.numtry,dl  ; Have we reached the maximum number of tries?
        jl      seot1                   ; l = no
        test    flags.remflg,dquiet     ; quiet display mode?
        jnz     seot0                   ; nz = yes. Don't write to screen
        call    erpos                   ; Position cursor.
        mov     dx,offset erms14
        mov     ah,prstr
        int     dos                     ; Print an error message.
seot0:  mov     bx,offset erms24
        call    errpack                 ; Send error packet just in case.
        jmp     abort                   ; Change the state to abort.
seot1:  inc     pack.numtry             ; Increment it.
        mov     ax,pack.pktnum          ; Get the packet number.
        mov     pack.argblk,ax
        mov     pack.argbk1,0           ; No data.
        call    pktsize                 ; report packet size
        mov     ah,'B'                  ; EOF packet.
        call    spack                   ; Send the packet.
         jmp    abort
        call    rpack                   ; Get a packet.
         jmp    tryagn          ; Trashed packet don't change state, retry.
        call    dodec                   ; Decode packet.
        call    acknak                  ; see if good ack or nak
        cmp     al,0                    ; ack'd ok?
        je      seot12                  ; yes, done with this
        cmp     al,1                    ; maybe a nak?
        jne     seot3                   ; no, check for error
        ret                             ; else just return
seot12: mov     pack.state,'C'          ; Set the state to file send.
        ret

seot3:  cmp     ah,'E'                  ; Is it an error packet.
        jne     seot4
        call    error
seot4:  jmp     abort
SEOT    ENDP

tryagn: call    updrtr
        ret

; newfn -- move replacement name from buffer sendas to buffer data

newfn:  cmp     difsiz,0                ; Sending file under different name?
        je      newf4                   ; e = no, so don't give new name
        test    flags.remflg,dquiet     ; quiet display mode?
        jnz     newfa                   ; nz = yes. Don't write to screen
        mov     ah,prstr
        mov     dx,offset asmsg ; display ' as '
        int     dos
        mov     ah,conout               ; use printable output
newfa:  mov     si,offset sendas        ; Buffer where the name is
        mov     di,offset data
        mov     cx,difsiz               ; Length of name.
        inc     cx                      ; plus null terminator
newf0:  lodsb                           ; Get a character into al.
        stosb
        test    flags.remflg,dquiet    ; quiet display mode (should we print)?
        jnz     newf2                   ; nz = yes
        mov     dl,al                   ; set into dl for display
        int     dos                     ; Print them.
newf2:  loop    newf0
        mov     cx,difsiz               ; Reset the length field.
newf4:  test    flags.remflg,dserial    ; serial display mode?
        jz      newf5                   ; z = no
        push    ax
        push    dx
        mov     dx,offset crlf          ; start with cr/lf for serial display
        mov     ah,prstr
        int     dos
        pop     dx
        pop     ax
newf5:  ret

; Do encoding.  Expect CX to be the data size.

doenc:  jcxz    doen0
        mov     chrcnt,cx               ; Number of bytes of source data
        mov     bufpnt,offset data      ; Source of data.
        mov     bx,offset nulref        ; Null routine for refilling buffer.
        mov     ah,rptq
        mov     origr,ah                ; Save repeat prefix here.
        mov     rptct,1                 ; Number of times char is repeated.
        mov     rptval,0                ; Value of repeated char.
        call    encode                  ; Make a packet with size in AX.
         nop
         nop
         nop
        mov     pack.argbk1,ax          ; Store length of data field
        mov     cx,ax
        call    movpak                  ; Move to data part of packet.
        cmp     chrcnt,0                ; Did all chars fit into the buffer?
        jne     doen1                   ; ne = no, we have an error condition
        clc                             ; clear c bit for success
doen0:  ret
doen1:  stc                             ; set c bit for did not fit condition
        ret
; CX is set before this is called.
movpak: push    es
        mov     ax,ds
        mov     es,ax
        cld
        mov     si,offset filbuf        ; Move from here
        mov     di,offset data          ; to here
        cmp     cx,0
        jle     movpak1
        repne   movsb
movpak1:pop     es
        ret

; Dodecoding.
dodec:
        mov     cx,pack.argbk1          ; Size of data.
        jcxz    dodc0                   ; z = nothing to transfer
        push    ax                      ; Save packet size.
        mov     bx,offset data          ; Address of data.
        mov     ax,offset nulr          ; Routine to dump buffer (null)
        mov     bufpnt,offset decbuf    ; Where to put output.
        mov     chrcnt,maxpack          ; Buffer size
        call    decode
         nop
         nop
         nop
        call    decmov          ; Move decoded data back to "data" buffer.
        pop     ax
dodc0:  ret

; Move decoded data from decode buffer back to "data".
decmov: push    si
        push    di
        push    es
        mov     ax,ds
        mov     es,ax
        cld
        mov     cx,bufpnt               ; Last char we added.
        sub     cx,offset decbuf        ; Get actual number of characters.
        mov     pack.argbk1,cx          ; Remember size of real data.
        mov     si,offset decbuf        ; Data is here
        mov     di,offset data          ; Move to here
        cmp     cx,0
        jle     decmov1                 ; le = none to do
        repne   movsb                   ; Copy the data.
decmov1:mov     al,0                    ; Null to end the string.
        stosb
        pop     es
        pop     di
        pop     si
        ret

;       Abort

ABORT   PROC    NEAR
        mov     difsiz,0                ; clear original filename
        mov     byte ptr sendas,0       ; clear sendas name
        cmp     filopn,0                ; Any disk files open?
        je      abort0                  ; No so don't do a close.
        mov     ah,close2               ; DOS 2.0 close file
        push    bx
        mov     bx,diskio.handle        ; file handle
        int     dos
        pop     bx
        mov     filopn,0                ; say file is closed now
abort0: mov     pack.state,'A'          ; Otherwise abort.
        or      errlev,1                ; set DOS error level
        ret
ABORT   ENDP

; This is where we go if we get an error packet.  A call to ERROR
; positions the cursor and prints the message.  A call to ERROR1
; just prints a CRLF and then the message.

ERROR   PROC    NEAR
        mov     pack.state,'A'          ; Set the state to abort.
        test    flags.remflg,dquiet     ; quiet display mode?
        jnz     errorx                  ; nz = yes. Don't write to screen
        call    erpos                   ; Position the cursor.
        jmp     error2
ERROR1: mov     ah,prstr                ; entry point for Server Generic cmds.
        mov     dx,offset crlf
        int     dos
error2: push    bx
        mov     bx,pack.argbk1          ; Get the length of the data.
        add     bx,offset data          ; Get to the end of the string.
        mov     ah,'$'                  ; Put a dollar sign at the end.
        mov     [bx],ah
        mov     ah,prstr                ; Print the error message.
        mov     dx,offset data
        int     dos
        pop     bx
errorx: ret
ERROR   ENDP

; Set the maximum send data packet size; modified for long packets.
PACKLEN PROC    NEAR
        push    ax
        push    cx
        xor     ah,ah
        mov     al,trans.spsiz  ; Maximum send packet size for Regular pkts.
        cmp     ax,trans.slongp         ; negotiated long packet max size
        jae     pack2                   ; ae = use regular packets
        mov     ax,trans.slongp         ; else use long kind
        sub     ax,3                    ; minus extended count & checksum
        cmp     ax,(95*94-1-2)          ; longer than Long?
        jle     pack2                   ; le = no, Long will do
        dec     ax                      ; minus one more for extra long count
pack2:  sub     ax,2                    ; minus Sequence, Type
        sub     al,trans.chklen         ; And minus Checksum chars.
        sbb     ah,0                    ; borrow propagate
        cmp     trans.ebquot,'N'        ; Doing 8-bit Quoting?
        je      pack0                   ; Nope so we've got our size.
        cmp     trans.ebquot,'Y'
        je      pack0                   ; Not doing it in this case either.
        dec     ax                      ; Another 1 for 8th-bit Quoting.
pack0:  cmp     rptq,0                  ; Doing repeat character Quoting?
        je      pack1                   ; Nope, so that's all for now.
        dec     ax                      ; minus repeat prefix
        dec     ax                      ;  and repeat count
pack1:  dec     ax                  ; for last char might being a control code
        mov     trans.maxdat,ax         ; Save max length for data field.
        pop     cx
        pop     ax
        ret
PACKLEN ENDP

 ; Print the number in AX on the screen in decimal rather that hex.

NOUT    PROC    NEAR
        test    flags.remflg,dserial    ; serial display mode?
        jnz     pnout               ; nz = use "dot and plus" for serial mode
        test    flags.remflg,dquiet     ; quiet display mode?
        jnz     nout1                   ; nz = yes. Don't write to screen
        call    decout                  ; call standard decimal output routine
nout1:  ret


pnout:  or      ax,ax                   ; display packet in serial display mode
        jz      pnoutx                  ; z = nothing to display
        push    ax                      ; for serial mode display
        push    dx                      ; output .........+.........+  etc
        mov     temp,10
        mov     dx,0
        div     temp                    ; number/10. (AX=quo, DX=rem)
        push    ax                      ; save around printing
        push    dx                      ; save around initial printing
        cmp     dx,0                    ; remainder non-zero?
        jne     pnout1                  ; ne = yes
        mov     dl,'+'                  ; symbol plus for tens
        jmp     pnout2                  ; display it
pnout1: mov     dl,'.'                  ; symbol for between tens
pnout2: mov     ah,conout               ; output to console
        int     dos
        pop     dx                      ; recover remainder
        pop     ax                      ; recover quotient
        or      dx,dx           ; check for multiples of 70, to break lines
        jnz     pnout3                  ; nz = non-zero remainder, just exit
        mov     temp,7          ; divide ax by 7 (dx is zero by construction)
        div     temp                    ; ax = quotient, dx = remainder
        or      dx,dx                   ; zero remainder?
        jnz     pnout3                  ; nz = non-zero remainder, just exit
        mov     ah,prstr                ; output cr/lf after every 70th chars
        mov     dx,offset crlf
        int     dos
pnout3: pop     dx
        pop     ax
pnoutx: ret
NOUT    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
