/*
 * xmodem.c copyright 1986 Maple Lawn Farm, Inc.
 * modified for MS-DOS and MSC 5.0 by Matthew Pfleger
 * supports Xmodem Checksum, Xmodem CRC and Xmodem CRC 1K
 * (c) 1988 Anticlimactic Teleservices Ltd.
 *
 * Updated June 1990 for use with DragCit
 */

#ifndef WINCIT
#include "ctdl.h"
#pragma hdrstop


static int rchar(uint timeout, uchar *ch);
static Bool xget(FILE *fp);
static Bool xput(FILE *fp);
static int fillbuf(FILE *fp, char *buf);
static void upsum(char c);
static void status(const char *string, int blocknum, int errcount);
static void complete(int success, int blocknum, int errcount, FILE *fp);
static void cksend(char ch);



#define MAXERR 10

#define SOH 	0x01
#define STX 	0x02
#define EOT 	0x04
#define ACK 	0x06
#define NAK 	0x15
#define CAN 	0x18
#define CRCINIT 'C'

static uchar cksum;

static Bool crc;		// must be set before calling xget()
static Bool oneK;		// must be set before calling xget() or xput()
static int	bsize;

static uint crcsum;

static char *buf;

/*
 *	note:	Mode = 0: Xmodem Checksum
 *			Mode = 1: Xmodem CRC
 *			Mode = 2: Xmodem CRC 1K
 */

Bool xsend(const char *filename, int mode)
	{
	FILE *fp;
	Bool retval;

	crc = !!mode;
	oneK = (mode == 2);
	bsize = (mode == 2) ? 1024 : 128;

	if ((fp = fopen(filename, FO_RB)) == NULL)
		{
		CRmPrintfCR(getmsg(78), filename);
		return (FALSE);
		}

	assert(!buf);

	if ((buf = new char[bsize]) == NULL)
		{
		CRmPrintf(getmsg(188), getmsg(1358));
		fclose(fp);
		return (FALSE);
		}

	retval = xput(fp);
	delete [] buf;
	buf = NULL;
	return (retval);
	}


Bool xreceive(const char *filename, int mode)
	{
	FILE *fp;
	Bool retval;

	crc = !!mode;
	oneK = (mode == 2);
	bsize = (mode == 2) ? 1024 : 128;

	if ((fp = fopen(filename, FO_WB)) == NULL)
		{
		CRmPrintfCR(getmsg(661), filename);
		return (FALSE);
		}

	assert(!buf);

	if ((buf = new char[bsize]) == NULL)
		{
		CRmPrintfCR(getmsg(188), getmsg(1359));
		fclose(fp);
		return (FALSE);
		}

	retval = xget(fp);
	delete [] buf;
	buf = NULL;

	if (!retval)
		{
		if (unlink(filename) == -1)
			{
			CRmPrintfCR(getmsg(662), filename);
			return (FALSE);
			}
		}

	return (TRUE);
	}


static Bool xget(FILE *fp)
	{
	uchar b = 1, inch, inch2, crchi, errcount = 0;
	int blocknum = 1;
	register int i;

	(crc) ? (*putrs)(CRCINIT) : (*putrs)(NAK);
	(*flushrs)();

	for (;;)
		{
		if (!errcount)
			{
			status(getmsg(663), blocknum, errcount);
			}

		if (!(*carrstatrs)())
			{
			status(getmsg(166), blocknum, errcount);
			pause(200);
			complete(0, blocknum, errcount, fp);
			return (FALSE);
			}

		if (errcount == MAXERR)
			{
			status(getmsg(664), blocknum, errcount);
			pause(200);
			complete(0, blocknum, errcount, fp);
			return (FALSE);
			}

		errcount++;

		if (rchar(10, &inch) == -1)
			{
			if (oneK)
				{
				status(getmsg(665), blocknum, errcount);
				}
			else
				{
				status(getmsg(666), blocknum, errcount);
				}

			cksend((char) (crc ? CRCINIT : NAK));
			continue;
			}

		if (inch == EOT)
			{
			break;
			}

		if (inch == CAN)
			{
			status(getmsg(1479), blocknum, errcount);
			pause(200);
			complete(0, blocknum, errcount, fp);
			return (FALSE);
			}

		if (oneK)
			{
			if (inch != STX)
				{
				status(getmsg(667), blocknum, errcount);
				cksend(NAK);
				continue;
				}
			}
		else
			{
			if (inch != SOH)
				{
				status(getmsg(668), blocknum, errcount);
				cksend(NAK);
				continue;
				}
			}

		if (rchar(2, &inch) == -1)
			{
			status(getmsg(669), blocknum, errcount);
			cksend(NAK);
			continue;
			}

		if (rchar(2, &inch2) == -1)
			{
			status(getmsg(670), blocknum, errcount);
			cksend(NAK);
			continue;
			}

		if ((~inch &0xff) != (inch2 &0xff))
			{
			status(getmsg(671), blocknum, errcount);
			cksend(NAK);
			continue;
			}

		if (inch == (uchar) ((b - 1) &0xff))
			{
			status(getmsg(672), blocknum, errcount);
			cksend(ACK);
			continue;
			}

		if ((uchar) (inch & 0xff) != b)
			{
			status(getmsg(673), blocknum, errcount);
			cksend(NAK);
			continue;
			}

		if ((inch2 & 0xff) != (~b & 0xff))
			{
			status(getmsg(674), blocknum, errcount);
			cksend(NAK);
			continue;
			}

		/* Read in 128 byte block without taking time for checksums or crc. */
		for (i = 0; i < bsize; i++)
			{
			if (rchar(2, (uchar *) &buf[i]) == -1)
				{
				break;
				}
			}

		if (i < bsize)
			{
			status(getmsg(675), blocknum, errcount);
			cksend(NAK);
			continue;
			}

		if (crc)
			{
			if (rchar(2, &crchi) == -1)
				{
				status(getmsg(676), blocknum, errcount);
				cksend(NAK);
				continue;
				}
			crchi &= 0xff;
			}

		if (rchar(2, &inch) == -1)
			{
			if (crc)
				{
				status(getmsg(677), blocknum, errcount);
				}
			else
				{
				status(getmsg(678), blocknum, errcount);
				}

			cksend(NAK);
			continue;
			}

		/* Now, when we have the whole packet, do the checksum or crc. */
		for (cksum = 0, crcsum = 0, i = 0; i < bsize; i++)
			{
			upsum(buf[i]);
			}

		if (crc)
			{
			upsum(0);							// needed for crcsum
			upsum(0);
			if ((uint) ((inch & 0xff) + (crchi << 8)) != crcsum)
				{
				status(getmsg(679), blocknum, errcount);
				cksend(NAK);
				continue;
				}
			}
		else
			{
			cksum %= 256;
			if (cksum != (uchar) (inch & 0xff))
				{
				status(getmsg(680), blocknum, errcount);
				cksend(NAK);
				continue;
				}
			}

		(*putrs)(ACK);
		(*flushrs)();

		fwrite(buf, bsize, 1, fp);
		b++;
		b %= 256;
		blocknum++;
		errcount = 0;
		}

	(*putrs)(ACK);
	(*flushrs)();

	complete(1, blocknum, errcount - 1, fp);
	return (TRUE);
	}

static Bool xput(FILE *fp)
	{
	uchar b = 1, cb, crclo, inch, errcount = 0;
	int blocknum = 1;
	register int i;
	int cread;

	(*flushrs)();
	status(getmsg(681), blocknum, errcount);
	rchar(60, &cb);

	if (cb == CRCINIT)
		{
		crc = 1;
		}
	else if (cb == NAK && !oneK)
		{
		crc = 0;
		}
	else
		{
		if (oneK)
			{
			status(getmsg(682), blocknum, errcount);
			}
		else
			{
			status(getmsg(683), blocknum, errcount);
			}

		pause(200);
		complete(0, blocknum, errcount, fp);
		return (FALSE);
		}

	cread = fillbuf(fp, buf);
	while (cread)
		{
		if (!errcount)
			{
			status(getmsg(684), blocknum, errcount);
			}

		if (!(*carrstatrs)())
			{
			status(getmsg(166), blocknum, errcount);
			pause(200);
			complete(0, blocknum, errcount, fp);
			return (FALSE);
			}

		if (errcount == MAXERR)
			{
			status(getmsg(664), blocknum, errcount);
			pause(200);
			complete(0, blocknum, errcount, fp);
			return (FALSE);
			}

		errcount++;

		for (i = cread; i < bsize; i++)
			{
			buf[i] = 0x1a;
			}

		if (oneK)
			{
			(*putrs)(STX);
			}
		else
			{
			(*putrs)(SOH);
			}

		(*putrs)(b);
		cb = (uchar) (~b & 0xff);
		(*putrs)(cb);

		for (cksum = 0, crcsum = 0, i = 0; i < bsize; i++)
			{
			(*putrs)(buf[i]);
			upsum(buf[i]);
			}

		if (crc)
			{
			upsum(0);		 /* needed for crcsum */
			upsum(0);
			crclo = (uchar) crcsum;
			cb = (uchar) (crcsum >> 8);
			(*putrs)(cb);
			(*putrs)(crclo);
			}
		else
			{
			cksum %= 256;
			(*putrs)(cksum);
			}

		(*flushrs)();

		if (rchar(15, &inch) == -1)
			{
			status(getmsg(685), blocknum, errcount);
			continue;
			}

		if (inch == CAN)
			{
			status(getmsg(686), blocknum, errcount);
			pause(200);
			complete(0, blocknum, errcount, fp);
			return (FALSE);
			}

		if (inch != ACK)
			{
			status(getmsg(687), blocknum, errcount);
			continue;
			}

		cread = fillbuf(fp, buf);
		b++;
		b %= 256;
		blocknum++;
		errcount = 0;
		}

	for (;;)
		{
		(*putrs)(EOT);
		(*flushrs)();

		if (rchar(15, &inch) == -1)
			{
			status(getmsg(688), blocknum, errcount);
			continue;
			}
		if (inch == CAN)
			{
			status(getmsg(689), blocknum, errcount);
			pause(200);
			complete(0, blocknum, errcount, fp);
			return (FALSE);
			}
		if (inch != ACK)
			{
			status(getmsg(690), blocknum, errcount);
			continue;
			}
		break;
		}

	complete(1, blocknum, errcount, fp);
	return (TRUE);
	}


static int fillbuf(FILE *fp, char *buf)
	{
	int i;

	i = fread(buf, 1, bsize, fp);
	return (i);
	}


static void upsum(char c)
	{
	register uint shift;
	register uint flag;

	if (crc)
		{
		for (shift = 0x80; shift; shift >>= 1)
			{
			flag = (crcsum & 0x8000);
			crcsum <<= 1;
			crcsum |= ((shift & c) ? 1 : 0);
			if (flag)
				{
				crcsum ^= 0x1021;
				}
			}
		}
	else
		{
		cksum += c;
		}
	}


static int rchar(uint timeout, uchar *ch)
	{
	long t;

	t = time(NULL);

	while ((uint) (time(NULL) - t) < timeout)
		{
		if (!(*carrstatrs)())
			{
			return (-1);
			}

		if ((*statrs)())
			{
			*ch = (char) (*getrs)();
			return (1);
			}
		}

	return (-1);
	}


static void status(const char *string, int blocknum, int errcount)
	{
	label Block;
	strcpy(Block, ltoac(blocknum));

	cPrintf(br);
	cPrintf(getmsg(691), string, Block, ltoac(errcount));
	cPrintf(spcspc);
	}


static void complete(int success, int blocknum, int errcount, FILE *fp)
	{
	char message[80];

	fclose(fp);
	sprintf(message, getmsg(692), (success) ? getmsg(693) : getmsg(694));
	status(message, blocknum, errcount);
	cPrintf(bn);
	}


static void cksend(char ch)
	{
	int j;
	uchar cp;

	do
		{
		j = rchar(2, &cp);
		} while (j != -1);

	(*putrs)(ch);
	(*flushrs)();
	}
#endif
