/***************************************************************************
 *                                                                         *
 *   FTFZMOD.C                                                             *
 *                                                                         *
 *   Copyright (c) 1991-1997 Galacticomm, Inc.      All Rights Reserved.   *
 *                                                                         *
 *   File Transfer Software for ZMODEM                                     *
 *                                                                         *
 *   With all operating-system-specific functions removed (I/O, disk,      *
 *   memory, time), the pure file transfer algorithm can be isolated.      *
 *                                                                         *
 *   Some of the code in this file, and all of the ZMODEM concepts, were   *
 *   derived from code and documentation written by Chuck Forsberg,        *
 *   and downloaded from the Omen Technology BBS.                          *
 *                                                                         *
 *                                               - R. Stein  9/04/91       *
 *                                                                         *
 ***************************************************************************/

#include "gcomm.h"
#include "ftf.h"
#include "ftfzmod.h"

#define FILREV "$Revision: 6 $"

#ifdef GCDOS
#pragma inline
/* For accessing fields of zmrscb in asm code: */

#define zmrscbword(field) word ptr [di.(struct zmrdat)field]
#define zmrscbbyte(field) byte ptr [di.(struct zmrdat)field]
#endif

#define ZRISTRB "\43\0\0\0"           /* Big endian form                    */
#define ZRISTRL "\0\0\0\43"           /* Little endian form                 */

#undef  UPDC32            /* (recast from CRCTAB.H to avoid compiler error) */
#define UPDC32(b, c) (cr3tab[((SHORT)c ^ b) & 0xff] ^ ((c >> 8) & 0x00FFFFFFL))

/*--- ZMODEM Transmit substates ---*/
#define ZDINIT    1                                     /* start of session */
#define ZDRQINIT  2                 /* very beginning, need to send ZRQINIT */
#define ZDRINIT   3              /* waiting for ZRINIT header from receiver */
#define ZDRPOS    4         /* waiting for first ZRPOS header from receiver */
#define ZDGOING   5   /* transmiting, but with ear cocked for receiver msgs */
#define ZDEOF     6                               /* transmitted last block */
#define ZDNEXT    7                 /* transmitted ZEOF, waiting for ZRINIT */
#define ZDFIN     8               /* transmitted ZFIN, waiting or ZFIN back */
#define ZDCRCING  9       /* computing file CRC in response to ZCRC command */
#define ZDEND     10     /* the end of a successful ZMODEM transmit session */
#define ZDFROWN   11      /* wait for goofy Telix ZFIN's to stop after xmit */

/*--- ZMODEM Receive substates ---*/
#define ZUINIT    21
#define ZURINIT   22                /* Begin receiving file(s), send ZRINIT */
#define ZUFILE    23            /* Sent ZRINIT, waiting for ZFILE (or ZFIN) */
#define ZUSINIT   24               /* Got ZSINIT header, waiting for packet */
#define ZUGOING   25            /* Got ZDATA header, receiving data packets */
#define ZULOGGED  26                           /* Got ZEOF, logging in file */

#define ZUOOUT1   31     /* (THESE MUST BE              Waiting for 1st 'O' */
#define ZUOOUT2   32     /* THE HIGHEST NUMBERED        Waiting for 2nd 'O' */
#define ZUEND     33     /* OF ALL THE STATES!)              Done receiving */


/* ZMODEM constants (from Chuck Forsberg / Omen Technology's ZMODEM.H) */
/*---------------------------------------------------------------------*/
#define ZGARBAGE 0xFF   /* header had bad CRC (or ZCRCW during file xmit) */
#define ZRQINIT 0       /* Request receive init */
#define ZRINIT  1       /* Receive init */
#define ZSINIT 2        /* Send init sequence (optional) */
#define ZACK 3          /* ACK to above */
#define ZFILE 4         /* File name from sender */
#define ZSKIP 5         /* To sender: skip this file */
#define ZNAK 6          /* Last packet was garbled */
#define ZABORT 7        /* Abort batch transfers */
#define ZFIN 8          /* Finish session */
#define ZRPOS 9         /* Resume data trans at this position */
#define ZDATA 10        /* Data packet(s) follow */
#define ZEOF 11         /* End of file */
#define ZFERR 12        /* Fatal Read or Write error Detected */
#define ZCRC 13         /* Request for file CRC and response */
#define ZCHALLENGE 14   /* Receiver's Challenge */
#define ZCOMPL 15       /* Request is complete */
#define ZCAN 16         /* Other end canned session with CAN*5 */
#define ZFREECNT 17     /* Request for free bytes on filesystem */
#define ZCOMMAND 18     /* Command from sending program */
#define ZSTDERR 19      /* Output to standard error, data follows */

#define XOFF '\x13'     /* ASCII XOFF character */
#define ZDLE '\x18'     /* ZMODEM data link escape character (aka ^X, CAN) */
#define ASMZDLE 018H    /* ZDLE in a form ASM statements can use */
/* other ZDLE sequences */
#define ZCRCE 'h'       /* CRC next, frame ends, header packet follows */
#define ZCRCG 'i'       /* CRC next, frame continues nonstop */
#define ZCRCQ 'j'       /* CRC next, frame continues, ZACK expected */
#define ZCRCW 'k'       /* CRC next, ZACK expected, end of frame */
#define ZRUB0 'l'       /* Translate to rubout 0177 */
#define ZRUB1 'm'       /* Translate to rubout 0377 */

#define ZF0     3       /* First flags byte */
#define ZF1     2
#define ZF2     1
#define ZF3     0

/* Bit Masks for ZRINIT flags byte ZF0 */
#define CANFC32 040     /* Receiver can use 32 bit Frame Check */
#define ESCCTL 0100     /* Receiver expects ctl chars to be escaped */

/* Parameters for ZSINIT frame */
/* Bit Masks for ZSINIT flags byte ZF0 */
#define TESCCTL 0100    /* Transmitter expects ctl chars to be escaped */

/* Parameters for ZFILE frame */
/* Conversion options one of these in ZF0 */
#define ZCBIN   1       /* Binary transfer - inhibit conversion */
#define ZCNL    2       /* Convert NL to local end of line convention */
#define ZCRESUM 3       /* Resume interrupted file transfer */
/* Management include options, one of these ored in ZF1 */
#define ZMSKNOLOC       0200    /* Skip file if not present at rx */
/* Management options, one of these ored in ZF1 */
#define ZMMASK  037     /* Mask for the choices below */
#define ZMNEWL  1       /* Transfer if source newer or longer */
#define ZMCRC   2       /* Transfer if different file CRC or length */
#define ZMAPND  3       /* Append contents to existing file (if any) */
#define ZMCLOB  4       /* Replace existing file */
#define ZMNEW   5       /* Transfer if source newer */
        /* Number 5 is alive ... */
#define ZMDIFF  6       /* Transfer if dates or lengths different */
#define ZMPROT  7       /* Protect destination file */
/* Transport options, one of these in ZF2 */
#define ZTLZW   1       /* Lempel-Ziv compression */
#define ZTCRYPT 2       /* Encryption */
#define ZTRLE   3       /* Run Length encoding */
/* Extended options for ZF3, bit encoded */
#define ZXSPARS 64      /* Encoding for sparse file operations */
/* ------------------------------ */
/* end of constants from ZMODEM.H */

#define ZBLOCK 1024               /* maximum nominal ZMODEM sub-packet size */
         /* IMPORTANT:  fbleng (size of ftfbuf[]) MUST BE AT LEAST ZBLOCK*2 */

#define ZBRECOVER 512           /* Block size after ZMODEM transmit recover */
 /* Note: Omen's DSZ seems to need 1st block 512 bytes to recover transmits */

#define ZBEOF  12     /* maximum size of xmit ZEOF header (type 'A' or 'C') */
#define ZCHUNK 1024     /* min number of bytes to receive & process at once */
                     /* also num of bytes chunked from file to compute ZCRC */
#define ZABWAIT  (2*16)       /* number of ticks to wait after ZMODEM abort */
#define ZURETRY (10*16)     /* # seconds before retry during ZMODEM receive */
#define ZOOWAIT (10*16)    /* number of seconds to wait for "OO" after ZFIN */
#define ZUEWAIT  (5*16)              /* patience for final output to finish */
#define ZDRQWAIT (5*16)             /* repeat ZRQINIT messages for transmit */
#define ZDFRETRY (1*16)   /* pause before responding to 2nd ZRINIT for xmit */
#define ZDFWAIT  (1*16)   /* wait for goofy Telix ZFIN's after "OO" on xmit */
#define ZDFINW   (7*16)                /* sender retries an unanswered ZFIN */
#define ZDEWAIT     (8)       /* wait for xmits to finish at end of session */

#define rawbuf (ftfbuf+ZBLOCK)              /* buffer for input to zsdata() */

/*--- ZMODEM header transmit variables and macros ---*/

static CHAR dummych;                  /* 1-char buffer for sendline() macro */
                              /* send a single character (no escape-coding) */
#define sendline(ch) (dummych=(ch),ftfout(&dummych,1))

                            /* zescape() - control character escaping macro */
       /* (note: no complex check for CR - '@' - CR:  all CR's are escaped) */
#define zescape(ch) ((ch&0x60) == 0 && (zctlesc || cetabl[ch&0x1F]))
static CHAR cetabl[32]={0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,              /* ^M */
                        1,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0};    /* ^P ^Q ^S ^X */
static CHAR zctlesc;                          /* global for zescape() macro */


/*--- ZMODEM header transmit utilities (used both for file xmt & file rcv) ---*/

static VOID
zsendline(                  /* send a zmodem character (with escape coding) */
register CHAR ch)
{
     if (zescape(ch)) {
          sendline(ZDLE);
          sendline(ch^0x40);
     }
     else {
          sendline(ch);
     }
}

static USHORT
upfcrc(                      /* function form of updcrc() macro in CRCTAB.H */
CHAR c,
USHORT crc)
{
     return(updcrc(c,crc));
}

static LONG
upfc32(                      /* function form of UPDC32() macro in CRCTAB.H */
CHAR c,
LONG crc)
{
     return(UPDC32(c,crc));
}

static VOID
zsbhdr(                                 /* Send ZMODEM binary-format header */
CHAR type,                                               /* Frame type code */
CHAR *hdr)                 /* ZF3,...,ZF0 or ZP0,...,ZP3 depending on frame */
{
     INT i;
     USHORT crc;
     ULONG lcrc;
     CHAR endbuf[4];

     if (type == ZEOF && isbigend()) {
       endbuf[0]=hdr[3];
       endbuf[1]=hdr[2];
       endbuf[2]=hdr[1];
       endbuf[3]=hdr[0];
       hdr=endbuf;
     }
     sendline('*');                                                    /* * */
     sendline(ZDLE);                                                /* ZDLE */
     zctlesc=(zmxscb->flags&ZCTLESC) != 0;
     if (zmxscb->flags&ZCRC32) {           /* binary header with 32-bit CRC */
          sendline('C');                                               /* C */
          lcrc=0xFFFFFFFFL;
          lcrc=upfc32(type,lcrc);
          zsendline(type);                                        /* <type> */
          for (i=0 ; i < 4 ; i++,hdr++) {
               lcrc=upfc32(*hdr,lcrc);
               zsendline(*hdr);                  /* <zf3> <zf2> <zf1> <zf0> */
          }
          lcrc=~lcrc;
          for (i=0 ; i < 4 ; i++) {
               zsendline((CHAR)lcrc);   /* <crc-lo> . . . <crc-hi> */
               lcrc>>=8;
          }
     }
     else {                                /* binary header with 16-bit CRC */
          sendline('A');                                               /* A */
          crc=upfcrc(type,0);
          zsendline(type);                                        /* <type> */
          for (i=0 ; i < 4 ; i++,hdr++) {
               crc=upfcrc(*hdr,crc);
               zsendline(*hdr);                  /* <zf3> <zf2> <zf1> <zf0> */
          }
          crc=upfcrc(0,upfcrc(0,crc));
          zsendline(crc>>8);                                   /* <crc-hi> */
          zsendline(crc);                                      /* <crc-lo> */
     }
}

static VOID
zshhdr(                                    /* Send ZMODEM HEX-format header */
CHAR type,                                               /* Frame type code */
CHAR *hdr)                 /* ZF3,...,ZF0 or ZP0,...,ZP3 depending on frame */
{
     CHAR rqbuff[40],*rp;
     INT i;
     USHORT crc;
     CHAR endbuf[4];

     if (isbigend()) {
          endbuf[0]=hdr[3];
          endbuf[1]=hdr[2];
          endbuf[2]=hdr[1];
          endbuf[3]=hdr[0];
          hdr=endbuf;
     }
     crc=upfcrc(type,0);
     sprintf(rqbuff,"**%cB%02x",ZDLE,type);            /* * * ZDLE B <type> */
     rp=rqbuff+strlen(rqbuff);
     for (i=0 ; i < 4 ; i++,hdr++,rp+=2) {
          crc=upfcrc(*hdr,crc);
          sprintf(rp,"%02x",*hdr);               /* <zf3> <zf2> <zf1> <zf0> */
     }
     crc=upfcrc(0,upfcrc(0,crc));
     sprintf(rp,"%02x%02x\r\n",(crc>>8)&0xFF,crc&0xFF);
                                             /* <crc-hi> <crc-lo> <CR> <LF> */
     if (type != ZFIN && type != ZACK) {
          strcat(rqbuff,"\021");                                   /* <XON> */
     }
     ftfout(rqbuff,strlen(rqbuff));
}


/*--- ZMODEM Receive File routines ---*/

static VOID
zmrini(VOID)                                  /* Initialize ZMODEM receiver */
{
     if (fbleng < max(ZBLOCK*2,ZCHUNK)) {
          fbleng=max(ZBLOCK*2,ZCHUNK);
     }
}

static VOID
zmrsrt(VOID)                                /* Begin ZMODEM receive session */
{
     setmem(zmrscb,sizeof(*zmrscb),0);
     ftfstf();
     zmrscb->numhdr=-2;               /* quiescent header-search state code */
     ftfnew(ZUINIT);
}

static VOID
gbgpkt(VOID)                                   /* respond to garbage packet */
{
     ftfscb->garbag++;
     ftfscb->stecnt++;
     if (!(ftfpsp->flags&FTFXMT)) {
          ftfout(zmrscb->attn,strlen(zmrscb->attn));
          zmrscb->numpkt=0;
     }
     zshhdr(ftfscb->state == ZUGOING ? ZRPOS : ZNAK,(CHAR *)&ftfscb->actbyt);
     zmrscb->pstate=0;
}

static VOID
svcpkt(             /* service a recieved sub-packet during ZMODEM transfer */
CHAR ptype)                           /* packet terminator:  ZCRCE,..,ZCRCW */
{
     CHAR *cp;
     INT doit;            /* should we transfer this file (versus skip it)? */
     INT isfile;           /* does this (DOS) file exist on the svr already */
     INT apnd,asci,resum;
     CHAR noack=0;     /* don't send ZACK if sending some other type of hdr */
     LONG newtim,newbyt;
     LONG oldtim,oldbyt;
     CHAR *skipreason;
     INT rc;

     ftfscb->stecnt=0;
     ftfnwp();
     switch(ftfscb->state) {
     case ZUSINIT:                 /* receiving ZSINIT sub-packet: attn seq */
          stzcpy(zmrscb->attn,zmrscb->packet,ZATTNLEN+1);
          ftfnew(ZUFILE);
          break;
     case ZUFILE:           /* receiving ZFILE sub-packet: file information */
          ftfcli();                            /* toss any piled-up ZFILE's */
          noack=1;
          movmem(zmrscb->packet,ftfscb->fname,8+1+3+1);
          ftfscb->fname[8+1+3]='\0';      /* (crude but secure string copy) */
          cp=zmrscb->packet+strlen(zmrscb->packet)+1;
          newtim=0L;
          newbyt=0L;
          if (*cp != '\0') {
               sscanf(cp,"%ld%lo",&newbyt,&newtim);
          }
          isfile=ftfrex();
          oldtim=ftfscb->unxtim;
          oldbyt=ftfscb->estbyt;
          switch(zmrscb->sopt1&ZMMASK) {
          case ZMNEW:                                      /* DSZ -n option */
               doit=!isfile || newtim > oldtim;
               skipreason="file up to date";
               break;
          case ZMNEWL:                                     /* DSZ -N option */
               doit=!isfile || newtim > oldtim || newbyt > oldbyt;
               skipreason="file up to date, and big enough";
               break;
          case ZMPROT:                                     /* DSZ -p option */
               doit=!isfile;
               skipreason="file exists already";
               break;
          case ZMCLOB:                                     /* DSZ -y option */
          case ZMAPND:                                     /* DSZ -+ option */
          default:                                         /* DSZ no option */
               doit=1;
               break;
          }
          if (zmrscb->sopt1&ZMSKNOLOC) {                   /* DSZ -Y option */
               if (doit && !isfile) {
                    doit=0;
                    skipreason="-Y option skipped because file DOESN'T exist already";
               }
          }
          if (doit) {
               asci=zmrscb->sopt0 == ZCNL;
               apnd=(zmrscb->sopt1&ZMMASK) == ZMAPND;
               resum=isfile == 2 && zmrscb->sopt0 == ZCRESUM;    /* resume? */
                                                      /* keep old contents! */
               ftfscb->unxtim=newtim;
               ftfscb->estbyt=newbyt;
               if (ftfrop(apnd,asci,resum) != 0) {
                    ftfabt(ftfscb->abwhy);
                    break;
               }
               ftfsrt();
               if (resum && !apnd) {                              /* resume */
                    ftfrsk(ftfscb->actbyt=(oldbyt&~(ZBRECOVER-1)));
               }
               else {
                    ftfscb->actbyt=0L;
               }
               zmrscb->uplcnt++;
               zshhdr(ZRPOS,(CHAR *)&ftfscb->actbyt);
               ftfnew(ZUGOING);
          }
          else {
               zshhdr(ZSKIP,"\0\0\0\0");
               ftfrsq(skipreason);
               ftfnew(ZUFILE);
          }
          zmrscb->fnucnt++;
          break;
     case ZUGOING:                               /* receiving ZDATA packets */
          if ((rc=ftfrwr(zmrscb->packet,zmrscb->numpkt)) != zmrscb->numpkt) {
               ftfabt(rc < 0 ? "Error writing file." : "Disk full.");
               noack=1;
          }
          else {                                        /* got that packet! */
               ftfscb->actbyt+=zmrscb->numpkt;
          }
          ftfnew(ZUGOING);
          ftfcbl();
          break;
     }
     if (!noack && (ptype == ZCRCW || ptype == ZCRCQ)) {
          zshhdr(ZACK,(CHAR *)&ftfscb->actbyt);             /* ACK expected */
     }
     if (ptype == ZCRCE || ptype == ZCRCW) {
          zmrscb->pstate=0;                        /* no sub-packet follows */
     }
     else {
          zmrscb->pstate=1;                    /* more sub-packet(s) follow */
          zmrscb->numpkt=0;
     }
}

static VOID
svctimeout(VOID)                          /* service ZMODEM receive timeout */
{
     switch(ftfscb->state) {
     case ZUSINIT:
     case ZUFILE:                         /* CANFC32+CANOVIO+CANFDX         */
          zshhdr(ZRINIT,isbigend() ? ZRISTRB : ZRISTRL);
          break;
     case ZUGOING:
          zshhdr(ZRPOS,(CHAR *)&ftfscb->actbyt);
          zmrscb->pstate=0;
          zmrscb->numhdr=-2;
          break;
     default:
          return;
     }
}

static INT
zmrctn(VOID)                                      /* Service ZMODEM receive */
{                                  /* returns 1=still working on it, 0=done */
     switch(ftfscb->state) {                   /* constant service tasks... */
     case ZUINIT:                              /* CANFC32+CANOVIO+CANFDX */
          zshhdr(ZRINIT,isbigend() ? ZRISTRB : ZRISTRL);
          ftfnew(ZUFILE);
          break;
     case FTFABORT:
          ftfrca();
          ftfout(zmrscb->attn,strlen(zmrscb->attn));
          ftfcan();
          ftfnew(FTFABWAIT);
          break;
     case FTFABWAIT:
          if (ftftck-ftfscb->tckstt > ZABWAIT) {
               return(-1);
          }
          break;
     case ZUOOUT1:                     /* sent ZFIN, wait a little for "OO" */
     case ZUOOUT2:
          if (ftftck-ftfscb->tckstt >= ZOOWAIT) {  /* Outa patience for OO? */
               zshhdr(ZFIN,"\0\0\0\0");   /* Send another ZFIN just in case */
               ftfscb->retrys++;
               ftfscb->stecnt++;
               ftfnew(ZUEND);                /* sender never got it before. */
          }
          break;
     case ZULOGGED:                          /* CANFC32+CANOVIO+CANFDX */
          zshhdr(ZRINIT,isbigend() ? ZRISTRB : ZRISTRL);
          ftfnew(ZUFILE);
          break;
     case ZUEND:
          if (ftfoba() == ftfomt || ftftck-ftfscb->tckstt > ZUEWAIT) {
               return(0);
          }
          break;
     }
     if (ftftck-ftfscb->tckstt >= ZURETRY) {
          svctimeout();
          ftfscb->tmouts++;
          ftfscb->stecnt++;
          ftfnew(ftfscb->state);
     }
     return(1);
}



/*--- ZMODEM Transmit File routines ---*/

static VOID
zmxini(VOID)                               /* Initialize ZMODEM transmitter */
{
     if (fbleng < ZCHUNK) {
          fbleng=ZCHUNK;
     }
}

static INT
zescapem(          /* encode a sequence of bytes using ZMODEM escape-coding */
CHAR *source,                                /* (zctlesc is implicit input) */
CHAR *dest,                           /* (returns # of actual output bytes) */
INT nbytes)
{
#ifdef GCDOS
     /* C-language equivalent:                                     */
     /*                                                            */
     /* CHAR c;                                           */
     /* CHAR *zp;                                                  */
     /* CHAR *buf;                                                 */
     /* INT i;                                                     */
     /*                                                            */
     /* zp=dest;                                                   */
     /* for (i=0,buf=source ; i < nbytes ; i++,buf++) {            */
     /*      c=*buf;                                               */
     /*      if (!zescape(c)                                       */
     /*       || c == '\r' && i > 0 && buf[-1] != '@'              */
     /*                    && i < nbytes-1 && buf[1] != '@') {     */
     /*           *zp++=c;                                         */
     /*      }                                                     */
     /*      else {                                                */
     /*           *zp++=ZDLE;                                      */
     /*           *zp++=c^0x40;                                    */
     /*      }                                                     */
     /* }                                                          */
     /* return((int)(zp-dest));                                    */

#ifdef __HUGE__
asm  push ds
asm  mov  ax,FTFZMOD_DATA          /*  Manually load DS!                    */
asm  mov  ds,ax                    /*  ...Borland 4 won't without a C ref   */
#endif
     asm  cld
     asm  mov  cx,nbytes                                       /* CX=nbytes */
     asm  test zctlesc,0FFH
     asm  push ds
     asm  les  di,dword ptr dest      /* ES:DI points to destination buffer */
     asm  lds  si,dword ptr source         /* DS:SI points to source buffer */
     asm  jcxz dunloops
     asm  jz   crc32nml                                    /* If zctlesc... */

     loop32ctl:
     asm  mov  al,ds:[si]
     asm  inc  si
     asm  test al,060H
     asm  jz   isctl
     asm  mov  es:[di],al                /* non-ctrl CHARacters are literal */
     asm  inc  di
     asm  dec  cx
     asm  jnz  loop32ctl
     asm  jmp  dunloops
     isctl:
     asm  xor  al,40H               /* all ctrl CHARacters are escape-coded */
     asm  mov  ah,al
     asm  mov  al,ASMZDLE
     asm  stos word ptr es:[di]
     asm  dec  cx
     asm  jnz  loop32ctl
     asm  jmp  dunloops

     asm  ccetabl label byte       /* CS-addressable dup of cetabl[], above */
     asm  db   0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0                        /* ^M */
     asm  db   1,1,0,1,0,0,0,0,1,0,0,0,0,0,0,0               /* ^P ^Q ^S ^X */

     crc32nml:                                          /* Else !zctlesc... */
     asm  xor  bh,bh
     loop32nml:
     asm  mov  al,ds:[si]
     asm  inc  si
     asm  test al,060H
     asm  jz   isctl2
     asm  mov  es:[di],al                /* non-ctrl CHARacters are literal */
     asm  inc  di
     asm  dec  cx
     asm  jnz  loop32nml
     asm  jmp  dunloops
     isctl2:
     asm  mov  bl,al
     asm  and  bl,01FH
     asm  test cs:ccetabl[bx],0FFH
     asm  jnz  scapit2
     noscap:
     asm  mov  es:[di],al               /* most ctrl CHARacters are literal */
     asm  inc  di
     asm  dec  cx
     asm  jnz  loop32nml
     asm  jmp  dunloops
     scapit2:
     asm  cmp  al,0DH
     asm  je   iscr
     scapitq:
     asm  xor  al,40H                     /* but ctrl CHARacters identified */
     asm  mov  ah,al                        /* by cetabl[] ARE escape-coded */
     asm  mov  al,ASMZDLE
     asm  stos word ptr es:[di]
     asm  dec  cx
     asm  jnz  loop32nml
     dunloops:
     asm  pop  ds
     asm  sub  di,word ptr dest
     asm  jmp  zesdun
     iscr:
     asm  cmp  cx,1
     asm  je   scapitq
     asm  cmp  byte ptr ds:[si],'@'
     asm  je   scapitq
     asm  cmp  cx,nbytes
     asm  je   scapitq
     asm  cmp  byte ptr ds:[si-2],'@'
     asm  je   scapitq
     asm  jmp  noscap
     zesdun:
#ifdef __HUGE__
asm  pop  ds                        /*  restore DS to entry value           */
#endif
     return(_DI);               /* pointless return aVOIDs compiler warning */
#else
   CHAR c;
   CHAR *zp;
   CHAR *buf;
   INT i;

   zp=dest;
   for (i=0,buf=source ; i < nbytes ; i++,buf++) {
      c=*buf;
      if (zescape(c)) {
      *zp++=ZDLE;
      *zp++=c^0x40;
      }
      else {
      *zp++=c;
      }
   }
   return((INT)(zp-dest));
#endif
}

static VOID
zsdata(                                /* transmit a zmodem data sub-packet */
INT nbytes,                              /* number of bytes (could be zero) */
CHAR framend)                              /* pre-CRC frame terminator code */
{                /* rawbuf is implicit input, ftfbuf is used to format data */
     INT i;
     USHORT crc;
     ULONG lcrc;

     ASSERT(nbytes >= 0);
     zctlesc=(zmxscb->flags&ZCTLESC) != 0;
     if (zmxscb->flags&ZCRC32) {           /* binary header with 32-bit CRC */
          lcrc=crc32(rawbuf,nbytes);
          ftfout(ftfbuf,zescapem(rawbuf,ftfbuf,nbytes));
          sendline(ZDLE);
          sendline(framend);
          lcrc=upfc32(framend,lcrc);
          lcrc=~lcrc;
          for (i=0 ; i < 4 ; i++) {
               zsendline((CHAR)lcrc);   /* <crc-lo> . . . <crc-hi> */
               lcrc>>=8;
          }
     }
     else {                                /* binary header with 16-bit CRC */
          crc=crc16(rawbuf,nbytes);
          ftfout(ftfbuf,zescapem(rawbuf,ftfbuf,nbytes));
          sendline(ZDLE);
          sendline(framend);
          crc=upfcrc(framend,crc);
          crc=upfcrc(0,upfcrc(0,crc));
          zsendline(crc>>8);                                   /* <crc-hi> */
          zsendline(crc);                                      /* <crc-lo> */
     }
     if (framend == ZCRCW) {
          sendline('\021');                                        /* <XON> */
     }
}

static VOID
zfhdr(VOID)                                          /* send a ZFILE header */
{                                                    /* wit all de trimmins */
     INT n;
     static CHAR filehdr[4]={0,0,0,0};

     filehdr[ZF0]=(zmxscb->flags&(ZRESUM1+ZRESUMA)) ? ZCRESUM : ZCBIN;
     zsbhdr(ZFILE,filehdr);
     sprintf(rawbuf,"%s%c%lu %lo 0 0 1 %lu",ftfscb->fname,'\0',
                                            (ULONG)ftfscb->estbyt,
                                            ftfscb->unxtim,
                                            ftfscb->estbyt);
     strlwr(rawbuf);
     n=strlen(rawbuf)+1;
     n+=strlen(rawbuf+n)+1;           /* then format stats and another '\0' */
     zsdata(n,ZCRCW);                                /* and transmit it all */
}

static VOID
zdseek(VOID)                              /* respond to ZRPOS from receiver */
{
     cvtData(&zmxscb->hdrbuf[2],&zmxscb->confirm,sizeof(LONG),longFDA,
             CVTPACKED,CVTSERVER,CHAN_NUL);
     zmxscb->txpos=ftfscb->actbyt=zmxscb->confirm;

/*     zmxscb->confirm=                        everything starts over again */
/*     zmxscb->txpos=
     ftfscb->actbyt=lsl2n(&zmxscb->hdrbuf[2]);*/
     ftfxsk(ftfscb->actbyt);
     zsbhdr(ZDATA,zmxscb->hdrbuf+2);
     ftfnew(ZDGOING);
}

static VOID
zhdlri(VOID)               /* handle flags imbedded in ZRINIT from receiver */
{
     (zmxscb->hdrbuf[2+ZF0]&ESCCTL)  ? (zmxscb->flags|=ZCTLESC) : (zmxscb->flags&=~ZCTLESC);
     (zmxscb->hdrbuf[2+ZF0]&CANFC32) ? (zmxscb->flags|=ZCRC32)  : (zmxscb->flags&=~ZCRC32);
}

static VOID
zdgo(VOID)                               /* begin ZMODEM transmit of a file */
{
     zfhdr();
     ftfnew(ZDRPOS);
     zmxscb->txpos=0L;
}

static VOID
zdnext(VOID)                                   /* next ZMODEM transmit file */
{
     if (ftfxop() == 0) {
          ftfsrt();
          zmxscb->flags|=ZDBLK1;
          zdgo();
     }
     else {
          zshhdr(ZFIN,"\0\0\0\0");
          ftfnew(ZDFIN);
     }
     zmxscb->flags&=~ZRESUM1;
}

static VOID
zmxsrt(VOID)                               /* Begin ZMODEM transmit session */
{
     setmem(zmxscb,sizeof(*zmxscb),0);
     zmxscb->numhdr=-2;               /* quiescent header-search state code */
     ftfscb->paksiz=ftfpsp->paksiz;
     ftfscb->window=ftfpsp->window;
     while (ftfscb->paksiz*2+ZBOVHEAD > ftfomt) {
          ftfscb->paksiz>>=1;                          /* (but not too big) */
     }
     if (ftfxop() == 0) {
          ftfsrt();
          zmxscb->flags|=ZDBLK1;
          ftfnew(ZDINIT);
     }
     else {
          ftfnew(ZDEND);
     }
}

static VOID
zxrsrt(VOID)           /* Begin ZMODEM transmit session w/resume-if-you-can */
{
     zmxsrt();
     zmxscb->flags|=ZRESUMA;
}

static VOID
svchdr(                   /* service received ZMODEM header during transfer */
CHAR rtype)
{
     CHAR xmt;
     LONG dstlng;

     xmt=((ftfpsp->flags&FTFXMT) != 0);
     if (ftfscb->state == FTFABORT
      || ftfscb->state == FTFABWAIT) {
          if (!xmt && rtype == ZFIN) {              /* handle silly Telix's */
               zshhdr(ZFIN,"\0\0\0\0");                 /* ZFIN after abort */
          }
          return;
     }
     zmrscb->pstate=0;         /* assume (for the moment) no packets follow */
     switch(rtype) {                              /* easily handled headers */
     case ZABORT:
          ftfabt(xmt ? "Receiver aborted."
                     : "Sender aborted.");
          return;
     case ZCAN:
          ftfabt(xmt ? "Operator CTRL-X abort."
                     : "Sender or operator CTRL-X abort.");
          return;
     case ZGARBAGE:
          ftfscb->garbag++;
          ftfscb->stecnt++;
          zshhdr(ZNAK,"\0\0\0\0");
          return;
     }
     switch(ftfscb->state) {                  /* handle incoming header... */
     case ZUSINIT:
     case ZUFILE:
          switch(rtype) {
          case ZRQINIT:              /* aha!  sender just now coming awake! */
               zshhdr(ZRINIT,isbigend() ? ZRISTRB : ZRISTRL);
               ftfnew(ZUFILE);
               break;
          case ZSINIT:                       /* get ready for SINIT message */
               (zmxscb->hdrbuf[2+ZF0]&TESCCTL)  ? (zmrscb->flags|=ZCTLESC)
                                    : (zmrscb->flags&=~ZCTLESC);
               ftfnew(ZUSINIT);
               zmrscb->pstate=1;
               zmrscb->numpkt=0;
               break;
          case ZFILE:                    /* sender sending a file and stuff */
               ftfscb->stecnt=0;
               zmrscb->sopt0=zmxscb->hdrbuf[2+ZF0];
               zmrscb->sopt1=zmxscb->hdrbuf[2+ZF1];
               zmrscb->pstate=1;
               zmrscb->numpkt=0;
               break;
          case ZFIN:
               ftfscb->stecnt=0;
               zshhdr(ZFIN,"\0\0\0\0");
               ftfnew(ZUOOUT1);
               break;
          }
          break;
     case ZUGOING:
          switch(rtype) {
          case ZDATA:
               cvtData(&zmxscb->hdrbuf[2],&dstlng,sizeof(LONG),longFDA,
                       CVTPACKED,CVTSERVER,CHAN_NUL);
               if (dstlng != ftfscb->actbyt) {
                    ftfout(zmrscb->attn,strlen(zmrscb->attn));
                    zshhdr(ZRPOS,(CHAR *)&ftfscb->actbyt);
                    break;
               }
               else {
                    zmrscb->pstate=1;
                    zmrscb->numpkt=0;
                    ftfscb->stecnt=0;
               }
               break;
          case ZSKIP:
          case ZEOF:
               cvtData(&zmrscb->hdrbuf[2],&dstlng,sizeof(LONG),longFDA,
                       CVTPACKED,CVTSERVER,CHAN_NUL);
               if (dstlng != ftfscb->actbyt) {
                                   /* pos mismatch                         */
                                   /* ZMODEM says ignore wrong-position EOF*/
                                   /* force a ZUGOING timeout instead      */
                    break;
               }
               ftfrcl(1);
               ftfscb->isopen=0;
               ftfscb->actfil++;
               ftfnew(ZULOGGED);
               break;
          case ZFIN:
               ftfabt("Sender abruptly terminated.");
               break;
          }
          break;
     case ZUOOUT1:                            /* assume ZFIN's are retrying */
     case ZUOOUT2:
          zshhdr(ZFIN,"\0\0\0\0");
          break;
     case ZDRINIT:                /* waiting for first ZRINIT from receiver */
          switch(rtype) {
          case ZRINIT:                                         /* got it... */
               ftfscb->stecnt=0;
               zhdlri();
               zdgo();                    /* send header for the first file */
               break;
          case ZCHALLENGE:                          /* respond to challenge */
               zshhdr(ZACK,zmxscb->hdrbuf+2);
               break;
          default:                                        /* anything else: */
               ftfout("rz\r",3);
               zshhdr(ZRQINIT,"\0\0\0\0");                /* repeat ZRQINIT */
               break;
          }
          break;
     case ZDRPOS:                  /* waiting for first ZRPOS from receiver */
          switch(rtype) {
          case ZRPOS:                                          /* got ZRPOS */
               ftfscb->stecnt=0;
               ftfstf();
               zdseek();
               break;
          case ZSKIP:                      /* skip this file, go on to next */
               ftfxsq("file skipped by receiver");
               ftfxcl(0);
               ftfscb->isopen=0;
               zdnext();
               break;
          case ZCRC:
               zmxscb->filcrc=0xFFFFFFFFL;
               ftfxsk(0L);
               ftfnew(ZDCRCING);
               break;
          case ZFIN:
               ftfabt("Receiver abruptly terminated.");
               break;
          case ZRINIT:                         /* if we get another ZRINIT, */
          default:                                     /* or anything else, */
               if (ftftck-ftfscb->tckstt > ZDFRETRY) {   /* after 2 seconds */
                    zfhdr();                      /* retry the ZFILE header */
                    ftfnew(ftfscb->state);
                    ftfscb->retrys++;
                    ftfscb->stecnt++;
               }
               break;
          }
          break;
     case ZDGOING:                /* some error reply to data subpackets... */
     case ZDEOF:      /* some error reply to last data subpacket in file... */
          switch(rtype) {
          case ZRPOS:                                      /* retry request */
               ftfclo();
               ftfscb->retrys++;
               ftfscb->stecnt++;
               zsdata(0,ZCRCE);               /* (term current frame first) */
               zdseek();
               break;
          case ZRINIT:                          /* new file req (šoh yeah?) */
          case ZSKIP:                                     /* skip this file */
               ftfclo();
               zsdata(0,ZCRCE);               /* (term current frame first) */
               ftfxsq("file started and then skipped by receiver");
               ftfxcl(0);
               ftfscb->isopen=0;
               zdnext();
               break;
          case ZACK:                            /* listen to ZACK's address */
               ftfscb->stecnt=0;
               cvtData(&zmxscb->hdrbuf[2],&ftfscb->actbyt,sizeof(LONG),longFDA,
                       CVTPACKED,CVTSERVER,CHAN_NUL);
               break;
          case ZFIN:                                         /* be that way */
               ftfabt("Receiver abruptly terminated.");
               break;
          }
          break;
     case ZDNEXT:                    /* sent ZEOF, expecting next ZRINIT... */
          switch(rtype) {
          case ZACK:                         /* ignore ZACK's from old file */
               break;
          case ZRPOS:                      /* retries of old file after eof */
               zdseek();
               break;
          case ZSKIP:                  /* skip the last file, go on to next */
               ftfxsq("file skipped by receiver even after it had all been sent");
               ftfxcl(0);
               ftfscb->isopen=0;
               zdnext();
               break;
          case ZRINIT:                  /* here's the request for next file */
               ftfscb->stecnt=0;
               zhdlri();
               ftfscb->actbyt=zmxscb->txpos;
               ftfxcl(1);
               ftfscb->isopen=0;
               ftfscb->actfil++;               /* count successful transmit */
               zdnext();
               break;
          default:                             /* anything else is gibbrish */
               ftfabt("Bad reply to ZEOF.");
               break;
          }
          break;
     case ZDFROWN:
          ftfout("OO",2);                         /* redundant Over and Out */
          ftfnew(ZDFROWN);
          break;
     case ZDFIN:                                       /* got reply to ZFIN */
          switch(rtype) {
          case ZFIN:                                /* expect another ZFIN: */
               ftfscb->stecnt=0;
               ftfout("OO",2);                              /* Over and Out */
               ftfnew(ZDEND);
               break;
          default:                              /* but anything else means: */
               zshhdr(ZFIN,"\0\0\0\0");                       /* retry ZFIN */
               ftfscb->retrys++;
               ftfscb->stecnt++;
               break;
          }
          break;
     }
}

static VOID
svcdnl(VOID)               /* service routine checking for zmodem transmit */
{
     INT n;
     INT packend;
     LONG ztxwindow;
     INT block;

     ztxwindow=ftfscb->window;
     while (ftfoba() >= ftfscb->paksiz*2+ZBOVHEAD /* room for more packets? */
         && (zmxscb->flags&ZXOFF) == 0               /* no XOFF-throttling? */
         && (ztxwindow == 0                         /* receiver keeping up? */
             || zmxscb->txpos-ftfscb->actbyt < ztxwindow)
         && ftfscb->state == ZDGOING) {     /* still downloading this file? */
          block=((zmxscb->flags&(ZRESUMA+ZRESUM1)) && (zmxscb->flags&ZDBLK1))
                ? ZBRECOVER : ftfscb->paksiz;
          zmxscb->flags&=~ZDBLK1;
          n=ftfxrd(rawbuf,block);
          if (n < 0) {                               /* this is a DOS error */
               ftfabt("Error reading file.");
          }
          else if (n == block) {                    /* this is a full block */
               zmxscb->txpos+=n;
               if (ztxwindow != 0
                && zmxscb->txpos-zmxscb->confirm >= (ztxwindow>>2)) {
                    packend=ZCRCQ;             /* time to get some feedback */
                    zmxscb->confirm=zmxscb->txpos;
               }
               else {
                    packend=ZCRCG;               /* don't need feedback yet */
               }
               zsdata(n,packend);
          }
          else {                         /* this is a (partial) final block */
               zmxscb->txpos+=n;
               zsdata(n,ZCRCE);
               ftfnew(ZDEOF);
          }
          ftfnwp();                                /* count outgoing packet */
          if (ztxwindow == 0) {
               ftfscb->tckact=ftftck;
               ftfact=1;
          }
     }                  /* may never be ack'd!  may be retried & recounted! */
}

static VOID
svceof(VOID)          /* service routine checking after ZMODEM transmit eof */
{
     if (ftfoba() >= ZBEOF) {                  /* room for ZEOF terminator? */
          zsbhdr(ZEOF,(CHAR *)&zmxscb->txpos);
          ftfnew(ZDNEXT);
     }
}

static VOID
svccrc(VOID)                                /* service file crc computation */
{
     CHAR c,*zp;
     INT i,n;
     ULONG crc;

     n=ftfxrd(ftfbuf,ZCHUNK);
     if (n < 0) {
          ftfabt("Error reading file.");
     }
     else {
          crc=zmxscb->filcrc;
          for (i=0,zp=ftfbuf ; i < n ; i++) {
               c=*zp;
               crc=upfc32(c,crc);
          }
          zmxscb->filcrc=crc;
          if (n != ZCHUNK) {
               crc=~crc;
               ftfxsk(0L);
               zshhdr(ZCRC,(CHAR *)&crc);
               ftfnew(ZDRPOS);
          }
     }
}

static INT
zmxctn(VOID)                         /* continue processing zmodem transmit */
{                                        /* returns 1=still working, 0=done */
     switch(ftfscb->state) {                   /* constant service tasks... */
     case ZDINIT:
          ftfout("rz\r",3);
          zshhdr(ZRQINIT,"\0\0\0\0");               /* send ZRQINIT to rcvr */
          ftfnew(ZDRINIT);
          break;
     case ZDRINIT:
          if (ftftck-ftfscb->tckstt > ZDRQWAIT) {
               zshhdr(ZRQINIT,"\0\0\0\0");            /* send ZRQINIT again */
               ftfnew(ZDRINIT);
          }
          break;
     case ZDGOING:                                           /* transmiting */
          svcdnl();
          break;
     case ZDEOF:                                      /* sending eof header */
          svceof();
          break;
     case ZDCRCING:                                   /* computing file CRC */
          svccrc();
          break;
     case FTFABORT:
          ftfxca();
          ftfcan();
          ftfnew(FTFABWAIT);
          break;
     case FTFABWAIT:
          if (ftftck-ftfscb->tckstt > ZABWAIT) {
               return(-1);
          }
          break;
     case ZDFROWN:
          if (ftftck-ftfscb->tckstt > ZDFWAIT) {
               ftfcli();
               ftfnew(ZDEND);
          }
          break;
     case ZDFIN:
          if (ftftck-ftfscb->tckstt > ZDFINW) {
               zshhdr(ZFIN,"\0\0\0\0");                       /* retry ZFIN */
               ftfscb->retrys++;
               ftfscb->stecnt++;
               ftfnew(ZDFIN);
          }
          break;
     case ZDEND:
          if (ftfoba() == ftfomt || ftftck-ftfscb->tckstt > ZDEWAIT) {
               if (zmxscb->numhdr != -2) {
                    ftfnew(ZDFROWN);       /* (hold out for redundant ZFIN) */
                    break;
               }
               zmxscb->flags&=~ZRESUM1;
               return(0);
          }
          break;
     }
     return(1);
}

static VOID
prcpkc(VOID)                             /* process packet terminator & CRC */
{
     INT crcbad;
     INT iterm;

     if (zmrscb->flags&ZGOTH32) {                          /* 32-bit CRC's: */
          crcbad=(crc32(zmrscb->packet,zmrscb->numpkt) != CRC32GOOD);
          iterm=zmrscb->numpkt-5;
     }
     else {                                                /* 16-bit CRC's: */
          crcbad=(crc16(zmrscb->packet,zmrscb->numpkt) != 0);
          iterm=zmrscb->numpkt-3;
     }
     if (crcbad) {                                                  /* GACK */
          gbgpkt();
     }
     else {                                                 /* good packet! */
          zmrscb->numpkt=iterm;                         /* strip terminator */
          svcpkt(zmrscb->packet[iterm]);                   /* extract ZCRCx */
     }
}

static INT
chkhdr(VOID)                          /* check assembled header in hdrbuf[] */
{                                                   /* returns 1=good 0=bad */
#ifdef GCDOS
     switch(zmrscb->hdrbuf[0]) {
     case 'B':
#ifdef __HUGE__
asm  push ds
asm  mov  ax,FTFZMOD_DATA          /*  Manually load DS!                    */
asm  mov  ds,ax                    /*  ...Borland 4 won't without a C ref   */
#endif
          asm  mov  ax,seg _ftfscb
          asm  mov  es,ax
          asm  les  si,dword ptr es:_ftfscb
          asm  add  si,.(struct zmrdat)hdrbuf+1   /* ES:SI=zmrscb->hdrbuf+1 */
          asm  and  word ptr es:[si+14],7F7FH     /* ignore high-order bits */
          asm  cmp  word ptr es:[si+14],0A0DH        /* demand CR-LF at end */
          asm  je   okcrlf
          asm  cmp  word ptr es:[si+14],000DH        /* allow CR-NUL at end */
          asm  je   okcrlf                             /* (e.g. via Telnet) */
#ifdef __HUGE__
asm  pop  ds                       /*  restore DS to entry value           */
#endif
          break;
          okcrlf:
          asm  mov  di,si                               /* ES:DI=same place */
          asm  mov  cx,7
          asm  cld
          hxloop:     /* convert seven pairs of hex digits into seven bytes */
          asm  lods word ptr es:[si]
          asm  sub  ax,3030H
          asm  cmp  al,9                          /* 1st digit is hi nybble */
          asm  jbe  ldgok
          asm  sub  al,'A'-'9'-1;
          ldgok:
          asm  shl  al,4
          asm  cmp  ah,9                          /* 2nd digit is lo nybble */
          asm  jbe  hdgok
          asm  sub  ah,'A'-'9'-1;
          asm  and  ah,0FH                /* (supports lower case a-f also) */
          hdgok:
          asm  or   al,ah
          asm  stos byte ptr es:[di]
          asm  loop hxloop
#ifdef __HUGE__
asm  pop  ds                       /*  restore DS to entry value           */
#endif
     case 'A':
          if (crc16(zmrscb->hdrbuf+1,7) == 0) {
               zmrscb->flags&=~ZGOTH32;
               svchdr(zmrscb->hdrbuf[1]);
               return(1);
          }
          break;
     case 'C':
          if (crc32(zmrscb->hdrbuf+1,9) == CRC32GOOD) {
               zmrscb->flags|=ZGOTH32;
               svchdr(zmrscb->hdrbuf[1]);
               return(1);
          }
          break;
     }
     return(0);
#else
     CHAR *nibble;               /* Current nibble (part of 14 nibbles) */
     CHAR hinibl, lonibl;          /* Hi & Lo nibble pair (part of 7 pairs) */
     INT i;

     switch(zmrscb->hdrbuf[0]) {
     case 'B':
       zmrscb->hdrbuf[15]&=0x7F;              /* ignore high order bits */
       zmrscb->hdrbuf[16]&=0x7F;
       if ((zmrscb->hdrbuf[15] == 0x0D && zmrscb->hdrbuf[16] == 0x0A) ||
           (zmrscb->hdrbuf[15] == 0x0D && zmrscb->hdrbuf[16] == 0x00)) {
            nibble=zmrscb->hdrbuf+1;
            for(i=1; i < 8 ; i++) {
              if ((hinibl=(*nibble++) - 0x30) > 9) { /* 1st digit    */
                hinibl-='A'-'9'-1;                /* is hi nibble */
              }
              if ((lonibl=(*nibble++) - 0x30) > 9) { /* 2nd digit    */
                lonibl-='A'-'9'-1;                /* is lo nibble */
              }
              zmrscb->hdrbuf[i]=(hinibl<<4) | (lonibl & 0x0F);
            }
       }
       else {
            break;
       }
     case 'A':
          if (crc16(zmrscb->hdrbuf+1,7) == 0) {
               zmrscb->flags&=~ZGOTH32;
               svchdr(zmrscb->hdrbuf[1]);
               return(1);
          }
          break;
     case 'C':
          if (crc32(zmrscb->hdrbuf+1,9) == CRC32GOOD) {
               zmrscb->flags|=ZGOTH32;
               svchdr(zmrscb->hdrbuf[1]);
               return(1);
          }
          break;
     }
     return(0);
#endif
}

static INT
zmdinb(                /* handle incoming characters during ZMODEM transfer */
CHAR *bytes,
USHORT nbytes)
{
#ifdef GCDOS
     if (zmxscb->flags&ZXOFF && nbytes > 0) {
          ftfxlk(0);
          zmxscb->flags&=~ZXOFF;
     }
     asm  push ds
     asm  mov  ax,seg _ftfscb
     asm  mov  ds,ax
     asm  lds  di,dword ptr _ftfscb                         /* DS:DI=zmrscb */
     asm  les  si,dword ptr bytes                            /* ES:SI=bytes */
     asm  mov  cx,nbytes                                       /* CX=nbytes */
     asm  test cx,cx
     asm  jnz  inbloop
     asm  jmp  eozmd
inbloop:                       /* cautious looping -- check numhdr & pstate */
     asm  mov  al,es:[si]                                        /* AL=byte */
     asm  inc  si
     asm  mov  bl,al
     asm  xor  bh,bh
     asm  add  bx,bx
     asm  mov  dx,word ptr cs:zmctable[bx]      /* switch (zmctable[c]) ... */
     asm  cmp  dx,offset chr
     asm  jne  notchr                /* (fast check for those common chr's) */

     asm  chr  label byte          /* uninteresting non-control CHARacters: */
          chrhdl:
          asm  cmp  zmrscbword(numhdr),-2
          asm  jne  rcvhdr

     nml:                         /* normal literal CHARacters in a packet: */
          asm  cmp  zmrscbword(pstate),1     /* switch (zmrscb->pstate) ... */
          asm  jne  pstatenot1
          pstate1:           /* pstate == 1 means receiving a literal byte: */
               asm  mov  bx,zmrscbword(numpkt)                 /* BX=numpkt */
               asm  cmp  bx,ZINSIZE
               asm  jge  _pktbust                 /* if there's room for it */
               asm  mov  zmrscbbyte(packet+bx),al                  /* store */
               asm  inc  bx
               asm  mov  zmrscbword(numpkt),bx                   /* & count */
               asm  dec  cx
               asm  jnz  inbloopf                       /* hi-speed looping */
               asm  jmp  eozmd
          _pktbust: asm  jmp  pktbust
inbloopf:                  /* FASTEST loop -- assume pstate=1 and numhdr=-2 */
     asm  mov  al,es:[si]                                        /* AL=byte */
     asm  inc  si
     asm  mov  bl,al
     asm  xor  bh,bh
     asm  add  bx,bx
     asm  mov  dx,word ptr cs:zmctable[bx]      /* switch (zmctable[c]) ... */
     asm  cmp  dx,offset chr
     asm  jne  notchr                /* (fast check for those common chr's) */
     asm  mov  bx,zmrscbword(numpkt)                           /* BX=numpkt */
     asm  cmp  bx,ZINSIZE
     asm  jge  _pktbust                           /* if there's room for it */
     asm  mov  zmrscbbyte(packet+bx),al                            /* store */
     asm  inc  bx
     asm  mov  zmrscbword(numpkt),bx                             /* & count */
     asm  dec  cx
     asm  jnz  inbloopf                            /* highest-speed looping */
     asm  jmp  eozmd

     rcvhdr:                                            /* if not quiescent */
          asm  cmp  zmrscbword(numhdr),0
          asm  jg   _inhdr                       /* and zmrscb->numhdr <= 0 */
          asm  mov  zmrscbword(numhdr),-2           /* return to quiescence */
          asm  jmp  nml
          _inhdr:   asm  jmp  inhdr

     notchr:
          asm  cmp  dx,offset ctl
          asm  jne  spcchr           /* (fast check for those common ctl's) */

     asm  ctl  label near              /* uninteresting control CHARacters: */
          asm  test zmrscbword(flags),ZCTLESC
          asm  jz   chrhdl                /* they're literal if not ZCTLESC */
          asm  jmp  eozmc

     spcchr:
          asm  jmp  dx

          pstatenot1:
               asm  cmp  zmrscbword(pstate),0
               asm  je   pstate0
               asm  cmp  zmrscbword(pstate),-1
               asm  jne  pstateoth
          pstatem1:           /* pstate == -1 means receiving escaped byte: */
               asm  mov  zmrscbword(pstate),1
               asm  xor  al,40H
               asm  jmp  pstate1
          pstateoth:          /* |pstate| > 1 means rcving terminator & crc */
               asm  jg   gopacket
               asm  xor  al,40H          /* toggle bit 5 in byte after ZDLE */
               asm  neg  zmrscbword(pstate)           /* pstate <- |pstate| */
          gopacket:
               asm  mov  bx,zmrscbword(numpkt)                 /* BX=numpkt */
               asm  cmp  bx,ZINSIZE
               asm  jge  pktbust                  /* if there's room for it */
               asm  mov  zmrscbbyte(packet+bx),al                  /* store */
               asm  inc  bx
               asm  mov  zmrscbword(numpkt),bx                     /* count */
               asm  inc  zmrscbword(pstate)               /* advance pstate */
               asm  mov  dx,4                       /* to 4 if 16-bit CRC's */
               asm  test zmrscbword(flags),ZGOTH32
               asm  jz   not32
               asm  mov  dx,6                       /* to 6 if 32-bit CRC's */
               not32:
               asm  cmp  zmrscbword(pstate),dx
               asm  jne  notyet
               asm  push es
               asm  push cx
               asm  push ax
               #ifndef __HUGE__               /* Non-huge model expects DS  */
                    asm  push ds              /* to always be DGROUP. (In   */
                    asm  mov  ax,DGROUP       /* huge model, every function */
                    asm  mov  ds,ax           /* loads its own DS.)         */
               #endif
               prcpkc();                                  /* process packet */
               #ifndef __HUGE__
                    asm  pop  ds
               #endif
               asm  pop  ax
               asm  pop  cx
               asm  pop  es
               notyet:
               asm  jmp  eozmc
          pktbust:                               /* else packet buffer full */
               asm  push es
               asm  push cx
               asm  push ax
               #ifndef __HUGE__
                    asm  push ds
                    asm  mov  ax,DGROUP
                    asm  mov  ds,ax
               #endif
               gbgpkt();                                         /* garbage */
               #ifndef __HUGE__
                    asm  pop  ds
               #endif
               asm  pop  ax
               asm  pop  cx
               asm  pop  es
          pstate0:
               asm  jmp  eozmc       /* pstate == 0:  not expecting packets */

     asm  ZR0  label near  /* ZRUB0:  is this CAN + 'l' == 7F, or a literal 'l'? */
          asm  cmp  zmrscbword(pstate),0
          asm  jl   rub0                           /* if pstate < 0 it's 7F */
          asm  mov  bx,zmrscbword(numhdr)
          asm  cmp  bx,0
          asm  jle  _chr                    /* else if numhdr <= 0 it's 'l' */
          asm  cmp  zmrscbbyte(hdrbuf+bx),40H
          asm  jne  _chr           /* else if hdrbuf[numhdr] == 40H it's 7F */
          rub0:
          asm  mov  al,07FH XOR 40H          /* (tricky set up for XOR 40H) */
          _chr:asm  jmp  chrhdl

     asm  ZR1 label near  /* ZRUB1:  is this CAN + 'm' == FF, or a literal 'm'? */
          asm  cmp  zmrscbword(pstate),0
          asm  jl   rub1                           /* if pstate < 0 it's FF */
          asm  mov  bx,zmrscbword(numhdr)
          asm  cmp  bx,0
          asm  jle  _chr                    /* else if numhdr <= 0 it's 'm' */
          asm  cmp  zmrscbbyte(hdrbuf+bx),40H
          asm  jne  _chr          /* else if hdrbuf[numhdr] != 40H it's 'm' */
          rub1:                                             /* else it's FF */
          asm  mov  al,0FFH XOR 40H          /* (tricky set up for XOR 40H) */
          asm  jmp  chrhdl

     asm  ZCC label near    /* ZCRCE, ZCRCG, ZCRCQ, ZCRCW ('h','i','j','k') */
          asm  cmp  zmrscbword(pstate),0
          asm  je   hdrpak
          asm  cmp  zmrscbword(pstate),-1
          asm  jne  _chr
          asm  mov  zmrscbword(numhdr),-2
          asm  mov  zmrscbword(pstate),1
          asm  jmp  gopacket         /* go store term & transit to pstate 2 */
          hdrpak:
          asm  cmp  zmrscbword(numhdr),-2
          asm  jge  _chr
          asm  push es
          asm  push cx
          asm  push ax
          #ifndef __HUGE__
               asm  push ds
               asm  mov  ax,DGROUP
               asm  mov  ds,ax
          #endif
          if (ftfpsp->flags&FTFXMT) {     /* ZMODEM says transmitter should */
               gbgpkt();               /* immediately ZNAK a received ZCRCW */
          }                         /* for some kind of fast error recovery */
          #ifndef __HUGE__
               asm  pop  ds
          #endif
          asm  pop  ax
          asm  pop  cx
          asm  pop  es
          asm  jmp  chrhdl

     /*--- zmdinb() CHARacter-handler vector table ---*/

     asm  zmctable label word
    /*  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F  */
asm dw ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl,EOL,ctl,ctl,EOL,ctl,ctl /* 00 */
asm dw ctl,XON,ctl,XOF,ctl,ctl,ctl,ctl,ZDE,ctl,ctl,ctl,ctl,ctl,ctl,ctl /* 10 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,ATK,chr,chr,chr,chr,chr /* 20 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr /* 30 */
asm dw chr,_A_,_B_,_C_,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,_O_ /* 40 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr /* 50 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,ZCC,ZCC,ZCC,ZCC,ZR0,ZR1,chr,chr /* 60 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr /* 70 */
asm dw ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl,EOL,ctl,ctl,EOL,ctl,ctl /* 80 */
asm dw ctl,XON,ctl,XOF,ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl,ctl /* 90 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr /* A0 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr /* B0 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr /* C0 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr /* D0 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr /* E0 */
asm dw chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr,chr /* F0 */

     inhdr:                          /* if in the midst of receiving header */
          asm  mov  bx,zmrscbword(numhdr)
          asm  xor  zmrscbbyte(hdrbuf+bx),al             /* store in hdrbuf */
          asm  inc  bx
          asm  mov  zmrscbword(numhdr),bx
          asm  cmp  bx,zmrscbword(hdrexp)
          asm  jl   _nml               /* if we've got a full header yet... */
          asm  push es
          asm  push cx
          asm  push ax
          #ifndef __HUGE__
               asm  push ds
               asm  mov  ax,DGROUP
               asm  mov  ds,ax
          #endif
          chkhdr();                          /* ...see if the header's good */
          asm  test ax,ax
          #ifndef __HUGE__
               asm  pop  ds
          #endif
          asm  pop  ax
          asm  pop  cx
          asm  pop  es
          asm  mov  zmrscbword(numhdr),-2
          asm  jz   _nml          /* bad header - see if byte is for packet */
          asm  jmp  eozmc           /* good header - bypass packet handling */

     abchdr:          /* received A B or C - beginning or middle of header? */
          asm  jg   inhdr
     gothid:                                  /* brand new header beginning */
          asm  mov  zmrscbword(hdrexp),dx
          asm  mov  zmrscbbyte(hdrbuf),al           /* store type in hdrbuf */
          asm  xor  dx,dx
          asm  mov  zmrscbbyte(hdrbuf+1),dh      /* zero out rest of hdrbuf */
          asm  mov  zmrscbword(hdrbuf+2),dx
          asm  mov  zmrscbword(hdrbuf+4),dx
          asm  mov  zmrscbword(hdrbuf+6),dx
          asm  mov  zmrscbword(hdrbuf+8),dx
          asm  mov  zmrscbword(hdrbuf+10),dx
          asm  mov  zmrscbword(hdrbuf+12),dx
          asm  mov  zmrscbword(hdrbuf+14),dx
          asm  mov  zmrscbword(hdrbuf+16),dx
          asm  mov  zmrscbword(numhdr),1                     /* numhdr <- 1 */
          _nml:asm  jmp  nml

     asm  _A_  label near           /* received 'A' -- beginning of header? */
          asm  mov  dx,8                                        /* Anddddcc */
          asm  cmp  zmrscbword(numhdr),0
          asm  jge  abchdr
          asm  mov  zmrscbword(numhdr),-2
          asm  jmp  nml

     asm  _B_  label near           /* received 'B' -- beginning of header? */
          asm  mov  dx,17                        /* Bnnddddddddcccc<CR><LF> */
          asm  cmp  zmrscbword(numhdr),0
          asm  jge  abchdr
          asm  mov  zmrscbword(numhdr),-2
          asm  jmp  nml

     asm  _C_  label near           /* received 'C' -- beginning of header? */
          asm  mov  dx,10                                     /* Cnddddcccc */
          asm  cmp  zmrscbword(numhdr),0
          asm  jge  abchdr
          asm  mov  zmrscbword(numhdr),-2
          asm  jmp  nml

          dblneg:             /* (ZDLE ZDLE == ZDLE, if received in packet) */
     asm  ZDE  label near                                 /* ZDLE received: */
          asm  neg  zmrscbword(pstate)           /* packet escape CHARacter */
          asm  jg   dblneg
          zdejoin:                              /* transition header state: */
          asm  cmp  zmrscbword(numhdr),-2
          asm  jne  notm2
          asm  mov  zmrscbword(numhdr),-3                       /* -2 => -3 */
          asm  jmp  eozmc

          notm2:
          asm  mov  bx,zmrscbword(numhdr)
          asm  cmp  bx,-1
          asm  jne  notm1
          asm  mov  zmrscbword(numhdr),0       /* -1 => 0 (could be header) */
          asm  jmp  eozmc

          notm1:
          asm  cmp  bx,0
          asm  jl   caning
          asm  je   iszer
          asm  mov  zmrscbbyte(hdrbuf+bx),40H
          asm  jmp  eozmc           /* 1-N => escape sequence inside header */

          caning:
          asm  cmp  zmrscbword(numhdr),-6
          asm  jle  ism6
          asm  dec  bx                              /* -3 => -4 => -5 => -6 */
          asm  mov  zmrscbword(numhdr),bx
          asm  jmp  eozmc

          ism6:
          asm  mov  zmrscbword(numhdr),-2;             /* -6 => -2 & CANCEL */
          asm  push es
          asm  push cx
          asm  push ax
          #ifndef __HUGE__
               asm  push ds
               asm  mov  ax,DGROUP
               asm  mov  ds,ax
          #endif
          svchdr(ZCAN);
          #ifndef __HUGE__
               asm  pop  ds
          #endif
          asm  pop  ax
          asm  pop  cx
          asm  pop  es
          asm  jmp  eozmc

          iszer:
          asm  mov  zmrscbword(numhdr),-3                        /* 0 => -3 */
          asm  jmp  eozmc


     asm  ATK  label near                                   /* '*' received */
          asm  cmp  zmrscbword(pstate),0    /* (if expecting packets, don't */
          asm  jne  __nml                      /* even THINK about headers) */
          asm  cmp  zmrscbword(numhdr),0
          asm  jge  atkhdr                       /* if waiting for a header */
          asm  mov  zmrscbword(numhdr),-1         /* this may be the ticket */
          asm  jmp  eozmc
          atkhdr:
          asm  je   eozmc         /* "* ZDLE *" is bogus as header, give up */
          __inhdr:  asm  jmp  inhdr  /* looks like an '*' INSIDE the header */

     asm  EOL  label near                                    /* CR's & LF's */
          asm  cmp  zmrscbword(pstate),0
          asm  je   eolhdr                       /* if expecting packets... */
          asm  test zmrscbword(flags),ZCTLESC          /* if escaping CTL's */
          asm  jnz  eozmc                               /* then just ignore */
          __nml:asm jmp  nml                        /* else store in packet */
          eolhdr:                            /* but if expecting headers... */
          asm  cmp  zmrscbword(numhdr),0
          asm  jg   __inhdr                  /* perhaps we're INSIDE a hdr? */
          asm  mov  zmrscbword(numhdr),-2   /* certainly not STARTING a hdr */
          asm  jmp  eozmc

     asm  XOF  label near                                           /* XOFF */
          asm  cmp  cx,1      /* (only counts when it's the last CHARacter) */
          asm  jne  notxof
          asm  or   zmrscbword(flags),ZXOFF               /* set ZXOFF flag */
          asm  push es
          asm  push cx
          asm  push ax
          #ifndef __HUGE__
               asm  push ds
               asm  mov  ax,DGROUP
               asm  mov  ds,ax
          #endif
          ftfxlk(1);                                /* begin locking output */
          #ifndef __HUGE__
               asm  pop  ds
          #endif
          asm  pop  ax
          asm  pop  cx
          asm  pop  es
          notxof:
     asm  XON  label near                                            /* XON */
          asm  jmp  eozmc                                       /* (ignore) */

     asm  _O_  label near /* 'O' - perhaps this is the "OO" at end of receive? */
          asm  cmp  zmrscbword(scb.state),ZUOOUT1
          asm  jge  oout
          asm  jmp  chrhdl       /* normal 'O' is treated like a normal 'O' */
          oout:
          asm  jg   not1
          asm  mov  zmrscbword(scb.state),ZUOOUT2
          asm  jmp  eozmc          /* first 'O' in "OO" transits to ZUOOUT2 */
          not1:
          asm  mov  zmrscbword(scb.state),ZUEND
          asm  dec  cx              /* second 'O' in "OO" transits to ZUEND */
          asm  jmp  eozmd                     /* and we exit the byte loop! */

     eozmc:
     asm  dec  cx
     asm  jz   eozmd
     asm  jmp  inbloop

     eozmd:
     asm  pop  ds
     asm  mov  ax,nbytes
     asm  sub  ax,cx
     return(_AX);
#else
     enum states {   CTL, EOL, XON, XOF, ZDE, CHR, _A_, _B_, _C_, _O_,
               ZCC, ZR0, ZR1, ATK };
     CHAR ch;
     INT bx,dx,totbyt;
     enum states zmdstt[256] = {
   /*  0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F  */
      CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL,EOL,CTL,CTL,EOL,CTL,CTL, /* 00 */
      CTL,XON,CTL,XOF,CTL,CTL,CTL,CTL,ZDE,CTL,CTL,CTL,CTL,CTL,CTL,CTL, /* 10 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,ATK,CHR,CHR,CHR,CHR,CHR, /* 20 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR, /* 30 */
      CHR,_A_,_B_,_C_,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,_O_, /* 40 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR, /* 50 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,ZCC,ZCC,ZCC,ZCC,ZR0,ZR1,CHR,CHR, /* 60 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR, /* 70 */
      CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL,EOL,CTL,CTL,EOL,CTL,CTL, /* 80 */
      CTL,XON,CTL,XOF,CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL,CTL, /* 90 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR, /* A0 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR, /* B0 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR, /* C0 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR, /* D0 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR, /* E0 */
      CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR,CHR  /* F0 */
     };
     if (zmxscb->flags&ZXOFF && nbytes > 0) {
          ftfxlk(0);
          zmxscb->flags&=~ZXOFF;
     }
     if (nbytes == 0) {
       goto eozmd;
     }
     totbyt=nbytes;
inbloop:
     ch=*bytes++;
     if (zmdstt[(SHORT)ch] != CHR) {/* fast check for common chr's */
       goto notchr;
     }
chr:                       /* uninteresting non-control chars   */
chrhdl:
     if (zmrscb->numhdr != -2) {       /*  if not quiescent                 */
       if (zmrscb->numhdr > 0) {    /*  and zmrscb->numhdr <= 0          */
            goto inhdr;
       }
       else {
            zmrscb->numhdr=-2;      /*  then return to quiescence        */
       }

     }
nml:                        /* normal literal characters in packet */
     if (zmrscb->pstate != 1) {
       goto pstatenot1;
     }
pstate1:             /* pstate == 1 means reseiving a literal byte */
     if ((bx=zmrscb->numpkt) >= ZINSIZE) {        /* is there room for byte? */
       goto pktbust;                           /* no                      */
     }
     zmrscb->packet[bx]=ch;                                   /* yes, store */
     zmrscb->numpkt=bx+1;                                        /* & count */
     if (--nbytes == 0) {
       goto eozmd;
     }
     do {
       ch=*bytes++;
       if (zmdstt[(SHORT)ch] != CHR) {/* fast chk for chr's */
            goto notchr;
       }
       if ((bx=zmrscb->numpkt) >= ZINSIZE) {   /* is there room for byte? */
            goto pktbust;                   /* no                      */
       }
       zmrscb->packet[bx]=ch;                              /* yes, store */
       zmrscb->numpkt=bx+1;
     } while(--nbytes);
     goto eozmd;
notchr:
     if (zmdstt[(SHORT)ch] != CTL) {/* fast check for common ctl's */
       goto spcchr;
     }
ctl:                             /* uninteresting control characters: */
     if ((zmrscb->flags & ZCTLESC) == 0) {
       goto chrhdl;                    /* they're literal if not ZCTLESC */
     }
     goto eozmc;
spcchr:
     switch(zmdstt[(SHORT)ch]) {
     case CTL: goto ctl;
     case CHR: goto chr;
     case EOL: goto eol;
     case XON: goto xon;
     case XOF: goto xof;
     case ATK: goto atk;
     case _A_: goto _a_;
     case _B_: goto _b_;
     case _C_: goto _c_;
     case _O_: goto _o_;
     case ZDE: goto zde;
     case ZCC: goto zcc;
     case ZR0: goto zr0;
     case ZR1: goto zr1;
     default:
       catastro("zmdinb: Invalid state for %c [%d]\n", ch,zmdstt[(SHORT)ch]);
     }
pstatenot1:
     if (zmrscb->pstate == 0) {     /* pstate == 0 -> not expecting packets */
       goto eozmc;                         /* goto pstate0 == goto eozmc */
     }
     if (zmrscb->pstate != -1) {
       goto pstateoth;
     }
     zmrscb->pstate=1;
     ch^=0x40;
     goto pstate1;
pstateoth:                 /* |pstate| > 1 means rcving terminator & crc */
     if (zmrscb->pstate <= -1) {
       ch^=0x40;                  /* toggle bit 5 in byte after ZDLE */
       zmrscb->pstate*=-1;                         /* pstate <= |pstate| */
     }
gopacket:
     if ((bx=zmrscb->numpkt) >= ZINSIZE) {        /* is there room for byte? */
       goto pktbust;                           /* no                      */
     }
     zmrscb->packet[bx]=ch;                                 /* yes, store */
     zmrscb->numpkt=bx+1;                                        /* count */
     zmrscb->pstate++;                                     /* advance pstate */
     if (zmrscb->flags & ZGOTH32) {
       if (zmrscb->pstate == 6) {                /* to 6 if 32-bit CRC's */
            prcpkc();                              /* process packet */
       }
     }
     else if (zmrscb->pstate == 4) {                /* to 4 if 16-bit CRC's */
       prcpkc();                                      /* process packet */
     }
     goto eozmc;
pktbust:                             /* else packet buffer full */
     gbgpkt();                                            /* garbage */
     goto eozmc;
zr0:           /* ZRUB0: is this CAN + 'l' == 7F, or a literal 'l' */
     if (zmrscb->pstate < 0 || (zmrscb->numhdr > 0 &&
                    zmrscb->hdrbuf[zmrscb->numhdr] == 0x40)) {
       ch=0x7F ^ 0x40;
     }
     goto chrhdl;
zr1:                 /* ZRUB1: is this CAN + 'm' == FF, or a literal 'm' */
     if (zmrscb->pstate < 0 || (zmrscb->numhdr > 0 &&
                    zmrscb->hdrbuf[zmrscb->numhdr] == 0x40)) {
       ch=0xFF ^ 0x40;
     }
     goto chrhdl;
zcc:                  /* ZCRCE, ZCRCG, ZCRCQ, ZCRCW ('h', 'i', 'j', 'k') */
     if (zmrscb->pstate == 0) {
       if (zmrscb->numhdr >= -2) {
            goto chrhdl;
       }
       if (ftfpsp->flags & FTFXMT) {
            gbgpkt();
       }
       goto chrhdl;
     }
     if (zmrscb->pstate != -1) {
       goto chrhdl;
     }
     zmrscb->numhdr=-2;
     zmrscb->pstate=1;
     goto gopacket;
inhdr:                   /* if in the midst of receiving header */
     zmrscb->hdrbuf[zmrscb->numhdr++]^=ch;
     if (zmrscb->numhdr < zmrscb->hdrexp) {   /* if we've got a full header */
       goto nml;
     }
     bx=chkhdr();                       /* see if the header is good */
     zmrscb->numhdr=-2;
     if (bx == 0) {
       goto nml;           /* bad header - see if byte is for packet */
     }
     goto eozmc;             /* good header - bypass packet handling */
_a_:                    /* received 'A' -- beginning of header? */
     dx=8;
     if (zmrscb->numhdr >= 0) {
       goto abchdr;
     }
     zmrscb->numhdr=-2;
     goto nml;
_b_:                    /* received 'B' -- beginning of header? */
     dx=17;
     if (zmrscb->numhdr >= 0) {
       goto abchdr;
     }
     zmrscb->numhdr=-2;
     goto nml;
_c_:                    /* received 'C' -- beginning of header? */
     dx=10;
     if (zmrscb->numhdr >= 0) {
       goto abchdr;
     }
     zmrscb->numhdr=-2;
     goto nml;
zde:                                                    /* ZDLE received */
     if (zmrscb->pstate > 0) {
       zmrscb->pstate*=-1;
     }
     if (zmrscb->numhdr != -2) {
       goto notm2;
     }
     zmrscb->numhdr=-3;                                       /* -2 => -3 */
     goto eozmc;
notm2:
     if (zmrscb->numhdr != -1) {               /* -1 => 0 (could be header) */
       goto notm1;
     }
     zmrscb->numhdr=0;
     goto eozmc;
notm1:
     if (zmrscb->numhdr < 0) {
       goto caning;
     }
     if (zmrscb->numhdr == 0) {
       goto iszer;
     }
     zmrscb->hdrbuf[zmrscb->numhdr]=0x40;
     goto eozmc;             /* 1-N => escape sequence inside header */
caning:
     if (zmrscb->numhdr <= -6) {
       goto ism6;
     }
     zmrscb->numhdr--;                              /* -3 => -4 => -5 => -6 */
     goto eozmc;
ism6:
     zmrscb->numhdr=-2;
     svchdr(ZCAN);
     goto eozmc;
iszer:                                                        /* 0 => -3 */
     zmrscb->numhdr=-3;
     goto eozmc;
atk:                                                     /* '*' received */
     if (zmrscb->pstate != 0) {             /* (if expecting packets, don't */
       goto nml;                             /* even THINK about headers */
     }
     if (zmrscb->numhdr >= 0) {
       goto atkhdr;
     }
     zmrscb->numhdr=-1;
     goto eozmc;
atkhdr:
     if (zmrscb->numhdr == 0) {
       goto eozmc;         /* "* ZDLE *" is bogus as header, give up */
     }
     goto inhdr;
eol:                                             /* CR's & LF's */
     if (zmrscb->pstate == 0) {
       goto eolhdr;                       /* if expecting packets... */
     }
     if (zmrscb->flags & ZCTLESC) {                    /* if escaping CTL's */
       goto eozmc;                                /* the just ignore */
     }
     goto nml;                                     /* else store in packet */
eolhdr:                          /* but if expecting headers... */
     if (zmrscb->numhdr > 0) {
       goto inhdr;                 /* perhaps we are INSIDE a header */
     }
     zmrscb->numhdr=-2;                /* certainly not STARTING a header */
     goto eozmc;
xof:                                                    /* XOFF */
     if (nbytes == 1) {
       zmrscb->flags|=ZXOFF;
       ftfxlk(1);                            /* begin locking output */
     }
xon:
     goto eozmc;
_o_:             /* 'O' - perhaps this is the "OO" at end of receive? */
     if (ftfscb->state < ZUOOUT1) {
       goto chrhdl;       /* normal 'O' is treated like a normal 'O' */
     }
     if (ftfscb->state <= ZUOOUT1) {
       ftfscb->state=ZUOOUT2;   /* first 'O' in "OO" transits to ZUOOUT2 */
       goto eozmc;
     }
     ftfscb->state=ZUEND;           /* second 'O' in "OO" transits to ZUEND */
     nbytes--;
     goto eozmd;                          /* and we exit the byte loop! */
abchdr:            /* received A B or C - beginning or middle of header? */
     if (zmrscb->numhdr > 0) {
       goto inhdr;
     }
     zmrscb->hdrexp=dx;
     zmrscb->hdrbuf[0]=ch;
     memset(zmrscb->hdrbuf+1, 0, 17);
     zmrscb->numhdr=1;
     goto nml;
eozmc:
     if (--nbytes) {
       goto inbloop;
     }
eozmd:
     return(totbyt-nbytes);
#endif
}

INT
ftfzad(                              /* check for automatic ZMODEM download */
CHAR c)            /* call .start(), then pass all incoming characters here */
                                 /* returns 1 when download should be begun */
                                        /* global inputs: ftfpsp and ftfscb */
{
     static CHAR zadtrg[]="*\030""B00";

     if (c == '*') {
          zmrscb->zadctr=1;
     }
     else if (c == zadtrg[zmrscb->zadctr]) {
          if (++zmrscb->zadctr == 5) {
               zmrscb->zadctr=0;
               ftfpsp->start();
               ftfpsp->hdlinc('*');
               ftfpsp->hdlinc('\030');
               ftfpsp->hdlinc('B');
               ftfpsp->hdlinc('0');
               ftfpsp->hdlinc('0');
               return(1);
          }
     }
     else {
          zmrscb->zadctr=0;
     }
     return(0);
}

struct ftfpsp ftpzmr={                                  /* ZMODEM receiving */
     NULL,
     "Z",                                  /* 1-3 code letters for protocol */
     "ZMODEM",                                          /* name of protocol */
     FTFMUL+FTFXTD,                            /* protocol capability flags */
     sizeof(struct zmrdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     2048L,     /* .window   max window size (packets/bytes as appropriate) */
     1024,      /* .paksiz                        packet size 0=auto-figure */
     zmrini,    /* .initze()    Initialize this protocol (recompute scblen) */
     zmrsrt,    /* .start()                                Start a transfer */
     zmrctn,    /* .contin()              Continuously call, 1=more, 0=done */
     ftfinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     ftfabt,    /* .term()        Initiate graceful termination of transfer */
     ftfrca,    /* .abort()  Immediately unconditionally abort the transfer */
     zmdinb,    /* .hdlinb()              Handle an array of incoming bytes */
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpzmx={                               /* ZMODEM transmitting */
     NULL,
     "Z",                                  /* 1-3 code letters for protocol */
     "ZMODEM",                                          /* name of protocol */
     FTFXMT+FTFMUL+FTFXTD,                     /* protocol capability flags */
     sizeof(struct zmxdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     2048L,     /* .window   max window size (packets/bytes as appropriate) */
     1024,      /* .paksiz                        packet size 0=auto-figure */
     zmxini,    /* .initze()    Initialize this protocol (recompute scblen) */
     zmxsrt,    /* .start()                                Start a transfer */
     zmxctn,    /* .contin()              Continuously call, 1=more, 0=done */
     ftfinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     ftfabt,    /* .term()        Initiate graceful termination of transfer */
     ftfxca,    /* .abort()  Immediately unconditionally abort the transfer */
     zmdinb,    /* .hdlinb()              Handle an array of incoming bytes */
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};

struct ftfpsp ftpzrx={          /* ZMODEM transmitting w/attempted RESUMING */
     NULL,
     "ZR",                                 /* 1-3 code letters for protocol */
     "ZMODEM (resume after abort)",                     /* name of protocol */
     FTFXMT+FTFMUL+FTFXTD,                     /* protocol capability flags */
     sizeof(struct zmxdat),        /* total length of session control block */
     3*16,      /* .byttmo                             default byte timeout */
     10*16,     /* .paktmo                           default packet timeout */
     10,        /* .retrys                              default max retries */
     2048L,     /* .window   max window size (packets/bytes as appropriate) */
     1024,      /* .paksiz                        packet size 0=auto-figure */
     zmxini,    /* .initze()    Initialize this protocol (recompute scblen) */
     zxrsrt,    /* .start()                                Start a transfer */
     zmxctn,    /* .contin()              Continuously call, 1=more, 0=done */
     ftfinc,    /* .hdlinc()                       Handle one incoming byte */
     NULL,      /* .hdlins()                   Handle incoming line of text */
     ftfabt,    /* .term()        Initiate graceful termination of transfer */
     ftfxca,    /* .abort()  Immediately unconditionally abort the transfer */
     zmdinb,    /* .hdlinb()              Handle an array of incoming bytes */
     "",        /* .secur                App-specific security of some kind */
     {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
