NAME ccssen ; File CCSSEN.ASM ;CHINESE ifdef MSDOS include msssen.dat else include ccssen.dat endif 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, prtasz:near extrn erpos:near, rtpos:near, cxmsg:near, stpos:near, decout:near extrn encode:near, nulref:near, decode:near, nulr:near, lnout:near extrn errpack:near, updrtr:near, clrmod:near, prompt:near extrn prtfn:near, strcpy:near, strlen:near, strcat: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 ; Lines marked ;M energize the second CAPAS byte. Leave them as comments for ; because earlier versions of MS Kermit (and C Kermit) are confused by it ; (fail to decode bit saying second CAPAS byte follows and thus loose sync). 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,'0' ; 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,2 ; CAPAS, bit1 = can do long packets cmp flags.attflg,0 ; allowing attributes packets? je rpar1 ; e = no or ah,8 ; bit #3, can do file attributes rpar1: ;M or ah,1 ; say second CAPAS byte follows add ah,20h ; apply tochar() to byte mov 9[bx],ah ; add to packet ; additional CAPAS go in here mov byte ptr 10[bx],20h+20h ; Allow M (message) pkts, (#6, bit5) mov ah,20h ; WINDO field, null applied through tochar() ;M mov 11[bx],ah ; put into packet 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 ;M mov 12[bx],al ; add to packet mov 11[bx],al ; add to packet add dl,20h ; apply tochar() to remainder ;M mov 13[bx],dl ; add to packet mov 12[bx],dl ; add to packet pop dx ; restore regs pop ax ;M mov ah,14 ; 14 bytes of data 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 mov ah,al ; number of args is now in ah push si mov si,bx cld cmp ah,0 ; any data? jg spara ; g = yes, want more than bare minimum mov al,dspsiz ; nothing supplied by host use default jmp short sparc spara: lodsb ; get the max packet size dec ah ; ah = bytes remaining to be examined sub al,' ' ; subtract a space cmp al,spmin ; below the minimum? jge sparb ; ge = no mov al,spmin jmp short sparc sparb: cmp al,spmax ; or above the maximum? jle sparc ; le = no mov al,spmax sparc: mov trans.spsiz,al ; save it mov al,dtrans.stime ; pick up default stime cmp ah,0 ; more data? jle spar02 ; le = no, use default lodsb ; get the timeout value dec ah sub al,' ' ; subtract a space cmp dtrans.stime,dstime ; Is current value the default? je spar0 ; e = yes, else user value overrides mov al,dtrans.stime ; pick up user selected stime spar0: cmp al,0 jg spar01 ; must be non-negative mov al,0 ; negative, so use zero spar01: cmp al,trans.rtime ; same as other side's timeout jne spar02 ; ne = no add al,1 ; yes, but make it a little different spar02: mov trans.stime,al ; save it mov al,dtrans.spad ; get default send padding cmp ah,0 ; more data? jle spar11 ; le = no, use default spar1: lodsb ; get the number of padding chars dec ah sub al,' ' cmp al,0 jge spar11 ; must be non-negative mov al,0 spar11: mov trans.spad,al mov al,dtrans.spadch ; pick up default send pad character cmp ah,0 ; more data? jle spar21 ; le = no, use default spar2: lodsb ; get the padding char dec ah add al,40h ; remove ascii bias and al,7FH cmp al,del ; Delete? je spar21 ; e = yes, then it's OK cmp al,31 ; control char? jle spar21 ; le = yes, then OK mov al,0 ; no, use null spar21: mov trans.spadch,al mov al,dtrans.seol ; get default send eol char cmp ah,0 ; more data? jle spar31 ; le = no, use default spar3: lodsb ; get the EOL char dec ah sub al,' ' cmp al,31 ; control char? jle spar31 ; le = yes, then use it mov al,cr ; else use the default spar31: mov trans.seol,al mov al,dtrans.squote ; send quote cmp ah,0 ; more data? jle spar41 ; le = no, use default spar4: lodsb ; get the quote char dec ah cmp al,' ' ; less than a space? jge spar40 ; ge = no mov al,dsquot ; yes, use default jmp spar41 spar40: cmp al,7eh ; must also be less than a tilde jbe spar41 ; be = is ok mov al,dsquot ; else use default spar41: mov trans.squote,al cmp ah,0 ; more data? jg spar5 ; g = yes mov al,dtrans.ebquot ; use default mov trans.ebquot,al jmp short spar51 spar5: lodsb ; get other side's 8-bit quote request dec ah call doquo ; and set quote char spar51: cmp ah,0 ; more data? jg spar6 ; g = yes mov trans.chklen,1 ; use default jmp short spar61 spar6: mov al,inichk mov trans.chklen,al ;checksum length we really want to use lodsb ; get other side's checksum length dec ah call dochk ; determine what size to use spar61: cmp ah,0 ; more data? jg spar7 ; g = yes mov rptq,0 jmp short spar71 spar7: lodsb ; get other side's repeat count prefix dec ah mov ch,drpt ; default repeat count prefix mov rptq,0 ; clear active repeat count prefix call dorpt ; negotiate new prefix spar71: mov al,0 ; get default operating Capabilities cmp ah,0 ; more data? jle spar81 ; le = no, use default lodsb ; get capas bitmap from other side dec ah and al,not (1) ; remove least significant bit sub al,20h ; apply unchar() spar81: mov trans.capas,al ; store result in active byte spar82: cmp ah,0 ; more data? jle spar85 ; le = no test byte ptr [si-1],1 ; is CAPAS byte continued to next? jz spar85 ; z = no lodsb ; get 2nd CAPAS bitmap from other side dec ah and al,not (1) ; remove least significant bit sub al,20h ; apply unchar(). Store nothing jmp short spar82 ; seek more CAPAS bytes spar85: mov al,0 ; setup default window size cmp ah,0 ; more data? jle spar9 ; le = no, use default lodsb ; get other side's window size dec ah sub al,20h ; apply unchar() call dewind ; negotiate window size back into al spar9: mov trans.windo,al ; 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 ah,2 ; more data (long packet needs two)? jae spar9d ; ae = more to look at mov ax,trans.slongp ; put above size in ax for final checks 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 lodsb ; long pkt length, high order byte sub al,20h ; apply unchar() xor ah,ah mul ninefive ; times 95 to dx(hi),ax(lo) mov trans.slongp,ax ; store that much lodsb ; long pkt length, low order byte sub al,20h ; apply unchar() xor ah,ah 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: mov ax,temp4 ; recover number of pieces of data sparx: pop si ret SPAR ENDP ; Set 8-bit quote character based on my capabilities and the other ; Kermit's request DOQUO PROC NEAR cmp dtrans.ebquot,'N' ; Can I do 8-bit quoting at all? je dq3 ; No - so forget it cmp dtrans.ebquot,'Y' ; Can I do it if requested? jne dq0 ; No - it's a must that I do it mov trans.ebquot,al ; Do whatever he wants jmp dq1 dq0: cmp al,'Y' ; I need quoting - can he do it? je dq1 ; Yes - then all is settled cmp al,'N' ; No - then don't quote je dq3 cmp al,trans.ebquot ; Both need quoting - chars must match jne dq3 dq1: mov al,trans.ebquot cmp al,'Y' ; If Y or N, don't validate prefix je dq2 cmp al,'N' je dq2 call prechk ; Is it in range 33-62, 96-126? jnc dq4 ; nc = in range mov al,'Y' ; don't do quoting dq4: cmp al,trans.rquote ; Same prefix? je dq3 ; Not allowed, so don't do quoting. cmp al,trans.squote ; Same prefix here? je dq3 ; This is illegal too mov trans.ebquot,al ; Remember what we decided on dq2: ret dq3: mov trans.ebquot,'N' ; Quoting will not be done ret DOQUO ENDP ; Check if prefix in AL is in the proper range: 33-62, 96-126. ; Return carry clear if in range, else return carry set. prechk: cmp al,33 jb prechk2 ; b = out of range cmp al,62 jbe prechk1 ; be = in range 33-62 cmp al,96 jb prechk2 ; b = out of range cmp al,126 ja prechk2 ; a = out of range 96-126 prechk1:clc ; carry clear for in range ret prechk2:stc ; carry set for out of range ret ; Set checksum length. dochk: cmp al,'1' ; Must be '1', '2', or '3' jb doc1 ; b = not '1' to '3' cmp al,'3' jbe doc2 ; be = ok doc1: mov al,'1' ; else use default of '1' doc2: sub al,'0' ; remove ascii bias mov trans.chklen,al ; other side's request is do-able here cmp al,trans.chklen ; Do we want the same thing? je dochk0 ; e = yes, then we're done mov trans.chklen,1 ; No, use single character checksum dochk0: ret ; 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? jnc dorpt1 ; nc = in range mov al,0 ; don't use their value dorpt1: cmp al,trans.squote ; Same as the control quote char? je dorpt2 ; Yes, that's illegal, no repeats cmp al,trans.rquote ; How about this one? je dorpt2 ; No good cmp al,trans.ebquot ; Same as eight bit quote char? je dorpt2 ; Yes, that's illegal too, no repeats cmp al,ch ; Are we planning to use same char? jne dorpt2 ; No, that's no good either mov rptq,ch ; Use repeat quote char now dorpt2: ret ; negotiate window size in al dewind: xor al,al ; no windowing at our end ret ; Send command ; MAIL filspec user@node command SEND PROC NEAR mov mailflg,0 ; send, not mail mov temp,0 jmp short sendm0 MAIL: mov mailflg,1 ; set flag for mail command vs send mov temp,1 ; temp copy of mailflag sendm0: mov difsiz,0 ; Assume we'll use original filename mov byte ptr sendas,0 ; clear sendas name (in case none) mov dx,offset diskio.string ; address of filename string ; mov bx,offset filmsg ; Text of help message mcmsgb filmsg, cfilmsg cmp mailflg,0 ; Mail command? je sendm1 ; e = no mov mailflg,0 ; clear in case error exit ; mov bx,offset mailhlp ; Text of help message mcmsgb mailhlp, cmailhlp sendm1: mov ah,cmfile ; get an input file spec call comnd ret ; Give up on bad parse nop nop cmp flags.cxzflg,0 ; ^X, ^Z, ^C typed? je send0 ; e = no, continue or errlev,1 ; say send failed or fsta.xstatus,1+80h ; set status, failed + intervention mov kstatus,1+80h ; global status jmp rskp ; yes, quit send0: cmp ah,0 ; any text given? je send0d ; e = no, prompt cmp temp,0 ; Mail command? je send00 jmp send0c ; ne = yes, require address send00: mov bx,offset sendas ; See if want to send file under dif name ; mov dx,offset filhlp ; In case user needs help mcmsg filhlp, cfilhlp mov ah,cmtxt ; allow embedded white space call comnd ret nop nop jmp sendm3a ; join common completion code send0d: ; mov dx,offset lclfnm ; prompt for local filename mcmsg lclfnm, clclfnm call prompt mov dx,offset diskio.string ; reload destination of user's text ; mov bx,offset filhlp ; help file mcmsgb filhlp, cfilhlp mov ah,cmfile ; allow paths call comnd ; try again for a local filename ret nop nop mov temp4,ax mov ah,cmcfm call comnd ret nop nop mov ax,temp4 cmp flags.cxzflg,0 ; ^X, ^Z, ^C typed? je send0a ; e = no, continue or errlev,1 ; say send failed or fsta.xstatus,1+80h ; set status, failed + intervention mov kstatus,1+80h ; global status mov mailflg,0 jmp rskp ; yes, quit send0a: cmp ah,0 ; user's byte count je send0d ; e = nothing was typed, get some send0b: ; mov dx,offset remfnm ; ask for remote name first mcmsg remfnm, cremfnm cmp temp,0 ; Mail command? je sendm2 ; e = no ; mov dx,offset mailto ; ask for name@host mcmsg mailto, cmailto sendm2: 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 mcmsg filhlp, cfilhlp cmp temp,0 ; Mail command? je sendm3 ; e = no ; mov dx,offset mailtohlp ; In case user needs help mcmsg mailtohlp, cmailtohlp sendm3: mov ah,cmtxt ; allow embedded white space call comnd ret nop nop cmp ah,0 ; text entered? je send0b ; e = no, get some sendm3a:cmp flags.cxzflg,0 ; ^X, ^Z, ^C typed? je send1 ; e = no, continue or errlev,1 ; say send failed or fsta.xstatus,1+80h ; set status, failed + intervention mov kstatus,1+80h ; global status mov mailflg,0 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 ax,temp ; get temp mailflag mov mailflg,al ; store in secure area for later 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 short 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 kstatus,0 ; global status, success 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 mcmsgb remmsg1, cremmsg1 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' mcmsg erms15, cerms15 int dos or errlev,1 ; set DOS error level or fsta.xstatus,1 ; set status mov kstatus,1 ; global status mov ax,1 ; tell statistics this was a send operation call endtim ; stop statistics counter mov mailflg,0 ; clear Mail flag jmp rskp ; pretend successful completion send16: call serini ; Initialize serial port jnc send17 ; nc = success or errlev,1 ; say send failed or fsta.xstatus,1 ; set status mov kstatus,1 ; global status test flags.remflg,dquiet ; quiet display mode? jnz send16a ; nz = yes. Don't write to screen ; mov dx,offset erms14 mcmsg erms14, cerms14 mov ah,prstr int dos ; Print an error message send16a:ret ; return failure send17: 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 mcmsg infms2, cinfms2 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 send3a ; ne = no call sfile ; Call send file jmp send2 send3a: cmp pack.state,'a' ; are we in send attributes state? jne send3b ; ne = no call sattr ; call send attributes jmp send2 send3b: cmp pack.state,'d' ; are we in initialize send data state jne send4 ; ne = no call sdatini ; do setup for file reading 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 ;;;; call serrst ; Reset serial port pop ax mov mailflg,0 ; clear Mail flag ; mov dx,offset infms3 ; Completed message mcmsg infms3, cinfms3 cmp pack.state,'C' ; Are we in the send complete state? je send8 ; e = yes, else failure ; mov dx,offset infms4 ; Failed message mcmsg infms4, cinfms4 or errlev,1 ; say send failed or fsta.xstatus,1 ; set status mov kstatus,1 ; global status send8: cmp flags.cxzflg,0 ; completed normally? je send8b ; e = yes, don't bother with this or errlev,1 ; say send failed or fsta.xstatus,1+80h ; set status, failed + intervention mov kstatus,1+80h ; global status send8b: mov ax,1 ; tell statistics this was a send operation call endtim ; stop statistics counter 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 mcmsg infms6, cinfms6 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 mcmsg erms14, cerms14 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 mcmsgb erms20, cerms20 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 mov ah,dtrans.seol ; restore default end-of-line char mov trans.seol,ah mov ah,dtrans.ebquot ; our default 8-bit quoting mov trans.ebquot,ah ; active 8-bit quoting call rpar ; Set up the parameter information xchg ah,al mov ah,0 mov pack.datlen,ax ; Save the number of arguments mov ax,pack.numpkt ; Get the packet number mov pack.seqnum,ax 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 sndpak ; send the packet call rpack ; Get a packet jmp sini23 ; Trashed packet don't change state, retry nop 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.datlen 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 cmp mailflg,0 ; non-zero to do Mail command je sini24 ; e = send, not mail command cmp flags.attflg,0 ; allowed to do file attributes? je sinit6 ; e = no, so no Mail test trans.capas,8 ; can they do file attributes? jz sinit6 ; z = no, so cannot do Mail sini24: 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 jmp updrtr ; Update retry counter and return sinit4: cmp ah,'M' ; Message packet? jne sinit4e ; ne = no call dodec ; decode it jmp error1 ; display it and return sinit4e:cmp ah,'E' ; Is it an error packet jne sinit5 call error sinit5: jmp abort ; say Mail not supported by host sinit6: test flags.remflg,dquiet ; quiet display mode? jnz sinit7 ; nz = yes. Don't write to screen call erpos ; mov dx,offset erms25 mcmsg erms25, cerms25 mov ah,prstr int dos ; Print an error message sinit7: mov pack.state,'B' ; go to EOT state ret 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 mcmsg erms14, cerms14 mov ah,prstr int dos ; Print an error message sfile0: ; mov bx,offset erms21 mcmsgb erms21, cerms21 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.seqnum,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 sndpak ; send the packet call rcvpak ; Get a packet 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: call fackmsg ; get/show any embedded message mov pack.state,'a' ; set file attributes as next state ret sfile3: cmp ah,'M' ; Message packet? jne sfile4 ; ne = no call dodec ; decode it jmp error1 ; display it and return sfile4: cmp ah,'E' ; Is it an error packet jne sfile4 ; ne = no call dodec ; Do all decoding call error sfile5: jmp abort SFILE ENDP ; Send file attributes. Attributes: file size in bytes and kilobytes, ; file time and date, machine identification. [jrd] SATTR PROC NEAR cmp flags.attflg,0 ; allowed to do file attributes? je satt0 ; e = no test trans.capas,8 ; can we do file attributes? jnz satt1 ; nz = yes satt0: mov pack.state,'d' ; set the state to initiate send-data ret satt1: push es ; save es around this work push ds pop es ; set es to datas segment for es:di cld mov data,'1' ; File length (Bytes) specifier mov dx,diskio.sizehi ; high word of length mov ax,diskio.sizelo ; low word of length mov di,offset data+2 ; where to store data (for lnout) call lnout ; convert file length, write to [di++] mov cx,di ; compute field length sub cx,offset data+2 add cl,32 ; field length to ascii mov data+1,cl ; length. Done with File Size ; Kilobyte attribute mov byte ptr[di],'!' ; File length (Kilobytes) specifier inc di mov temp4,di ; remember place for count field inc di ; data field mov dx,diskio.sizehi ; high word of length, from file open mov ax,diskio.sizelo ; low word of length add ax,1023 ; add 1023 to round up adc dx,0 mov al,ah ; do divide by 1024 bytes mov ah,dl mov dl,dh ; divide by 256 part mov dh,0 ror dl,1 ; low bit to carry flag rcr ax,1 ; divide by two, with carry in clc ror dl,1 ; low bit to carry flag rcr ax,1 ; divide by two, with carry in and dl,3fh ; keep low six bits call lnout ; convert file length mov cx,di ; compute field length sub cx,temp4 ; count field location add cl,32-1 ; field length to ascii push di mov di,temp4 ; point at count field mov byte ptr[di],cl ; store field length pop di ; Done with Kilobyte attribute ; File date and time: mov al,'#' ; creation date/time specifier stosb ; and point at field length mov al,17+32 ; length of date/time field, to ascii stosb mov ah,0 mov al,diskio.dta+25 ; yyyyyyym from DOS via file open shr al,1 ; get year add ax,1980 ; add bias mov dx,0 call lnout ; put year (1988) in buffer mov ax,word ptr diskio.dta+24 ; yyyyyyyym mmmddddd year+month+day shr ax,1 ; month to al mov ah,0 mov cl,4 shr al,cl ; month to low nibble mov byte ptr[di],'0' ; leading digit inc di cmp al,9 ; more than one digit? jbe satt2 ; be = no mov byte ptr[di-1],'1' ; new leading digit sub al,10 ; get remainder satt2: add al,'0' ; to ascii stosb ; end of month mov al,diskio.dta+24 ; get day of month and al,1fh ; select day bits mov ah,0 mov cl,10 div cl ; quot = al, rem = ah add ax,'00' ; add ascii bias stosw ; leading digit and end of date mov al,' ' ; space separator stosb mov al,diskio.dta+23 ; hours hhhhhmmm mov cl,3 shr al,cl ; move to low nibble mov ah,0 mov cl,10 div cl ; quot = al, rem = ah add ax,'00' ; add ascii bias stosw ; store hours mov al,':' ; separator stosb mov ax,word ptr diskio.dta+22 ; get minutes: hhhhhmmm mmmsssss mov cl,5 shr ax,cl ; minutes to low byte and al,3fh ; six bits for minutes mov ah,0 mov cl,10 div cl add ax,'00' ; add ascii bias stosw mov al,':' ; separator stosb mov al,byte ptr diskio.dta+22 ; get seconds (double secs really) and al,1fh shl al,1 ; DOS counts by two sec increments mov ah,0 mov cl,10 div cl add ax,'00' ; add ascii bias stosw mov ax,'".' ; machine indicator(.), 2 data bytes stosw mov ax,'8U' ; U8 = Portable O/S, MSDOS stosw pop es ; recover es register cmp mailflg,0 ; Mailing? je satt3 ; e = no mov byte ptr [di],'+' ; Mail specification inc di mov si,offset sendas ; user@host field mov dx,si call strlen ; get length into cl push cx ; save address length inc cl ; include M for disposition = mail add cl,' ' ; add ascii bias mov [di],cl ; store in length field inc di mov byte ptr [di],'M' ; mail the file inc di pop cx ; recover address length jcxz satt3 ; z = empty field push es push ds pop es ; use es:di pointing to datas segment cld rep movsb ; append address text to field pop es satt3: sub di,offset data ; get length of data mov pack.datlen,di ; data length for packet call pktsize ; report packet size mov ax,pack.pktnum ; get the packet number mov pack.seqnum,ax mov ah,'A' ; Attributes packet call sndpak ; send the packet call rcvpak ; get response call acknak ; see what they had to say cmp al,0 ; ack'd ok? je satt5 ; e = yes, on to next state cmp al,1 ; maybe a nak? jne satt6 ; ne = no, check for error ret ; if nak, just return and try again satt5: cmp pack.datlen,0 ; any data in the ACK? je satt5d ; e = no cmp data,'N' ; are they refusing this file? jne satt5d ; ne = no test flags.remflg,dquiet ; quiet display mode? jnz satt5a ; nz = yes. Don't write to screen call erpos ; Position the cursor mov ah,prstr ; mov dx,offset erms26 ; say host rejected the file mcmsg erms26, cerms26 int dos satt5a: mov pack.state,'Z' ; send EOF with Discard mov flags.cxzflg,'X' ; simulate Control-X to discard or errlev,1 ; say send failed or fsta.xstatus,1+80h ; set status, failed + intervention mov kstatus,1+80h ; global status ret satt5d: mov pack.state,'d' ; next state is initiate send-data ret satt6: cmp ah,'E' ; Is it an error packet jne satt7 ; ne = no call dodec ; Do all decoding call error satt7: jmp abort SATTR ENDP ; Send data ; ; set up initial data buffer from file, 'd' state sdatini proc near mov flags.filflg,0FFH ; Indicate file buffer is empty mov pack.state,'D' jmp sdat23 ; read first buffer from file sdatini endp ; Send main body of file, 'D' state 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 mcmsg erms14, cerms14 mov ah,prstr int dos ; Print an error message sdat2a: ; mov bx,offset erms22 mcmsgb erms22, cerms22 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 pack.datlen,cx ; length of data in packet call movpak ; from filbuf to buffer data mov ax,pack.pktnum ; Get the packet number mov pack.seqnum,ax ; store in packet call pktsize ; report packet size mov ah,'D' ; Data packet call sndpak ; send the packet call rcvpak ; Get a packet call acknak ; see if ack or nak, check packet number cmp al,0 ; 0 => ack ok, go on je sdat11 ; ack, check for data in response cmp al,1 ; 1 => nak, retry count incremented, try again jne sdat15 ; else look for other packet types ret ; else return sdat11: cmp pack.datlen,0 ; any data in ACK response? je sdat23 ; e = no call dackmsg ; get/show any embedded message 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 ; fill buffer from file 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 ; e = yes jmp abort ; If not give up sdat13: mov pack.state,'Z' ; Set the state to EOF ret sdat15: cmp ah,'M' ; Message packet? jne sdat16 ; ne = no call dodec ; decode it jmp error1 ; display it and return sdat16: cmp ah,'E' ; Is it an error packet jne sdat17 call dodec ; Do all decoding call error ; display and change state to Abort sdat17: jmp abort SDATA 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 mcmsg erms14, cerms14 mov ah,prstr int dos ; Print an error message seof0: ; mov bx,offset erms23 mcmsgb erms23, cerms23 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.seqnum,ax mov pack.datlen,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.datlen,1 ; Set data size to 1 or errlev,1 ; say send failed or fsta.xstatus,1+80h ; set status, failed + intervention mov kstatus,1+80h ; global status seof11: mov cx,pack.datlen ; Put size in CX call doenc ; Encode the packet call pktsize ; report packet size mov ah,'Z' ; EOF packet call sndpak ; send the packet call rcvpak ; Get a packet call acknak ; see what they had to say cmp al,0 ; ack? je seof12 ; e = 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: call dackmsg ; get/show any embedded message mov ah,close2 ; DOS 2.0 close file push bx mov bx,diskio.handle ; file handle int dos pop bx call GTNFIL ; Get the next file jmp seof13 ; No more nop ; make three bytes 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 or errlev,1 ; say send failed or fsta.xstatus,1+80h ; set status, failed + intervention mov kstatus,1+80h ; global status 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 dodec ; Decode packet 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 mcmsg erms14, cerms14 mov ah,prstr int dos ; Print an error message seot0: ; mov bx,offset erms24 mcmsgb erms24, cerms24 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.seqnum,ax mov pack.datlen,0 ; No data call pktsize ; report packet size mov ah,'B' ; End of Session packet call sndpak ; send the packet call rcvpak ; Get a packet call acknak ; see if good ack or nak cmp al,0 ; ack'd ok? je seot12 ; e = yes, done with this cmp al,1 ; maybe a nak? jne seot3 ; ne = no, check for error ret ; else just return seot12: call fackmsg ; get/show any embedded message mov pack.state,'C' ; Set state to file completed ret seot3: cmp ah,'E' ; Is it an error packet jne seot4 call dodec ; Do all decoding call error seot4: jmp abort SEOT ENDP sndpak proc near ; send packet with retries call spack jmp updrtr nop ret sndpak endp rcvpak proc near ; receive packet with retries call rpack ; Get a packet jmp updrtr ; Trashed packet, retry nop ret rcvpak 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. ; A Timeout packet (type 'T') simply invokes a retry and a returned 1. ; Finally, 2 is returned if anything else is seen ; ACKNAK PROC NEAR cmp ah,'Y' ; ack packet? jne ackna1 ; ne = no, keep going mov bx,pack.pktnum cmp bx,pack.seqnum ; 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 cmp ah,'T' ; Timeout? je ackna3 ; e = yes, not a NAK but do a retry mov al,2 ret ; unknown packet type ackna5: mov bx,pack.pktnum inc bx and bx,3fh inc fsta.nakrcnt ; count received NAK for statistics cmp bx,pack.seqnum ; maybe a nak for pktnum+1? je ackna0 ; yes, treat as ack jne ackna3 ; nak or bad ack, update retry stuff ackna2: inc fsta.nakrcnt ; count received NAK for statistics ackna3: push ax call rtpos ; Position cursor inc pack.numrtr ; Increment the number of retries mov ax,pack.numrtr call nout ; Write the number of retries pop ax mov al,1 ; nak code ret ; and return ACKNAK ENDP ; Display message in ACK's to D packets. Requires a leading protocol char ; and expects message to be encoded. dackmsg proc near cmp pack.datlen,1 ; any embedded message? jbe dackmsgx ; be = no (skip single char msgs) test flags.remflg,dquiet ; quiet display mode? jnz dackmsgx ; nz = yes, don't write to screen push ax push dx call cxmsg ; clear message space in warning area call dodec ; decode message, including X/Z/other mov dx,offset data+1 ; point to asciiz message call prtasz ; display it pop dx pop ax dackmsgx:ret dackmsg endp ; Display messages in ACKs to F packets. Expects message to be not encoded. fackmsg proc near ; look for in ack cmp pack.datlen,0 ; any embedded message? je fackmsgx ; e = no test flags.remflg,dquiet ; quiet display mode? jnz fackmsgx ; nz = yes, don't write to screen push ax push dx call cxmsg ; clear message space in warning area call dodec;;;decode mov dx,offset data ; point to asciiz message call prtasz ; display it pop dx pop ax fackmsgx:ret fackmsg endp ; newfn -- move replacement name from buffer sendas to buffer data ; update cx to new filename length newfn: cmp difsiz,0 ; Sending file under different name? je newf4 ; e = no, so don't give new name mov si,offset sendas ; source field mov di,offset fsta.xname ; statistics name area call strcpy 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 ' mcmsg asmsg, casmsg cmp mailflg,0 ; mail? je newfn1 ; e = no mov dx,offset mailto ; display ' To: ' newfn1: int dos mov ah,conout ; use printable output cmp mailflg,0 ; mail? je newfa ; e = no mov dx,offset sendas ; get name call prtasz ; print asciiz string jmp newf4 ; don't replace filename 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 mov dx,offset crlf ; start with cr/lf for serial display mov ah,prstr int dos 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.datlen,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 shr cx,1 ; divide by two (words), set carry jnc movpak1 ; nc = even number of bytes movsb ; do the single move for odd count movpak1:cmp cx,0 jle movpak2 rep movsw movpak2:pop es ret ; Dodecoding dodec: 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 mov cx,pack.datlen ; Size of data jcxz dodc0 ; z = nothing to transfer call decode nop nop nop dodc0: call decmov ; Move decoded data back to "data" buffer pop ax 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.datlen,cx ; Remember size of real data mov si,offset decbuf ; Data is here mov di,offset data ; Move to here shr cx,1 ; divide by two (words), set carry jnc decmov1 ; nc = even number of bytes movsb ; do single move decmov1:cmp cx,0 jle decmov2 ; le = none to do rep movsw ; Copy the data decmov2: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 mov mailflg,0 ; clear Mail flag 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 or fsta.xstatus,1 ; set status mov kstatus,1 ; global status 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: mov dx,offset data ; error message string push bx mov bx,pack.datlen ; Get the length of the data add bx,dx ; Get to the end of the string mov byte ptr [bx],0 ; terminate string pop bx call prtasz ; print asciiz string 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 code ends end