
/* JMODEM protocol implementation, version 1.1. Information extracted
 * from MS DOS files JMODEM.ASM and JMODEM.DOC by Richard B. Johnson
 * (version 1.20) This program was made 25.4 - 7.5 1989 by Kenneth
 * Österberg.
 * Command line syntax:
 * JModem devname unit baud {R|S} filename ...
 */

/* files to include */

#include <exec/types.h>
#include <exec/nodes.h>
#include <exec/memory.h>
#include <exec/libraries.h>
#include <exec/devices.h>
#include <devices/serial.h>
#include <libraries/dos.h>
#include <intuition/intuition.h>

#include "protsupp.h" /* Symbols and definition for protsupp.c */

char *WinTxt[] = {
  "File name        :",
  "File size        :",
  "Block number     :",
  "Block size       :",
  "Bytes transferred:",
  "Status           :",
  NULL
};

#define TXPOS 19    /* XPos where to print information texts */
#define WINWID 500  /* Width of our JModem window */
#define WINHGT 80   /* Height */
#define GXPOS 36    /* Gadget left edge relative to our window */
#define GYPOS WINHGT - 28 /* Gadget top edge relative to window bottom */

struct windef wdef = {
  WINWID,WINHGT,
  GXPOS,GYPOS,
  " JModem v1.0 \0receive ",
  WinTxt
};

/* Other definitions */

#define JC_NONE 0        /* Error codes for Jcleanup() */
#define JC_IOREQ 1
#define JC_CBUF 2
#define JC_SBUF 3

#define NAK 21          /* Negative Acknowledge, a block was not accepted */
#define SYN 22          /* Sync character, used in long handshaking */
#define CAN 24          /* Cancel, used to abort transfer */
#define ACK 6           /* Acknowledge, signals that all is OK so far */
#define BB 0xBB         /* The 'sentinel', an escape char. in JModem */
#define MAXSTRING 8192  /* Max size of a data block */
#define MINSTRING 256   /* Minimum size, the value we start with */
#define DSTRING 512     /* The amount we increase the block size with */
#define MAXRETRIES 20   /* The number of times we try to resend a block */

/* Three data buffers are used, one receive buffer, one send buffer
 * and one compress buffer. The required size of these buffers is
 * MAXSTRING + 256 + 6, and the compress buffer must be a few bytes
 * larger to prevent overrun when compressing. */

#define BUFSIZE1 MAXSTRING + 256L + 6L  /* The diskbuffer size */
#define BUFSIZE2 BUFSIZE1 + 4L   /* The size of the 'codedbuffer' */

/* Magical JModem control byte constants */

#define NORMAL 1       /* A normal uncompressed block */
#define COMP 2         /* A compressed block */
#define EOF 4          /* When this is set we have the last block */
#define RETRY 8        /* Not currecntly used */
#define TIMEOUT 16     /* Not currently used  */
#define ABORT 32       /* Transmission aborted. */
#define SYNC 64        /* Noy currently used  */
#define ERROR 128      /* Not currently used  */

/* A macro for sending characters */

#define SENDCHAR(x) *sbuf = (x); sendserchar(sbuf)

/* Variables visible to all routines */

int stringsize, retries;
ULONG baudrate, unit;
UWORD recnum;
UBYTE *rbuf, *sbuf, *cbuf;
int RetCode;

/* Library routines we use */

int Write(), Read();
APTR AllocMem(), Open(), Lock(), Seek();
void FreeMem(), Close(), DeleteFile(), Rename(), UnLock();
struct Message *GetMsg();

/* Routines in this file and docrc.asm */

extern int docrc();
int jsend(), readfile(), encode(), chkres(), rxchar(), docrc();
void sendx(), sdata(), clearbuf(), Jcleanup();
ULONG calctime();
APTR htol();

/* Some strings */

char nomemstr[] = "Cannot allocate enough memory";
char numform[] = "%ld";

/* The main program starts here */

void main(argc, argv)
register int argc;
char *argv[];
{
  char devname[32];          /* Name of serial device */
  register char tmpchar;
  register int noerrors, fcount;
  struct recptrs *recptr;    /* Ptr to structure containing IORequest ptrs */

  RetCode = RETURN_FAIL;     /* Assume failure */

  if (!SetupWindow(&wdef))   /* Open the JModem window */
    exit(RetCode);

  if (argc < 5) {
    PText(5,TXPOS,"Too few arguments. Read JAmiga.txt");
    Delay(200L);
    Wcleanup(WC_NONE);
    exit(RetCode);
  }

  if ((rbuf = (UBYTE *)AllocMem(BUFSIZE1,MEMF_CHIP)) == NULL) {
    PText(5,TXPOS,nomemstr);
    Delay(200L);
    Wcleanup(WC_NONE);
    exit(RetCode);
  }

  if ((sbuf = (UBYTE *)AllocMem(BUFSIZE1,MEMF_CHIP)) == NULL) {
    PText(5,TXPOS,nomemstr);
    Delay(200L);
    Wcleanup(WC_NONE);
    Jcleanup(JC_SBUF);
  }

  if ((cbuf = (UBYTE *)AllocMem(BUFSIZE2,MEMF_CHIP)) == NULL) {
    PText(5,TXPOS,nomemstr);
    Delay(200L);
    Wcleanup(WC_NONE);
    Jcleanup(JC_CBUF);
  }

  strcpy(devname,argv[1]);                  /* Name of the serial device */
  unit = (ULONG)atoi(argv[2]);              /* Unit number in the device */
  baudrate = (ULONG)atoi(argv[3]);          /* The baudrate */
  argv += 4;
  argc -= 5;
  if ((baudrate < 110) || (baudrate > 19200)) {
    PText(5,TXPOS,"Baudrate %ld out of range",baudrate);
    Delay(200L);
    Wcleanup(WC_NONE);
    Jcleanup(JC_IOREQ);
  }

  if (!(OpenUpSerial(&RR,&WR,BUFSIZE1,baudrate,unit,devname))) {
    PText(5,TXPOS,"Can't open %s unit %ld",devname,unit);
    Delay(200L);
    Wcleanup(WC_NONE);
    Jcleanup(JC_IOREQ);
  }

  if (!(OpenUpTimer(&TR))) {
    PText(5,TXPOS,"Can't open timer.device");
    Delay(200L);
    Wcleanup(WC_NONE);
    Scleanup(SC_NONE);
    Jcleanup(JC_IOREQ);
  }

  tmpchar = argv[0][0];  /* This should be either R (receive) or S (send) */
  argv++;                /* argv now points at the file name */
  
  stringsize = MINSTRING;  /* We start with the smallest block size */
  noerrors = TRUE;
  fcount = 0;              /* File transfer counter */
  RetCode = RETURN_ERROR;
  while ((argc--) && (noerrors)) {       /* Process all file names */
    switch (tmpchar) {
    case 'R':
    case 'r':
      if (!fcount) { /* Change the window title only the first time */
        *(pwin->Title + strlen(pwin->Title)) = ' '; /* Remove \0 char */
        SetWindowTitles(pwin,pwin->Title,-1L);      /* Show 'JModem receive' */
      }
      PText(0,TXPOS,"%s",argv[0]);                   /* Display file name */
      PText(1,TXPOS,"?");
      PText(2,TXPOS,"1");
      PText(3,TXPOS,"?");
      PText(4,TXPOS,"0");

      if (jrec(argv[0], rbuf, sbuf, cbuf)) {
        PText(5,TXPOS,"File received!");
        fcount++;
      }
      else {
        PText(5,TXPOS,"File transfer failed!");
        noerrors = FALSE;
      }
      break;
    case 'S':
    case 's':
      if (!fcount)
        SetWindowTitles(pwin," JModem v1.0 send ",-1L);
      PText(0,TXPOS,"%s",argv[0]);
      if (jsend(argv[0], rbuf, sbuf, cbuf)) {
        PText(5,TXPOS,"File sent!");
        fcount++;
      }
      else {
        PText(5,TXPOS,"File transfer failed");
        noerrors = FALSE;
      }
      break;
    default:
      PText(5,TXPOS,"Invalid args. R[eceive] or S[end] expected");
      noerrors = FALSE;
      RetCode = RETURN_FAIL;
      break;
    }
    argv++;
  }

/* The return code gives the number of files successfully transferred, or if
 * no files were transferred RETURN_ERROR (10) is returned. A complete
 * JModem failure (out of memory, no serial device, or invalid command line)
 * results in RETURN_FAIL (20). These return codes should give a good idea
 * which files were sent, and which were not. */

  if (fcount)
    RetCode = fcount;

  Tcleanup(TC_NONE);
  Scleanup(SC_NONE);

  Delay(200L);
  Wcleanup(WC_NONE);
  Jcleanup(JC_NONE);
}

int jrec(name, rbuf, sbuf, cbuf)
/* Receive a file from the remote system and write it to the disk
 * file 'name'. The function returns TRUE if the file was successfully
 * received and stored. */

char *name;
UBYTE *rbuf, *sbuf, *cbuf;
{
  int blklen, recrec, status, success = FALSE;
  register int a;
  register UBYTE ctrl, *bufptr, *tmpptr;
  struct IntuiMessage *imsg;
  APTR file;
  ULONG totlen = 0;

/* If the file name we wish to receive exists already, then add the
 * suffix '.old' to the existing file. */

  if (file = Lock(name,ACCESS_READ)) {
    a = strlen(name);
    bufptr = (UBYTE *)AllocMem((ULONG)a+6L,MEMF_PUBLIC);
    if (bufptr) {
      PText(5,TXPOS,"File already exists. Appending '.old'");
      tmpptr = (UBYTE *)name;
      while (*tmpptr) *bufptr++ = *tmpptr++;
      strcpy(bufptr,".old");
      bufptr -= a;
      Rename(name,bufptr);
      FreeMem(bufptr,(ULONG)a+6L);
    }
    UnLock(file);
  }

/* Open the file to receive and get into sync with sender */

  if (((file = Open(name,MODE_NEWFILE))==0) || (!rxsync())) {
    sendx();
    if (file) {
      PText(5,TXPOS,"Syncronization failed. Aborting");
      Close(file);
      DeleteFile(name);
    }
    else
      PText(5,TXPOS,"Can't open file. Aborting");
  }
  else {
    recnum = 1;
    do {
      retries = MAXRETRIES;
      PText(2,TXPOS,numform,(ULONG)recnum);

/* Try to get the block until no errors occurred or the retry count
 * reached 0. */

      while ((retries) &&
             (((status = rdata(rbuf,baudrate,&blklen)) != GS_NONE) ||
             (!docrc(rbuf,blklen+6)))
            ) {

/* Reception of block failed (timeout or CRC mismatch). Send NAK */

        if (--retries) {
          if (CheckAbort()) {
            sendx();
            retries = 0;
          }
          else {
            SENDCHAR(NAK);      /* Send NAK if receive failed */
            if (status == GS_TIMEOUT)
              PText(5,TXPOS,"Timeout waiting for block. Retry %ld",
                                               (ULONG)MAXRETRIES-retries); 
            if (status == GS_NONE)
              PText(5,TXPOS,"CRC checksum mismatch. Retry %ld",
                                               (ULONG)MAXRETRIES-retries);
          }
        }
        else
          PText(5,TXPOS,"Retry count exceeded. Aborting");
      }

      if (CheckAbort()) {
        retries = 0;
        sendx();
      }

      ctrl = *(rbuf + 2);         /* The control byte */
      recrec = *(rbuf + 3);       /* Low byte of block number we received */
      if (retries) {
        bufptr = rbuf + 4;        /* Default ptr to uncompressed data */
        if (ctrl & COMP) {        /* Uncompress if data is compressed */
          blklen = decode(bufptr,cbuf,blklen);
          bufptr = cbuf;
        }

        if (ctrl & ABORT) {
          sendx();
          PText(5,TXPOS,"Transmission aborted by sender");
          retries = 0;
        }
        else {
          if (recrec == (recnum & 0xff)) {
            if (Write(file,bufptr,(ULONG)blklen) < blklen) {
              sendx();
              PText(5,TXPOS,"File write failed. Aborting");
              retries = 0;
            }
            else {
              PText(5,TXPOS,"Block %ld received",(ULONG)recnum);
              totlen += blklen;
              PText(4,TXPOS,numform,totlen);
              recnum++;                /* Update expected record number */
              SENDCHAR(ACK);           /* Allow sender to continue */
              checkeof(ctrl,&success); /* See if this is the last block */         
            }
          }
          else {
            PText(5,TXPOS,"Expected block %ld, received %ld",
                       (ULONG)recnum & 0xff, (ULONG)recrec);
            SENDCHAR(ACK);
            checkeof(ctrl,&success);
            if (success == TRUE) {
              success = FALSE;
              retries = 0;
            }
          }
        }
      }
      else {
        sendx();
      }
    } while ((!success) && (retries)); /* Continue until success or failure */

/* Find out length of file before closing it */

    bufptr = (UBYTE *)Seek(file,0L,OFFSET_CURRENT);
    PText(1,TXPOS,numform,bufptr);
    Close(file);                       /* Close the file */
    if (bufptr == NULL)                /* Delete file if length is 0 */
      DeleteFile(name);
  }
  if (success)
    return(TRUE);
  else
    return(FALSE);
}

checkeof(ctrl,success)
register UBYTE ctrl;
register int *success;
{
  register int a;

  if (ctrl & EOF) {                  /* This was the last block */
    for (a=4; a; a--) SENDCHAR(ACK); /* Send 4 ACK's */
    *success = TRUE;                 /* We have success */
  }
}

int jsend(name, rbuf, sbuf, cbuf)
/* This routine takes a file name and sends it to the remote system
 * using the JMODEM transfer protocol. The function returns TRUE if
 * the file was successfully sent.
 */
char *name;
register UBYTE *rbuf, *sbuf, *cbuf;
{
  UBYTE *bufptr;
  int blklen = 6, status;
  APTR file;
  ULONG totsize = 0,fsize = 0;
  register struct IntuiMessage *imsg;
  register int abort = FALSE;

  retries = 0;
  recnum = 0;

/* Open the source file and if that succeeds try to get in touch with
 * the receiver */

  PText(2,TXPOS,"1");
  PText(3,TXPOS,numform,(ULONG)stringsize);
  PText(4,TXPOS,"0");

  if ((file = Open(name,MODE_OLDFILE))==0)
    PText(5,TXPOS,"Could not open file");

  if ((file == 0) || (!txsynch(file,&totsize)))
    sendx();                /* No sync. Abort transfer */
  else {

/* File is open and sync has been established. Proceed to send the file */

    do {
      fsize += (blklen - 6); /* Header is not counted in bytes transferred */
      PText(4,TXPOS,"%ld  (%ld%% remains)", fsize, 100 - fsize * 100 / totsize);

/* Read the source file */

      if ((blklen = readfile(file, sbuf+4, cbuf+4, &bufptr)) >= 6) {
        retries = MAXRETRIES;
        do {

/* Check if user has hit the 'Abort' gadget */

          if (abort = CheckAbort()) {
            PText(5,TXPOS,"User abort! Telling receiver to quit");
            *(bufptr + 2) |= ABORT;   /* Set ABORT bit in control byte */
            docrc(bufptr,blklen);     /* Recalculate checksum */
          }

/* Send the block */

          sendserdata(bufptr,(ULONG)blklen);

/* Get ACKnowldge from receiver.
 * Loops until ACK, NAK or CAN is received (or user aborted) */

          do {
            if ((status = getserchar(13*50L,rbuf)) != GS_NONE) {
              if (status == GS_TIMEOUT)
                PText(5,TXPOS,"Timeout waiting for response. Aborting");
              abort = TRUE;
            }
          } while ((*rbuf != ACK) &&
                   (chkres(*rbuf,&retries,&blklen) == FALSE) &&
                   (abort == FALSE)); 
        } while ((*rbuf != ACK) && (retries > 0) && (abort == FALSE));
 
        if ((retries) && (blklen > 0) && (abort == FALSE))
          PText(5,TXPOS,"Block %ld sent",(ULONG)recnum);
      }
    } while (((*(bufptr + 2) & EOF) == 0) && (blklen >= 6) && (abort == FALSE));

    if (blklen < 0) {
      sendx();
      PText(5,TXPOS,"File read failed. Transmission aborted");
    }
  }
  if (abort)
    sendx();
  if (file) Close(file);
  if ((retries) && (abort == FALSE)) {
    fsize += (blklen - 6);
    PText(4,TXPOS,"%ld  (0% remains)", fsize);
    return TRUE;
  }
  else
    return FALSE;
}

int txsynch(file,totsize)
/* Get in sync. with remote and wait 0.3 seconds before returning */
register APTR file;
register APTR *totsize;
{
  (void) Seek(file,0L,OFFSET_END);
  *totsize = Seek(file,0L,OFFSET_BEGINNING);
  PText(1,TXPOS,numform,*totsize);
  if (rxsync()) {
    Delay(15L);
    return(1);
  }
  else
    return(0);
}

int rxsync() {
/* Get in syncronization with remote. Returns 0 if sync not
 * achieved. */

  register int a, abort = FALSE;
  register struct IntuiMessage *imsg;

  PText(5,TXPOS,"Synchronizing...");
  clearbuf();

/* See if remote has sent something. If nothing is read in 2 seconds, we
 * send a NAK character. This is repeated until something is read */

  while ( (!(abort = CheckAbort())) && (getserchar(100L,rbuf)==GS_TIMEOUT)) {
    SENDCHAR(NAK);
  }

/* If user pressed Abort gadget then cancel and return */

  if (abort) {
    sendx();
    return(0);
  }

/* Check if we have quick syncronization */

  if ((*rbuf == ACK) || (*rbuf == NAK)) {
    if (*rbuf == NAK) {
      SENDCHAR(ACK);              /* Send ACK if we got NAK */
    }
    PText(5,TXPOS,"Synchronized");
    return(1);
  }

/* Well, quick sync. apparently failed. Do long handshaking with SYN
 * characters */

  for (a=0; a<64; a++) 
    do {
      SENDCHAR(SYN);               /* Send a SYN and wait for response */
      if (!(abort = CheckAbort())) {
        if (getserchar(150L,rbuf) != GS_NONE) {
          PText(5,TXPOS,"Synchronization not acquired");
          clearbuf();
          return(0);
        }
        if (*rbuf == CAN) {
          PText(5,TXPOS,"Remote system cancelled");
          clearbuf();
          return(0);
        }
      }
      else
        a = 64;
    } while ((*rbuf != SYN) && (!abort));

  if (!abort) {
    SENDCHAR(SYN);
    clearbuf();
    PText(5,TXPOS,"Synchronized");
    return(1);
  }
  else {
    sendx();
    return(0);
  }
}

void clearbuf() {
/* Empty receiving buffer */
  RR->IOSer.io_Command = CMD_CLEAR; /* Flush receive buffer */
  DoIO(RR);
}

int rdata(buffer, baud, length)
/* Receive bytes from the serial port. Variable 'baud' specifies the
 * current baud rate. From this value a timeout value is calculated
 * once we know the length of the block we are receiving.
 * The function returns an error code, and GS_NONE if all went well
 * The variable 'length' gets the block size (header not included),
 * which can be inspected from outside of this routine. */

UBYTE *buffer;
ULONG baud;
UWORD *length;
{
  register UWORD temp;
  register UWORD slen;
  ULONG timeout;
  int status;

/* Get two bytes from the serial port. This is the length of the block,
 * (with low byte first of course :-( Timeout value is 6 seconds. */

  *length = 0;
  status = getserdata(300L,buffer,2L);
  if (status == GS_NONE) {
    slen = (*(buffer + 1) << 8) + *buffer; /* Extract length of string */
    *length = slen - 6;
    PText(3,TXPOS,numform,(ULONG)slen - 6);
    buffer += 2;
    timeout = 300L + ((3 * calctime(baud,slen-2)) >> 1); /* Estimate timeout */
    status = getserdata(timeout,buffer,(ULONG)slen-2L);
  }
  return(status);
}

ULONG calctime(baud,numbytes)
/* This routine estimates how long it will take to receive
 * a specific number of bytes over a serial link at a specific
 * baud rate. The result is given in 20 millisecond (1/50 s) units
 */
register ULONG baud;
register UWORD numbytes;
{
  return(400L * numbytes / baud);
}

int readfile(file, sbuf, cbuf, usebuffer)
/* Read file to send into data buffer and change the string size according
 * to previous results. Build the string header, syntax (bytes):
 * LOWLEN, HILEN, CONTROL, REC, data bytes according to string size, CRC, CRC
 * Compress the read data and use the compressed version if it is shorter
 * than the uncompressed. The function returns the actual number of bytes to
 * transmit, or negative if an error occurred.
 * Uses external variables int stringsize, UWORD recnum
 */
ULONG file;
register UBYTE *sbuf, *cbuf, **usebuffer;
{
  register int rawcount,codedcount;

  if (retries == MAXRETRIES) {  /* If no transfer error occured in last block */
    if (stringsize < MAXSTRING) /* and the block size is not already 8TXPOS2 */
      stringsize +=DSTRING;     /* then increase block size by 512 bytes */
  }
  else                          /* A transfer error occurred in last block. */
    if (stringsize > MINSTRING) /* If the blocksize isn't already at minimum */
      stringsize >>= 2;         /* stringsize = stringsize DIV 4 */

/* Read data from file. Create compressed version of data */

  if ((rawcount = Read(file,sbuf,(LONG)stringsize)) <= 0)
    return (rawcount); 
  codedcount = encode(sbuf,cbuf,rawcount);

/* If compressed data is shorter than uncompressed, then use the
 * compressed version. */

  if (codedcount < rawcount) {
    PText(3,TXPOS,"%ld (%ld%% compressed)",
               (ULONG)codedcount,
               (ULONG)100 - (LONG)codedcount * 100 / rawcount);
    codedcount += 6;                 /* Include the header bytes into length */
    cbuf -= 4;                       /* To beginning of header */
    *cbuf++ = (codedcount & 0xff);   /* Low byte of string length */
    *cbuf++ = (codedcount >> 8);     /* High byte of string length */
    *cbuf = COMP;                    /* Compressed block */
    if (rawcount < stringsize)
      *cbuf |= EOF;                  /* Set EOF flag if needed */
    cbuf++;
    *cbuf++ = ++recnum & 0xff;       /* Record number (low byte) */
    docrc(cbuf-4,codedcount);        /* Calculate checksum for the block */
    *usebuffer = cbuf-4;
    PText(2,TXPOS,numform,(ULONG)recnum);
    return(codedcount);
  }
  else {
    PText(3,TXPOS,"%ld (not compressed)",(ULONG)rawcount);
    rawcount += 6;
    sbuf -= 4;
    *sbuf++ = (rawcount & 0xff);
    *sbuf++ = (rawcount >> 8);
    *sbuf = NORMAL;                  /* Normal block, not compressed */
    if (rawcount < stringsize)
      *sbuf |= EOF;                  /* Set EOF flag if needed */
    sbuf++;
    *sbuf++ = ++recnum & 0xff;
    docrc(sbuf-4,rawcount);
    *usebuffer = sbuf-4;
    PText(2,TXPOS,numform,(ULONG)recnum);
    return(rawcount);
  }
}

int decode(srcbuf, dstbuf, srccnt)
/* Uncompress data pointed to by srcbuf, place resulting data into
 * buffer pointed to by dstbuf. We have srccnt bytes to uncompress.
 * Returns the destination length. */

register UBYTE *srcbuf, *dstbuf;
register int srccnt;
{
  register UBYTE chr;
  register UWORD dstcnt = 0, count;

  while (srccnt-- > 0) {              /* Until whole source buffer processed */
    if ((chr = *srcbuf++) != BB) {    /* Do we have the 'sentinel' byte? */
      *dstbuf++ = chr;                /* Just transfer to destbuffer if not */
      dstcnt++;
    }
    else {                            /* The 'sentinel' byte was met */
       count = (*(srcbuf + 1) << 8);  /* Fetch high byte of repeat count */
       count += *srcbuf;              /* Fetch low byte of repeat count */
       srcbuf += 2;
       chr = *srcbuf++;               /* Fetch character to repeat */
       srccnt -= 3;
       dstcnt += count;
       for (; count > 0; *dstbuf++ = chr, count--);
    }
  }
  return(dstcnt);
}

int encode(srcbuf, dstbuf, srccnt)
/* Compress data pointed to by srcbuf, place resulting data into buffer
 * pointed to by dstbuf. We have srccnt bytes to compress. Returns the
 * destination length */

UBYTE *srcbuf, *dstbuf;
int srccnt;
{
  register UBYTE curchar, *tmpsrc;
  register int dstcnt = 0, srcc = srccnt;
  register int remaining;
  register UWORD dupcnt;

/* Start compressing. If the destination buffer size gets equal or larger
 * than the source buffer then the compression is immediately aborted.
 * You may use the compressed version only if it is smaller than the
 * uncompressed data. */

  while ((srcc > 0) && (dstcnt < srccnt)) {
    curchar = *srcbuf++;   /* Get current character to process */
    tmpsrc = srcbuf;       /* Ptr to next position in buffer */
    dupcnt = 1;            /* Number of identical copies we have found */
    remaining = --srcc;    /* Number of characters still unprocessed in buffer */
    while ((*tmpsrc++ == curchar) && (remaining--))
      dupcnt++;            /* Count number of identical chars */

/* Perform compression if the 'sentinel' byte 0xBB was met or the number of
 * identical characters was more than 4 */

    if ((curchar == BB) || (dupcnt > 4)) {
      *dstbuf++ = BB;              /* Sentinel character for identification */
      *dstbuf++ = (dupcnt & 0xff); /* Store low byte of length first */
      *dstbuf++ = (dupcnt >> 8);   /* then high byte */
      *dstbuf++ = curchar;         /* The character we are processing */
      dstcnt += 4;                 /* Update destination count */
      srcbuf = tmpsrc - 1;         /* Continue reading after handled chars */
      srcc -= dupcnt - 1;          /* Decrement byte count of source buf. */
    }
    else {                         /* Store uncompressed */
      *dstbuf++ = curchar;
      dstcnt++;
    }
  }
  return (dstcnt);
}

int chkres(chr,retries,len)
/* We should have got a ACK, but didn't. Find out what we did get.
 * This function returns FALSE if garbage was received. Returns TRUE
 * if we could interpret the character */
register UBYTE chr;
register int *retries, *len;
{
  if (chr == CAN) {
    sendx();               /* Receiver sent CAN. Abort transfer */
    PText(5,TXPOS,"Receiver aborted. Transmission ended");
    *retries = 0;
    *len = 0;
    return TRUE;
  }
  if (chr != NAK)           /* If not NAK it must be garbage. Get ACK again */
    return FALSE;
  else
    if (--(*retries))       /* Receiver didn't get. Update retry counter */
      return TRUE;          /* Resend until retry counter is zero */
    else {
      sendx();              /* Tell receiver to stop waiting. We stop trying */
      *len = 0;
      PText(5,TXPOS,"Bad connection, giving up");
      return TRUE;
    }
}

void sendx() { 
/* Send 10 CAN chars (== CTRL-X) to remote */
  register int a;
  for (a=10; a!=0; a--) {
    SENDCHAR(CAN);
  }
}

APTR htol(str)
/* Convert a hexadecimal string to a long integer */
register char *str;
{
  register char chr;
  register ULONG num = 0, multiplier = 1;
  register int cnt = 0;
  
  while (*(str + cnt++));    /* Find last character in string */

  while (--cnt) {            /* Process all digits */
    chr = *(str + cnt - 1);  /* Get character into a register */
    if (chr >= 'a')          /* Convert to upper case letter */
      chr -= 32;
    num += (chr<='9') ? multiplier*(chr-'0') : multiplier*(chr-'A'+10);
    multiplier <<= 4;        /* Multiplier * 16 */
  }
  return((APTR)num);
}

void Jcleanup(num)
/* Perform resource deallocation. Uses global variable 'RetCode' */
register int num;
{
  switch(num) {
  case JC_NONE:
  case JC_IOREQ:
    FreeMem(cbuf,BUFSIZE2);
  case JC_CBUF:
    FreeMem(sbuf,BUFSIZE1);
  case JC_SBUF:
    FreeMem(rbuf,BUFSIZE1);
  default: ;
  }
  exit(RetCode);
}
