/*
**  ZModem.c
**
**  Built-in ZModem file transfer protocol.
**
**  :ts=4
**
**  Based upon the zmodem.c code used by MacTerminal, written by
**
**      Erny Tontlinger
**      33, route d'Arlon
**      L-8410 Steinfort
**      Luxembourg
**
**  Which in turn is based upon work by
**
**      Chuck Forsberg
**      Omen Technology, Inc
**      Post Office Box 4681
**      Portland OR 97208
**      USA
**
**  All modifications for `term', great and small, are
**  copyright © 1990-1996 by Olaf `Olsen' Barthel
**      All Rights Reserved
*/

#ifdef BUILTIN_ZMODEM

#ifndef _GLOBAL_H
#include "Global.h"
#endif

#include <setjmp.h>

#ifdef FINE
#undef FINE
#endif /* FINE */

#ifdef XON
#undef XON
#endif /* XON */

#ifdef CAN
#undef CAN
#endif /* CAN */

#ifdef DEL
#undef DEL
#endif /* DEL */

enum
{
	GAD_NameBox=1000,
	GAD_BatchBox,
	GAD_MessageList,
	GAD_TransferBox,
	GAD_FileProgress,
	GAD_BatchProgress,
	GAD_Abort
};

enum
{
	NAMEBOX_Name,
	NAMEBOX_Size,
	NAMEBOX_Completion
};

enum
{
	BATCHBOX_Count,
	BATCHBOX_Size,
	BATCHBOX_Completion
};

enum
{
	TRANSFERBOX_Errors,
	TRANSFERBOX_Retries,
	TRANSFERBOX_CPS
};

enum
{
  FINE = 0,                     /* No error */
  NOTHING = 1000,               /* No data available */
  TIMEOUT,                      /* Timeout error */
  CANCEL,                       /* Cancel by button or menu */
  ABORT,                        /* Abort by ctrl-X received */
  ERROR,                        /* Other error */
  NO_CARRIER,                   /* Carrier lost */
  LINE_ERROR,
  PARITY_ERROR,
  TIMER_ERROR,
  BUFFER_OVERFLOW,
  NO_DSR,
  BREAK_DETECTED
};

STATIC BOOL ZEscapeCtl = FALSE;                 /* escape all control characters. */
STATIC BOOL ZOverwrite = FALSE;                 /* clobber existing files. */
STATIC LONG ZTimeout = 10;                      /* receive timeout (seconds) */
STATIC LONG ZBuffer = 0;                        /* receive buffer size (bytes) */
STATIC LONG ZRetries = 10;                      /* receive maximum retries */
STATIC LONG ZErrors = 10;                       /* maximum errors */
STATIC LONG ZPacket = 1024;                     /* transmit sub-packet length (bytes) */
STATIC LONG ZWindow = 0;                        /* transmit window size (bytes) */
STATIC LONG Zcrcq = 0;                          /* transmit ZCRCQ spacing (bytes) */
STATIC BOOL ZKeepPartial = TRUE;                /* keep partial files; do not delete them */
STATIC BOOL ZDeleteAfterSending = FALSE;        /* delete the file after sending */
STATIC BOOL ZProtectAfterSending = FALSE;       /* add archived bit after sending */
STATIC BOOL ZSkipIfArchived = FALSE;            /* skip file if archived bit is set */
STATIC BOOL ZSendFullPath = FALSE;              /* send complete file path */
STATIC BOOL ZUseFullPath = FALSE;               /* use complete file path */
STATIC BOOL ZTransferIfNewer = FALSE;           /* transfer file if newer than existing file */
STATIC BOOL ZTransferIfSize = FALSE;            /* transfer file if size different from existing file */

/* ----- Special characters -------------------------------------------- */

#define BS          0x08        /* Backspace */
#define LF          0x0A        /* Linefeed */
#define CR          0x0D        /* Carriage return */
#define DLE         0x10        /* Data link escape */
#define XON         0x11        /* Flow control */
#define XOFF        0x13        /* Flow control */
#define CAN         0x18        /* Cancel */
#define DEL         0x7F        /* Delete */

#define ZPAD        '*'         /* Padding character begins frames */
#define ZDLE        ('X'&0x1F)  /* Zmodem escape character ctrl-X */
#define ZBIN        'A'         /* Binary header indicator (CRC-16) */
#define ZHEX        'B'         /* HEX header indicator */

/* ----- Frame types --------------------------------------------------- */

#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 5 CANs */
#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 */

/* ----- ZDLE sequences ------------------------------------------------ */

#define ZCRCE       'h'         /* CRC next, frame ends, header 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 DEL 0x7F */
#define ZRUB1       'm'         /* Translate to DEL 0xFF */

/* ----- Header structures --------------------------------------------- */

/* olsen: neat idea, but assumes that bit fields will be allocated
 *        from left to right. Luckily, that's just what SAS/C 6.50 does.
 */

typedef struct
{
  unsigned int pad:24;
  unsigned int command:8;       /* 0 or ZCOMMAND */
}
ZRQINITflags;

typedef struct
{
  unsigned int bufsize:16;      /* Receiver's buffer size (bytes swapped) */

  unsigned int pad:7;
  unsigned int canvhdr:1;       /* Variable headers OK */

  unsigned int esc8:1;          /* Rx expects 8th bit to be escaped */
  unsigned int escctl:1;        /* Rx expects ctl chars to be escaped */
  unsigned int canfc32:1;       /* Rx can use 32 bit CRC */
  unsigned int canlzw:1;        /* Rx can uncompress */
  unsigned int canrle:1;        /* Rx can decode RLE */
  unsigned int canbrk:1;        /* Rx can send a break signal */
  unsigned int canovio:1;       /* Rx can receive data during disk I/O */
  unsigned int canfdx:1;        /* Rx can send and receive true FDX */
}
ZRINITflags;

typedef struct
{
  unsigned int pad0:24;

  unsigned int esc8:1;          /* Tx expects 8th bit to be escaped */
  unsigned int escctl:1;        /* Tx expects ctl chars to be escaped */
  unsigned int pad1:6;
}
ZSINITflags;

typedef struct
{
  unsigned int pad:1;
  unsigned int sparse:1;        /* Encoding for sparse file operations */
  unsigned int canvhdr:6;       /* Variable headers OK */

  unsigned int transport:8;     /* Transport options */

  unsigned int skip:1;          /* Skip file if not present at rx */
  unsigned int manage:7;        /* Management options */

  unsigned int conv:8;          /* Conversion options */
}
ZFILEflags;

typedef struct
{
  unsigned int pad:31;
  unsigned int ack:1;           /* Acknowledge, then do command */
}
ZCOMMANDflags;

typedef union
{
  UBYTE b[4];
  ULONG position;               /* File position (low byte first) */
  ZRQINITflags zrqinit;
  ZRINITflags zrinit;
  ZSINITflags zsinit;
  ZFILEflags zfile;
  ZCOMMANDflags zcommand;
}
HEADER;

/* ----- Conversion options, ZFILE frame ------------------------------- */

#define ZCBIN       1           /* Binary transfer - inhibit conversion */
#define ZCNL        2           /* Convert NL to local eol convention */
#define ZCRESUM     3           /* Resume interrupted file transfer */

/* ----- Management options, ZFILE frame ------------------------------- */

#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 */
#define ZMDIFF      6           /* Transfer if dates or lengths different */
#define ZMPROT      7           /* Protect destination file */

/* ----- Transport options, ZFILE frame -------------------------------- */

#define ZTLZW       1           /* Lempel-Ziv compression */
#define ZTRLE       3           /* Run Length encoding */

/* ----- Other constants ----------------------------------------------- */

#define ZATTNLEN    32          /* Max length of attention string */
#define ZMAXSPLEN   1024        /* Max subpacket length */

/* ----- Return values (internal) -------------------------------------- */

#define GOTOR   0x0100
#define GOTCRCE (GOTOR | ZCRCE) /* ZDLE-ZCRCE received */
#define GOTCRCG (GOTOR | ZCRCG) /* ZDLE-ZCRCG received */
#define GOTCRCQ (GOTOR | ZCRCQ) /* ZDLE-ZCRCQ received */
#define GOTCRCW (GOTOR | ZCRCW) /* ZDLE-ZCRCW received */

/* ----- Globals ------------------------------------------------------- */

UBYTE ZAutoReceiveString[] =
{
  ZPAD, ZDLE, ZHEX,
  '0', '0',                     /* Type: ZRQINIT */
  '0', '0', '0', '0', '0', '0', '0', '0',
  '0', '0', '0', '0',           /* CRC1 CRC2 */
  CR,
  0
};

UBYTE ZLastRx[] =
{
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

#define GARBAGE 1400+2400       /* Garbage count before header */

STATIC jmp_buf CancelEnv;       /* Long jump if cancel or abort */

STATIC BOOL Zctlesc;            /* TRUE: escape all control characters */
STATIC UBYTE *TxBuffer;         /* Transmit buffer */
STATIC UBYTE *TxPtr;            /* Pointer into transmit buffer */

/* Shared by transmit and receive */
STATIC BPTR FileHandle;         /* File handle */
STATIC UBYTE FileName[MAX_FILENAME_LENGTH];     /* Current file name */
STATIC ULONG Mark;              /* Current file position */
STATIC ULONG Length;            /* Length of file */
STATIC ULONG StartPos;          /* Start file position */
STATIC ULONG StartupTime;       /* Start time */
STATIC ULONG ZBytesOut, ZBytesIn; /* Number of bytes sent/received */
STATIC UBYTE *Buffer;           /* Block buffer + attention string + ... */
STATIC ULONG LastBPS;           /* Throughput (bytes/second) */

#define SERIALBUFFERSIZE (2 * ZPacket)
STATIC UBYTE *SerialBuffer;
STATIC UBYTE *SerialIndex;
STATIC LONG SerialCache;

/* Transmit only */
STATIC ULONG Txwindow;          /* Size of the transmitted window */
STATIC ULONG Txwspac;           /* Spacing between ZCRCQ requests */
STATIC UWORD Rxbuflen;          /* Receiver's max buffer length */
STATIC ULONG Lastsync;          /* Last offset to which we got a ZRPOS */
STATIC ULONG Lrxpos;            /* Receiver's last reported offset */
STATIC WORD BeenHereB4;         /* How many times ZRPOS same place */

/* Receive only */
STATIC UBYTE TryZhdrType;       /* Header type to send */
STATIC UWORD Count;             /* Bytes in block buffer */

#define Attn (Buffer+ZMAXSPLEN) /* Sender's attention string */
STATIC UWORD AttnLen;           /* Bytes in attention string */

#define BUFFERSIZE (ZMAXSPLEN+ZATTNLEN)
STATIC BOOL ConvertNL;          /* Convert newline to local eol */

STATIC UBYTE zc;
STATIC BPTR ReceiveDir;
STATIC BOOL IsBlockMapped;
STATIC LONG errors;
STATIC LayoutHandle *handle;
STATIC struct List *MessageList;
STATIC LONG MessageListLen;
STATIC BOOL Transmitting;
STATIC ULONG BytesThisBatch,FilesThisBatch;

/* ===== LOCAL PROTOTYPES=============================================== */
STATIC ULONG Unix2AmigaMode(ULONG Unix);
STATIC ULONG Amiga2UnixMode(ULONG Amiga);
STATIC LONG MapSerialError(LONG err);
STATIC VOID CloseUI(LayoutHandle *handle, LONG err);
STATIC VOID AddMessage(STRPTR Message, ...);
STATIC VOID ResetUI(LayoutHandle *handle);
STATIC LayoutHandle *OpenUI(STRPTR Title);
STATIC ULONG CalculateBPS(VOID);
STATIC VOID UpdateError(LONG error);
STATIC VOID NameProgress(STRPTR name);
STATIC BOOL CheckCancel(VOID);
STATIC VOID UpdateRetries(LONG retries);
STATIC VOID UpdateBatch(ULONG number, ULONG numberLeft, ULONG bytes, ULONG bytesLeft);
STATIC VOID UpdateProgress(LONG bytes, LONG maximum);
STATIC VOID ToOctal(ULONG v, UBYTE *s);
STATIC ULONG GetTime(VOID);
STATIC BOOL Escape(VOID);
STATIC LONG NewTxBuffer(VOID);
STATIC VOID DisposeTxBuffer(VOID);
STATIC VOID InitTxBuffer(VOID);
STATIC VOID TransmitTxBuffer(VOID);
STATIC VOID SendString(UBYTE *s, UWORD n);
STATIC VOID SendCancel(VOID);
STATIC UBYTE SendByte(UBYTE c);
STATIC VOID SendAttn(UBYTE *s);
STATIC UWORD __inline Swap2(UWORD n);
STATIC ULONG __inline Swap4(ULONG n);
STATIC VOID AdjustHeader(UBYTE type, HEADER *p);
STATIC UBYTE PutHex(UBYTE c);
STATIC UBYTE LocalSendLine(UBYTE c);
STATIC VOID SendHexHeader(UBYTE type, HEADER *hdr);
STATIC VOID SendBinaryHeader(UBYTE type, HEADER *hdr);
STATIC VOID SendData(UBYTE *buf, UWORD length, UBYTE frameend);
STATIC WORD ReadByte(UBYTE *c, ULONG timeout);
STATIC WORD WaitZPAD(ULONG timeout, UWORD n);
STATIC WORD ReadByte2(UBYTE *c, ULONG timeout);
STATIC WORD GetHex(UBYTE *c);
STATIC WORD ZdleRead(UBYTE *c);
STATIC WORD ReceiveBinaryHeader(UBYTE *type, UBYTE *hdr);
STATIC WORD ReceiveHexHeader(UBYTE *type, UBYTE *hdr);
STATIC WORD GetHeader(UBYTE *type, HEADER *hdr);
STATIC WORD ReceiveData(UBYTE *buf, UWORD length, UWORD *count, UBYTE *framend);
STATIC VOID ConvertTime(BOOL toAmiga, ULONG *t);
STATIC LONG LocalReadBuffer(LONG *count, UBYTE *buf);
STATIC LONG LocalReadBufferAndConvert(LONG *count, UBYTE *buf, UBYTE *lastc);
STATIC VOID SetFilePosition(ULONG position);
STATIC WORD GetInSync(BOOL flag, UBYTE *type);
STATIC WORD SendFileData(VOID);
STATIC WORD Invitation(VOID);
STATIC WORD SessionStartup(VOID);
STATIC WORD SendFileInfo(UBYTE *name, ULONG size, BOOL text, ULONG modif, ULONG mode, ULONG *startpos, ULONG TotalFiles, ULONG TotalBytes);
STATIC VOID SessionCleanup(VOID);
STATIC ULONG GetFreeSpace(VOID);
STATIC VOID AckOverAndOut(VOID);
STATIC WORD TryZReceive(HEADER *filehdr);
STATIC LONG ProcedeHeader(HEADER *filehdr, STRPTR name, ULONG *length, ULONG *modif, ULONG *mode, ULONG *filesleft, ULONG *bytesleft);
STATIC LONG WriteFile(VOID);
STATIC VOID CleanupFile(STRPTR name, ULONG modif, ULONG mode);
STATIC WORD ReceiveFile(HEADER *filehdr);
STATIC WORD ReceiveFiles(VOID);

/* ===== FILE MODE HANDLING ============================================ */
#define S_IRWXU 0000700         /* RWX mask for owner */
#define S_IRUSR 0000400         /* R for owner */
#define S_IWUSR 0000200         /* W for owner */
#define S_IXUSR 0000100         /* X for owner */

#define S_IRWXG 0000070         /* RWX mask for group */
#define S_IRGRP 0000040         /* R for group */
#define S_IWGRP 0000020         /* W for group */
#define S_IXGRP 0000010         /* X for group */

#define S_IRWXO 0000007         /* RWX mask for other */
#define S_IROTH 0000004         /* R for other */
#define S_IWOTH 0000002         /* W for other */
#define S_IXOTH 0000001         /* X for other */

#define S_IFMT  0170000         /* type of file */
#define S_IFREG 0100000         /* regular */

STATIC ULONG
Unix2AmigaMode (ULONG Unix)
{
  if (Unix & S_IFMT)
  {
    ULONG Amiga;

    Amiga = 0;

    if (Unix & S_IXOTH)
      Amiga |= FIBF_OTR_EXECUTE;

    if (Unix & S_IWOTH)
      Amiga |= FIBF_OTR_WRITE | FIBF_OTR_DELETE;

    if (Unix & S_IROTH)
      Amiga |= FIBF_OTR_READ;


    if (Unix & S_IXGRP)
      Amiga |= FIBF_GRP_EXECUTE;

    if (Unix & S_IWGRP)
      Amiga |= FIBF_GRP_WRITE | FIBF_GRP_DELETE;

    if (Unix & S_IRGRP)
      Amiga |= FIBF_GRP_READ;


    if (Unix & S_IXUSR)
      Amiga |= FIBF_EXECUTE;

    if (Unix & S_IWUSR)
      Amiga |= FIBF_WRITE | FIBF_DELETE;

    if (Unix & S_IRUSR)
      Amiga |= FIBF_READ;

    return (Amiga ^ (FIBF_EXECUTE | FIBF_WRITE | FIBF_DELETE | FIBF_READ));
  }
  else
    return (0);
}

STATIC ULONG
Amiga2UnixMode (ULONG Amiga)
{
  ULONG Unix;

  Amiga ^= FIBF_EXECUTE | FIBF_WRITE | FIBF_DELETE | FIBF_READ;

  Unix = S_IFREG;

  if (Amiga & FIBF_OTR_EXECUTE)
    Unix |= S_IXOTH;

  if (Amiga & (FIBF_OTR_WRITE | FIBF_OTR_DELETE))
    Unix |= S_IWOTH;

  if (Amiga & FIBF_OTR_READ)
    Unix |= S_IROTH;


  if (Amiga & FIBF_GRP_EXECUTE)
    Unix |= S_IXGRP;

  if (Amiga & (FIBF_GRP_WRITE | FIBF_GRP_DELETE))
    Unix |= S_IWGRP;

  if (Amiga & FIBF_GRP_READ)
    Unix |= S_IRGRP;


  if (Amiga & FIBF_EXECUTE)
    Unix |= S_IXUSR;

  if (Amiga & (FIBF_WRITE | FIBF_DELETE))
    Unix |= S_IWUSR;

  if (Amiga & FIBF_READ)
    Unix |= S_IRUSR;

  return (Unix);
}

/* ===== MISC ========================================================== */

STATIC LONG
MapSerialError (LONG err)
{
  switch (err)
  {
  case SerErr_LineErr:

    err = LINE_ERROR;
    break;

  case SerErr_ParityErr:

    err = PARITY_ERROR;
    break;

  case SerErr_TimerErr:

    err = TIMER_ERROR;
    break;

  case SerErr_BufOverflow:

    err = BUFFER_OVERFLOW;
    break;

  case SerErr_NoDSR:

    err = NO_DSR;
    break;

  case SerErr_DetectedBreak:

    err = BREAK_DETECTED;
    break;

  default:

    err = 0;
    break;
  }

  return (err);
}

STATIC VOID
CloseUI (LayoutHandle *handle, LONG err)
{
  if (err > 0)
  {
    LT_ShowWindow(handle,TRUE);
    DisplayBeep (handle->Window->WScreen);

    do
      WaitPort(handle->Window->UserPort);
    while(!CheckCancel());
  }

  LT_DeleteHandle(handle);

  DeleteList(MessageList);
  MessageList = NULL;
}

STATIC VOID
AddMessage(STRPTR Message,...)
{
	struct Node *Node;
	va_list VarArgs;
	ULONG Size;

	va_start(VarArgs,Message);
	Size = GetFormatLength(Message,VarArgs);

	LT_SetAttributes(handle,
		GTLV_Labels,~0,
	TAG_DONE);

	if(MessageListLen == 50)
	{
		FreeVecPooled(RemHead(MessageList));
		MessageListLen--;
	}

	if(Node = (struct Node *)AllocVecPooled(sizeof(struct Node) + Count,MEMF_ANY))
	{
		VSPrintf(Node->ln_Name = (char *)(Node + 1),Message,VarArgs);
		AddTail(MessageList,Node);
		MessageListLen++;
	}

	va_end(VarArgs);

	LT_SetAttributes(handle,
		GTLV_Labels,MessageList,
	TAG_DONE);
}

STATIC VOID
ResetUI(LayoutHandle *handle)
{
	STRPTR nothing;
	LONG i;

	nothing = "-";

	for(i = 0 ; i < 4 ; i++)
	{
	  LT_SetAttributes(handle,GAD_NameBox,
	  	LABX_Index,		i,
	  	LABX_Text,		nothing,
	  TAG_DONE);

	  LT_SetAttributes(handle,GAD_BatchBox,
	  	LABX_Index,		i,
	  	LABX_Text,		nothing,
	  TAG_DONE);

	  LT_SetAttributes(handle,GAD_TransferBox,
	  	LABX_Index,		i,
	  	LABX_Text,		nothing,
	  TAG_DONE);
	}
}

STATIC LayoutHandle *
OpenUI (STRPTR Title)
{
	LayoutHandle *handle;

	if(!(MessageList = CreateList()))
		return(NULL);

	MessageListLen = 0;

	if(handle = LT_CreateHandleTagList(Window->WScreen,NULL))
	{
		LT_New(handle,
			LA_Type,	VERTICAL_KIND,
		TAG_DONE);
		{
			LT_New(handle,
				LA_Type,VERTICAL_KIND,
			TAG_DONE);
			{
				STATIC STRPTR BoxLabels1[] =
				{
					"Name",
					"Size",
					"Completion",
					NULL
				};

				STATIC STRPTR BoxLabels2[] =
				{
					"Batch count",
					"Batch size",
					"Batch completion",
					NULL
				};

				STATIC STRPTR BoxLabels3[] =
				{
					"Number of errors",
					"Number of retries",
					"Bytes/second",
					NULL
				};

				LT_New(handle,
					LA_Type,		BOX_KIND,
					LA_ID,			GAD_NameBox,
					LA_Chars,		40,
					LABX_Labels,	BoxLabels1,
				TAG_DONE);

				LT_New(handle,
					LA_Type,		BOX_KIND,
					LA_ID,			GAD_BatchBox,
					LABX_Labels,	BoxLabels2,
				TAG_DONE);

				LT_New(handle,
					LA_Type,		LISTVIEW_KIND,
					LA_ID,			GAD_MessageList,
					LA_NoKey,		TRUE,
					LA_LabelText,	"Information",
					LA_LabelPlace,	PLACE_Left,
					LALV_MaxGrowY,	5,
					LALV_Lines,		2,
					GTLV_ReadOnly,	TRUE,
				TAG_DONE);

				LT_New(handle,
					LA_Type,		BOX_KIND,
					LA_ID,			GAD_TransferBox,
					LABX_Labels,	BoxLabels3,
				TAG_DONE);

				LT_New(handle,
					LA_Type,		GAUGE_KIND,
					LA_ID,			GAD_FileProgress,
					LA_LabelText,	"File progress",
				TAG_DONE);

				LT_New(handle,
					LA_Type,		GAUGE_KIND,
					LA_ID,			GAD_BatchProgress,
					LA_LabelText,	"Batch progress",
					GA_Disabled,	TRUE,
				TAG_DONE);

				LT_EndGroup(handle);
			}

			LT_New(handle,
				LA_Type,		VERTICAL_KIND,
			TAG_DONE);
			{
				LT_New(handle,
					LA_Type,		XBAR_KIND,
					LAXB_FullWidth,	TRUE,
				TAG_DONE);

				LT_EndGroup(handle);
			}

			LT_New(handle,
				LA_Type,		HORIZONTAL_KIND,
				LAGR_SameSize,	TRUE,
				LAGR_Spread,	TRUE,
			TAG_DONE);
			{

				LT_New(handle,
					LA_Type,		BLANK_KIND,
				TAG_DONE);

				LT_New(handle,
					LA_Type,		BUTTON_KIND,
					LA_ID,			GAD_Abort,
					LA_LabelText,	"Abort",
					LABT_ExtraFat,	TRUE,
					LABT_EscKey,	TRUE,
				TAG_DONE);

				LT_EndGroup(handle);
			}

			LT_EndGroup(handle);
		}

		if(LT_Build(handle,
			LAWN_TitleText,	Title,
			LAWN_Zoom,		TRUE,
			LAWN_Parent,	Window,
			WA_DepthGadget,	TRUE,
			WA_DragBar,		TRUE,
			WA_RMBTrap,		TRUE,
			WA_Activate,	TRUE,
		TAG_DONE))
		{
			ResetUI(handle);
			return(handle);
		}

		LT_DeleteHandle(handle);
	}

    DeleteList(MessageList);
    MessageList = NULL;

	return(NULL);
}

STATIC ULONG
CalculateBPS()
{
	ULONG Now;

	Now = GetTime();

	if(Now > StartupTime)
	{
		ULONG Bytes;

		Bytes = Transmitting ? ZBytesOut : ZBytesIn;
		Now -= StartupTime;

		LastBPS = Bytes / Now;
	}

	return(LastBPS);
}

STATIC VOID
UpdateError (LONG error)
{
  if (error > 0)
  {
    UBYTE LocalBuffer[256];
    STRPTR Message;

    switch (error)
    {
    case TIMEOUT:
      Message = "Timeout";
      break;
    case CANCEL:
      Message = "Local abort";
      break;
    case ABORT:
      Message = "Remote abort";
      break;
    case ERROR:
      Message = "Transmission error";
      break;
    case NO_CARRIER:
      Message = "Carrier lost";
      break;
    case LINE_ERROR:
      Message = "Hardware data overrun";
      break;
    case PARITY_ERROR:
      Message = "Parity error";
      break;
    case TIMER_ERROR:
      Message = "Timer error";
      break;
    case BUFFER_OVERFLOW:
      Message = "Buffer overflow";
      break;
    case NO_DSR:
      Message = "No DSR signal detected";
      break;
    case BREAK_DETECTED:
      Message = "Break signal detected";
      break;
    default:
      Fault (error, NULL, Message = LocalBuffer, sizeof (LocalBuffer));
      break;
    }

    AddMessage(Message);
  }
}

STATIC VOID
NameProgress (STRPTR name)
{
	LT_SetAttributes(handle,GAD_NameBox,
		LABX_Index,	NAMEBOX_Name,
		LABX_Text,	name,
	TAG_DONE);
}

STATIC BOOL
CheckCancel ()
{
  struct IntuiMessage *Message;
  ULONG MsgClass;
  UWORD MsgGadgetID;
  BOOL Result;

  Result = FALSE;

  while (Message = LT_GetIMsg (handle))
  {
    MsgClass = Message->Class;

    if(MsgClass == IDCMP_GADGETUP)
      MsgGadgetID = ((struct Gadget *)Message->IAddress)->GadgetID;

    LT_ReplyIMsg(Message);

    if(MsgClass == IDCMP_GADGETUP && MsgGadgetID == GAD_Abort)
      Result = TRUE;
  }

  return (Result);
}

STATIC VOID
UpdateRetries(LONG retries)
{
  UBYTE LocalBuffer[256];
  STRPTR String;

  if(retries > 0)
    LimitedSPrintf(sizeof(LocalBuffer),String = LocalBuffer,"%lD of %lD",retries,ZRetries);
  else
    String = "-";

  LT_SetAttributes(handle,GAD_TransferBox,
  	LABX_Index,		TRANSFERBOX_Retries,
  	LABX_Text,		String,
  TAG_DONE);
}

STATIC VOID
UpdateBatch(ULONG number,ULONG numberLeft,ULONG bytes,ULONG bytesLeft)
{
  if(numberLeft > 0 && bytesLeft > 0)
  {
    UBYTE LocalBuffer[256];
    ULONG Percent;
    ULONG bps;

    LimitedSPrintf(sizeof(LocalBuffer),LocalBuffer,"%lD of %lD",number + 1,number + numberLeft);

    LT_SetAttributes(handle,GAD_BatchBox,
  	  LABX_Index,		BATCHBOX_Count,
  	  LABX_Text,		LocalBuffer,
    TAG_DONE);

    LimitedSPrintf(sizeof(LocalBuffer),LocalBuffer,"%lD of %lD",bytes,bytes + bytesLeft);

    LT_SetAttributes(handle,GAD_BatchBox,
  	  LABX_Index,		BATCHBOX_Size,
  	  LABX_Text,		LocalBuffer,
    TAG_DONE);

    if((bps = CalculateBPS()) > 0)
    {
      ULONG time;
      STRPTR String;

      time = bytesLeft / bps;

      if(time < 1)
        String = "< 00:00:01 h";
      else
        LimitedSPrintf(sizeof(LocalBuffer),String = LocalBuffer,"  %2ld:%02ld:%02ld h",time / (60 * 60),(time / 60) % 60,time % 60);

      LT_SetAttributes(handle,GAD_BatchBox,
  		  LABX_Index,		BATCHBOX_Completion,
  		  LABX_Text,		String,
      TAG_DONE);

      LimitedSPrintf(sizeof(LocalBuffer),LocalBuffer,"%lD bytes/s",bps);

      LT_SetAttributes(handle,GAD_TransferBox,
  		  LABX_Index,		TRANSFERBOX_CPS,
  		  LABX_Text,		LocalBuffer,
      TAG_DONE);
    }

    Percent = (100 * number) / (number + numberLeft);

    if(Percent > 100)
      Percent = 100;

    LT_SetAttributes(handle,GAD_BatchProgress,
      GA_Disabled,  FALSE,
      LAGA_Percent, Percent,
    TAG_DONE);
  }
  else
  {
    LT_SetAttributes(handle,GAD_BatchProgress,
      GA_Disabled,TRUE,
    TAG_DONE);
  }
}

STATIC VOID
UpdateProgress (LONG bytes, LONG maximum)
{
  UBYTE LocalBuffer[256];
  ULONG Percent;

  if(bytes < maximum)
  {
    ULONG bps;

    if((bps = CalculateBPS()) > 0)
    {
      ULONG time,delta;
      STRPTR String;

      delta = maximum - bytes;

      time = delta / bps;

      if(time < 1)
        String = "< 00:00:01 h";
      else
        LimitedSPrintf(sizeof(LocalBuffer),String = LocalBuffer,"  %2ld:%02ld:%02ld h",time / (60 * 60),(time / 60) % 60,time % 60);

      LT_SetAttributes(handle,GAD_NameBox,
  		  LABX_Index,		NAMEBOX_Completion,
  		  LABX_Text,		String,
      TAG_DONE);

      LimitedSPrintf(sizeof(LocalBuffer),LocalBuffer,"%lD bytes/s",bps);

      LT_SetAttributes(handle,GAD_TransferBox,
  		  LABX_Index,		TRANSFERBOX_CPS,
  		  LABX_Text,		LocalBuffer,
      TAG_DONE);
    }
  }

  LimitedSPrintf(sizeof(LocalBuffer),LocalBuffer,"%lD of %lD",bytes,maximum);

  LT_SetAttributes(handle,GAD_NameBox,
  	LABX_Index,		NAMEBOX_Size,
  	LABX_Text,		LocalBuffer,
  TAG_DONE);

  if(maximum > 0)
    Percent = (100 * bytes) / maximum;
  else
    Percent = 0;

  LT_SetAttributes(handle,GAD_FileProgress,
  	LAGA_Percent,	Percent,
  TAG_DONE);
}

/* ===== CRC =========================================================== */
/* crctab calculated by Mark G. Mendel, Network Systems Corporation */
STATIC UWORD crctab[256] =
{
  0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
  0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
  0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
  0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
  0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
  0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
  0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
  0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
  0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
  0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
  0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
  0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
  0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
  0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
  0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
  0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
  0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
  0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
  0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
  0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
  0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
  0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
  0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
  0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
  0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
  0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
  0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
  0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
  0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
  0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
  0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
  0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
};

/*
 * updcrc macro derived from article Copyright (C) 1986 Stephen Satchell.
 *  NOTE: First argument must be in range 0 to 255.
 *        Second argument is referenced twice.
 *
 * Programmers may incorporate any or all code into their programs,
 * giving proper credit within the source. Publication of the
 * source routines is permitted so long as proper credit is given
 * to Stephen Satchell, Satchell Evaluations and Chuck Forsberg,
 * Omen Technology.
 */

#define updcrc(cp, crc) ( crctab[crc >> 8] ^ (((crc & 255) << 8) | (cp)))

/* ===== COMMON FUNCTIONS ============================================== */

STATIC VOID
ToOctal (ULONG v, UBYTE * s)
{
  UBYTE t[20], *u;

  u = t;

  do
  {
    *u++ = '0' + (v & 7);

    v >>= 3;
  }
  while (v != 0);

  while (u > t)
    *s++ = *(--u);

  *s = 0;
}

STATIC ULONG
GetTime ()
{
  struct timeval Now;

  GetSysTime (&Now);

  return (Now.tv_secs);
}

/* ----- Should we escape control characters? -------------------------- */

STATIC BOOL
Escape ()
{
  return ((BOOL) (ZEscapeCtl || Config->SerialConfig->BitsPerChar < 8));
}

/* ----- Allocate memory for transmit buffer --------------------------- */

STATIC LONG
NewTxBuffer (VOID)
{
  DB (kprintf ("»txbuffer size is %ld\«\n", block ? 2 * (ZMAXSPLEN + 6) : 32));

  TxBuffer = (UBYTE *) AllocVecPooled (3 * (ZMAXSPLEN + 6),MEMF_ANY);

  SerialBuffer = (UBYTE *) AllocVecPooled (SERIALBUFFERSIZE, MEMF_ANY);

  if (TxBuffer && SerialBuffer)
  {
    SerialIndex = SerialBuffer;
    SerialCache = 0;

    return (0);
  }
  else
    return (ERROR_NO_FREE_STORE);
}

/* ----- Dispose of transmit buffer ------------------------------------ */

STATIC VOID
DisposeTxBuffer ()
{
  FreeVecPooled (TxBuffer);
  TxBuffer = NULL;

  FreeVecPooled (SerialBuffer);
  SerialBuffer = NULL;
}

/* ----- Prepare new transmission -------------------------------------- */

STATIC VOID
InitTxBuffer ()
{
  TxPtr = TxBuffer;
}

/* ----- Start transmission, don't wait -------------------------------- */

STATIC VOID
TransmitTxBuffer ()
{
  if (TxPtr > TxBuffer)
  {
    ULONG Size;

    Size = (ULONG) TxPtr - (ULONG) TxBuffer;

    DoSerialWrite (TxBuffer, Size);
  }
}

/* ----- Send more bytes ----------------------------------------------- */

STATIC VOID
SendString (UBYTE * s, UWORD n)
{
  InitTxBuffer ();
  memcpy (TxPtr, s, n);
  TxPtr += n;
  TransmitTxBuffer ();
}

/* ----- Send cancel string -------------------------------------------- */

STATIC VOID
SendCancel ()
{
  STATIC UBYTE cs[] =
  {
    CAN, CAN, CAN, CAN, CAN, CAN, CAN, CAN,
    BS, BS, BS, BS, BS, BS, BS, BS, BS, BS
  };

  DoSerialWrite (cs, sizeof (cs));
}

/* ----- Send a byte --------------------------------------------------- */

STATIC UBYTE
SendByte (UBYTE c)
{
  *TxPtr++ = c;
  return c;
}

/* ----- Send attention string ----------------------------------------- */

STATIC VOID
SendAttn (UBYTE * s)
{
  while (*s)
  {
    switch (*s)
    {
    case 0xDD:                  /* Send break signal */

      DoSerialBreak ();
      break;

    case 0xDE:                  /* Sleep 1 second */

      DelayTime (1, 0);
      break;

    default:

      DoSerialWrite (s, 1);
      break;
    }

    ++s;
  }
}

/* ----- Swap 2 bytes in WORD ----------------------------------------- */

STATIC UWORD __inline
Swap2 (UWORD n)
{
  return ((UWORD) (((n & 0xFF) << 8) | (n >> 8)));
}

/* ----- Swap 4 bytes in long ------------------------------------------ */

STATIC ULONG __inline
Swap4 (ULONG n)
{
  return (((ULONG) Swap2 (n & 0xffff) << 16) | Swap2 (n >> 16));
}

/* ----- Adjust header ------------------------------------------------- */

STATIC VOID
AdjustHeader (UBYTE type, HEADER * p)
{
  switch (type)
  {
  case ZACK:
  case ZRPOS:
  case ZDATA:
  case ZEOF:
  case ZCRC:
  case ZCHALLENGE:
  case ZFREECNT:
  case ZCOMPL:

    p->position = Swap4 (p->position);
    break;

  case ZRINIT:

    p->zrinit.bufsize = Swap2 (p->zrinit.bufsize);
    break;
  }
}

/* ----- Send a byte as two hex digits --------------------------------- */

STATIC UBYTE
PutHex (UBYTE c)
{
  STATIC UBYTE digits[] =
  {
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
  };

  SendByte (digits[(c >> 4) & 0xF]);
  SendByte (digits[c & 0xF]);

  return c;
}

/* ----- Send a byte with ZMODEM escape sequence encoding -------------- */

STATIC UBYTE
LocalSendLine (UBYTE c)
{
  UBYTE b = c;

  STATIC UBYTE lastsent = 0;    /* Last byte sent */

  if (c & 0x60)                 /* Non control character */
    SendByte (lastsent = c);
  else
  {
    switch (c)
    {
    case ZDLE:

      SendByte (ZDLE);
      SendByte (lastsent = c ^ 0x40);
      break;

    case CR:                    /* Escape CR followed by @ (Telenet net escape) */
    case 0x80 | CR:

      if (!Zctlesc && (lastsent & 0x7F) != '@')
      {
        SendByte (lastsent = c);
        break;
      }

      /* Fall thru */

    case DLE:
    case 0x80 | DLE:
    case XON:
    case 0x80 | XON:
    case XOFF:
    case 0x80 | XOFF:

      SendByte (ZDLE);
      c ^= 0x40;
      SendByte (lastsent = c);
      break;

    default:

      if (Zctlesc && !(c & 0x60))
      {
        SendByte (ZDLE);
        c ^= 0x40;
      }

      SendByte (lastsent = c);
    }
  }
  return b;
}

/* ----- Send ZMODEM HEX header ---------------------------------------- */

STATIC VOID
SendHexHeader (UBYTE type, HEADER * hdr)
{
  WORD i;
  UWORD crc;

  AdjustHeader (type, hdr);

  InitTxBuffer ();
  SendByte (ZPAD);              /* 1 */
  SendByte (ZPAD);              /* 1 */
  SendByte (ZDLE);              /* 1 */
  SendByte (ZHEX);              /* 1 */

  crc = updcrc (PutHex (type), 0);

  for (i = 0; i < 4; ++i)
    crc = updcrc (PutHex (hdr->b[i]), crc);     /* 8 */

  crc = updcrc (0, updcrc (0, crc));

  PutHex (crc >> 8);            /* 2 */
  PutHex (crc & 0xFF);          /* 2 */

  SendByte (CR);                /* 1 */
  SendByte (LF);                /* 1 */

  if (type != ZFIN && type != ZACK)
    SendByte (XON);             /* 1 */

  TransmitTxBuffer ();          /* Worst case:  19 bytes */
}

/* ----- Send ZMODEM binary header ------------------------------------- */

STATIC VOID
SendBinaryHeader (UBYTE type, HEADER * hdr)
{
  WORD i;
  UWORD crc;

  AdjustHeader (type, hdr);

  InitTxBuffer ();
  SendByte (ZPAD);              /* 1 */
  SendByte (ZDLE);              /* 1 */
  SendByte (ZBIN);              /* 1 */

  crc = updcrc (LocalSendLine (type), 0);

  for (i = 0; i < 4; ++i)
    crc = updcrc (LocalSendLine (hdr->b[i]), crc);      /* 8 */

  crc = updcrc (0, updcrc (0, crc));
  LocalSendLine (crc >> 8);     /* 2 */
  LocalSendLine (crc & 0xFF);   /* 2 */

  TransmitTxBuffer ();          /* Worst case:  15 bytes */
}

/* ----- Send binary array with ending ZDLE sequence ------------------- */

STATIC VOID
SendData (UBYTE * buf, UWORD length, UBYTE frameend)
{
  UWORD crc;

  InitTxBuffer ();
  for (crc = 0; length; ++buf, --length)
  {
    LocalSendLine (*buf);
    crc = updcrc (*buf, crc);
  }
  SendByte (ZDLE);
  SendByte (frameend);
  crc = updcrc (frameend, crc);
  crc = updcrc (0, updcrc (0, crc));
  LocalSendLine (crc >> 8);
  LocalSendLine (crc & 0xFF);

  if (frameend == ZCRCW)
    SendByte (XON);

  TransmitTxBuffer ();
}

/* ----- Read character with timeout ----------------------------------- */

STATIC WORD
ReadByte (UBYTE * c, ULONG timeout)
{
  STATIC WORD abort;            /* CAN counter */

  LONG err;

  DB (kprintf ("» ReadByte timeout=%ld\n", timeout));

  if (SerialCache > 0)
  {
    if (SetSignal (0, (1L << handle->Window->UserPort->mp_SigBit)) & (1L << handle->Window->UserPort->mp_SigBit))
    {
      if (CheckCancel ())
        longjmp (CancelEnv, CANCEL);
    }

    *c = *SerialIndex++;

    if (--SerialCache == 0)
    {
      DB (kprintf ("» Cache exhausted\n"));

      SerialIndex = SerialBuffer;

      if (CheckSerialRead ())
      {
        err = WaitSerialRead ();

        if (err)
        {
          if (err = MapSerialError (err))
            longjmp (CancelEnv, err);
        }

        SerialBuffer[0] = zc;

        if (SerialCache = GetSerialWaiting ())
        {
          if (SerialCache > SERIALBUFFERSIZE - 1)
            SerialCache = SERIALBUFFERSIZE - 1;

          err = DoSerialRead (&SerialBuffer[1], SerialCache);

          if (err)
          {
            if (err = MapSerialError (err))
              longjmp (CancelEnv, err);
          }
        }

        SerialCache++;

        StartSerialRead (&zc, 1);
      }

      DB (kprintf ("» Cache size = %ld\n", SerialCache));
    }
  }
  else
  {
    ULONG mask;

    if (!timeout)
      return (NOTHING);

    StartTime (timeout,0);

    for (;;)
    {
      mask = Wait (SIG_TIMER | SIG_SERIAL | (1L << handle->Window->UserPort->mp_SigBit));

      if (mask & (1L << handle->Window->UserPort->mp_SigBit))
      {
        if (CheckCancel ())
        {
          StopTime ();

          longjmp (CancelEnv, CANCEL);
        }
      }

      if (mask & SIG_SERIAL)
      {
        err = WaitSerialRead ();

        StopTime ();

        if (err)
        {
          if (err = MapSerialError (err))
            longjmp (CancelEnv, err);
        }

        *c = zc;

        if (SerialCache = GetSerialWaiting ())
        {
          if (SerialCache > SERIALBUFFERSIZE)
            SerialCache = SERIALBUFFERSIZE;

          err = DoSerialRead (SerialBuffer, SerialCache);

          if (err)
          {
            if (err = MapSerialError (err))
              longjmp (CancelEnv, err);
          }
        }

        StartSerialRead (&zc, 1);

        DB (kprintf ("» Cache size = %ld\n", SerialCache));

        break;
      }

      if (mask & SIG_TIMER)
      {
        WaitTime ();

        if (GetSerialStatus () & CIAF_COMCD)
          longjmp (CancelEnv, NO_CARRIER);
        else
          return (TIMEOUT);
      }
    }
  }

  if (*c == CAN)
  {
    if (++abort >= 5)
      longjmp (CancelEnv, ABORT);
  }
  else
    abort = 0;

  return FINE;
}

/* ----- Wait for ZPAD character --------------------------------------- */

STATIC WORD
WaitZPAD (
           ULONG timeout,       /* Timeout in ticks */
           UWORD n)             /* Max bytes before start of frame */
{
  UBYTE c;

  for (;;)
  {
    switch (ReadByte (&c, timeout))
    {
    case FINE:

      if ((c & 0x7F) == ZPAD)
        return FINE;

      if (--n == 0)
        return ERROR;

      break;

    case TIMEOUT:

      return TIMEOUT;
    }
  }
}

/* ----- Read character with timemout, eat parity, XON and XOFF -------- */

STATIC WORD
ReadByte2 (
            UBYTE * c,          /* Character (result) */
            ULONG timeout)      /* Timeout in ticks */
{
  for (;;)
  {
    if (ReadByte (c, timeout))
      return TIMEOUT;

    switch (*c &= 0x7F)
    {
    case XON:
    case XOFF:

      break;

    default:

      if (Zctlesc && !(*c & 0x60))
        break;

    case CR:
    case LF:
    case ZDLE:

      return FINE;
    }
  }
}

/* ----- Decode two lower case hex digits into an 8 bit byte value ----- */

STATIC WORD
GetHex (UBYTE * c)
{
  WORD i;
  UBYTE n;

  *c = 0;

  for (i = 0; i < 2; ++i)
  {
    *c <<= 4;

    if (ReadByte2 (&n, ZTimeout))
      return TIMEOUT;

    if (n >= '0' && n <= '9')
      n -= '0';
    else
    {
      if (n >= 'a' && n <= 'f')
        n -= 'a' - 10;
      else
        return ERROR;
    }

    *c += n;
  }

  return FINE;
}

/* ----- Read a byte, checking for ZMODEM escape encoding -------------- */

STATIC WORD
ZdleRead (UBYTE * c)
{
  do
  {
    if (ReadByte (c, ZTimeout))
      return TIMEOUT;

    if (*c & 0x60)              /* Non-control character */
      return FINE;

    switch (*c)                 /* Control character */
    {
    case ZDLE:
    case XON:
    case 0x80 | XON:
    case XOFF:
    case 0x80 | XOFF:

      break;                    /* Ignore XON/XOFF */

    default:

      if (!Zctlesc)
        return FINE;

      break;                    /* Ignore ctrl if escaping */
    }
  }
  while (*c != ZDLE);

  /* Previous character was ZDLE */

  for (;;)
  {
    if (ReadByte (c, ZTimeout))
      return TIMEOUT;

    switch (*c)
    {
    case ZCRCE:
    case ZCRCG:
    case ZCRCQ:
    case ZCRCW:

      return (WORD) (*c | GOTOR);       /* Frame end */

    case ZRUB0:

      *c = DEL;
      return FINE;

    case ZRUB1:

      *c = 0x80 | DEL;
      return FINE;

    case XON:
    case 0x80 | XON:
    case XOFF:
    case 0x80 | XOFF:

      break;                    /* Ignore XON/XOFF */

    default:
      if (Zctlesc && !(*c & 0x60))
        break;                  /* Ignore ctrl if escaping */

      if ((*c & 0x60) != 0x40)  /* Must be -10- ---- */
        return ERROR;

      *c ^= 0x40;               /* Invert bit 6 */

      return FINE;
    }
  }
}

/* ----- Receive a binary style header (type and position) ------------- */

STATIC WORD
ReceiveBinaryHeader (UBYTE * type,UBYTE * hdr)
{
  WORD n, err;
  UWORD crc;
  UBYTE c;

  /* Header type */

  if (err = ZdleRead (type))
    return err;                 /* TIMEOUT, ERROR, GOTxxxx */
  crc = updcrc (*type, 0);

  /* header info (4 bytes) */
  for (n = 0; n < 4; ++n, ++hdr)
  {
    if (err = ZdleRead (hdr))
      return err;               /* TIMEOUT, ERROR, GOTxxxx */
    crc = updcrc (*hdr, crc);
  }

  /* CRC (2 bytes) */
  if (err = ZdleRead (&c))
    return err;                 /* TIMEOUT, ERROR, GOTxxxx */
  crc = updcrc (c, crc);
  if (err = ZdleRead (&c))
    return err;                 /* TIMEOUT, ERROR, GOTxxxx */
  if (crc = updcrc (c, crc))
    return ERROR;               /* Bad CRC */

  return FINE;
}

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

STATIC WORD
ReceiveHexHeader (UBYTE * type,UBYTE * hdr)
{
  WORD n, err;
  UWORD crc;
  UBYTE c;

  DB (kprintf ("»ReceiveHexHeader«\n"));

  /* Header type */

  if (err = GetHex (type))
    return err;
  DB (kprintf ("»header type is %ld«\n", *type));
  crc = updcrc (*type, 0);

  /* Header info (4 bytes) */

  for (n = 0; n < 4; ++n, ++hdr)
  {
    if (err = GetHex (hdr))
      return err;
    crc = updcrc (*hdr, crc);
  }

  DB (kprintf ("»header info is %08lx, crc %02lx«\n", *(ULONG *) & hdr[-4], crc));

  /* CRC (2 bytes) */

  if (err = GetHex (&c))
    return err;
  DB (kprintf ("»%lx", c));
  crc = updcrc (c, crc);
  if (err = GetHex (&c))
    return err;
  DB (kprintf ("%lx«\n", c));
  if (crc = updcrc (c, crc))
  {
    DB (kprintf ("»CRC %02lx is wrong«\n", crc));
    return ERROR;               /* Bad CRC */
  }

  /* Throw away possible CR/LF */

  if (ReadByte (&c, 1) == FINE && (c & 0x7F) == CR)
    ReadByte (&c, 1);

  return FINE;
}

/* ----- Read a ZMODEM header (either binary or hex) ------------------- */

STATIC WORD
GetHeader (UBYTE * type,HEADER * hdr)
{
  WORD err;
  WORD n = GARBAGE;
  UBYTE c;

  DB (kprintf ("»GetHeader«\n"));

  /* Wait for ZPAD character */

  if (err = WaitZPAD (ZTimeout, GARBAGE))
  {
    DB (kprintf ("»didn't get ZPAD«\n"));
    goto done;
  }

  /* Just got ZPAD character, wait for ZDLE character */

  do
  {
    if (err = ReadByte2 (&c, ZTimeout))
    {
      DB (kprintf ("»didn't get ZDLE«\n"));
      goto done;
    }
    switch (c)
    {
    case ZDLE:                  /* This is what we want */
    case ZPAD:                  /* May be more than one ZPAD */
    case ZPAD | 0x80:
      break;
    again:
    default:                    /* Garbage */
      if (--n == 0)
      {
        err = ERROR;
        goto done;
      }
      break;
    }
  }
  while (c != ZDLE);

  /* Just got ZPAD-ZDLE sequence */

  if (err = ReadByte2 (&c, ZTimeout))
  {
    DB (kprintf ("»didn't get header type«\n"));
    goto done;
  }
  DB (kprintf ("»got header type %ld«\n", c));
  switch (c)
  {
  case ZBIN:                    /* It's a binary header */
    err = ReceiveBinaryHeader (type, hdr->b);
    break;
  case ZHEX:                    /* It's a hex header */
    err = ReceiveHexHeader (type, hdr->b);
    break;
  default:                      /* Garbage */
    goto again;
  }
  AdjustHeader (*type, hdr);

  /* ZPAD ZDLE ZBIN/ZHEX ... */

done:
  DB (kprintf ("»GetHeader done, err %ld«\n", err));
  return err;
}

/* ----- Receive array with ending ZDLE sequence and CRC --------------- */

STATIC WORD
ReceiveData (
              UBYTE * buf,      /* Buffer for data */
              UWORD length,     /* Max size of buffer */
              UWORD * count,    /* Number of data bytes received */
              UBYTE * framend)  /* Frame end character received */
{
  UWORD crc = 0;
  UBYTE *max = buf + length;
  WORD err;
  UBYTE c;

  *framend = 0xFF;
  while (buf <= max)
  {
    switch (err = ZdleRead (&c))
    {
    case FINE:                  /* Data byte */
      crc = updcrc (c, crc);

      if (ConvertNL)
      {
        if (c != CR)
          *buf++ = c;
      }
      else
        *buf++ = c;

      break;
    case GOTCRCE:               /* Frame end, CRC follows */
    case GOTCRCG:
    case GOTCRCQ:
    case GOTCRCW:
      *count = length - (max - buf);
      crc = updcrc (*framend = c, crc);
      if (err = ZdleRead (&c))
        goto done;
      crc = updcrc (c, crc);
      if (err = ZdleRead (&c))
        goto done;
      if (crc = updcrc (c, crc))
        err = ERROR;            /* Bad CRC */
      else
        err = FINE;
      goto done;
    default:                    /* TIMEOUT, ERROR, ... */
      *count = length - (max - buf);
      goto done;
    }
  }
  *count = length - (max - buf);
  err = ERROR;                  /* Data subpacket too LONG */

done:
  return err;
}

/* ----- Convert between Amiga time and ZModem time ---------------- */

STATIC VOID
ConvertTime (BOOL toAmiga, ULONG * t)
{
  const ULONG UTC_Offset = 252482400;

  if (toAmiga)
  {
    if (*t < UTC_Offset)
      *t = 0;
    else
      *t = *t - UTC_Offset;
  }
  else
    *t = *t + UTC_Offset;
}

/* ===== TRANSMIT ====================================================== */

/* ----- Fill buffer --------------------------------------------------- */

STATIC LONG
LocalReadBuffer (LONG * count,UBYTE * buf)
{
  *count = Read (FileHandle, buf, *count);

  return (*count == 0);
}

STATIC LONG
LocalReadBufferAndConvert (LONG * count,UBYTE * buf,UBYTE * lastc)
{
  LONG last, c;
  LONG i;

  last = *lastc;

  for (i = 0; i < *count; i++)
  {
    if ((c = FGetC (FileHandle)) < 0)
    {
      *count = i;
      *lastc = last;
      return (TRUE);
    }

    if (c == LF && last != CR)
    {
      UnGetC (FileHandle, LF);
      c = CR;
    }

    *buf++ = c;
    last = c;
  }

  *lastc = last;

  return (FALSE);
}

/* ----- Set file position --------------------------------------------- */

STATIC VOID
SetFilePosition (ULONG position)
{
  Seek (FileHandle, position, OFFSET_BEGINNING);
}

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

STATIC WORD
GetInSync (
            BOOL flag,          /* TRUE: ZACK always returns */
            UBYTE * type)
{
  WORD retry;
  WORD err;
  HEADER hdr;

  for (retry = 0; retry <= ZRetries; ++retry)
  {
    AddMessage("Synchronizing...");
    UpdateRetries(retry);

    switch (GetHeader (type, &hdr))
    {
    case FINE:
      switch (*type)
      {
      case ZRPOS:
        SetFilePosition (hdr.position);
        Lrxpos = Mark = hdr.position;
        if (Lastsync == hdr.position &&
            ++BeenHereB4 > 4 && Count > 32)
          Count /= 2;
        Lastsync = hdr.position;
        err = FINE;
        goto done;
      case ZACK:
        Lrxpos = hdr.position;
        if (flag || Mark == hdr.position)
        {
          err = FINE;
          goto done;
        }
        break;                  /* Ignore this ZACK */
      case ZCAN:
      case ZABORT:
      case ZFIN:
        goto error;
      case ZRINIT:
      case ZSKIP:
        err = FINE;
        goto done;
      }
      break;
    case TIMEOUT:
      goto error;
    }
    hdr.position = 0;
    SendHexHeader (ZNAK, &hdr);
  }
error:
  err = ERROR;
done:
  return err;
}

/* ----- Send the data in the file ------------------------------------- */

STATIC WORD
SendFileData ()
{
  WORD junkcount;               /* Counts garbage chars received */
  ULONG txwcnt;                 /* Counter used to space ACK requests */
  WORD eofseen;                 /* End of file seen (file read error) */
  UBYTE type;                   /* Header type */
  HEADER hdr;                   /* Header */
  WORD err;                     /* Error code */
  WORD newcnt;                  /* Controls receiver's buffer */
  LONG n;                       /* Transmit buffer length */
  UBYTE e;                      /* Frame end character */
  UBYTE c;
  UBYTE lastc;

  Lastsync = (StartPos = Lrxpos = Mark) - 1;
  junkcount = 0;
  BeenHereB4 = 0;
  lastc = 0;

somemore:
  if (FALSE)
  {

  waitack:
    junkcount = 0;
    err = GetInSync (FALSE, &type);
  gotack:
    if (err)
      return ERROR;
    switch (type)
    {
    case ZSKIP:
    case ZRINIT:
      return FINE;
    case ZACK:
    case ZRPOS:
      break;
    default:
      return ERROR;
    }

    /* Check reverse channel (but don't wait) */

    if (CheckCancel ())
      longjmp (CancelEnv, CANCEL);
    while (ReadByte (&c, 0) == FINE)
    {
      switch (c)
      {
      case CAN:
      case ZPAD:
        err = GetInSync (TRUE, &type);
        goto gotack;
      case XOFF:                /* Wait a while for an XON */
      case XOFF | 0x80:
        ReadByte (&c, 100);
      }
    }

  }                             /* somemore */

  /* Send ZDATA header */

  hdr.position = Mark;
  SendBinaryHeader (ZDATA, &hdr);

  /* Send one or more data subpackets */

  newcnt = Rxbuflen;
  txwcnt = 0;
  do
  {
    UpdateProgress (Mark,Length);

    /* Send data subpacket */

    n = Count;

    if (ConvertNL)
      eofseen = LocalReadBufferAndConvert (&n, Buffer, &lastc);
    else
      eofseen = LocalReadBuffer (&n, Buffer);

    if (eofseen)
      e = ZCRCE;
    else if (junkcount > 3)
      e = ZCRCW;
    else if (Mark == Lastsync)
      e = ZCRCW;
    else if (Rxbuflen && (newcnt -= n) <= 0)
      e = ZCRCW;
    else if (Txwindow && (txwcnt += n) >= Txwspac)
    {
      txwcnt = 0;
      e = ZCRCQ;
    }
    else
      e = ZCRCG;

    SendData (Buffer, n, e);

    ZBytesOut += n;
    Mark += n;

    /* Wait for ZACK if necessary */

    if (e == ZCRCW)
      goto waitack;

    /* Check reverse channel (but don't wait) */

    if (CheckCancel ())
      longjmp (CancelEnv, CANCEL);
    while (ReadByte (&c, 0) == FINE)
    {
      switch (c)
      {
      case CAN:
      case ZPAD:
        err = GetInSync (TRUE, &type);
        if (!err && type == ZACK)
          break;
        /* zcrce - dinna wanna starta ping-pong game */
        SendData (Buffer, 0, ZCRCE);
        goto gotack;
      case XOFF:                /* Wait a while for an XON */
      case XOFF | 0x80:
        ReadByte (&c, 100);
      default:
        ++junkcount;
        break;
      }
    }

    /* Make sure to stay in transmit window */

    if (Txwindow)
    {
      while ((Mark - Lrxpos) >= Txwindow)
      {
        if (e != ZCRCQ)
          SendData (Buffer, 0, e = ZCRCQ);
        err = GetInSync (TRUE, &type);
        if (err || type != ZACK)
        {
          SendData (Buffer, 0, ZCRCE);
          goto gotack;
        }
      }
    }
  }
  while (!eofseen);

  /* Send ZEOF header, wait for ZRINIT header from receiver */

  for (;;)
  {
    hdr.position = Mark;
    SendBinaryHeader (ZEOF, &hdr);
    if (GetInSync (FALSE, &type))
      return ERROR;
    switch (type)
    {
    case ZRINIT:
    case ZSKIP:
      return FINE;
    case ZACK:
      break;
    case ZRPOS:
      goto somemore;
    default:
      return ERROR;
    }
  }
}

/* ----- Invite receiver ----------------------------------------------- */

STATIC WORD
Invitation ()
{
  HEADER hdr;
  WORD retry;
  WORD err;

  DB (kprintf ("»Invitation«\n"));

  for (retry = 0; retry <= ZRetries; ++retry)
  {
    UpdateRetries(retry);
    hdr.position = 0;
    SendHexHeader (ZRQINIT, &hdr);
    if ((err = WaitZPAD (ZTimeout, 10)) == FINE)
    {
      DB (kprintf ("»Got ZPad«\n"));
      return FINE;
    }
  }
  return TIMEOUT;
}

/* ----- Send invitation to receiver, get receiver's parameters -------- */

STATIC WORD
SessionStartup ()
{
  WORD retry;
  WORD err;
  UBYTE *message;
  UBYTE type;
  HEADER hdr;

  DB (kprintf ("»SessionStartup«\n"));

  StartupTime = GetTime ();
  ResetUI(handle);
  ZBytesIn = ZBytesOut = LastBPS = 0;
  message = NULL;
  for (retry = 0; retry <= ZRetries; ++retry)
  {
    if(message)
    {
      AddMessage(message);
      message = NULL;
    }

    UpdateRetries(retry);
    switch (GetHeader (&type, &hdr))
    {
    case FINE:
      DB (kprintf ("»FINE: type %ld«\n", type));

      switch (type)
      {
      case ZRINIT:              /* This is what we want */
        message = (UBYTE *) "ZRINIT";

        DB (kprintf ("»got ZRInit«\n"));

        /* Set transfer parameters */

        Rxbuflen = hdr.zrinit.bufsize;
        Txwindow = ZWindow;
        Txwspac = Zcrcq;
        Count = ZPacket;
#ifdef XXX
        if (Txwindow < 256)
          Txwindow = 256;
        Txwindow = (Txwindow / 64) * 64;
        Txwspac = Txwindow / 4;
        if (Count > Txwspac ||
            (!Count && Txwspac < ZMAXSPLEN))
          Count = Txwspac;
#endif
        if (!hdr.zrinit.canfdx)
          Txwindow = 0;
        if (Rxbuflen && Count > Rxbuflen)
          Count = Rxbuflen;

        /* No need to send an ZSINIT frame if ... */

        Zctlesc = hdr.zrinit.escctl || Escape ();
        if (!Escape () || hdr.zrinit.escctl)
        {
          err = FINE;
          goto done;
        }
        for (retry = 0; retry <= ZRetries; ++retry)
        {
          DB (kprintf ("»sending ZSInit\n«"));
          /* ZSINIT header */
          hdr.position = 0;
          hdr.zsinit.escctl = 1;
          SendBinaryHeader (ZSINIT, &hdr);

          /* Our attention string (empty string) */
          type = 0;
          SendData (&type, 1, ZCRCW);
          /* Wait for ZACK header from receiver */
          if (!GetHeader (&type, &hdr) && type == ZACK)
          {
            err = FINE;
            goto done;
          }
        }
        err = TIMEOUT;
        goto done;

      case ZCHALLENGE:          /* Echo challenge number */
        SendHexHeader (ZACK, &hdr);
        message = (UBYTE *) "ZCHALLENGE";
        break;
      case ZCOMMAND:            /* They didn't see out ZRQINIT */
        hdr.position = 0;
        SendHexHeader (ZRQINIT, &hdr);
        message = (UBYTE *) "ZRQINIT";
        break;
      case ZRQINIT:
        if (!hdr.zrqinit.command)
          break;                /* Ignore (our echo) */
        /* Fall thru */
      default:
        hdr.position = 0;
        SendHexHeader (ZNAK, &hdr);
        message = (UBYTE *) "->ZNAK";
        break;
      }
    case TIMEOUT:
      err = TIMEOUT;
      goto done;
    default:
      hdr.position = 0;
      SendHexHeader (ZNAK, &hdr);
      message = (UBYTE *) "->ZNAK";
      break;
    }
  }
  err = TIMEOUT;                /* Retry count exhausted */
done:
  DB (kprintf ("»Done«\n"));
  if(message)
    AddMessage(message);
  return err;
}

/* ----- Send file information ----------------------------------------- */

STATIC WORD
SendFileInfo (
               UBYTE * name,            /* File name */
               ULONG size,              /* File size */
               BOOL text,               /* Text file? */
               ULONG modif,             /* Modification date */
               ULONG mode,              /* File mode data */
               ULONG * startpos,        /* Start position */
               ULONG TotalFiles,        /* Number of files to send */
               ULONG TotalBytes)        /* Number of bytes to transmit */
{
  UBYTE *p;
  UBYTE s[30];
  WORD retry;
  WORD err;
  UBYTE *message;
  UBYTE buffer[128];
  UBYTE type;
  HEADER hdr;
  LONG len;

  if (!ZSendFullPath)
    name = FilePart (name);
  else
  {
    LONG i;

    /* Follow Unix file name conventions, i.e. strip the
       * leading device/volume name if any.
     */

    for (i = 0; i < strlen (name); i++)
    {
      if (name[i] == ':')
      {
        name = &name[i + 1];
        break;
      }
    }
  }

  /* File name as zero terminated ASCII string */

  p = buffer;
  strcpy (p, name);
  p += strlen (name) + 1;

  /* File size as decimal ASCII digits */

  LimitedSPrintf (sizeof (s), s, "%ld", size);
  len = strlen (s);

  memcpy (p, s, len);
  p += len;
  *p++ = ' ';

  /* Modification date: octal string = seconds since 1/1/1970 UTC */
  ConvertTime (FALSE, &modif);  /* -> ZModem time */

  ToOctal (modif, s);
  len = strlen (s);
  memcpy (p, s, len);
  p += len;
  *p++ = ' ';

  /* File mode (octal string) */

  ToOctal (mode, s);
  len = strlen (s);
  memcpy (p, s, len);
  p += len;
  *p++ = ' ';

  /* Serial number (octal string) */

  *p++ = '0';
  *p++ = ' ';

  /* Number of files remaining (decimal number) */

  LimitedSPrintf (sizeof (s), s, "%ld", TotalFiles);
  len = strlen (s);
  memcpy (p, s, len);
  p += len;
  *p++ = ' ';

  /* Number of bytes remaining (decimal number) */
  LimitedSPrintf (sizeof (s), s, "%ld", TotalBytes);
  len = strlen (s);
  memcpy (p, s, len);
  p += len;
  *p++ = 0;                     /* Final NULL */

  /* Send ZFILE header with ZModem conversion, management and transport
     options followed by a ZCRCW data subpacket containing the file name,
     file length, modification date. */

  message = NULL;
  for (retry = 0; retry <= ZRetries; ++retry)
  {
    UpdateRetries(retry);

    hdr.position = 0;
    hdr.zfile.conv = text ? 0 : ZCBIN;

    ConvertNL = text;

    if (ZOverwrite)
      hdr.zfile.manage = ZMCLOB;        /* Replace existing file */

    DB (kprintf ("»Sending ZFILE header (flags %08lx)«\n", hdr.position));
    SendBinaryHeader (ZFILE, &hdr);
    DB (kprintf ("»Sending data«\n"));
    SendData (buffer, p - buffer, ZCRCW);
    DB (kprintf ("»waiting for response«\n"));
  again:
    if(message)
    {
      AddMessage(message);
      message = NULL;
    }

    if (GetHeader (&type, &hdr))
    {                           /* Error */
      continue;
    }
    switch (type)
    {
    case ZRINIT:
      message = (UBYTE *) "ZRINIT";
      if (WaitZPAD (ZTimeout, 10) == FINE)
        goto again;
      break;
    case ZSKIP:
      message = (UBYTE *) "ZSKIP";
      /* The receiver may respond with a ZSKIP header, which
         makes the sender proceed to the next file (if any) in the
         batch. */
      *startpos = 0x7FFFFFFF;
      err = FINE;
      goto done;
    case ZRPOS:
      message = (UBYTE *) "ZRPOS";
      *startpos = hdr.position;
      err = FINE;
      goto done;
    }
  }
  err = TIMEOUT;
done:
  if(message)
    AddMessage(message);

  return err;
}

/* ----- Session cleanup ----------------------------------------------- */

STATIC VOID
SessionCleanup ()
{
  WORD retry;
  HEADER hdr;
  UBYTE type;
  STATIC UBYTE oo[] = "OO";     /* Over and out */

  for (retry = 0; retry <= ZRetries; ++retry)
  {
    hdr.position = 0;
    SendHexHeader (ZFIN, &hdr);
    if (GetHeader (&type, &hdr))
      continue;
    if (type == ZFIN)
    {
      SendString (oo, strlen (oo));
      return;
    }
  }
}

/* ----- Send a file --------------------------------------------------- */

LONG
ZTransmit (struct WBArg * ArgList, LONG NumArgs, BOOL TextMode)
{
  LONG err;
  ULONG modif;
  ULONG mode;
  ULONG TotalFiles, TotalBytes;
  BPTR OldCD, FileLock;
  LONG i;
  D_S (struct FileInfoBlock, FileInfo);

  Transmitting = TRUE;

  TotalFiles = TotalBytes = 0;
  BytesThisBatch = FilesThisBatch = 0;

  for (i = 0; i < NumArgs; i++)
  {
    OldCD = CurrentDir (ArgList[i].wa_Lock);

    if (!(FileLock = Lock (ArgList[i].wa_Name, SHARED_LOCK)))
      err = IoErr ();
    else
    {
      if (!Examine (FileLock, FileInfo))
        err = IoErr ();
      else
      {
        if (FileInfo->fib_DirEntryType > 0)
          err = ERROR_OBJECT_WRONG_TYPE;
        else
        {
          err = 0;

          if (!ZSkipIfArchived || !(FileInfo->fib_Protection & FIBF_ARCHIVE))
          {
            TotalFiles++;
            TotalBytes += FileInfo->fib_Size;
          }
        }
      }

      UnLock (FileLock);
    }

    CurrentDir (OldCD);

    if (err)
      return (err);
  }

  DB (kprintf ("»Buffer size is %ld«\n", ZMAXSPLEN));

  Buffer = (UBYTE *) AllocVecPooled (ZMAXSPLEN, MEMF_ANY);
  err = NewTxBuffer ();
  handle = OpenUI ("ZModem transmit");

  if (Buffer != NULL && err == 0 && handle != NULL)
  {
    Zctlesc = Escape ();

    ClearSerial ();

    if (SerialCache = GetSerialWaiting ())
    {
      if (SerialCache > SERIALBUFFERSIZE)
        SerialCache = SERIALBUFFERSIZE;

      DoSerialRead (SerialBuffer, SerialCache);
    }

    StartSerialRead (&zc, 1);

    for (i = 0; i < NumArgs; i++)
    {
      OldCD = CurrentDir (ArgList[i].wa_Lock);

      FileHandle = NULL;

      if (!(FileLock = Lock (ArgList[i].wa_Name, SHARED_LOCK)))
        err = IoErr ();
      else
      {
        if (!Examine (FileLock, FileInfo))
          err = IoErr ();
        else
        {
          if (FileInfo->fib_DirEntryType > 0)
            err = ERROR_OBJECT_WRONG_TYPE;
          else
          {
            LimitedStrcpy (sizeof (FileName), FileName, ArgList[i].wa_Name);

            if (!(FileHandle = Open (FileName, MODE_OLDFILE)))
              err = IoErr ();
            else
              err = 0;
          }
        }

        UnLock (FileLock);
      }

      CurrentDir (OldCD);

      if (ZSkipIfArchived && (FileInfo->fib_Protection & FIBF_ARCHIVE))
        continue;

      if (err != 0 || FileHandle == NULL)
        break;
      else
      {
        StartupTime = GetTime ();
        ZBytesIn = ZBytesOut = LastBPS = 0;
        Mark = StartPos = 0;
        ResetUI(handle);

        SetVBuf (FileHandle, NULL, BUF_FULL, 4096);

        Length = FileInfo->fib_Size;
        modif = (FileInfo->fib_Date.ds_Days * 24 * 60 + FileInfo->fib_Date.ds_Minute) * 60 + FileInfo->fib_Date.ds_Tick / TICKS_PER_SECOND;
        mode = Amiga2UnixMode (FileInfo->fib_Protection);

        UpdateProgress (0, Length);

        if (err = setjmp (CancelEnv))
        {                       /* Cancel or abort */
          if (err == CANCEL)
            SendCancel ();      /* Send cancel string */

          break;
        }

        if (i == 0)
        {
          if (!(err = Invitation ()))
            err = SessionStartup ();
        }
        else
          err = 0;

        if (!err)
        {
          if (!(err = SendFileInfo (FileName, Length, TextMode, modif, mode, &Mark, TotalFiles, TotalBytes)))
          {
            UpdateBatch(FilesThisBatch,TotalFiles,BytesThisBatch,TotalBytes);

            TotalFiles--;
            TotalBytes -= Length;

            BytesThisBatch += Length;
            FilesThisBatch++;

            if (Mark != 0x7FFFFFFF)
            {                   /* Not skip */
              SetFilePosition (Mark);

              NameProgress (FilePart (FileName));

              /* Send file data */
              err = SendFileData ();
            }
          }
        }

        Close (FileHandle);
        FileHandle = NULL;

        if (err == 0)
        {
          if (ZDeleteAfterSending)
            DeleteFile (FileName);
          else
          {
            if (ZProtectAfterSending)
              SetProtection (FileName, FileInfo->fib_Protection | FIBF_ARCHIVE);
          }
        }

        if (TotalFiles == 0)
          SessionCleanup ();

        if (err != 0)
          break;
      }
    }

    StopSerialRead ();
    RestartSerial ();
  }

  if (handle)
  {
    UpdateError (err);

    CloseUI (handle, err);
    handle = NULL;
  }
  if (FileHandle)
  {
    Close (FileHandle);
    FileHandle = NULL;
  }

  DisposeTxBuffer ();

  if (Buffer)
    FreeVecPooled (Buffer);

  return err;
}

/* ===== RECEIVE ======================================================= */

/* ----- Free bytes on the current file system ------------------------- */

STATIC ULONG
GetFreeSpace ()
{
  if (IsBlockMapped)
  {
    D_S (struct InfoData, InfoData);

    if (Info (ReceiveDir, InfoData))
    {
      if (InfoData->id_NumBlocks && InfoData->id_BytesPerBlock)
        return ((ULONG) ((InfoData->id_NumBlocks - InfoData->id_NumBlocksUsed) * InfoData->id_BytesPerBlock));
    }
  }

  return (0xFFFFFFFF);          /* many free bytes ... */
}

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

STATIC VOID
AckOverAndOut ()
{
  WORD retry;
  WORD err;
  HEADER hdr;
  UBYTE c;

  hdr.position = 0;
  for (retry = 0; retry < 3; ++retry)
  {
    SendHexHeader (ZFIN, &hdr);

    err = ReadByte (&c, ZTimeout);

    if (err == FINE && c == 'O')
    {
      ReadByte (&c, 60);
      return;                   /* Over and Out */
    }
  }
}

/* ----- Initialize for Zmodem receive attempt ------------------------- */

STATIC WORD
TryZReceive (
              HEADER * filehdr) /* Return sender's options */
{
  UBYTE *message;               /* Message to display */
  HEADER hdr;                   /* Header */
  UBYTE type;                   /* Header type received */
  WORD retry;                   /* Retry counter */
  WORD err;                     /* Error code */

  for (retry = 0, message = NULL; retry <= ZRetries;)
  {
    UpdateRetries(retry);

    hdr.position = 0;
    if (TryZhdrType == ZRINIT)
    {                           /* Capability flags */
      hdr.zrinit.bufsize = ZBuffer;
      hdr.zrinit.canfdx = 1;
      hdr.zrinit.canovio = 1;
      hdr.zrinit.escctl = Escape ();
    }
    SendHexHeader (TryZhdrType, &hdr);
    if (TryZhdrType == ZSKIP)   /* Don't skip too far */
      TryZhdrType = ZRINIT;
  again:
    if(message)
    {
      AddMessage(message);
      message = NULL;
    }
    ++retry;
    if (GetHeader (&type, &hdr) == FINE)
    {
      DB (kprintf ("»FINE: type %ld«\n", type));

      switch (type)
      {
      case ZRQINIT:
        continue;

      case ZFILE:
        *filehdr = hdr;         /* Return file options */
        TryZhdrType = ZRINIT;
        if (!ReceiveData (Buffer, ZMAXSPLEN, &Count, &type) &&
            type == ZCRCW)
        {
          err = FINE;
          goto done;            /* File name received */
        }
        SendHexHeader (ZNAK, &hdr);
        message = (UBYTE *) "ZFILE error";
        goto again;
      case ZSINIT:
        Zctlesc = hdr.zsinit.escctl;
        if (!ReceiveData (Attn, ZATTNLEN, &AttnLen, &type) &&
            type == ZCRCW)
        {
          hdr.position = 1;
          SendHexHeader (ZACK, &hdr);
          message = (UBYTE *) "ZSINIT ok";
          goto again;
        }
        SendHexHeader (ZNAK, &hdr);
        message = (UBYTE *) "ZSINIT error";
        goto again;
      case ZFREECNT:
        hdr.position = GetFreeSpace ();
        SendHexHeader (ZACK, &hdr);
        message = (UBYTE *) "ZFREECNT";
        goto again;
      case ZCOMMAND:
        if (!ReceiveData (Buffer, ZMAXSPLEN, &Count, &type) && type == ZCRCW)
        {
          STRPTR String;

          String = Buffer;

          while (*String == ' ' || *String == '\t')
            String++;

          if (!Strnicmp (String, "echo", 4))
            AddMessage(&String[4]);
          else
            AddMessage("Ignoring ZCOMMAND %s",String);

          do
          {
            hdr.position = 0;
            SendHexHeader (ZCOMPL, &hdr);

            if (GetHeader (&type, &hdr) == ZFIN)
            {
              AckOverAndOut ();
              Count = 0;        /* Nothing in Buffer */
              err = FINE;
              goto done;
            }
          }
          while (++errors < ZErrors);
        }
        else
        {
          SendHexHeader (ZNAK, &hdr);
          message = (UBYTE *) "ZCOMMAND error";
        }

        goto again;
      case ZCOMPL:
        message = (UBYTE *) "ZCOMPL";
        goto again;
      case ZFIN:
        message = (UBYTE *)"ZFIN";
        AckOverAndOut ();
        Count = 0;              /* Nothing in Buffer */
        err = FINE;
        goto done;
      }
    }
    else
      message = NULL;
  }
  err = TIMEOUT;
  Count = 0;                    /* Nothing in Buffer */
done:
  if(message)
     AddMessage(message);
  return err;
}

/* ----- Process incoming file information header ---------------------- */

STATIC LONG
ProcedeHeader (
                HEADER * filehdr,       /* Sender's options */
                STRPTR name,            /* File name extracted */
                ULONG * length,         /* File length extracted */
                ULONG * modif,          /* Modification time extracted */
                ULONG * mode,           /* File mode extracted */
                ULONG * filesleft,      /* Number of files left (0 == not known) */
                ULONG * bytesleft)      /* Number of bytes left (0 == not known) */
{
  WORD err;
  UBYTE *p, *max;
  UBYTE c;
  BOOL exists;                  /* File exists */
  LONG len;
  BPTR fileLock;
  D_S (struct FileInfoBlock, FileInfo);

  *name = 0;
  *length = 0;
  *modif = 0;
  *mode = 0;
  *filesleft = 0;
  *bytesleft = 0;

  FileHandle = NULL;

  /* Extract fields from file information header */

  max = (p = Buffer) + Count;
  err = 0;
  while (p < max)
  {
    c = *p++;
    switch (err)
    {
    case 0:                     /* File name (C-string) */
      if (c == '\0')
      {
        len = p - 1 - Buffer;
        memcpy (name, Buffer, len);
        name[len] = 0;
        ++err;                  /* Goto next state */
      }
      break;
    case 1:                     /* File length (decimal string, space) */
      if (c == ' ' || c == '\0')
        ++err;                  /* Goto next state */
      else
        *length = *length * 10 + (c & 0x0F);
      break;
    case 2:                     /* File modification time (octal string, space) */
      if (c == ' ' || c == '\0')
      {
        ConvertTime (TRUE, modif);      /* -> Amiga time */
        ++err;                  /* Goto next state */
      }
      else
        *modif = *modif * 8 + (c & 7);
      break;
    case 3:                     /* File modes (octal string, space) */
      if (c == ' ' || c == '\0')
      {
        *mode = Unix2AmigaMode (*mode);
        ++err;                  /* Goto next state */
      }
      else
        *mode = *mode * 8 + (c & 7);
      break;
    case 4:                     /* Serial number. */
      if (c == ' ' || c == '\0')
        err++;
      break;
    case 5:                     /* Number of files left */
      if (c == ' ' || c == '\0')
        err++;
      else
        *filesleft = *filesleft * 10 + (c & 15);
      break;
    case 6:                     /* Number of bytes left */
      if (c == ' ' || c == '\0')
        err++;
      else
        *bytesleft = *bytesleft * 10 + (c & 15);
      break;
    default:
      break;
    }
  }

  if (err < 3)                  /* Got no modification time...  */
    *modif = GetTime ();        /* ...so use actual time        */

  if (!ZUseFullPath)
    name = FilePart (name);

  /* See if file exists */

  if (fileLock = Lock (name, SHARED_LOCK))
  {
    if (!Examine (fileLock, FileInfo))
      memset (FileInfo, 0, sizeof (struct FileInfoBlock));

    UnLock (fileLock);

    exists = TRUE;
  }
  else
    exists = FALSE;

  /* Skip file if not present at rx */

  if (filehdr->zfile.skip && !exists)
    return 100;

  if (exists)
  {
    if (ZTransferIfNewer && err > 2)
    {
      struct DateStamp Stamp;

      Stamp.ds_Days = *modif / (24 * 60 * 60);
      Stamp.ds_Minute = (*modif % (24 * 60 * 60)) / 60;
      Stamp.ds_Tick = (*modif % 60) * TICKS_PER_SECOND;

      if (CompareDates (&Stamp, &FileInfo->fib_Date) < 0)
        exists = FALSE;
    }

    if (ZTransferIfSize && err > 1)
    {
      if (FileInfo->fib_Size != *length)
        exists = FALSE;
    }
  }

  /* Delete existing file */

  if (exists && filehdr->zfile.manage == ZMCLOB)
    exists = FALSE;

  /* Create new file */

  if (!exists)
    FileHandle = Open (name, MODE_NEWFILE);
  else
  {
    if (!(fileLock = Lock (name, EXCLUSIVE_LOCK)))
      FileHandle = Open (name, MODE_NEWFILE);
    else
    {
      if (!(FileHandle = OpenFromLock (fileLock)))
      {
        UnLock (fileLock);

        FileHandle = Open (name, MODE_READWRITE);
      }

      if (FileHandle)
      {
        if (Seek (FileHandle, 0, OFFSET_END) == -1)
        {
          LONG Error;

          Error = IoErr ();

          Close (FileHandle);

          SetIoErr (Error);

          FileHandle = NULL;
        }
      }
    }
  }

  if (FileHandle)
  {
    LimitedStrcpy (sizeof (FileName), FileName, name);

    return (FINE);
  }
  else
    return (IoErr ());
}

/* ----- Write buffer to file ------------------------------------------ */

STATIC LONG
WriteFile ()
{
  if (Write (FileHandle, Buffer, Count) == Count)
    return (0);
  else
    return (IoErr ());
}

/* ----- File was completely received ----------------------------------- */

STATIC VOID
CleanupFile (
              STRPTR name,      /* File name from ZModem header */
              ULONG modif,      /* File modification time */
              ULONG mode)       /* File mode data */
{
  struct DateStamp date;

  /* Set file creation/modification time and file attributes */

  date.ds_Days = modif / (24 * 60 * 60);
  date.ds_Minute = (modif % (24 * 60 * 60)) / 60;
  date.ds_Tick = (modif % 60) * TICKS_PER_SECOND;

  SetFileDate (name, &date);
  SetProtection (name, mode);
}

/* ----- Receive a file ------------------------------------------------ */

STATIC WORD
ReceiveFile (
              HEADER * filehdr) /* Sender's options */
{
  WORD err;             /* Error code */
  WORD retry;           /* Retry counter */
  UBYTE *message;       /* Message to display */
  UBYTE name[256];      /* File name from Z header */
  ULONG modif;          /* File modification date (Amiga time) */
  ULONG mode;           /* File mode data. */
  ULONG filesleft;      /* Number of files left. */
  ULONG bytesleft;      /* Number of bytes left. */
  HEADER hdr;           /* Header */
  UBYTE type;           /* Header type received */
  UBYTE end;            /* Data frame type */

  /* Get ready to receive new file */

  if (ProcedeHeader (filehdr, name, &Length, &modif, &mode, &filesleft, &bytesleft))
  {
    /* Error or skip, but that's fine! */
    TryZhdrType = ZSKIP;
    return FINE;
  }

  UpdateBatch(FilesThisBatch,filesleft,BytesThisBatch,bytesleft);

  Mark = StartPos = Seek (FileHandle, 0, OFFSET_CURRENT);

  NameProgress (name);
  Rxbuflen = ZBuffer;

  /* Receiving loop */

  retry = 0;
  message = NULL;

  for (;;)
  {
    UpdateRetries(retry);
    UpdateProgress (Mark,Length);

    hdr.position = Mark;
    SendHexHeader (ZRPOS, &hdr);
  nxthdr:
    if(message)
    {
      AddMessage("%s (position = %lD)",message,Mark);
      message = NULL;
    }
    switch (GetHeader (&type, &hdr))
    {
    case FINE:
      switch (type)
      {
      case ZNAK:
        ++retry;
        if (retry > ZRetries)
        {
          err = ERROR;
          goto done;
        }
        message = (UBYTE *) "hdr-ZNAK";
        break;
      case ZFILE:
        err = ReceiveData (Buffer, ZMAXSPLEN, &Count, &end);

        if (err != FINE)
          goto done;

        message = (UBYTE *) "hdr-ZFILE";
        break;
      case ZEOF:
        /* Ignore eof if it's at wrong place - force a
           timeout because the eof might have gone out before
           we sent our zrpos. */
        message = (UBYTE *) "hdr-ZEOF";
        if (hdr.position != Mark)
          goto nxthdr;          /* Ignore this eof */

        Close (FileHandle);
        FileHandle = NULL;

        BytesThisBatch += Mark;
        FilesThisBatch++;
        CleanupFile (name, modif, mode);
        err = FINE;             /* Normal eof */
        goto done;
      case ZSKIP:
        message = (UBYTE *) "hdr-ZSKIP";
        Close (FileHandle);
        FileHandle = NULL;
        err = FINE;             /* Sender skipped it */
        goto done;
      case ZDATA:
        message = (UBYTE *) "hdr-ZDATA";
        if (hdr.position != Mark)
        {
          ++retry;
          if (retry > ZRetries)
          {
            err = ERROR;
            goto done;
          }
          SendAttn (Attn);
          break;
        }
      moredata:
        UpdateProgress (Mark,Length);
        if(message)
        {
          AddMessage("%s (position = %lD)",message,Mark);
          message = NULL;
        }
        switch (ReceiveData (Buffer, ZMAXSPLEN, &Count, &end))
        {
        case TIMEOUT:
          ++retry;
          if (retry > ZRetries)
          {
            err = ERROR;
            goto done;
          }
          message = (UBYTE *) "data-TIMEOUT";
          break;
        case ERROR:
          ++retry;
          if (retry > ZRetries)
          {
            err = ERROR;
            goto done;
          }
          SendAttn (Attn);
          message = (UBYTE *) "data-ERROR";
          break;
        case FINE:
          retry = 0;
          WriteFile ();
          Mark += Count;
          ZBytesIn += Count;
          switch (end)
          {
          case ZCRCW:
            message = (UBYTE *) "data-ZCRCW";
            hdr.position = Mark;
            SendHexHeader (ZACK, &hdr);
            goto nxthdr;
          case ZCRCQ:
            message = (UBYTE *) "data-ZCRCQ";
            hdr.position = Mark;
            SendHexHeader (ZACK, &hdr);
            goto moredata;
          case ZCRCG:
            message = (UBYTE *) "data-ZCRCG";
            goto moredata;
          case ZCRCE:
            message = (UBYTE *) "data-ZCRCE";
            goto nxthdr;
          }
          break;
        }
        break;
      default:
        err = ERROR;
        goto done;
      }
      break;
    case TIMEOUT:
      ++retry;
      if (retry > ZRetries)
      {
        err = ERROR;
        goto done;
      }
      message = (UBYTE *) "hdr-TIMEOUT";
      break;
    case ERROR:
      ++retry;
      if (retry > ZRetries)
      {
        err = ERROR;
        goto done;
      }
      SendAttn (Attn);
      message = (UBYTE *) "hdr-ERROR";
      break;
    }
  }

done:
  if(message)
     AddMessage("%s (position = %lD)",message,Mark);
  return err;
}

/* ----- Receive one or more files ------------------------------------- */

STATIC WORD
ReceiveFiles ()
{
  WORD err;
  HEADER filehdr;               /* Sender's options */

  Mark = StartPos = 0;
  StartupTime = GetTime ();
  ZBytesIn = ZBytesOut = LastBPS = 0;
  Zctlesc = Escape ();
  TryZhdrType = ZRINIT;
  Attn[0] = 0;                  /* Clear sender's attention string */
  AttnLen = 0;

  for (;;)
  {
    StartupTime = GetTime ();
    ZBytesIn = ZBytesOut = LastBPS = 0;
    ResetUI(handle);

    errors = 0;
    err = TryZReceive (&filehdr);

    if (!Count)
    {
      if (err)
        UpdateError (err);

      return err;
    }

    ConvertNL = (BOOL) (filehdr.zfile.conv == ZCNL);

    err = ReceiveFile (&filehdr);

    UpdateError (err);

    if (err)
      return err;
  }
}

/* ----- ZModem receive ------------------------------------------------ */

LONG
ZReceive ()
{
  struct FileLock *FileLock;
  struct DosList *Entry;
  LONG err;
  BPTR OldCD;

  Transmitting = FALSE;
  BytesThisBatch = FilesThisBatch = 0;

  if (!(ReceiveDir = Lock (Config->PathConfig->BinaryDownloadPath, SHARED_LOCK)))
    return (IoErr ());

  FileLock = (struct FileLock *) BADDR (ReceiveDir);

  IsBlockMapped = FALSE;

  Entry = LockDosList (LDF_DEVICES | LDF_READ);

  while (Entry = NextDosEntry (Entry, LDF_DEVICES))
  {
    if (Entry->dol_Task == FileLock->fl_Task)
    {
      struct FileSysStartupMsg *Startup;

      Startup = (struct FileSysStartupMsg *) BADDR (Entry->dol_misc.dol_handler.dol_Startup);

      if (TypeOfMem (Startup))
      {
        struct DosEnvec *Environ;
        STRPTR Name;

        Name = (STRPTR) BADDR (Startup->fssm_Device);
        Environ = (struct DosEnvec *) BADDR (Startup->fssm_Environ);

        if (TypeOfMem (Name) && TypeOfMem (Environ))
        {
          struct IOStdReq Request;

          memset (&Request, 0, sizeof (Request));
          Request.io_Message.mn_Length = sizeof (Request);

          if (!OpenDevice (Name + 1, Startup->fssm_Unit, (struct IORequest *) &Request, Startup->fssm_Flags))
          {
            IsBlockMapped = TRUE;

            CloseDevice ((struct IORequest *) &Request);

            break;
          }
        }
      }
    }
  }

  UnLockDosList (LDF_DEVICES | LDF_READ);

  OldCD = CurrentDir (ReceiveDir);

  FileHandle = NULL;            /* No file open yet */

  DB (kprintf ("»Buffer size is %ld«\n", BUFFERSIZE));

  Buffer = (UBYTE *) AllocVecPooled (BUFFERSIZE, MEMF_ANY);
  err = NewTxBuffer ();
  handle = OpenUI ("ZModem receive");

  if (Buffer != NULL && err == 0 && handle != NULL)
  {
    ClearSerial ();

    if (SerialCache = GetSerialWaiting ())
    {
      if (SerialCache > SERIALBUFFERSIZE)
        SerialCache = SERIALBUFFERSIZE;

      DoSerialRead (SerialBuffer, SerialCache);
    }

    StartSerialRead (&zc, 1);

    if (err = setjmp (CancelEnv))       /* Prepare cancel or abort */
    {
      if (err == CANCEL)
        SendCancel ();          /* Send cancel string */
    }
    else
    {
      StartupTime = GetTime ();
      ZBytesIn = ZBytesOut = LastBPS = 0;
      Length = 0;
      ResetUI(handle);

      err = ReceiveFiles ();    /* Receive file(s) */
    }

    UpdateError (err);

    StopSerialRead ();
    RestartSerial ();
  }

  if (handle)
  {
    CloseUI (handle, err);
    handle = NULL;
  }

  if (FileHandle)
  {
    Close (FileHandle);
    FileHandle = NULL;

    if (!ZKeepPartial)
      DeleteFile (FileName);
  }

  DisposeTxBuffer ();

  if (Buffer)
    FreeVecPooled (Buffer);

  UnLock (CurrentDir (OldCD));

  return err;
}

/****************************************************************************/

STATIC VOID
InternalZModemUpload(BOOL TextMode)
{
	UBYTE LocalBuffer[MAX_FILENAME_LENGTH];
	struct FileRequester *FileRequester;

	LimitedStrcpy(sizeof(LocalBuffer),LocalBuffer,Config->PathConfig->BinaryUploadPath);

	if(FileRequester = OpenSeveralFiles(Window,"ZModem transfer","Send",NULL,LocalBuffer,sizeof(LocalBuffer)))
	{
		ZTransmit(FileRequester->fr_ArgList,FileRequester->fr_NumArgs,TextMode);

		FreeAslRequest(FileRequester);
	}
}

VOID
InternalZModemTextUpload()
{
	InternalZModemUpload(TRUE);
}

VOID
InternalZModemBinaryUpload()
{
	InternalZModemUpload(FALSE);
}

STATIC STRPTR STACKARGS SAVE_DS
TimeoutDispFunc(struct Gadget *UnusedGadget,UWORD Level)
{
	STATIC UBYTE LocalBuffer[40];

	FormatTime(LocalBuffer,sizeof(LocalBuffer),Level / (60 * 60),(Level / 60) % 60,Level % 60);

	return(LocalBuffer);
}

STATIC STRPTR STACKARGS SAVE_DS
FrameDispFunc(struct Gadget *UnusedGadget,UWORD Level)
{
	if(Level > 0)
	{
		STATIC UBYTE LocalBuffer[40];

		LimitedSPrintf(sizeof(LocalBuffer),LocalBuffer,ConvNumber,Level);
		LimitedStrcat(sizeof(LocalBuffer),LocalBuffer," Bytes");

		return(LocalBuffer);
	}
	else
		return((STRPTR)"« Streaming »");
}

VOID
ZSettings()
{
	LayoutHandle *Handle;

	if(Handle = LT_CreateHandleTagList(Window->WScreen,NULL))
	{
		enum { GAD_Ok=1000 };

		LT_New(Handle,
			LA_Type,VERTICAL_KIND,
		TAG_DONE);
		{
			LT_New(Handle,
				LA_Type,VERTICAL_KIND,
			TAG_DONE);
			{
				LT_New(Handle,
					LA_Type,		CHECKBOX_KIND,
					LA_LabelText,	"Escape control characters",
					LA_BOOL,		&ZEscapeCtl,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		CHECKBOX_KIND,
					LA_LabelText,	"Clobber existing files",
					LA_BOOL,		&ZOverwrite,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		SliderType,
					LA_LabelText,	"Receive timeout",
					LA_Chars,		20,
					GTSL_Min,		1,
					GTSL_Max,		12 * 60 * 60,
					GTSL_DispFunc,	TimeoutDispFunc,
					GTSL_LevelFormat,"%s h",
					LA_LONG,		&ZTimeout,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		SliderType,
					LA_LabelText,	"Retry limit",
					GTSL_Min,		0,
					GTSL_Max,		1000,
					LA_LONG,		&ZRetries,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		SliderType,
					LA_LabelText,	"Error limit",
					GTSL_Min,		0,
					GTSL_Max,		1000,
					LA_LONG,		&ZErrors,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		SliderType,
					LA_LabelText,	"Frame size",
					GTSL_Min,		0,
					GTSL_Max,		1024,
					LA_LONG,		&ZWindow,
					GTSL_DispFunc,	FrameDispFunc,
					GTSL_LevelFormat,"%s",
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		CHECKBOX_KIND,
					LA_LabelText,	"Keep partial files",
					LA_BOOL,		&ZKeepPartial,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		CHECKBOX_KIND,
					LA_LabelText,	"Delete files after sending",
					LA_BOOL,		&ZDeleteAfterSending,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		CHECKBOX_KIND,
					LA_LabelText,	"Mark files as archived after sending",
					LA_BOOL,		&ZProtectAfterSending,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		CHECKBOX_KIND,
					LA_LabelText,	"Do not send archived files",
					LA_BOOL,		&ZSkipIfArchived,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		CHECKBOX_KIND,
					LA_LabelText,	"Send full path names",
					LA_BOOL,		&ZSendFullPath,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		CHECKBOX_KIND,
					LA_LabelText,	"Use full received path names",
					LA_BOOL,		&ZUseFullPath,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		CHECKBOX_KIND,
					LA_LabelText,	"Overwrite files if newer",
					LA_BOOL,		&ZTransferIfNewer,
				TAG_DONE);

				LT_New(Handle,
					LA_Type,		CHECKBOX_KIND,
					LA_LabelText,	"Overwrite files if size is different",
					LA_BOOL,		&ZTransferIfSize,
				TAG_DONE);

				LT_EndGroup(Handle);
			}

			LT_New(Handle,
				LA_Type,HORIZONTAL_KIND,
			TAG_DONE);
			{
				LT_New(Handle,
					LA_Type,		XBAR_KIND,
					LAXB_FullWidth,	TRUE,
				TAG_DONE);

				LT_EndGroup(Handle);
			}

			LT_New(Handle,
				LA_Type,HORIZONTAL_KIND,
			TAG_DONE);
			{
				LT_New(Handle,
					LA_Type,		BUTTON_KIND,
					LA_LabelText,	"Ok",
					LA_ID,			GAD_Ok,
					LABT_ExtraFat,	TRUE,
					LABT_ReturnKey,	TRUE,
				TAG_DONE);

				LT_EndGroup(Handle);
			}

			LT_EndGroup(Handle);
		}

		if(LT_Build(Handle,
			LAWN_TitleText,	"Internal ZModem settings",
			LAWN_Zoom,		TRUE,
			LAWN_Parent,	Window,
			WA_DepthGadget,	TRUE,
			WA_DragBar,		TRUE,
			WA_RMBTrap,		TRUE,
			WA_Activate,	TRUE,
		TAG_DONE))
		{
			struct IntuiMessage *Message;
			ULONG MsgClass;
			struct Gadget *MsgGadget;
			BOOL Done;

			Done = FALSE;

			do
			{
				WaitPort(Handle->Window->UserPort);

				while(Message = LT_GetIMsg(Handle))
				{
					MsgClass	= Message->Class;
					MsgGadget	= (struct Gadget *)Message->IAddress;

					LT_ReplyIMsg(Message);

					if(MsgClass == IDCMP_GADGETUP && MsgGadget->GadgetID == GAD_Ok)
						Done = TRUE;
				}
			}
			while(!Done);
		}

		LT_DeleteHandle(Handle);
	}
}

#endif	/* BUILTIN_ZMODEM */
