#include <exec/types.h>
#include <exec/io.h>
#include <devices/serial.h>
#include <devices/timer.h>
#include <libraries/dos.h>
#include <proto/dos.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <ctype.h>

#include <dlg/dlgproto.h>
#include <dlg/file.h>
#include <dlg/misc.h>

#include <link/file.h>
#include <link/io.h>

#include <proto/dlg.h>

#include <pragmas/dlg.h>

#include "zmodem.h"

#define SLEEP_LOGOFF 2

extern struct Library *DLGBase;
extern struct DLGSerInfo *dsi;

struct Batch  MyBatch;
extern char   tempdownloads[];
extern char   destfile[];

extern long misses, hmdelay;
extern UBYTE batchmode, quiet;
extern BPTR fh;
extern long zbaud;
extern char *sendbuff;
extern int sendlen;
extern char *zdiskbuff;
extern char *zbuffer;
extern long zbufsize;
extern int doublebuffer;
extern int zresume,ZFlag;
extern char sbuff[];
extern int Online_NFiles;
extern long TTEFF,TTCPS,TTTM,TBT;
extern long ZFlagSize;

int noxrd7(void);
int zdlread(void);
int zgethex(void);
int zrhhdr(char *);
int zrbhdr(char *);
int zrbhdr32(char *);
int zgethdr(char *);
int zrdata(char *, int );
int zrdat32(char *, int );
long rclhdr(char *);
void zsendbyte(int );
void zputhex(int );
void zmputs(char *);
void zsda32(char *, int , int );
void zsdata(char *, int , int );
void zsbh32(int , char *);
void zshhdr(int , char *);
void zsbhdr(int , char *);
void stohdr(long );
void ackbibi(void);
void canit(void);
void putsec(void);
int tryz(void);
int rzfiles(void);
int rzfile(void);
int procheader(char *);
char * GetNextFile(char *);
int getzrxinit(void);
int zsendfile(char *, int );
int zsendfdata(void);
int getinsync(void);
int saybibi(void);


struct SearchCookie *cookie=NULL;

int tryzhdrtype;  /* Header type to send corresponding to Last rx close */
int Zctlesc;      /* Don't escape control characters */

char txbuf[1024];

long  chinseg;
char  ZPathname[512];
char  ZFilename[256];
ULONG filesize;
long  resumesize;
int   Rxbuflen;
int   Rxflags;

int   Rxframeind;    /* ZBIN ZBIN32, or ZHEX type of frame received */
int   Rxtype;     /* Type of header received */
int   Rxcount;    /* Count of data bytes received */
char  Rxhdr[4];      /* Received header */
char  Txhdr[4];      /* Transmitted header */
long  Rxpos;      /* Received file position */
long  Txpos;      /* Transmitted file position */
int   Txfcs32;    /* TRUE means send binary frames with 32 bit FCS */
int   Crc32t;     /* Display flag indicating 32 bit CRC being sent */
int   Crc32;      /* Display flag indicating 32 bit CRC being received */
char  Attn[ZATTNLEN+1]; /* Attention string rx sends to tx on err */
int   lastsent;      /* Last char we sent */
int   zsrCDstat;
FILE *f;
UBYTE fstfile = TRUE;


/*
 * ZMODEM protocol primitives
 * Chuck Forsberg Omen Technology Inc.
 *
 * Entry point Functions:
 * zsbhdr(type, hdr) send binary header
 * zshhdr(type, hdr) send hex header
 * zgethdr(hdr, eflag) receive header - binary or hex
 * zsdata(buf, len, frameend) send data
 * zrdata(buf, len) receive data
 * stohdr(pos) store position data in Txhdr
 * long rclhdr(hdr) recover position offset from header
 */

/*
 * Read a character from the modem line with timeout.
 *  Eat parity, XON and XOFF characters.
 */

int noxrd7(void)
{
  register int c;

  for (;;) {
    if ((c = readbyte()) < 0) return(c);
    switch (c &= 0x7F) {
    case XON:
    case XOFF:
      continue;
    default:
      if (Zctlesc && !(c & 0x60)) continue;
    case '\r':
    case '\n':
    case ZDLE: return c;
    }
  }
}

/*
 * Read a byte, checking for ZMODEM escape encoding
 *  including CAN*5 which represents a quick abort
 */

int zdlread(void)
{
  register int c;

again:
   /* Quick check for non control characters */
  if ((c = readbyte()) & 0x60) return(c);

  switch (c) {
    case ZDLE:
      break;
    case 0x13:
    case 0x93:
    case 0x11:
    case 0x91:
      goto again;
    default:
      if (Zctlesc && !(c & 0x60)) goto again;
      return c;
  }

again2:
  if ((c = readbyte()) < 0) return(c);
  if (c == CAN && (c = readbyte()) < 0) return(c);
  if (c == CAN && (c = readbyte()) < 0) return(c);
  if (c == CAN && (c = readbyte()) < 0) return(c);

  switch (c) {
    case CAN:
      return GOTCAN;
    case ZCRCE:
    case ZCRCG:
    case ZCRCQ:
    case ZCRCW:
      return (c | GOTOR);
    case ZRUB0:
      return 0x7F;
    case ZRUB1:
      return 0xFF;
    case 0x13:
    case 0x93:
    case 0x11:
    case 0x91:
      goto again2;
    default:
      if (Zctlesc && ! (c & 0x60)) goto again2;
      if ((c & 0x60) ==  0x40) return(c ^ 0x40);
      break;
  }

  sprintf(sbuff, "Bad escape sequence %x", c);
  SendMsg(sbuff);
  return(ERROR);
}

/* Decode two lower case hex digits into an 8 bit byte value */
int zgethex(void)
{
  register int c, n;

  if((c=noxrd7())<0) return(c);

  n = c - '0';

  if (n > 9) n -= ('a' - ':');

  if (n & ~0xF) return(ERROR);

  if ((c = noxrd7()) < 0) return(c);

  c -= '0';

  if (c > 9) c -= ('a' - ':');

  if (c & ~0xF) return(ERROR);

  c += (n<<4);

  return(c);
}

/* Receive a hex style header (type and position) */

int zrhhdr(char *hdr)
{
  register int c;
  register unsigned short crc;
  register int n;

  if ((c = zgethex()) < 0) return(c);
  Rxtype = c;
  crc = update_crc16(c, 0);

  for (n=4; --n >= 0; ++hdr) {
    if ((c = zgethex()) < 0) return(c);
    crc = update_crc16(c, crc);
    *hdr = c;
  }

  if ((c = zgethex()) < 0) return(c);
  crc = update_crc16(c, crc);
  if ((c = zgethex()) < 0) return(c);
  crc = update_crc16(c, crc);

  if (crc & 0xFFFF) {
    SendMsg("Bad CRC"); return ERROR;
  }

  switch ( c = readbyte()) {
    case 0x8D:
    case 0xD:
   /* Throw away possible cr/lf */
      readbyte();
  }

  return(Rxtype);
}

/* Receive a binary style header (type and position) */
int zrbhdr(char *hdr)
{
  register int c, n;
  register unsigned short crc;

  if ((c = zdlread()) & ~0xFF) return(c);
  Rxtype = c;
  crc = update_crc16(c, 0);

  for (n=4; --n >= 0; ++hdr) 
  {
    if ((c = zdlread()) & ~0xFF) return(c);
    crc = update_crc16(c, crc);
    *hdr = c;
  }

  if ((c = zdlread()) & ~0xFF) return(c);
  crc = update_crc16(c, crc);
  if ((c = zdlread()) & ~0xFF) return(c);
  crc = update_crc16(c, crc);
  if (crc & 0xFFFF) 
  {
    SendMsg("Bad CRC");
    return(ERROR);
  }

  return(Rxtype);
}

/* Receive a binary style header (type and position) with 32 bit FCS */
int zrbhdr32(char *hdr)
{
  register int c, n;
  register unsigned long crc;

  if ((c = zdlread()) & ~0xFF) return(c);
  Rxtype = c;
  crc = 0xFFFFFFFFL; crc = update_crc32(c, crc);

  for (n=4; --n >= 0; ++hdr) 
  {
    if ((c = zdlread()) & ~0xFF) return(c);
    crc = update_crc32(c, crc);
    *hdr = c;
  }

  for (n=4; --n >= 0;) 
  {
    if ((c = zdlread()) & ~0xFF) return(c);
    crc = update_crc32(c, crc);
  }

  if (crc != 0xDEBB20E3L) 
  {
    SendMsg("Bad CRC");
    return(ERROR);
  }

  return(Rxtype);
}

/*
 * Read a ZMODEM header to hdr, either binary or hex.
 *  eflag controls local display of non zmodem characters:
 * 0:  no display
 * 1:  display printing characters only
 * 2:  display all non ZMODEM characters
 *   set Rxpos and return type of header.
 *   Otherwise return negative on error.
 *   Return ERROR instantly if ZCRCW sequence, for fast error recovery.
 */

int zgethdr(char *hdr)
{
  register int c, n, cancount;

  n = 1400 + zbaud;  /* Max bytes before start of frame */
  Rxframeind = Rxtype = 0;

startover:
  cancount = 5;

again:
  /* Return immediate ERROR if ZCRCW sequence seen */
  switch (c = readbyte()) 
  {
    case RCDO:
    case TIMEOUT:
    {
      zsrCDstat=CheckCarrier();
      if(zsrCDstat==FALSE)
      {
   return NOCARRERROR;
      }
      goto fifi;
    }
    case CAN:
gotcan:
      if (--cancount <= 0) 
      {
   c = ZCAN;
   goto fifi;
      }
      switch (c = readbyte()) 
      {
   case TIMEOUT:
     goto again;
   case ZCRCW:
     c = ERROR;
   /* **** FALL THRU TO **** */
   case RCDO:
     goto fifi;
   default:
     break;
   case CAN:
     if (--cancount <= 0) 
     {
       c = ZCAN; goto fifi;
     }
     goto again;
      }
   /* **** FALL THRU TO **** */
      default:
agn2:
   if ( --n == 0) 
   {
     SendMsg("Garbage count exceeded");
     return(ERROR);
   }
   goto startover;
      case ZPAD|0x80:      /* This is what we want. */
      case ZPAD:     /* This is what we want. */
   break;
  }
  cancount = 5;
splat:

  switch (c = noxrd7()) 
  {
    case ZPAD:
      goto splat;
    case RCDO:
    case TIMEOUT:
      goto fifi;
    default:
      goto agn2;
    case ZDLE:    /* This is what we want. */
      break;
  }

  switch (c = noxrd7()) 
  {
    case RCDO:
    case TIMEOUT:
      goto fifi;
    case ZBIN:
      Rxframeind = ZBIN;  Crc32 = FALSE;
      c =  zrbhdr(hdr);
      break;
    case ZBIN32:
      Crc32 = Rxframeind = ZBIN32;
      c =  zrbhdr32(hdr);
      break;
    case ZHEX:
      Rxframeind = ZHEX;  Crc32 = FALSE;
      c =  zrhhdr(hdr);
      break;
    case CAN:
      goto gotcan;
    default:
      goto agn2;
  }

  Rxpos = hdr[ZP3] & 0xFF;
  Rxpos = (Rxpos<<8) + (hdr[ZP2] & 0xFF);
  Rxpos = (Rxpos<<8) + (hdr[ZP1] & 0xFF);
  Rxpos = (Rxpos<<8) + (hdr[ZP0] & 0xFF);

fifi:
  switch (c) 
  {
    case GOTCAN:
      c = ZCAN;
/* **** FALL THRU TO **** */
    case ZNAK:
    case ZCAN:
    case ERROR:
    case TIMEOUT:
    case RCDO:
/* **** FALL THRU TO **** */
    default:
      break;
  }

  return(c);
}

/*
 * Receive array buf of max length with ending ZDLE sequence
 *  and CRC.  Returns the ending character or error code.
 *  NB: On errors may store length+1 bytes!
 */
zrdata(char *buf,int length)
{
  register int c, d;
  register unsigned short crc;
  register char *end;

  if (Rxframeind == ZBIN32) return zrdat32(buf, length);

  crc = Rxcount = 0;  end = buf + length;

  while (buf <= end) 
  {
    if ((c = zdlread()) & ~0xFF) 
    {
crcfoo:
      switch (c) 
      {
   case GOTCRCE:
   case GOTCRCG:
   case GOTCRCQ:
   case GOTCRCW:
     crc = update_crc16(d=c, crc);
     if ((c = zdlread()) & ~0xFF) goto crcfoo;
     crc = update_crc16(c, crc);
     if ((c = zdlread()) & ~0xFF) goto crcfoo;
     crc = update_crc16(c, crc);
     if (crc & 0xFFFF) 
     {
       SendMsg("Bad CRC");
       return(ERROR);
     }
     Rxcount = length - (end - buf);
     return(d);
   case GOTCAN:
     SendMsg("Sender canceled transfer");
     return(ZCAN);
   case TIMEOUT:
     zsrCDstat=CheckCarrier();
     if(zsrCDstat==FALSE)
     {
       return(ERROR);
     }
     SendMsg("TIMEOUT");
     return(c);
   default:
     SendMsg("Bad data subpacket");
     return(c);
      }
    }

    *buf++ = c;
    crc = update_crc16(c, crc);
  }

  SendMsg("Data subpacket too long");
  return(ERROR);
}

int zrdat32(char *buf,int length)
{
  register int c, d;
  register unsigned long crc;
  register char *end;

  crc = 0xFFFFFFFFL;  Rxcount = 0;  end = buf + length;
  while (buf <= end) 
  {
    if ((c = zdlread()) & ~0xFF) 
    {
crcfoo:
      switch (c) 
      {
   case GOTCRCE:
   case GOTCRCG:
   case GOTCRCQ:
   case GOTCRCW:
     crc = update_crc32(d=c, crc);
     if ((c = zdlread()) & ~0xFF) goto crcfoo;
     crc = update_crc32(c, crc);
     if ((c = zdlread()) & ~0xFF) goto crcfoo;
     crc = update_crc32(c, crc);
     if ((c = zdlread()) & ~0xFF) goto crcfoo;
     crc = update_crc32(c, crc);
     if ((c = zdlread()) & ~0xFF) goto crcfoo;
     crc = update_crc32(c, crc);
     if (crc != 0xDEBB20E3L) 
     {
       SendMsg("Bad CRC");
       return(ERROR);
     }
     Rxcount = length - (end - buf);
     return(d);
   case GOTCAN:
     SendMsg("Sender canceled transfer");
     return(ZCAN);
   case TIMEOUT:
     zsrCDstat=CheckCarrier();
     if(zsrCDstat==FALSE)
     {
       return(ERROR);
     }
     SendMsg("TIMEOUT");
     return(c);
   default:
     SendMsg("Bad data subpacket");
     return(c);
      }
    }

    *buf++ = c;
    crc = update_crc32(c, crc);
  }

  SendMsg("Data subpacket too long");
  return(ERROR);
}

/* Recover a long integer from a header */

long rclhdr(char *hdr)
{
  register long l;

  l = (hdr[ZP3] & 0xFF);
  l = (l << 8) | (hdr[ZP2] & 0xFF);
  l = (l << 8) | (hdr[ZP1] & 0xFF);
  l = (l << 8) | (hdr[ZP0] & 0xFF);
  return(l);
}

/*
 * Send character c with ZMODEM escape sequence encoding.
 *  Escape XON, XOFF. Escape CR following @ (Telenet net escape)
 */

void zsendbyte(int c)
{
  /* Quick check for non control characters */
  if (c & 0x60) sendbyte(lastsent = c);
  else 
  {
    switch (c &= 0xFF) 
    {
      case ZDLE:
   sendbyte(ZDLE);
   sendbyte(lastsent = (c ^= 0x40));
   break;
      case 0xD:
      case 0x8D:
   if (!Zctlesc && (lastsent & 0x7F) != '@') goto sendit;
/* **** FALL THRU TO **** */
      case 0x10:
      case 0x11:
      case 0x13:
      case 0x90:
      case 0x91:
      case 0x93:
   sendbyte(ZDLE);
   c ^= 0x40;
sendit:
   sendbyte(lastsent = c);
   break;
      default:
   if (Zctlesc && ! (c & 0x60)) 
   {
     sendbyte(ZDLE);
     c ^= 0x40;
   }
   sendbyte(lastsent = c);
    }
  }
}

/* Send a byte as two hex digits */

void zputhex(int c)
{
  static char digits[] = "0123456789abcdef";

  sendbyte(digits[(c&0xF0)>>4]);
  sendbyte(digits[(c)&0xF]);
}

/*
 * Send a string to the modem
 */
void zmputs(char *s)
{
 while(*s) sendbyte(*s++);
}

void zsda32(char *buf,int length,int frameend)
{
  register int c;
  register unsigned long crc;

  crc = 0xFFFFFFFFL;
  for (;--length >= 0; ++buf) 
  {
    c = *buf & 0xFF;
    if (c & 0x60) sendbyte(lastsent = c);
    else zsendbyte(c);
    crc = update_crc32(c, crc);
  }

  sendbyte(ZDLE); sendbyte(frameend);
  crc = update_crc32(frameend, crc);

  crc = ~crc;
  for (length=4; --length >= 0;)
  {
    zsendbyte((int)crc);
    crc >>= 8;
  }
}

/*
 * Send binary array buf of length length, with ending ZDLE sequence frameend
 */

void zsdata(char*buf,int length,int frameend)
{
  register unsigned short crc;

  if (Crc32t) zsda32(buf, length, frameend);
  else 
  {
    crc = 0;
    for (;--length >= 0; ++buf) 
    {
      zsendbyte(*buf); crc = update_crc16(*buf, crc);
    }
    sendbyte(ZDLE); sendbyte(frameend);
    crc = update_crc16(frameend, crc);

    crc = update_crc16(0, crc);
    crc = update_crc16(0, crc);
    zsendbyte(crc>>8); zsendbyte(crc);
  }

  if (frameend == ZCRCW) sendbyte(XON);
}

/* Send ZMODEM binary header hdr of type type */

void zsbh32(int type,char *hdr)
{
  register int n;
  register unsigned long crc;

  sendbyte(ZBIN32);  zsendbyte(type);
  crc = 0xFFFFFFFFL; crc = update_crc32(type, crc);

  for (n=4; --n >= 0; ++hdr) 
  {
    crc = update_crc32(*hdr, crc);
    zsendbyte(*hdr);
  }
  crc = ~crc;
  for (n=4; --n >= 0;) 
  {
    zsendbyte((int)crc);
    crc >>= 8;
  }
}

/* Send ZMODEM HEX header hdr of type type */
void zshhdr(int type,char *hdr)
{
  register int n;
  register unsigned short crc;

  sendbyte(ZPAD); sendbyte(ZPAD); sendbyte(ZDLE); sendbyte(ZHEX);
  zputhex(type);
  Crc32t = 0;

  crc = update_crc16(type, 0);
  for (n=4; --n >= 0; ++hdr) 
  {
    zputhex(*hdr); crc = update_crc16(*hdr, crc);
  }

  crc = update_crc16(0, crc);
  crc = update_crc16(0, crc);
  zputhex(crc>>8); zputhex(crc);

  /* Make it printable on remote machine */
  sendbyte(0xD); sendbyte(0x8A);

  /*
   * Uncork the remote in case a fake XOFF has stopped data flow
   */
  if (type != ZFIN && type != ZACK) sendbyte(XON);
}

/* Send ZMODEM binary header hdr of type type */

void zsbhdr(int type,char *hdr)
{
  register int n;
  register unsigned short crc;

  sendbyte(ZPAD); sendbyte(ZDLE);

  if (Crc32t=Txfcs32) zsbh32(type, hdr);
  else 
  {
    sendbyte(ZBIN); zsendbyte(type); crc = update_crc16(type, 0);

    for (n=4; --n >= 0; ++hdr) 
    {
      zsendbyte(*hdr);
      crc = update_crc16(*hdr, crc);
    }

    crc = update_crc16(0, crc);
    crc = update_crc16(0, crc);
    zsendbyte(crc>>8);
    zsendbyte(crc);
  }
}

/* Store long integer pos in Txhdr */

void stohdr(long pos)
{
  Txhdr[ZP0] = pos;
  Txhdr[ZP1] = pos>>8;
  Txhdr[ZP2] = pos>>16;
  Txhdr[ZP3] = pos>>24;
}

/*
 * Ack a ZFIN packet, let byegones be byegones
 */

void ackbibi(void)
{
  register int n;

  stohdr(0L);
  for (n=3; --n>=0; ) 
  {
    purgeline();
    zshhdr(ZFIN, Txhdr);
    switch (readbyte()) 
    {
      case 'O':
   readbyte(); /* Discard 2nd 'O' */
   return;
      case RCDO:
   return;
      case TIMEOUT:
      default:
   break;
    }
  }
}

void canit(void)
{
  static char canistr[] =
  {
    24,24,24,24,24,24,24,24,24,24,8,8,8,8,8,8,8,8,8,8,0
  };

  zmputs(canistr);
}

/*
 * Putsec writes the n characters of buf to receive file fh
 * remember to put in write err checking in here... then longjmp.. 
 */

void putsec(void)
{
  if(doublebuffer)
  {
    WriteAsync(zbuffer, chinseg);
    if(zbuffer == zdiskbuff) zbuffer += 10240L;
    else zbuffer = zdiskbuff;
  }
  else Write(fh, zbuffer, chinseg);

  chinseg = 0L;
}  


BOOL zmodemreceive(char *flname)    /* NEED pathname!!! */

{register c;

 strcpy(ZPathname, flname);

 tryzhdrtype = ZRINIT;
 Zctlesc     = FALSE;

 if (c=tryz()) 
    {if (c == ZCOMPL)  return(TRUE);

     if (c == ERROR)
        {canit();
         Aclose();
         return(FALSE);
        }

     if (c == NOCARRERROR)  return(FALSE);

     if (c == ZFILE)
        {if (c = rzfiles())
            {canit();
             Aclose();
             return(FALSE);
            }

         return(TRUE);
        }
    }

 return(FALSE);
}

/*
 * Initialize for Zmodem receive attempt, try to activate Zmodem sender
 *  Handles ZSINIT  frame
 *  Return ZFILE if Zmodem filename received, -1 on error,
 *   ZCOMPL if transaction finished,  else 0
 */

int tryz(void)

{register int c, n;

 for(n=10; --n>=0; ) 
    {if (check_abort())     return(ERROR);
     zsrCDstat = CheckCarrier();
     if (zsrCDstat==FALSE)  return(NOCARRERROR);

     /* Set buffer length (0) and capability flags */
     stohdr(0);
     Txhdr[ZF0] = CANFC32|CANFDX|CANOVIO|CANBRK;
     zshhdr(tryzhdrtype, Txhdr);
     if (tryzhdrtype == ZSKIP)   /* Don't skip too far */
         tryzhdrtype = ZRINIT;

     again:  switch(zgethdr(Rxhdr)) 
                   {case NOCARRERROR:  return(NOCARRERROR);

                    case ZRQINIT:      continue;

                    case ZEOF:         continue;

                    case TIMEOUT:      continue;

                    case ZFILE:        tryzhdrtype = ZRINIT;
                                       c = zrdata(zbuffer, 1024);
                                       if (c==ZCAN)       return(ERROR);
                                       if (c == GOTCRCW)  return(ZFILE);
                                       zshhdr(ZNAK, Txhdr);
                                       goto again;

                    case ZSINIT:       Zctlesc = TESCCTL & Rxhdr[ZF0];
                                       c       = zrdata(Attn, ZATTNLEN);
                                       if (c == GOTCRCW)
                                          {stohdr(1L);
                                           zshhdr(ZACK, Txhdr);
                                           goto again;
                                          }
                                       if (c==ZCAN) return(ERROR);

                                       zshhdr(ZNAK, Txhdr);
                                       goto again;

                    case ZFREECNT:     stohdr(1000000L);
                                       zshhdr(ZACK, Txhdr);
                                       goto again;
                    case ZCOMMAND:     return(ERROR);

                    case ZCOMPL:       goto again;

                    case ZFIN:         ackbibi();
                                       return(ZCOMPL);

                    case ZCAN:         return(ERROR);

                    default:           continue;
                   }
    }

 return(0);
}

/*
 * Receive 1 or more files with ZMODEM protocol
 */
int rzfiles(void)

{register int c;

 for(;;) 
    {if (check_abort())     return ERROR;
     zsrCDstat = CheckCarrier();
     if (zsrCDstat==FALSE)  return(ERROR);

     switch(c = rzfile())
           {case ZEOF:
            case ZSKIP:  switch(tryz()) 
                               {case ZCOMPL:  return(OK);

                                case ZFILE:   break;

                                default:      return(ERROR);
                               }
                         continue;

            case ERROR:  return(c);

            default:     return(c);
           }
    }
}


/*
 * Receive a file with ZMODEM protocol
 *  Assumes file name frame is in buffer
 */

int rzfile(void)

{register int c, n;
 long     rxbytes;

 if (procheader(zbuffer) == ERROR) return(tryzhdrtype = ZSKIP);

 n       = 10;
 rxbytes = resumesize;

 for(;;) 
  {
    chinseg = 0L;
    stohdr(rxbytes);
    zshhdr(ZRPOS, Txhdr);
nxthdr:
    if(check_abort())
    {
      putsec();
      return(ERROR);
    }

    switch (c = zgethdr(Rxhdr)) 
    {
      default:
      case ZNAK:
      case TIMEOUT:
   putsec();
   zsrCDstat=CheckCarrier();
   if(zsrCDstat==FALSE)
   {
     return(ERROR);
   }
   if ( --n < 0)
   {
     return(ERROR);
   }
      case ZFILE:
   c=zrdata(zbuffer, 1024);
   if(c==ZCAN) return(ERROR);
   continue;
      case ZEOF:
   putsec();
   if (rclhdr(Rxhdr) != rxbytes) goto nxthdr;

/*
 * Ignore eof if it's at wrong place - force
 *  a timeout because the eof might have gone
 *  out before we sent our zrpos.
 */

   ++Online_NFiles;
   Aclose(); 
   return c;
      case ERROR: /* Too much garbage in header search error */
   putsec();
   if ( --n < 0) 
   {
     return(ERROR);
   }
   zmputs(Attn);
   continue;
      case ZSKIP:
   putsec();
   Aclose();
   return c;
      case ZDATA:
   if (rclhdr(Rxhdr) != rxbytes) 
   {
     if ( --n < 0) return(ERROR);
     putsec();
     zmputs(Attn);
     continue;
   }
moredata:
   if(check_abort())
   {
     putsec();
     return(ERROR);
   }

   if(chinseg >= ((doublebuffer) ? 8192L : 16384L)) putsec();

   switch (c = zrdata(zbuffer+chinseg, 1024))
   {
     case ZCAN:
       putsec();
       return(ERROR);
     case ERROR:  /* CRC error */
       putsec();
       if ( --n < 0) 
       {
         SendMsg("Too many errors");
         return(ERROR);
       }
       zmputs(Attn);
       continue;
     case TIMEOUT:
       putsec();
       zsrCDstat=CheckCarrier();
       if(zsrCDstat==FALSE) return(ERROR);
       if ( --n < 0) 
       {
         SendMsg("Too many errors");
         return(ERROR);
       }
       continue;
     case GOTCRCW:
       n = 10;
       chinseg += Rxcount;
       rxbytes += Rxcount;
       stohdr(rxbytes);
       zshhdr(ZACK, Txhdr);
       sendbyte(XON);
       if(!quiet) DLGProtoStatus(dsi,NULL,rxbytes,NULL);
       goto nxthdr;
     case GOTCRCQ:
       n = 10;
       chinseg += Rxcount;
       rxbytes += Rxcount;
       stohdr(rxbytes);
       zshhdr(ZACK, Txhdr);
       if(!quiet) DLGProtoStatus(dsi,NULL,rxbytes,NULL);
       goto moredata;
     case GOTCRCG:
       n = 10;
       chinseg += Rxcount;
       rxbytes += Rxcount;
       if(!quiet) DLGProtoStatus(dsi,NULL,rxbytes,NULL);
       goto moredata;
     case GOTCRCE:
       n = 10;
       chinseg += Rxcount;
       rxbytes += Rxcount;
       if(!quiet) DLGProtoStatus(dsi,NULL,rxbytes,NULL);
       goto nxthdr;
   }
    }
  }
}


/*
 * Process incoming file information header
 */
int procheader(char *name)

{register char *p;
 BPTR           l;
 char           fullname[512];

 strcpy(fullname, ZPathname);
 strcat(fullname, FilePart(name));

 filesize = 0;
 p        = name + 1 + strlen(name);
 if (*p) sscanf(p, "%ld", &filesize);

 l = Lock(fullname, ACCESS_READ);
 if (l)
     UnLock(l);
   else
     zresume = FALSE;

 if (Exists(fullname))  ResetComment(fullname);

 if (!Aopen(fullname, (long)((zresume) ? MODE_OLDFILE : MODE_NEWFILE)))
    {SendMsg("Unable to open output file");
     return(ERROR);
    }

 Seek(fh, 0, OFFSET_END);
 resumesize = Seek(fh, 0L, OFFSET_CURRENT);

 if (!quiet) DLGProtoStatus(dsi,filesize,NULL,NULL);

 ZFlagSize = (filesize-resumesize);
 return(OK);
}

/* flname = full file name(s) */

BOOL zmodemsend(char *flname)

{int            rc;
 register char *q, *r, *fn;

 fn = GetNextFile(flname);
 if (!fn)                  return(FALSE);
 if (getzrxinit()==ERROR)  return(FALSE);

 for(;;)
    {FileSize(fn, &filesize);
     if (!(fh = Open(fn, MODE_OLDFILE)))
           return(FALSE);

     r = FilePart(fn);
     for(q = txbuf; *r; r++)
        *q++ = tolower(*r);
     r = q;
     r++;

     while(q < (txbuf + 1024))
          *q++ = 0;

     sprintf(r, "%ld %lo %o", filesize, 0, 0);

     if (!quiet)  DLGProtoStatus(dsi,filesize,NULL,NULL);

     rc = zsendfile(txbuf, 2+strlen(r)+strlen(txbuf));
     Close(fh);

     if (rc == ERROR)
        {canit();
         return(FALSE);
        }

   ++Online_NFiles;
     if (!(fn = GetNextFile(flname)))
        {saybibi();
         return(TRUE);
        }
    }
}


char *GetNextFile(char *fname)

{BPTR    lock;
 char   *name;
 char   *rname;

 if (!batchmode)
    {if (!strchr(fname, '?')  &&  !strchr(fname, '*'))
        {if (!fstfile)  return(NULL);
         fstfile = FALSE;
         strcpy(ZPathname, fname);
         return(ZPathname);
        }

     name = FilePart(fname);

     if (name != fname)
        {strncpy(ZPathname, fname, name - fname);
         ZPathname[name - fname + 1] = 0;
         if (ZPathname[name - fname] == '/') ZPathname[name - fname] = 0;
         if (!cookie)  cookie = SearchStart(ZPathname, name);
        }
      else
         if (!cookie)  cookie = SearchStart(NULL, fname);

     rname = SearchNext(cookie);
     if (rname)
        {strncpy(ZPathname, fname, name - fname);
         ZPathname[name - fname + 1] = 0;
         strcat(ZPathname, rname);
         return(ZPathname);
        }

     SearchEnd(cookie);
     cookie=NULL;
     return(NULL);
    }

 if (!fstfile)
    {DeleteStruct(fname,MyBatch.filename,sizeof(struct Batch),40);
     if (MyBatch.free & TEMP_DOWNLOAD)  DeleteFile(destfile);
    }
 fstfile = FALSE;

 if (GetFirstStruct(fname,(char *)&MyBatch,sizeof(MyBatch))==-1)  return(NULL);

 strcpy(ZPathname, MyBatch.path);
 strcat(ZPathname, MyBatch.filename);

 if (MyBatch.free & TEMP_DOWNLOAD)
    {if (!Exists(tempdownloads))
        {if (!(lock = CreateDir(tempdownloads)))  return(FALSE);
         UnLock(lock);
        }

     ASPrintf(NULL, destfile, "%s/%s", tempdownloads, MyBatch.filename);
     if (Copy(ZPathname, destfile))               return(FALSE);
     strcpy(ZPathname, destfile);
    }

 return(ZPathname);
}


/*
 * Get the receiver's init parameters
 */
int getzrxinit(void)
{
  int n;

  zmputs("rz\r");
  stohdr(0L);
  zshhdr(ZRQINIT, Txhdr);

  for (n=10; --n>=0; )
  {
    if(check_abort()) return(ERROR);

    switch (zgethdr(Rxhdr))
    {
      case ZCHALLENGE:  /* Echo receiver's challenge numbr */
   stohdr(Rxpos);
   zshhdr(ZACK, Txhdr);
   continue;
      case ZCOMMAND:    /* They didn't see out ZRQINIT */
   stohdr(0L);
   zshhdr(ZRQINIT, Txhdr);
   continue;
      case ZRINIT:
   Rxflags = 0xFF & Rxhdr[ZF0];
   Txfcs32 = (Rxflags & CANFC32);
   Zctlesc |= Rxflags & TESCCTL;
   Rxbuflen = (0xFF & Rxhdr[ZP0])+((0xFF & Rxhdr[ZP1])<<8);

   return(OK);
      case ZCAN:
   return ERROR;
      case TIMEOUT:
   zsrCDstat=CheckCarrier();
   if(zsrCDstat==FALSE) return ERROR;
   stohdr(0L);
   zshhdr(ZRQINIT, Txhdr);
   continue;
      case ZRQINIT:
   if (Rxhdr[ZF0] == ZCOMMAND) continue;
      default:
   zshhdr(ZNAK, Txhdr);
   continue;
    }
  }

  return(ERROR);
}

/* Send file name and related info */

int zsendfile(char *buf,int blen)
{
  register int c, n;

  for (n=5; --n>=0;)
  {
    if(check_abort()) return(ERROR);

    Txhdr[ZF0] = 0;  /* file conversion request */
    Txhdr[ZF1] = 0;  /* file management request */
    Txhdr[ZF2] = 0;  /* file transport request */
    Txhdr[ZF3] = 0;
    zsbhdr(ZFILE, Txhdr);
    zsdata(buf, blen, ZCRCW);
again:
    c = zgethdr(Rxhdr);
    switch (c)
    {
      case ZRINIT:
   while ((c = readbyte()) > 0)
     if (c == ZPAD) goto again;
      case ZCAN:
      case ZABORT:
      case ZFIN:
   return(ERROR);
      case ZSKIP:
   return(c);
      case ZRPOS:
   Seek(fh, Rxpos, OFFSET_BEGINNING);
   Txpos = Rxpos;
   purgeline();
   return(zsendfdata());
      case TIMEOUT:
   zsrCDstat=CheckCarrier();
   if(zsrCDstat==FALSE) return(ERROR);
      default:
   continue;
    }
  }

  return(ERROR);
}

/* Send the data in the file */

int zsendfdata(void)
{
  register int c, e, n;
  int newcnt, goodblks = 0, goodneeded = 1;
  int blklen, maxblklen;

  blklen = maxblklen = 1024;
  if(Rxbuflen && Rxbuflen < maxblklen)
  blklen = maxblklen = Rxbuflen;

somemore:
  if(checkline())
  {
waitack:
    switch (c = getinsync()) 
    {
      default:
      case ZCAN:
   return(ERROR);
      case ZSKIP:
   return(c);
      case ZACK:
   break;
      case ZRPOS:
   blklen = (blklen>>1 > 64) ? blklen>>1 : 64;
   goodblks = 0;
   goodneeded = (goodneeded<<1 < 64) ? goodneeded<<1 : 64;
   break;
      case ZRINIT:
   return(OK);
    }

    while (checkline())
    {
      switch (readbyte())
      {
   case CAN:
   case ZPAD:
     SendMsg("Received ERROR");
     goto waitack;
      }
    }
  }

  newcnt = Rxbuflen;
  stohdr(Txpos);
  zsbhdr(ZDATA, Txhdr);

  do 
  {
    if(check_abort())
    {
      waitwrite();
      return(ERROR);
    }

    n = Read(fh, txbuf, (long)blklen);
    if (n < blklen) e = ZCRCE;
    else if (Rxbuflen && (newcnt -= n) <= 0) e = ZCRCW;
    else e = ZCRCG;
   
    sendbuff = zbuffer; /* tell sendbyte() to buffer the bytes in buffer */
    sendlen = 0;     /* sendlen is byte buffer count */
    zsdata(txbuf, n, e);
    sendbuff = 0L;

    while(checkline())
    {
      switch (readbyte())
      {
   case CAN:
   case ZPAD:
     purgewrite();
     SendMsg("Received ERROR");
     goto waitack;
      }
    }

    waitwrite();
    zsrCDstat=CheckCarrier();
    if(zsrCDstat==FALSE) return(ERROR);
    sendbuffer(zbuffer, sendlen);
    if(zbuffer == zdiskbuff) zbuffer += 10240L;
    else zbuffer = zdiskbuff;

    Txpos += n;

    if(!quiet) DLGProtoStatus(dsi,NULL,Txpos,NULL);

    if(blklen < maxblklen && ++goodblks > goodneeded)
    {
      blklen = (blklen<<1 < maxblklen) ? blklen<<1 : maxblklen;
      goodblks = 0;
      goodneeded = (goodneeded>>1 > 1) ? goodneeded>>1 : 1;
    }

    if (e == ZCRCW)
    {
      waitwrite();   
      goto waitack;
    }

  }
  while(e == ZCRCG);

  waitwrite();

  for (;;)
  {
    if(check_abort()) return(ERROR);

    stohdr(Txpos);
    zsbhdr(ZEOF, Txhdr);
    switch(getinsync())
    {
      case ZACK:
   continue;
      case ZRPOS:
   SendMsg("Received ERROR");
   goto somemore;
      case ZRINIT:
   return(OK);
      case ZSKIP:
   return(c);
      default:
   return(ERROR);
    }
  }
}

/*
 * Respond to receiver's complaint, get back in sync with receiver
 */

int getinsync(void)
{
  register int c, n;

  for (n = 5; --n>=0;)
  {
    c = zgethdr(Rxhdr);
    purgeline();
    switch(c)
    {
      case ZCAN:
      case ZABORT:
      case ZFIN:
   return(ERROR);
      case ZRPOS:
/* ************************************* */
/*  If sending to a buffered modem, you  */
/*   might send a break at this point to */
/*   dump the modem's buffer.     */
   SendBreak();
   Seek(fh, Rxpos, OFFSET_BEGINNING);
   Txpos = Rxpos;
   return(c);
      case ZACK:
   if (Txpos == Rxpos) return(c);
   continue;
      case ZRINIT:
      case ZSKIP:
   return(c);
      case TIMEOUT:
   zsrCDstat=CheckCarrier();
   if(zsrCDstat==FALSE) return(ERROR);
   continue;
      case ERROR :
      default:
   zsbhdr(ZNAK, Txhdr);
   continue;
    }
  }

  return(ERROR);
}

/* Say "bibi" to the receiver, try to do it cleanly */

int saybibi(void)
{
  int n;
   
  for (n = 5; --n>=0;)
  {
    stohdr(0L);      /* CAF Was zsbhdr - minor change */
    zshhdr(ZFIN, Txhdr);   /*  to make debugging easier */
    switch (zgethdr(Rxhdr))
    {
      case ZFIN:
   sendbyte('O'); sendbyte('O');
   return(OK);
      case ZCAN:
   return(ERROR);
      case TIMEOUT:
   continue;
    }
  }

  return(ERROR);
}
