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

	download.c
	2001, Z-World

	Firmware source code for the EG2100 RabbitLink.
	To use, compile to the RabbitLink, detatch the
	programming cable from the PROG IN port, and
	reset the RabbitLink board.

	1.03 - first release version
	1.04 - added background polling
	1.05 - upgraded for 7.05 BIOS changes
			 (will still work with older versions)
			
 ********************************************************/

/* default values */
#define MY_IP_ADDRESS	"10.10.1.100"
#define MY_NETMASK		"255.255.255.0"
#define MY_GATEWAY		"10.10.1.1"
#define MY_NAME			"RabbitLink"


/* debug messages - uncomment to have that class of debug messages */
//#define DBG_DL			// .bin download debugging
//#define DBG_DBGMODE	// debug mode debugging
//#define DBG_BACKUP		// backup of tables debugging
//#define DBG_GEN			// general debug messages - probably want this on?

/* flash file system */
#define FS_FLASH
//#define FS_DEBUG
//#define FS_DEBUG_FLASH

/* enable the serial console? */
#define SERIAL_CONSOLE

#define CONSOLE_VERSION "1.05"

/* grab the loader-files that DC uses */
#ximport "bios/coldload.bin" coldloader
#ximport "bios/pilot.bin" pilotbios

/* use serial port C (instead of the default port A) for the console */
//#define SERIAL_PORT_C

#define SSPEC_MAXSPEC 60
#define SSPEC_XMEMVARLEN 128

/*
 * Stuff for the webserver & console
 */
#define CONSOLE_BAUD				57600			// default console baud rate
#ifdef SERIAL_PORT_C
	#define CINBUFSIZE			1023
	#define COUTBUFSIZE			255
#else
	#define AINBUFSIZE			1023
	#define AOUTBUFSIZE			255
#endif
#define SERIAL_ECHO			1

#define HTTP_MAXSERVERS 2
#define MAX_TCP_SOCKET_BUFFERS 5
#define MAX_UDP_SOCKET_BUFFERS 1
#define UDP_BUF_SIZE 256
#define DISABLE_DNS

#define HTTP_NO_FLASHSPEC

/* Size of the variable buffer */
#define VAR_BUFFER 4096

/* Size of the filesystem, in 4k blocks */
#define FS_NUM_BLOCKS	64

/* File numbers for backup files */
#define BACKUP_FILE1		128
#define BACKUP_FILE2		129

#memmap xmem
#use "dcrtcp.lib"
#use "md5.lib"
#use "http.lib"
#use "smtp.lib"
#use "FileSystem.lib"

#define DISCOVERY_PORT	4242
#define CONTROL_PORT		4244

/* 1 to authenticate the user, 0 to just accept them */
#define CHALLENGE_AUTH	1

//#define MAX_RECEIVE_SIZE	1024
#define MAX_RECEIVE_SIZE	768
#define DC_ONE_PROTOCOL		0x0001

#define CONSOLE_TIMEOUT	60

/* baud rate of main target communications, by default */
#define PROG_BAUD		115200

/* watchdog timeout time. time in seconds == (WATCHDOG_TIMEOUT - 1)/16 */
#define WATCHDOG_TIMEOUT	161
#define HIT_WATCHDOG			VdHitWd(wd);

/* timeout, in ms, between debug polls */
#define POLL_INTERVAL	70
//#define POLL_INTERVAL	200

/* how long to wait for the packet a packet to be sent */
#define SEND_TIMEOUT		1000

/* how logn to wait in the steady state without any command packet or noop */
#define CONTROL_TIMEOUT	10000

/* number of breakpoints we can support at the same time */
#define MAXBREAKPOINTLIST 32

/* number of un-ACKed poll packets that may be pending */
#define MAX_PENDING_POLLS	3

/* number of un-ACKed statys packets that may be pending */
#define MAX_PENDING_STATUS	3

/* size of the main circular buffer the .bin file is received into */
#define PROGRAM_BUFFER_SIZE	32768

/* size of the programming port serial buffers - CAN THESE BE SMALLER?! */
#define BOUTBUFSIZE	1023
#define BINBUFSIZE  127

/* commands to the remote BIOS */
#define WRITE_BLOCK     0xa1
#define READ_BLOCK      0xa2
#define GET_RAM_SIZE    0xa3
#define GET_FLASH_ID    0xa4
#define GET_BOARD_ID    0xa5

/* answers from the remote BIOS */
#define	NAK			   0x15
#define  ACK			   0x06

/* Poll types */
#define P_COM		0x01
#define P_BREAK	0x02
#define P_WATCH	0x04
#define P_GETKEY	0x08

typedef unsigned int uint16;							
typedef unsigned long uint32;

/* Help file index structure */
struct HelpIndex {
	char* cmd1;
	char* cmd2;
	char* cmd3;
	long addr;
};

/* Help files */

/* grab the help files */
#ximport "RabbitLink/help/help.txt" help_txt
#ximport "RabbitLink/help/help_delete.txt" help_delete_txt
#ximport "RabbitLink/help/help_echo.txt" help_echo_txt
#ximport "RabbitLink/help/help_get.txt" help_get_txt
#ximport "RabbitLink/help/help_help.txt" help_help_txt
#ximport "RabbitLink/help/help_list.txt" help_list_txt
#ximport "RabbitLink/help/help_mail.txt" help_mail_txt
#ximport "RabbitLink/help/help_put.txt" help_put_txt
#ximport "RabbitLink/help/help_reset.txt" help_reset_txt
#ximport "RabbitLink/help/help_set.txt" help_set_txt
#ximport "RabbitLink/help/help_set_mail.txt" help_set_mail_txt
#ximport "RabbitLink/help/help_set_mail_from.txt" help_set_mail_from_txt
#ximport "RabbitLink/help/help_set_mail_server.txt" help_set_mail_server_txt
#ximport "RabbitLink/help/help_show.txt" help_show_txt
#ximport "RabbitLink/help/help_createv.txt" help_createv_txt
#ximport "RabbitLink/help/help_getv.txt" help_getv_txt
#ximport "RabbitLink/help/help_putv.txt" help_putv_txt

const struct HelpIndex helpindex[] =
{
	{ NULL, NULL, NULL, help_txt },
	{ "delete", NULL, NULL, help_delete_txt },
	{ "echo", NULL, NULL, help_echo_txt },
	{ "get", NULL, NULL, help_get_txt },
	{ "help", NULL, NULL, help_help_txt },
	{ "list", NULL, NULL, help_list_txt },
	{ "mail", NULL, NULL, help_mail_txt },
	{ "put", NULL, NULL, help_put_txt },
	{ "reset", NULL, NULL, help_reset_txt },
	{ "set", NULL, NULL, help_set_txt },
	{ "set", "mail", NULL, help_set_mail_txt },
	{ "set", "mail", "from", help_set_mail_from_txt },
	{ "set", "mail", "server", help_set_mail_server_txt },
	{ "show", NULL, NULL, help_show_txt },
	{ "createv", NULL, NULL, help_createv_txt },
	{ "putv", NULL, NULL, help_putv_txt },
	{ "getv", NULL, NULL, help_getv_txt }
};

/* Blink the light! */
#asm
;
; bc_usrledon
;


bc_usrledon::
			ld		a, (GOCRShadow)
        	or      a,0x02
        	and     a,0xfe
ioi     	ld      (GOCR),a
			ld		(GOCRShadow), a
        	ret


;
; bc_usrledoff
;
                
bc_usrledoff::
			ld		a, (GOCRShadow)
        	or      a,0x03
ioi     	ld      (GOCR),a
			ld		(GOCRShadow), a
        	ret
#endasm

nodebug void Blink(void)
{
	static char state;
#GLOBAL_INIT {
	state = 0;
	bc_usrledoff();
}
	if(state) {
		bc_usrledoff();
		state = 0;
	} else {
		bc_usrledon();
		state = 1;
	}
}

/* sleep for 'len' milliseconds */
void sleep(long len)
{
	auto long time;

	time = MS_TIMER;
	time += len;

	while(MS_TIMER < time)
		continue;
}

/************************************
 * Structure of various packets     *
 ************************************/
typedef unsigned short int 	uint;
typedef unsigned long  int		ulong;
 
/*
 * TCP Contol/Data packets
 */

#define DTP_ACK					0x4000
#define DTP_NAK					0x8000
#define DTP_CHALLENGE			0x0001
#define DTP_RESPONSE				0x0002
#define DTP_RESET					0x0003
#define DTP_COLDLOAD				0x0004
#define DTP_READ					0x0005
#define DTP_WRITE					0x0006
#define DTP_DOWNLOADSTART		0x0007
#define DTP_DOWNLOADDATA		0x0008
#define DTP_STATUS				0x0009
#define DTP_QUIT					0x000A
#define DTP_SINGLESTEP			0x000B
#define DTP_READTABLE			0x000C
#define DTP_DEBUGMODE			0x000D
#define DTP_POLL					0x000E
#define DTP_STARTPROG			0x000F
#define DTP_STOPPROG				0x0010
#define DTP_CSINGLESTEP			0x0011
#define DTP_EXECWATCH			0x0012
#define DTP_SETBRKPT				0x0013
#define DTP_REMOVEBRKPT			0x0014
#define DTP_NOOP					0x0015
#define DTP_ADDWATCH       	0x0016
#define DTP_DELETEWATCH    	0x0017
#define DTP_RUNALLWATCH    	0x0018
#define DTP_DELETEALLWATCH 	0x0019
#define DTP_SENDCHAR				0x001A

typedef struct {
	ulong	version;
	uint	type;
	uint	seq;
	ulong	length;
} DTPacketHeader;

/* char *data -> moved to the seperate typedefs below */

typedef struct {
	uint	checksum;
	ulong	reserved;
} DTPacketFooter;

typedef struct {
	ulong	ip;
	ulong	netmask;
} DTPNetworkDescription;

#define MAIL_FROM_SIZE 51
#define MAIL_SERV_SIZE 51
typedef struct {
	char	top; // used to mark the top of the struct 
	char	name[40];
	char	passwd[16];
	uint	control_port;
	ulong prog_baud;
	ulong console_baud;
	char	consolepasswd[16];
	ulong	my_ip_addr;
	ulong	sin_mask;
	ulong gate_ip;
	char	echo;
	char	run_background_poll;
	char  mail_from[MAIL_FROM_SIZE];
	char	mail_serv[MAIL_SERV_SIZE];
	uint  varstrlen[SSPEC_MAXSPEC];
	DTPNetworkDescription acceptableips[10];
} DTPIdentification;

/*
 * UDP Discovery Packets
 */
 
typedef struct {
	uint	protocol;
	uint	requesttype;
	char	name[40];
	char	data[40];
} DTPDiscovery;

#define DTPS_ERROR		0xff
#define DTPS_RUNNING		0x00
#define DTPS_PROGRAMMED	0x01
#define DTPS_WAITING		0x02

typedef struct {
	uint	protocol;
	uint	status;
	char	name[40];
	long	ip;
	char	mac[6];
	uint	control_port;
	char	data[30];
} DTPDiscoveryResponse;

/*
 * DATA Payloads
 */
typedef struct {
	ulong address;
	ulong length;
	// DATA
} DTPAckRead;

typedef struct {
	ulong address;
	ulong length;
} DTPAckWrite;

typedef struct {
	char challenge[6];
	char reserved[20];
} DTPChallenge;

typedef struct {
	char challenge[6];
	char response[16];
	char reservec[4];
} DTPResponse;

typedef struct {
	ulong address;
	ulong length;
	// DATA
} DTPColdLoad;

typedef struct {
	ulong length;
	uint	flags;
} DTPDownloadStart;

typedef struct {
	uint	flashid;
	char	ramsize;
	char	IDBlock[256];
} DTPDownloadACK;

#define	DOWNLOAD_BLOCKSIZE	512
typedef struct {
	ulong	downloadlength;
	uint	length;
	char	data[DOWNLOAD_BLOCKSIZE];
} DTPDownloadData;

typedef struct {
	ulong	offset;
	ulong	window;
} DTPDownloadDataAck;

typedef struct {
	ulong bytecomplete;
	ulong offset;
	ulong window;
	char message[32];
} DTPStatus;

typedef struct {
	uint16	tc_brkreq;
	uint16	tc_brkattn;
	uint16	tc_reg_buf;
	uint16	tc_combuffer;
	uint16	tc_int_stat;
	uint16	tc_comattn;
	uint16	tc_watchreq;
	uint16	tc_watchbuf;
	uint16	tc_watchadr;
	uint16	tc_keyreq;
	uint16	tc_brkvect;
	unsigned char	tc_brkbyoff;
	unsigned char	tc_brkbyte;
	uint16	tc_keybuf;
	uint16	tc_hardbrk;
	uint16	tc_doreset;
	uint16	tc_show;
	uint16	tc_hardbreakhit;
	uint16	tc_debugregs;
} DTPReadTable;

struct	registers
{
   uint16      AF,		/*Primary Registers 	   */
				   BC,
				   DE,
				   HL,
				   SP;   	/* Stack Pointer			*/
   uint16      PC;
   uint16      XPC;
   uint16      IX,		/* Index Registers		*/
					IY,
					af,      /* Alternate Registers	*/
					bc,
					de,
					hl;
	char			PC_XPC;
	int			PC_OFFSET;
};

#define ASMBUFSIZE	255
#define STACKBUFSIZE	16
#define COMBUFSIZE	128
typedef struct {
	char					flags;
	struct registers	regs;
	char					int_stat;
	char					asmbuf[ASMBUFSIZE];
	char					stackbuf[STACKBUFSIZE];
	char					combuf[COMBUFSIZE];
} DTPPollResults;

typedef struct {
	char	run;
	uint	PC;
} DTPRunProg;

typedef struct {
	char	isNext;
} DTPCSingleStep;

typedef struct {
	uint16	code;
	uint16	bufsize;
} DTPExecWatch;

#define MAXWATCHBUFSIZE	32
typedef struct {
	char		watchbuf[MAXWATCHBUFSIZE]; // is this big enough?
} DTPExecWatchACK;

typedef struct {
	char		xpc;
	uint16	offset;
	char		newopcode;
} DTPSetBreakpoint;

typedef struct {
	char 	oldopcode;
} DTPAckBreakpoint;

typedef struct {
	char		xpc;
	uint16	offset;
	char		opcode;
} DTPRemoveBreakpoint;
 
#define TCP_MAX_WATCHES 		40
#define TCP_MAX_WATCH_STRLEN	16
typedef struct
{
	TypedArg buf;
   unsigned char string[TCP_MAX_WATCH_STRLEN];
} TCPWatchData;

typedef struct
{
	uint16 Count;
   TCPWatchData Watches[TCP_MAX_WATCHES];
} _TCPWatchBuffer;

/*****************************
 * End structure definitions *
 *****************************/

 
/***************
 * Global vars *
 ***************/

/* What is our current status? */
int GlobalStatus;

DTPIdentification main_id;
char NextBackupFile;
unsigned long BackupVersion;
int wd; /* watchdog timer */
int PollsPending; /* number of poll packets transmitted and not ACKed */
char combuf[COMBUFSIZE];
int combufoffset, combuflen;
char combufpending;
char SendToGetchar;
 

/*******************
 * End global vars *
 *******************/

/* global prototypes */
void InitAllState(void);
void dtp_backup_id(void);
void dtp_hash(char *data, int length, char *digest);
char GetXPC(long a);
int GetOFFSET(long a);

/*********************
 * Discovery section *
 *********************/

/*
 * Data
 */
udp_Socket	discovery_sock;
sock_type *discovery_s;

/*
 * Init the discovery code (open the socket/etc)
 */
void dtp_broadcastinit(void)
{
	discovery_s = (sock_type *)&discovery_sock;
	
	udp_open(&discovery_sock, DISCOVERY_PORT, -1, 0, NULL);
}

/*
 * Run the discovery code - NON-Blockable!
 */
void dtp_broadcasthandler(void)
{
	static long	ip;
	static word	port;
	static DTPDiscovery			rx_packet;
	static DTPDiscoveryResponse	tx_packet;
	static int retval;
	static char *ptr;
	static longword remip;
	static word remport;
	static char tempipbuf[16];
	static int used;
	static long time;
	
	retval = udp_recvfrom(&discovery_sock, (char *)&rx_packet,
	                      sizeof(DTPDiscovery), &remip, &remport);
	if(retval >= 0) {
		//	/*printf("Discovery connection from: %s:%d\n",
		//		inet_ntoa(tempipbuf, remip), remport
		//		);*/

		if((ntohs(rx_packet.protocol) != 0x0100) && (ntohs(rx_packet.requesttype) != 0x0001)) {
			tx_packet.protocol = 0x0100;
			tx_packet.status = DTPS_ERROR;
		} else {
			tx_packet.status = GlobalStatus;
		}

		strcpy(tx_packet.name, main_id.name);
		tx_packet.ip = htonl(my_ip_addr);

		memset(tx_packet.mac, 0, 6);
		ptr = tx_packet.mac;
		pd_getaddress(0,tx_packet.mac);
		memset(tx_packet.data, 0, 30);

		/* send the control port */
		tx_packet.control_port = main_id.control_port;
		
		if(udp_sendto(&discovery_sock, (char *)&tx_packet, sizeof(DTPDiscoveryResponse),
		              remip, DISCOVERY_PORT+1) != sizeof(DTPDiscoveryResponse)) {
			printf("error in write\n");
		}

#if 0
		printf("Sent: name='%s', ip='%s', mac='%02x:%02x:%02x:%02x:%02x:%02x', port='%d'\n",
			tx_packet.name,
			inet_ntoa(tempipbuf,ntohl(tx_packet.ip)),
			tx_packet.mac[0],tx_packet.mac[1],tx_packet.mac[2],
			tx_packet.mac[3],tx_packet.mac[4],tx_packet.mac[5],
			tx_packet.control_port);
#endif
	}
}

/*************************
 * End discovery section *
 *************************/

 
/********************
 * Control section *
 ********************/
 
/*
 * Data
 */
tcp_Socket control_sock1,control_sock2;
sock_type *control_s; // = (sock_type *)&control_sock;
char	which_control_sock;
int control_state;
DTPacketHeader	packet_header;
DTPacketFooter packet_footer;
char packet_data[MAX_RECEIVE_SIZE];
uint seq_number;
int packet_pending;
int send_offset, send_length, send_state;
int rx_offset, rx_length, rx_state;
char challenge_value[32];
ulong DownloadLength;
int download_state;
ulong DLOffset;
long ProgramBuffer, ReadBuffer, WriteBuffer, BufferFree;
char ExecStepInit;
uint DLFlags;
char RamSize;
int watch_state;
char RunPoll;
int LastPoll;
int StatusPending;
char BackgroundPoll;


/* serial writer data */
int serial_state, serial_delay;
long BIOSLength, CODELength, BIOSStart, CODEStart;
int SectorSize, NumSectors;
char SendISRFree;
char SendISRBuf[512], SendISRHdr[16];
int SendISRLen, SendISRHdrLen;
char ReadISRFree;
char ReadISRHdr[16], *ReadISRBuf;
int ReadISRLen, ReadISRHdrLen;

/* storage for status messages */
char *status_msg;
ulong status_len;

/* poll data */
char RunTarget, StepTarget;
long poll_timeout;
DTPPollResults poll_oldpacketDATA;
DTPPollResults *poll_oldpacket;
DTPPollResults *poll_newpacket;

/*
 * After we download, "ProgramBuffer" will be segmented
 * into the following buffers.
 */
long GetcharBuf;
#define GETCHAR_BUFSIZE		2048
long GetcharStart, GetcharLength;

/* packet-send states */
#define SEND_INIT		1
#define SEND_HEADER	2
#define SEND_DATA		3
#define SEND_FOOTER	4

/* receive states */
#define RX_INIT	1
#define RX_HEADER	2
#define RX_BODY	3
#define RX_FOOTER	4

/* download states */
#define DOWNLOAD_INIT	1
#define DOWNLOAD_HEADER	2
#define DOWNLOAD_DATA	3
#define DOWNLOAD_FOOTER	4

/* control states */
#define CONTROL_LISTEN		1
#define CONTROL_AUTH			2
#define CONTROL_STEADY		3
#define CONTROL_READ			4
#define CONTROL_WRITE		5
#define CONTROL_SINGLESTEP	6
#define CONTROL_RUNTARGET	7
#define CONTROL_RESET		8
#define CONTROL_RUNWATCHES	9

/* serial write-port states */
/* THESE _MUST_ BE IN NUMERICAL ORDER! */
#define SERIAL_TESTBOARD	1
#define SERIAL_WAITFORPROG 2
#define SERIAL_NOTHING		3
#define SERIAL_RESET			4
#define SERIAL_UNRESET		5
#define SERIAL_COLDLOAD		6
#define SERIAL_PILOTINIT	7
#define SERIAL_PILOTSEND	8
#define SERIAL_FLASHID		9
#define SERIAL_FLASHID1		10
#define SERIAL_FLASHID2		11
#define SERIAL_FLASHID3		12
#define SERIAL_GETRAMSIZE	13
#define SERIAL_GETRAMSIZE1	14
#define SERIAL_GETRAMSIZE2	15
#define SERIAL_ACKDOWNLOAD	16
#define SERIAL_BIOSINIT		17
#define SERIAL_BIOS			18
#define SERIAL_BIOSWAIT		19
#define SERIAL_RUNBIOS		20
#define SERIAL_RUNWAIT		21
#define SERIAL_PROGINIT		22
#define SERIAL_PARTIALWAIT	23
#define SERIAL_PROG			24
#define SERIAL_PROGWAIT		25
#define SERIAL_FINISH		26
#define SERIAL_FINISH1		27
#define SERIAL_FINISH2		28

/* flags in the bitfield DLFlags */
#define DL_DYNAMICC			0x0001
#define DL_DEBUGMODE			0x0002
#define DL_PROGSTARTED		0x0004
#define DL_NOPOLL				0x0008

/* checksum the buffer, starting with the given seed */
uint dtp_checksum(char *buf, int length, uint seed)
{
	/*** this needs to be implemented ***/
	return seed;
}

void control_listen(void)
{
	switch(which_control_sock) {
	case 1:
		which_control_sock = 2;
		control_s = (sock_type *)&control_sock2;
		tcp_listen(&control_sock2, main_id.control_port, 0, 0, NULL, 0);
		break;
		
	case 2:
	default:
		which_control_sock = 1;
		control_s = (sock_type *)&control_sock1;
		tcp_listen(&control_sock1, main_id.control_port, 0, 0, NULL, 0);	
		break;
	}
}

/*
 * Init the control code (listen for a connection, etc)
 */
void dtp_controlinit(void)
{
#GLOBAL_INIT {
	which_control_sock = 2;
	BackgroundPoll = 0;
}

	GlobalStatus = DTPS_WAITING;

	seq_number = 0;
	packet_pending = 0;
	send_state = SEND_INIT;
	rx_state = RX_INIT;

	control_listen();	

	control_state = CONTROL_LISTEN;
	serial_state = SERIAL_NOTHING;

	BIOSLength = 0;

	DLFlags = 0;

	if(!BackgroundPoll) {
		RunPoll = 1;

		PollsPending = 0;

		LastPoll = 0;

		combufpending = 0;

		GetcharStart = ProgramBuffer;
		GetcharLength = 0;
	   SendToGetchar = 0;
	}
}

/* to init the stuff we didn't durring a background poll */
void dtp_controllinitpolling(void)
{
		BackgroundPoll = 0;
		
		RunPoll = 1;

		PollsPending = 0;

		LastPoll = 0;

		combufpending = 0;

		GetcharStart = ProgramBuffer;
		GetcharLength = 0;
	   SendToGetchar = 0;
}

/* build a footer by doing the checksum/etc */
void dtp_buildfooter(void)
{
	packet_footer.checksum = dtp_checksum((char *)&packet_header, sizeof(packet_header), 0);
	packet_footer.checksum = dtp_checksum((char *)packet_data, (int)packet_header.length, packet_footer.checksum);
	packet_footer.reserved = 0x89abcdef;
}

/* ack the authentication - will enable the steady state */
void dtp_ackauth(void)
{
	packet_header.version = htonl(DC_ONE_PROTOCOL);
	packet_header.type = htons(DTP_RESPONSE|DTP_ACK);
	packet_header.seq = htons(seq_number++);
	packet_header.length = htonl(0);

	packet_pending = 1;
	dtp_buildfooter();
}

/* if enables, challange the remote user */
void dtp_respondchallenge(void)
{
	static DTPChallenge *data;
	
	if(CHALLENGE_AUTH) {
		data = (DTPChallenge *)packet_data;
		/* setup header */
		packet_header.version = htonl(DC_ONE_PROTOCOL);
		packet_header.type = htons(DTP_CHALLENGE);
		packet_header.seq = htons(seq_number++);
		packet_header.length = htonl(sizeof(DTPChallenge));

		/* payload */
#asm xmemok
		push	ix
		push	af
		
		ld		ix,challenge_value
ioi	ld		a,(RTC0R)
		ld		(ix),a
		inc	ix
ioi	ld		a,(RTC1R)
		ld		(ix),a
		inc	ix
ioi	ld		a,(RTC2R)
		ld		(ix),a
		inc	ix
ioi	ld		a,(RTC3R)
		ld		(ix),a
		inc	ix
ioi	ld		a,(RTC4R)
		ld		(ix),a
		inc	ix
ioi	ld		a,(RTC5R)
		ld		(ix),a

		pop	af
		pop	ix
#endasm
		data->challenge[0] = challenge_value[0];
		data->challenge[1] = challenge_value[1];
		data->challenge[2] = challenge_value[2];
		data->challenge[3] = challenge_value[3];
		data->challenge[4] = challenge_value[4];
		data->challenge[5] = challenge_value[5];
		memset(data->reserved, 0, 10);

		/* footer */
		dtp_buildfooter();
		
		/* queue packet */
		packet_pending = 1;
		control_state = CONTROL_AUTH; /* wait for response to our challenge */
	} else {
		/* just ACK the connection */
		dtp_ackauth();
		control_state = CONTROL_STEADY;
	}
}

/* return 1 if challenge was good, 0 otherwise */
int dtp_verifychallenge(void)
{
	auto char digest[16];
	auto DTPResponse *pkt;

	pkt = (DTPResponse *)packet_data;

	/* build an array of the challange + the password */
	memcpy(challenge_value + 6, main_id.passwd, 16);

	dtp_hash(challenge_value, 22, digest);

	if(0 == memcmp(digest, pkt->response, 16)) {
		return 1;
	} else {
		return 0;
	}
}

void dtp_respondcommon(uint length)
{
	if((htons(packet_header.type) & 0xfff) > 0x0020) {
		printf("ERROR! (respondcommon) invalid packet type! (packet_header.type == %04x)\n",packet_header.type);
	}
	packet_header.version = htonl(DC_ONE_PROTOCOL);
	packet_header.seq = htons(seq_number++);
	packet_header.length = htonl(length);

	packet_pending = 1;
	dtp_buildfooter();
}

void dtp_respondack(uint length)
{
	packet_header.type = htons(DTP_ACK | packet_header.type);
	dtp_respondcommon(length);
}

void dtp_respondnak(uint length)
{
	packet_header.type = htons(DTP_NAK | packet_header.type);
	dtp_respondcommon(length);
}

int dtp_sendstatus(long len, char *msg)
{
	auto DTPStatus *data;

	if((DLFlags & DL_DYNAMICC) && (serial_state < SERIAL_BIOSINIT)) {
		return 0; // no status messages while waiting on DynamicC
	}

	if(StatusPending > MAX_PENDING_STATUS) {
		return 0; // too much on the network right now
	}

	/* only send a status message if the old one is empty */
	if(packet_pending == 0) {
		data = (DTPStatus *)packet_data;
		
		if(msg != NULL) {
			status_len = len;
			status_msg = msg;
		}

		packet_header.type = htons(DTP_STATUS);
		packet_header.version = htonl(DC_ONE_PROTOCOL);
		packet_header.seq = htons(seq_number++);
		packet_header.length = htonl(sizeof(DTPStatus));

		data->bytecomplete = status_len;
		data->offset = DLOffset;
		data->window = BufferFree;
		strcpy(data->message, status_msg);
		memset(data->message + strlen(status_msg), 0, 32 - strlen(status_msg));
		
		packet_pending = 1;
		dtp_buildfooter();

		StatusPending++;

		return 1;
	} else {
		return 0;
	}
}

/* receive a download start pkt, and switch states to receive the actual program */
void dtp_download(void)
{
	static DTPDownloadStart * const pkt = (DTPDownloadStart *)packet_data;
	
	DownloadLength = pkt->length;
	DLFlags = pkt->flags;
	download_state = DOWNLOAD_INIT;
	DLOffset = 0;
	StatusPending = 0;

#ifdef DBG_DL
	printf("DownloadLength == %ld\n",DownloadLength);
	printf("DLFlags == 0x%04x\n",DLFlags);
#endif
	
	/* init the receive buffer */
	ReadBuffer = WriteBuffer = ProgramBuffer;
	BufferFree = PROGRAM_BUFFER_SIZE;

	/* start the process of writing to the target */
	//serial_state = SERIAL_TESTBOARD; // test if we don't need to load the BIOS
	serial_state = SERIAL_RESET;
	serial_delay = 0;

	/* init the first status message */
	status_len = 0;
	status_msg = "Reset";

	if(DLFlags & DL_DYNAMICC) {
		/* nothing - ack will happen later */
	} else {
		dtp_respondack(0);
	}
}

/* actually receive the program */
void dtp_downloaddata(void)
{
	auto int retval;
	auto char *recv_buf;
	auto DTPDownloadData *pkt;
	auto DTPDownloadDataAck *ack;
	auto long temp;
	pkt = (DTPDownloadData *)packet_data;
	ack = (DTPDownloadDataAck *)packet_data;

	retval = pkt->length;
	recv_buf = pkt->data;
	
		/* check if we actually received the length at the start command */
		if(0 == DownloadLength) {
			DownloadLength = pkt->downloadlength;
#ifdef DBG_DL
			printf("DownloadLength == %ld\n",DownloadLength);
#endif
		}

		if(retval > BufferFree)
			printf("ERROR! Overflowed buffer! (retval == %d, BufferFree = %d)\n",retval,BufferFree);
		
		if((WriteBuffer + retval) < (ProgramBuffer + PROGRAM_BUFFER_SIZE)) {
			root2xmem(WriteBuffer, recv_buf, retval);
			WriteBuffer += retval;
		} else {
			temp = PROGRAM_BUFFER_SIZE - (WriteBuffer - ProgramBuffer);
			root2xmem(WriteBuffer, recv_buf, (int)temp);
			root2xmem(ProgramBuffer, recv_buf + (int)temp, retval - (int)temp);
			WriteBuffer = ProgramBuffer + retval - temp;
		}

		if(WriteBuffer >= (ProgramBuffer + PROGRAM_BUFFER_SIZE))
			WriteBuffer -= PROGRAM_BUFFER_SIZE;

		BufferFree -= retval;
		DLOffset += retval;

		if((DLOffset > 16) && (BIOSLength == 0)) {
			/* compute the size of the BIOS/etc */
			BIOSStart = 0;
			xmem2root(&retval, ProgramBuffer + BIOSStart + 6, 2);
			BIOSLength = retval;
			CODEStart = retval + BIOSStart;
			CODELength = DownloadLength - CODEStart;

#ifdef DBG_DL
			printf("BIOSLength -> 0x%lx, CODELength -> 0x%lx\n",BIOSLength,CODELength);
#endif
			
			/* fix the jump to InRAMNow */
			xmem2root(&retval, ProgramBuffer + BIOSStart + 8, 2);
			root2xmem(ProgramBuffer + BIOSStart + 1, &retval, 2);

			/*** SANITY CHECK THESE VALUES? ***/
		}

		dtp_sendstatus(0,NULL); // just update the window
	
		if(DLOffset < DownloadLength)
			return;

		/* signal the remote host that we are done? */
#ifdef DBG_DL
		printf("Download of .bin file complete!\n");
#endif
}

/*
 * Helper funcs for the serial handler
 */
#asm
ResetClone::						
	ld    a,0x00
	ioi   ld (PADR),a				; take reset pin low
	ret
	; now delay for 30ms, then call UnResetClone

UnResetClone::
	ld    a, 0x01
	ioi   ld (PADR), a			; take reset pin high
	ret

SendTriplet::
	ioi 	ld a, (SBSR)
	bit	3,a		
	jr		nz, SendTriplet		; wait for serial port A not busy
	ld		a,d			
	ioi 	ld (SBDR), a			; send byte in d
SndTrplet0:
	ioi 	ld a, (SBSR)
	bit	3,a		
	jr		nz, SndTrplet0			; wait for serial port A not busy
	ld		a,e			
	ioi 	ld (SBDR), a			; send byte in e
SndTrplet1:
	ioi 	ld a, (SBSR)
	bit	3,a		
	jr		nz, SndTrplet1			; wait for serial port A not busy
	ld		a,c			
	ioi 	ld (SBDR), a			; send byte in c
	ret
#endasm

root void send_tuple(int addr, char c)
{
	/* send one tuple */              
#asm  
	; load a triplet
	ld		hl,(sp+2)
	ld		d,h
	ld		e,l
	ld		hl,(sp+4)
	ld		c,l

	; send it
	call    SendTriplet
#endasm
}

void FlushSerial(void)
{
	serBwrFlush();
	serBrdFlush();
}

/*
 * Storage for use by the ISR
 */
char	SendState, SendChecksum, SendChkDone;
int	SendHdrLen, SendBufLen;
char	*SendHdrPtr, *SendBufPtr;

int TxIntCnt, RxIntCnt, RxCnt, TxCnt;
char AckVal;

#asm root
serialisr_start:
	call	serial_isr
	ret
	
serial_isr::
	push	af					; 7, 	restore registers needed by isr
	push	bc					; 7
	push	de					; 7
	push	hl					; 7

	ioi ld	a,(SBSR)			; 11,	interrupt source rx or tx
	ld		b,a
	rla						; 2
	jp		c,rx_int			; 5

tx_int:
	ld		hl,(TxIntCnt)
	inc	hl
	ld		(TxIntCnt),hl
	
	ld		a,(SendState)
	or		a
	jr		nz,tx_int_buf

	ld		hl,(SendHdrLen)
	ld		a,h
	or		l
	jr		nz,tx_sendnextHdr

	ld		a,b
ioi ld		(SBSR),a			; clear int
	jp		int_done

tx_sendnextHdr:
	dec	hl
	ld		(SendHdrLen),hl
	ld		hl,(SendHdrPtr)
	ld		a,(hl)			; grab the next character
	inc	hl
	ld		(SendHdrPtr),hl
ioi ld	(SBDR),a			; send next header character
	ld		hl,(TxCnt)
	inc	hl
	ld		(TxCnt),hl
	jp		int_done

tx_int_buf:
	ld		hl,(SendBufLen)
	ld		a,h
	or		l
	jr		nz,tx_sendnextBuf

	ld		a,(SendChkDone)
	or		a
	jr		nz,AllreadySentChecksum

	ld		a,(SendChecksum)
ioi ld	(SBDR),a			; send the checksum
	ld		hl,(TxCnt)
	inc	hl
	ld		(TxCnt),hl
	ld		a,1
	ld		(SendChkDone),a	; flag we are done sending
	jr		int_done

AllreadySentChecksum:
	ld		a,b
ioi ld		(SBSR),a			; clear int
	jr		int_done

tx_sendnextBuf:
	dec	hl
	ld		(SendBufLen),hl
	ld		hl,(SendBufPtr)
	ld		a,(hl)			; grab the next character
	inc	hl
	ld		(SendBufPtr),hl
ioi ld	(SBDR),a			; send next body character
	ld		b,a
	ld		a,(SendChecksum)
	add	a,b					; add the current byte to the running checksum
	ld		(SendChecksum),a
	ld		hl,(TxCnt)
	inc	hl
	ld		(TxCnt),hl
	jr		int_done
	
rx_int:
	ld		hl,(RxIntCnt)
	inc	hl
	ld		(RxIntCnt),hl
	
	ld		a,(SendState)
	or		a
	jr		nz,rx_int_buf

ioi ld		a,(SBDR)
	cp		ACK
	jr		nz,rx_hdr_noack
	ld		a,0x01
	ld		(SendState),a

	; send the first character of the body
WaitISRForTx:
ioi ld	a,(SBSR)
	bit 	3,a
	jr		nz,WaitISRForTx
	
	ld		a,(SendISRBuf)
ioi ld		(SBDR),a
	ld		(SendChecksum),a	; start the checksum being computed
	ld		hl,(TxCnt)
	inc	hl
	ld		(TxCnt),hl
	;call	Blink
	jr		int_done
	
rx_hdr_noack:
	ld		a,0x02
	ld		(SendISRFree),a	; flag the error
	jr		rx_term_isr
	
rx_int_buf:
	ld		a,0x01
	ld		(SendISRFree),a	; default to success
	
ioi ld		a,(SBDR)
	cp		ACK
	;ld		(AckVal),a
	jr		z,rx_buf_ack
	ld		a,0x03
	ld		(SendISRFree),a	; flag error
rx_buf_ack:
rx_term_isr:	
	; no more data to send - terminate the ISR
	call	serBclose
	;call	Blink
	;jr		int_done

int_done:
	ipres
	pop	hl
	pop	de
	pop	bc
	pop	af
	ret

;
; This function will start an ISR, that will send one block to
; the target, and verify the ACK on the header & body.
; EXPECTS:
;		StartISRLen		-> Length of body, must be >= 1
;		StartISRHdrLen	-> Length of Header, must be >= 1
;		StartISRBuf		-> Actual pointer to the body
;		StartISRHdr		-> Actual pointer to the header
; When the ISR finishes, 1 will be stored in SendISRFree on
; success, 2 if the header failed, and 3 if the body failed.
; None of the above values will be modified, and a retransmit can
; be queued simply by calling StartISRSend() again. StartISRSend()
; should not be called until SendISRFree != 0.
;	
StartISRSend::				; isr start code ripped from rs232.lib
	ld		hl,0
	ld		(TxIntCnt),hl		; reset the int-counts
	ld		(RxIntCnt),hl
	ld		(TxCnt),hl
	ld		(RxCnt),hl
	
	ld		a,iir				; hl=spbisr_start, de={iir,0xc0}
	ld		h,a
	ld		l,0xd0
	ex		de,hl
	ld		hl,serialisr_start
	ipset	1

	ld		bc,0x0010		; copy 16 bytes from spbisr_start into interrupt table
	ldir

	ld		b,0x10			; serial port b
	ld		a,(PCFRShadow)
	or		b
	ld		(PCFRShadow),a
ioi ld 	(PCFR),a
	ipres

	; prepare state-machine data
	xor	a
	ld		(SendState),a			; init to state-0, the header
	ld		(SendChkDone),a		; flag we have yet to send the checksum
	
	ld		hl,(SendISRLen)
	dec	hl							; first byte will be sent in the rx handler
	ld		(SendBufLen),hl
	
	ld		hl,(SendISRHdrLen)
	dec	hl							; we send the first byte now
	ld		(SendHdrLen),hl
	
	ld		hl,SendISRBuf
	inc	hl
	ld		(SendBufPtr),hl
	
	ld		hl,SendISRHdr
	inc	hl
	ld		(SendHdrPtr),hl
	
	; start the first byte transmitting
WaitForSendFree:
ioi ld	a,(SBSR)
	bit 	3,a
	jr		nz,WaitForSendFree

	bit	7,a
	jr		z,NoDataWaiting
ioi ld	a,(SBDR)		; clear remaining data
	
NoDataWaiting:
	ld		a,0x01
	ld		(SBCRShadow),a
ioi ld	(SBCR),a			; enable int

	ld		a,(SendISRHdr)	; write the first byte out
ioi ld		(SBDR),a
	ld		hl,(TxCnt)
	inc	hl
	ld		(TxCnt),hl
	ret
#endasm	

void GetUserData(char *buf, int len)
{
	auto int temp;
	static long timeout;

#GLOBAL_INIT {
	timeout = 0;
}
	
	/* store the packet data */
	if( ((ProgramBuffer + PROGRAM_BUFFER_SIZE) - ReadBuffer) < len) {
		/* split across the end of the buffer */
		temp = (int)PROGRAM_BUFFER_SIZE - (int)(ReadBuffer - ProgramBuffer);
		xmem2root(buf, ReadBuffer, temp);
		xmem2root(buf + temp, ProgramBuffer, len - temp);
		ReadBuffer = ProgramBuffer + len - temp;
	} else {
		/* the easy state ^_^ */
		xmem2root(buf, ReadBuffer, len);
		ReadBuffer += len;
	}
	if(ReadBuffer >= (ProgramBuffer + PROGRAM_BUFFER_SIZE))
		ReadBuffer -= PROGRAM_BUFFER_SIZE;
	BufferFree += len;

	if(timeout < MS_TIMER) {
		
			
		timeout = MS_TIMER;
		timeout += 1000;
	}
}

void BuildWriteHeader(long addr, int len)
{
	auto char xpc;
	auto int offset;
	
	/* build the header */
	xpc = (char)(addr >> 12) - 0xE;
	offset = ((int)addr) & 0xfff;
	offset += 0xE000;

	SendISRHdr[0] = WRITE_BLOCK;
	SendISRHdr[1] = (char)(0xff & len);
	SendISRHdr[2] = (char)(0xff & (offset));
	SendISRHdr[3] = (char)(0xff & (offset >> 8));
	SendISRHdr[4] = xpc;
	SendISRHdr[5] = SendISRHdr[0] + SendISRHdr[1] + SendISRHdr[2] + SendISRHdr[3] + SendISRHdr[4];
	SendISRHdrLen = 6;

	SendISRFree = 0;
	Blink();
}

nodebug void BuildWriteHeaderXPC(char xpc, int offset, int len)
{
	SendISRHdr[0] = WRITE_BLOCK;
	SendISRHdr[1] = (char)(0xff & len);
	SendISRHdr[2] = (char)(0xff & (offset));
	SendISRHdr[3] = (char)(0xff & (offset >> 8));
	SendISRHdr[4] = xpc;
	SendISRHdr[5] = SendISRHdr[0] + SendISRHdr[1] + SendISRHdr[2] + SendISRHdr[3] + SendISRHdr[4];
	SendISRHdrLen = 6;

	SendISRFree = 0;
	Blink();
}

#define SET_TIMEOUT		state_timeout = MS_TIMER; state_timeout += 2048;
#define CHECK_TIMEOUT	(MS_TIMER > state_timeout)

/* most of the code that writes to the target is here */
void dtp_serialhandler(void)
{
	static long timeout, sent, state_timeout;
	static char bytetosend[3], character;
	static int sum, answer, Flash_ID, *flash_p, sector_offset;
	static long length;
	static char xpc;
	static long status_time;
	static int old_serial_state;
	static long old_state_timeout;
	static char flashid_count;
	static DTPDownloadACK *DLack;

	#GLOBAL_INIT {
		timeout = 0;
	}

	if(old_serial_state != serial_state) {
		old_serial_state = serial_state;
		old_state_timeout = MS_TIMER;
		old_state_timeout += 4096*2;
	} else {
		if((serial_state != SERIAL_NOTHING) && (MS_TIMER > old_state_timeout)) {
			/* We've been in one state too long - error out */

			if(DownloadLength && (serial_state != SERIAL_BIOSINIT)) {
				printf("Serial TIMEOUT!\n");
				control_state = CONTROL_RESET;
				serial_state = SERIAL_NOTHING;
			} else {
				old_state_timeout = MS_TIMER;
				old_state_timeout += 4096*2;
			}
		}
	}
		
	
	if(MS_TIMER < timeout)
		return;
	
	switch(serial_state) {
	case SERIAL_NOTHING:
		/* nothing is running/pending */
		return;

#ifdef TEST_BOARD_FIRST
	case SERIAL_TESTBOARD:
		Blink();
		serBopen(main_id.prog_baud);
		FlushSerial();
		//printf("PING... ");
		serBputc(0xa0);
		SendISRLen = -1;
		length = MS_TIMER;
		length += 250;
		while(SendISRLen == -1) {
			if(MS_TIMER > length) {
				/* timeout - board must be reset/coldloaded */
				break;
			}
			SendISRLen = serBgetc();
		}

		if((SendISRLen == -1) || (SendISRLen == 0)) {
			/* start the board reseting */
			printf("Board not responding - starting reset/coldload.\n");
			serial_state = SERIAL_RESET;
		} else {
			printf("PONG! (0x%02x)\n",SendISRLen);
			serial_state = SERIAL_WAITFORPROG;
			length = 0;
		}
		serBclose();
		Blink();
		return;
#endif

	case SERIAL_WAITFORPROG:
		if(0 == BIOSLength)
			return;

		if(length >= (BIOSLength)) {
			/* done getting with the bios */
			serial_state = SERIAL_PROGINIT;
			return;
		}

		/* do we have a complete packet, or the last packet? */
		if((DLOffset - length) < 128)
			if((DownloadLength - DLOffset) > 0)
				return;
		
		SendISRLen = (int)(BIOSLength - length);
		if(SendISRLen > 128)
			SendISRLen = 128;
			
		/* remove the data from the circular buffer */
		GetUserData(SendISRBuf, SendISRLen);
		return;
		
	case SERIAL_RESET:
		Blink();
	   WrPortI ( PCFR,  &PCFRShadow, PCFRShadow|0x10 );
	   WrPortI ( TAT1R, &TAT1RShadow, 1 );             
   	WrPortI ( TACR,  &TACRShadow, TACRShadow|0x20 );   
	   WrPortI ( TAT5R, &TAT5RShadow, 144 );
	   WrPortI ( SBCR, &SBCRShadow, 0 );
	   WrPortI ( PBDR, &PBDRShadow, PBDRShadow | 0xC0 );		// Set SMODE[0,1] high
	   WrPortI ( SPCR, &SPCRShadow, 0x84 );						// Enable /RESET line   
#asm
		ld a, 0x01
ioi   ld (PADR), a			; take reset pin high		
		call	ResetClone				; pulse reset
#endasm
		serial_state = SERIAL_UNRESET;
		serial_delay = 30;
		dtp_sendstatus(0,"Reset");
		break;

	case SERIAL_UNRESET:
		Blink();
#asm
		call	UnResetClone
#endasm
		serial_state = SERIAL_COLDLOAD;
		serial_delay = 512;
		dtp_sendstatus(0,"Coldloader");
		break;
		
	case SERIAL_COLDLOAD:
		Blink();
		/* Yes, this blocks for a while, but it is a fixed time, is
			not that long, and isn't really worth putting in it's own
			state, isr, or somesuch */
		sent = 0;
		for(;;) {
			xmem2root(bytetosend, coldloader + 4 + sent, 3);

#asm
			ld		hl,bytetosend
			ld		a,(hl)
			ld		d,a
			inc 	hl
			ld		a,(hl)
			ld		e,a
			inc	hl
			ld		a,(hl)
			ld		c,a
			call	SendTriplet
#endasm
			if( (bytetosend[0] == 0x80) &&
				 (bytetosend[1] == 0x24) &&
				 (bytetosend[2] == 0x80) ) {
				 	break;
			}
			sent += 3;
		}
		serial_state = SERIAL_PILOTINIT;
		serial_delay = 256;
		break;

	case SERIAL_PILOTINIT:
		Blink();
		/* change to 19200 */
	   WrPortI ( TAT1R, &TAT1RShadow, 0);
	   WrPortI ( TACR,  &TACRShadow, TACRShadow|0x20 );        // Source A4, clock A1
   	WrPortI ( TAT5R, &TAT5RShadow, 11);
		serial_state = SERIAL_PILOTSEND;
		serial_delay = 256;
		dtp_sendstatus(0,"Pilot BIOS");
		break;

	case SERIAL_PILOTSEND:
		Blink();
	   sent = 0;
	   length = 0x0400;
   	while(sent < length) {
			xmem2root(&character, pilotbios + 4 + 0x4100 + sent, 1);
#asm
waitforsent:
			ioi 	ld a, (SBSR)
			bit	3,a		
			jr		nz, waitforsent		; wait for serial port not busy
			ld		a,(character)
			ioi 	ld (SBDR), a			; send byte in d
#endasm
	   	sent += 1;
   	}
	   /* Enable normal serial routines */
   	serBopen(57600);
		FlushSerial();
   	serial_state = SERIAL_FLASHID;
   	serial_delay = 512;
   	dtp_sendstatus(0,"Probing FlashID");
   	flashid_count = 8;
		break;

	case SERIAL_FLASHID:
		if(flashid_count < 1) {
			/* too many probes for the flashid */
			control_state = CONTROL_RESET;
			return;
		}
		flashid_count--;
		
		Blink();
		FlushSerial();
		serBputc(GET_FLASH_ID);
		serial_state = SERIAL_FLASHID1;
		SET_TIMEOUT;
		/* fall through */

	case SERIAL_FLASHID1:
		if(CHECK_TIMEOUT) {
			serial_state = SERIAL_FLASHID;
			return;
		}

		sum = serBgetc();
		if(-1 == sum)
			return;

		Flash_ID = (int)(sum & 0x00ff);
		serial_state = SERIAL_FLASHID2;
		SET_TIMEOUT;
		/* fall through */

	case SERIAL_FLASHID2:
		if(CHECK_TIMEOUT) {
			serial_state = SERIAL_FLASHID;
			return;
		}
		answer = serBgetc();
		if(-1 == answer)
			return;
		sum += answer;
		Flash_ID += answer << 8; 			// should be: Flash_ID += ((int)answer) << 8);, but that didn't work
		serial_state = SERIAL_FLASHID3;
		SET_TIMEOUT;
		/* fall through */

	case SERIAL_FLASHID3:
		if(CHECK_TIMEOUT) {
			serial_state = SERIAL_FLASHID;
			return;
		}
		answer = serBgetc();
		if(-1 == answer)
			return;
		if(answer != (0xFF - sum)) {
			//printf("Error reading flashID! (id == 0x%04x, chksum == %02x, real_chksum == %02x)\n",Flash_ID,sum,answer);
			/* ERROR */
			FlushSerial();
			serial_state = SERIAL_FLASHID;
			serial_delay = 512;
			break;
		} else {
			/* good id - look it up */
#asm
			ld		hl,FlashData
			ld		(flash_p),hl
#endasm

			SectorSize = 0;
			while(*flash_p != 0) {
				if(*flash_p == Flash_ID) {
					flash_p++;
					SectorSize = *flash_p;
						
					flash_p++;
					NumSectors = *flash_p;

					break;
				}
				flash_p += (int)FlashEntrySize / sizeof(int);
			}
			Blink();
#ifdef DBG_DL
			printf("FlashID == %04x, SectorSize == %d, NumSectors == %d\n",Flash_ID,SectorSize,NumSectors);
#endif

			FlushSerial();
			serial_state = SERIAL_GETRAMSIZE;
			serial_delay = 16;
	   	dtp_sendstatus(0,"Probing RamSize");

			flashid_count = 8;
		}
		break;

	case SERIAL_GETRAMSIZE:
		if(flashid_count < 1) {
			/* too many probes for the flashid */
			control_state = CONTROL_RESET;
			return;
		}
		flashid_count--;

#ifdef DBG_DL
		printf("\nProbing RamSize... ");
#endif
				
		Blink();
		FlushSerial();
		serBputc(GET_RAM_SIZE);
		serial_state = SERIAL_GETRAMSIZE1;
		SET_TIMEOUT;
		/* fall through */

	case SERIAL_GETRAMSIZE1:
		if(CHECK_TIMEOUT) {
			serial_state = SERIAL_GETRAMSIZE;
			//printf("State-1 timeout!\n");
			return;
		}

		answer = serBgetc();
		if(-1 == answer)
			return;

		serial_state = SERIAL_GETRAMSIZE2;
		SET_TIMEOUT;
		/* fall through */

	case SERIAL_GETRAMSIZE2:
		if(CHECK_TIMEOUT) {
			serial_state = SERIAL_GETRAMSIZE;
			//printf("State-2 timeout!\n");
			return;
		}
		sum = serBgetc();
		if(-1 == sum)
			return;

		if(answer != (char)~sum) {
			serial_state = SERIAL_GETRAMSIZE;
			//printf("Checksum error! (answer = 0x%02x, sum = 0x%02x, ~sum = 0x%02x)\n",answer,sum,(char)~sum);
			return;
		}

		RamSize = answer;
#ifdef DBG_DL
		printf("Ramsize == 0x%02x\n",RamSize);
#endif
		serial_state = SERIAL_ACKDOWNLOAD;
		serial_delay = 16;
		break;
		
	case SERIAL_ACKDOWNLOAD:
		if(DLFlags & DL_DYNAMICC) {
			/* ack the download-start, and give back the
				flashid & ram size to DynamicC */

			/* are there any status packets pending? wait for them to finish */
			if(packet_pending)
				return;

			DLack = (DTPDownloadACK *)packet_data;
			DLack->flashid = Flash_ID;
			DLack->ramsize = RamSize;

			/* read the System ID Block, and send it up as well */
			length = (((long)SectorSize * (long)NumSectors) - 128 - 128 + 0x40000) | 0x80000;
			//printf("Reading from: %02x:%04x\n",GetXPC(length),GetOFFSET(length));
			if(ReadBlock(128, GetXPC(length), GetOFFSET(length), &(DLack->IDBlock[0]))) {
				memset(&(DLack->IDBlock[0]),0,128);
				printf("Error reading ID Block!\n");
			} else {
				length += 128;
				//printf("Reading from: %02x:%04x\n",GetXPC(length),GetOFFSET(length));
				if(ReadBlock(128, GetXPC(length), GetOFFSET(length), &(DLack->IDBlock[0]) + 128)) {
					memset(&(DLack->IDBlock[0]),0,128);
#ifdef DBG_DL
					printf("Error reading ID Block!\n");
#endif
				} else {
#ifdef DBG_DL
					printf("ID Block read successfully!\n");
#endif
				}
			}
								
			dtp_respondack(sizeof(DTPDownloadACK));
#ifdef DBG_DL
			printf("Sending flashid/ramsize to target...\n");
#endif

			serial_state = SERIAL_BIOSINIT;
			return;
		} else {
			serial_state = SERIAL_BIOSINIT;
			/* fall through */
		}

	case SERIAL_BIOSINIT:
		/* do we have the beginning of the BIOS yet? */
		//printf("Waiting for the BIOS...\n");
		if(BIOSLength == 0)
			return;

#ifdef DBG_DL
		printf("\nSending the BIOS...\n");
		printf("-------------------\n");
#endif
		serBclose();	// terminate the old ISR, so we can load our own

		length = 0;
		serial_state = SERIAL_BIOS;
		SendISRFree = 1;
		status_time = MS_TIMER;
		status_time += 250;
		/* fall through */
		
	case SERIAL_BIOS:
		if(length >= (BIOSLength)) {
			serial_state = SERIAL_RUNBIOS;
			dtp_sendstatus(length, "Running the BIOS");
			return;
		}

		/* do we have a complete packet, or the last packet? */
		if((DLOffset - length) < 128)
			if((DownloadLength - DLOffset) > 0)
				return;
		
		/* send one 128b block */
		if(SendISRFree) {
			SendISRFree = 0;

			SendISRLen = (int)(BIOSLength - length);
			if(SendISRLen > 128)
				SendISRLen = 128;				

			/* store the packet data */
			GetUserData(SendISRBuf, SendISRLen);

			/* build the header */
			BuildWriteHeader(length, SendISRLen);

#if 0
			printf("Sending block at: 0x%04x\t",length);
			printf("DATA[%02x,%02x,%02x,%02x,%02x,%02x...]\n",
				SendISRBuf[0],SendISRBuf[1],SendISRBuf[2],SendISRBuf[3],SendISRBuf[4],SendISRBuf[5]);
#endif
#if 0
			printf("Sending block at: 0x%04lx\tHDR[%02x,%02x,%02x,%02x,%02x,%02x]\n",length,
				SendISRHdr[0],SendISRHdr[1],SendISRHdr[2],SendISRHdr[3],SendISRHdr[4],SendISRHdr[5]);
			printf("\tSendISRLen = %d, SendISRHdrLen = %d\n",SendISRLen,SendISRHdrLen);
#endif
			StartISRSend();

			if(MS_TIMER > status_time) {
				dtp_sendstatus(length, "BIOS");
				status_time = MS_TIMER;
				status_time += 250;
			}
			
			serial_state = SERIAL_BIOSWAIT;
			SET_TIMEOUT;
		}
		break;

	case SERIAL_BIOSWAIT:
		/* waiting for the ISR to signal we are done with a block */
		if(SendISRFree) {
			/* signal received */
			switch(SendISRFree) {
			case 1:
				/* ACK received */
				length += SendISRLen;
				serial_state = SERIAL_BIOS;
				break;
				
			case 2:
				/* error in header */
			case 3:
				/* error in body */
			default:
				/* resend it */
#ifdef DBG_DL
				printf("ERROR %d: re-sending block at: 0x%04lx (TxIntCnt: %d, RxIntCnt: %d, TxCnt: %d)\n",SendISRFree,length,TxIntCnt,RxIntCnt,TxCnt);
#endif
				StartISRSend();
				SendISRFree = 0;
				break;
			}
		} else {
#if 0
			if(CHECK_TIMEOUT) {
				//printf("TIMEOUT: re-sending block at: 0x%04lx (TxIntCnt: %d, RxIntCnt: %d, TxCnt: %d)\n",length,TxIntCnt,RxIntCnt,TxCnt);
				serBclose();
				StartISRSend();
				SendISRFree = 0;
				SET_TIMEOUT;
			}
#endif
		}
		break;

	case SERIAL_RUNBIOS:
		SendISRFree = 0;
		
		SendISRBuf[0] = 0x58;
		SendISRLen = 1;
	   length = 0x00004001;

		/* build the header */
		BuildWriteHeader(length, SendISRLen);

#ifdef DBG_DL
		printf("Running BIOS...\n");
#endif
		StartISRSend();

		serial_state = SERIAL_RUNWAIT;
		break;

	case SERIAL_RUNWAIT:
		if(SendISRFree) {
			if(SendISRFree != 1) {
				StartISRSend();
				SendISRFree = 0;
				return;			
			}

#ifdef DBG_DL
			printf("BIOS successfully running!\n");
#endif
			serial_state = SERIAL_PROGINIT;
			serial_delay = 2048 + 1024;
		}
		break;

	case SERIAL_PROGINIT:
		SectorSize = 128; /* BUGFIX FOR DC! Probably should be fixed elsewhere! */
		serBopen(main_id.prog_baud);
		FlushSerial();
		//printf("PING... ");
		serBputc(0xa0);
		SendISRLen = -1;
		while(SendISRLen == -1)
			SendISRLen = serBgetc();

		//printf("PONG! (0x%02x)\n",SendISRLen);
		serBclose();
	
		/* prepare the first (PARTIAL!) block */
		SendISRLen = (int)((((BIOSLength / SectorSize) * SectorSize) + SectorSize) - BIOSLength);
		
		/* do we have a complete packet, or the last packet? */
		if((DLOffset - (SendISRLen + BIOSLength)) < 128)
			if((DownloadLength - DLOffset) > 0)
				return;

		/* build the packet */
		GetUserData(SendISRBuf, SendISRLen);
		BuildWriteHeader(BIOSLength, SendISRLen);

#ifdef DBG_DL
		printf("\nSending the user program...\n---------------------------\n");
		printf("Sending the first (PARTIAL) block of user code...\n");
#endif

#if 0
			printf("PARTIAL BLOCK\tDATA[%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x]\n",
			SendISRBuf[0],SendISRBuf[1],SendISRBuf[2],SendISRBuf[3],SendISRBuf[4],SendISRBuf[5],SendISRBuf[6],SendISRBuf[7],
#endif
		StartISRSend();
		
		serial_state = SERIAL_PARTIALWAIT;		
		break;

	case SERIAL_PARTIALWAIT:
		if(SendISRFree) {
			if(SendISRFree != 1) {
#ifdef DBG_DL
				printf("ERROR #%d: Resending first (PARTIAL) user block.\n",SendISRFree);
#endif
				StartISRSend();
				SendISRFree = 0;
				return;			
			}

			sector_offset = SendISRLen;	// preperation for the next state
			length = 0;
#ifdef DBG_DL
			printf("First (PARTIAL) block successfully sent.\n");
#endif
			serial_state = SERIAL_PROG;

			status_time = MS_TIMER;
			status_time += 250;
		}
		break;

	case SERIAL_PROG:
		
		if(length >= (CODELength - sector_offset)) {
			/* all done! */
#ifdef DBG_GEN
			printf("\nAll done with download!\n");
#endif
			serial_state = SERIAL_FINISH;
			break;
		}

		if((DLOffset - (BIOSLength + sector_offset + length)) < 128)
			if((DownloadLength - DLOffset) > 128) {
				return;
			} else {
				if(DLOffset < DownloadLength)
					return;
			}
				
		if((CODELength - length) > SectorSize) {
			SendISRLen = SectorSize;
		} else {
			SendISRLen = (int)(CODELength - length);
		}

		GetUserData(SendISRBuf, SendISRLen);
		
		/* build the header */
		BuildWriteHeader(BIOSLength + sector_offset + length, SendISRLen);

#if 0
			printf("Write block 0x%04lx ",BIOSLength + sector_offset + length);
			printf("DATA[%02x,%02x,%02x,%02x,%02x,%02x,%02x,%02x...]\n",
				SendISRBuf[0],SendISRBuf[1],SendISRBuf[2],SendISRBuf[3],SendISRBuf[4],SendISRBuf[5],SendISRBuf[6],SendISRBuf[7]);
#endif
		StartISRSend();

		if(MS_TIMER > status_time) {
			dtp_sendstatus(BIOSLength + sector_offset + length, "User Program");
			status_time = MS_TIMER;
			status_time += 250;
		}
			
		serial_state = SERIAL_PROGWAIT;		
		break;

	/* Send complete blocks */
	
	case SERIAL_PROGWAIT:
		/* waiting for the ISR to signal we are done with a block */
		if(SendISRFree) {
			/* signal received */
			switch(SendISRFree) {
			case 1:
				/* ACK received */
				//printf("Block 0x%04lx sent successfully!\n",length);
				length += SendISRLen;
				serial_state = SERIAL_PROG;
				break;
				
			case 2:
				/* error in header */
			case 3:
				/* error in body */
			default:
				/* resend it */
#ifdef DBG_DL
				printf("ERROR %d: re-sending block at: 0x%04lx (TxIntCnt: %d, RxIntCnt: %d, TxCnt: %d)\n",SendISRFree,length,TxIntCnt,RxIntCnt,TxCnt);
#endif
				StartISRSend();
				SendISRFree = 0;
				break;
			}
		} else {
#if 0
			if(CHECK_TIMEOUT) {
				//printf("TIMEOUT: re-sending block at: 0x%04lx (TxIntCnt: %d, RxIntCnt: %d, TxCnt: %d)\n",length,TxIntCnt,RxIntCnt,TxCnt);
				serBclose();
				StartISRSend();
				SendISRFree = 0;
				SET_TIMEOUT;
			}
#endif
		}
		break;

	case SERIAL_FINISH:
	   if(DLFlags & DL_DYNAMICC) {
	   	/* skip the reset if we are talking to DynamicC */
	   	serial_state = SERIAL_FINISH2;
	   	break;
	   } else {
		   WrPortI ( PBDR, &PBDRShadow, PBDRShadow & 0x3F );		// Set SMODE[0,1] low
#asm
			call ResetClone
#endasm

			serial_state = SERIAL_FINISH1;
			serial_delay = 30;
			break;
		}

	case SERIAL_FINISH1:
#asm
		call UnResetClone
#endasm
		serial_state = SERIAL_FINISH2;
		/* fall through */

	case SERIAL_FINISH2:		
		/* wait for the last message to finish */
		bc_usrledoff();
		
		if(dtp_sendstatus(BIOSLength + CODELength, "All Done.") && (DLFlags & DL_DYNAMICC)) {
			serial_state = SERIAL_NOTHING;
			control_state = CONTROL_RUNTARGET;
		}

		break;		

	/* timeout state? (clean up ISR/etc) */

	default:
		/* error */
		serial_state = SERIAL_NOTHING;
		return;
	}

	if(serial_delay) {
		timeout = MS_TIMER;
		timeout += serial_delay;	
		serial_delay = 0;
	}
}

void dtp_reset(void)
{
	auto long timeout;
	
	// reset the device
#asm
	call	ResetClone
#endasm
	timeout = MS_TIMER;
	timeout += 30;
	while(MS_TIMER < timeout)
		continue;
#asm
	call	UnResetClone
#endasm

	dtp_respondack(0);
}

void dtp_coldload(void)
{
	dtp_respondnak(0);
}

/*
 * Storage for use by the Read-ISR
 */
char	ReadState, ReadChecksum, ReadChkDone;
int	ReadHdrLen, ReadBufLen;
char	*ReadHdrPtr, *ReadBufPtr;

char retval;

int	ReadRxCnt;

#asm root
readserialisr_start:
	call	readserial_isr
	ret
	
readserial_isr::
	push	af					; 7, 	restore registers needed by isr
	push	bc					; 7
	push	de					; 7
	push	hl					; 7

	ioi ld	a,(SBSR)			; 11,	interrupt source rx or tx
	ld		b,a
	rla						; 2
	jp		c,readrx_int			; 5

readtx_int:
	ld		a,(ReadState)
	or		a
	jr		nz,readtx_int_done

	ld		hl,(ReadHdrLen)
	ld		a,h
	or		l
	jr		nz,readtx_nextHdr

	ld		a,b
ioi ld		(SBSR),a			; clear int
	jp		readint_done

readtx_nextHdr:
	dec	hl
	ld		(ReadHdrLen),hl
	ld		hl,(ReadHdrPtr)
	ld		a,(hl)			; grab the next character
	inc	hl
	ld		(ReadHdrPtr),hl
ioi ld	(SBDR),a			; send next header character
	jp		readint_done

readtx_int_done:
	ld		a,b
ioi ld		(SBSR),a			; clear int
	jp		readint_done

readrx_int:
	ld		hl,(ReadRxCnt)
	inc	hl
	ld		(ReadRxCnt),hl

	ld		a,(ReadState)
	or		a
	jr		nz,readrx_int_buf

ioi ld		a,(SBDR)
	cp		ACK
	jr		nz,readrx_hdr_noack
	ld		a,0x01
	ld		(ReadState),a

	;call	Blink
	jr		readint_done
	
readrx_hdr_noack:
	ld		a,0x02
	ld		(ReadISRFree),a	; flag the error
	jr		readrx_term_isr
	
readrx_int_buf:
	ld		a,(ReadBufLen)
	or		a
	jr		z,readrx_checksum
	dec	a
	ld		(ReadBufLen),a

	; receive one character into the buffer
ioi ld	a,(SBDR)
	ld		hl,(ReadBufPtr)
	ld		(hl),a				; store the byte
	inc	hl
	ld		(ReadBufPtr),hl	; move pointer to next byte
	
	ld		b,a
	ld		a,(ReadChecksum)
	add	a,b
	ld		(ReadChecksum),a	; store the new checksum

	jr		readint_done

readrx_checksum:
	ld		a,(ReadChkDone)
	or		a
	jr		nz,readrx_chkdone
	inc	a
	ld		(ReadChkDone),a

	ld		a,0x01
	ld		(ReadISRFree),a	; default to success

ioi ld		a,(SBDR)
	ld		b,a
	ld		a,(ReadChecksum)
	cp		b						; verify the checksum
	jr		z,readrx_buf_ack
	ld		a,0x03
	ld		(ReadISRFree),a	; flag error
readrx_buf_ack:
	jr		readrx_term_isr

readrx_chkdone:		
readrx_term_isr:	
	; no more data to send - terminate the ISR
	call	serBclose
	;call	Blink
	;jr		readint_done

readint_done:
	ipres
	pop	hl
	pop	de
	pop	bc
	pop	af
	ret

StartISRRead::				; isr start code ripped from rs232.lib
	ld		a,iir				; hl=spbisr_start, de={iir,0xc0}
	ld		h,a
	ld		l,0xd0
	ex		de,hl
	ld		hl,readserialisr_start
	ipset	1

	ld		bc,0x0010		; copy 16 bytes from spbisr_start into interrupt table
	ldir

	ld		b,0x10			; serial port b
	ld		a,(PCFRShadow)
	or		b
	ld		(PCFRShadow),a
ioi ld 	(PCFR),a
	ipres

	; prepare state-machine data
	xor	a
	ld		(ReadState),a			; init to state-0, the header
	ld		(ReadChkDone),a		; flag we have yet to read the checksum
	ld		(ReadChecksum),a		; init the checksum to 0
	ld		(ReadRxCnt),a			; rx int count
	
	ld		hl,(ReadISRLen)
	ld		(ReadBufLen),hl
	
	ld		hl,(ReadISRHdrLen)
	dec	hl							; we send the first byte now
	ld		(ReadHdrLen),hl
	
	ld		hl,(ReadISRBuf)
	ld		(ReadBufPtr),hl
	
	ld		hl,ReadISRHdr
	inc	hl
	ld		(ReadHdrPtr),hl
	
	; start the first byte transmitting
ReadWaitForSendFree:
ioi ld	a,(SBSR)
	bit 	3,a
	jr		nz,ReadWaitForSendFree

	bit	7,a
	jr		z,ReadNoDataWaiting
ioi ld	a,(SBDR)		; clear remaining data
	
ReadNoDataWaiting:
	ld		a,0x01
	ld		(SBCRShadow),a
ioi ld	(SBCR),a			; enable int

	ld		a,(ReadISRHdr)	; write the first byte out
ioi ld		(SBDR),a
	ret
#endasm	

nodebug void BuildReadHeaderXPC(char xpc, int offset, int len)
{
	ReadISRHdr[0] = READ_BLOCK;
	ReadISRHdr[1] = (char)(0xff & len);
	ReadISRHdr[2] = (char)(0xff & (offset));
	ReadISRHdr[3] = (char)(0xff & (offset >> 8));
	ReadISRHdr[4] = xpc;
	ReadISRHdr[5] = ReadISRHdr[0] + ReadISRHdr[1] + ReadISRHdr[2] + ReadISRHdr[3] + ReadISRHdr[4];
	ReadISRHdrLen = 6;

	ReadISRFree = 0;
	Blink();
}

void dtp_read(void)
{
	static read_state, TryCount;
	static long time;
	static char xpc;
	static int offset;
	static char sendheader[6];
	
#GLOBAL_INIT {
	read_state = 0;
}

	switch(read_state) {
	case 0:
		memcpy(sendheader, packet_data, 6);

#ifdef DBG_DBGMODE
		printf("Reading from %02x:%02x%02x, %d bytes\n",sendheader[0],sendheader[2],sendheader[1],sendheader[3]);
#endif
		TryCount = 10;
		read_state = 1;
		DLFlags |= DL_NOPOLL;
		/* fall through */
		
	case 1:
ReadTop:
		xpc = sendheader[0];
		offset = (sendheader[2] << 8) + sendheader[1];
				
		ReadISRLen = sendheader[3];
		memset(packet_data,0,ReadISRLen);

		/* where to store the data? */
		ReadISRBuf = packet_data;

	   /* build the header */
		BuildReadHeaderXPC(xpc, offset, ReadISRLen);

		ReadISRFree = 0;
		StartISRRead();

		time = MS_TIMER;
		time += 100;
		read_state = 2;
		/* fall through */

	case 2:
		/* is the ISR finished? */
		if(0 == ReadISRFree) {
			if(MS_TIMER > time) {
				/* timeout - fake an error */
				serBclose();
				ReadISRFree = 4;
			} else {
				return;
			}
		}

		serBclose();

		if(1 == ReadISRFree) {
			//printf("Packet received: RxCnt = %d\n",ReadRxCnt);
			DLFlags &= ~DL_NOPOLL;
			dtp_respondack(ReadISRLen);
		} else {
			if(TryCount > 0) {
#ifdef DBG_DBGMODE
				printf("Read failed! (error #%d)\n",ReadISRFree);
#endif
				TryCount--;
				read_state = 1;
				goto ReadTop;
			}
			DLFlags &= ~DL_NOPOLL;
			dtp_respondnak(0);
		}

		bc_usrledoff();
		read_state = 0;
		control_state = CONTROL_STEADY;
	}
}

void dtp_write(void)
{
	static write_state;
	static long time;
	static char xpc;
	static int offset, TryCount;
	static char sendheader[6];
	
#GLOBAL_INIT {
	write_state = 0;
}

	switch(write_state) {
	case 0:
		memcpy(sendheader, packet_data, 6);

#ifdef DBG_DBGMODE
		printf("Writing to %02x:%02x%02x, %d bytes\n",sendheader[0],sendheader[2],sendheader[1],sendheader[3]);
#endif
		TryCount = 10;
		write_state = 1;
		DLFlags |= DL_NOPOLL;
		/* fall through */
		
	case 1:
WriteTop:
		xpc = sendheader[0];
		offset = (sendheader[2] << 8) + sendheader[1];
				
		SendISRLen = sendheader[3];

		/* store the packet data */
		memcpy(SendISRBuf, packet_data + 4, SendISRLen);

	   /* build the header */
		BuildWriteHeaderXPC(xpc, offset, SendISRLen);

		SendISRFree = 0;
		StartISRSend();

		time = MS_TIMER;
		time += 100;
		write_state = 2;
		/* fall through */

	case 2:
		/* is the ISR finished? */
		if(0 == SendISRFree) {
			if(MS_TIMER > time) {
				/* timeout - fake an error */
				serBclose();
				SendISRFree = 3;
			} else {
				return;
			}
		}

		serBclose();
		if(1 == SendISRFree) {
			DLFlags &= ~DL_NOPOLL;
			dtp_respondack(0);
		} else {
			if(TryCount > 0) {
#ifdef DBG_DBGMODE
				printf("Write failed! (error #%d)\n",SendISRFree);
#endif
				TryCount--;
				write_state = 1;
				goto WriteTop;
			}
			DLFlags &= ~DL_NOPOLL;
			dtp_respondnak(0);
		}

		bc_usrledoff();
		write_state = 0;
		control_state = CONTROL_STEADY;
	}
}

/*******
 * Helper functions for single-steping
 *******/

nodebug int ReadBlock(char len, char in_xpc, int in_offset, char *buf)
{
	auto char xpc;
	auto int offset, TryCount;
	auto char sendheader[6];
	auto long time;

	sendheader[0] = in_xpc;
	sendheader[1] = (char)(in_offset & 0xff);
	sendheader[2] = (char)((in_offset >> 8) & 0xff);
	sendheader[3] = len;

	//printf("Reading [block] from %02x:%02x%02x, %d bytes\n",sendheader[0],sendheader[2],sendheader[1],sendheader[3]);
	TryCount = 10;
		
ReadBlockTop:
	xpc = sendheader[0];
	offset = (sendheader[2] << 8) + sendheader[1];
				
	ReadISRLen = sendheader[3];
	memset(buf,0,ReadISRLen);

	/* where to store the data? */
	ReadISRBuf = buf;

	/* build the header */
	BuildReadHeaderXPC(xpc, offset, ReadISRLen);

	ReadISRFree = 0;
	StartISRRead();

	time = MS_TIMER;
	time += 100;

	/* is the ISR finished? */
ReadBlockWait:
	if(0 == ReadISRFree) {
		if(MS_TIMER > time) {
			/* timeout - fake an error */
			serBclose();
			ReadISRFree = 4;
		} else {
			goto ReadBlockWait;
		}
	}

	if(1 == ReadISRFree) {
		//printf("Packet received: RxCnt = %d\n",ReadRxCnt);
		bc_usrledoff();
		return 0;
	} else {
		if(TryCount > 0) {
#ifdef DBG_DBGMODE
			//printf("ReadBlock failed! (error #%d)\n",ReadISRFree);
#endif
			TryCount--;
			goto ReadBlockTop;
		}
		bc_usrledoff();
		return 1;
	}
}

nodebug WriteBlock(char len, char in_xpc, int in_offset, char *buf)
{
	auto char xpc;
	auto int offset, TryCount;
	auto char sendheader[6];
	auto long time;

	sendheader[0] = in_xpc;
	sendheader[1] = (char)(in_offset & 0xff);
	sendheader[2] = (char)((in_offset >> 8) & 0xff);
	sendheader[3] = len;
	
	//printf("Writing to %02x:%02x%02x, %d bytes\n",sendheader[0],sendheader[2],sendheader[1],sendheader[3]);
	TryCount = 10;

WriteTop:
	xpc = sendheader[0];
	offset = (sendheader[2] << 8) + sendheader[1];
				
	SendISRLen = sendheader[3];

	/* store the packet data */
	memcpy(SendISRBuf, buf, SendISRLen);

   /* build the header */
	BuildWriteHeaderXPC(xpc, offset, SendISRLen);

	SendISRFree = 0;
	StartISRSend();

	time = MS_TIMER;
	time += 100;

	/* is the ISR finished? */
WriteBlockWait:
	if(0 == SendISRFree) {
		if(MS_TIMER > time) {
			/* timeout - fake an error */
			serBclose();
			SendISRFree = 4;
		} else {
			goto WriteBlockWait;
		}
	}

	if(1 == SendISRFree) {
		bc_usrledoff();
		return 0;
	} else {
		if(TryCount > 0) {
#ifdef DBG_DBGMODE
			//printf("WriteBlock failed! (error #%d)\n",SendISRFree);
#endif
			TryCount--;
			goto WriteTop;
		}
		bc_usrledoff();
		return 1;
	}
}
 
typedef int int16;
#define	SBREAK		0xDF	  // Byte Opcode for Soft Break
#define	HBREAK		0xE7	  // Byte Opcode for Hard Break
#define	LBREAK		0xEF	  // Byte Opcode for Line Break

#define	J	0x50
#define	R	0x60
#define	C	0x70
#define	V	0xA0

const unsigned char opsize[]	=	{
								1|0,3|0,1|0,1|0, 1|0,1|0,2|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,2|0,1|0,
								2|J,3|0,1|0,1|0, 1|0,1|0,2|0,1|0,
								2|J,1|0,1|0,1|0, 1|0,1|0,2|0,1|0,
								2|J,3|0,3|0,1|0, 1|0,1|0,2|0,2|0,
								2|J,1|0,3|0,1|0, 1|0,1|0,2|0,1|0,
								2|J,3|0,3|0,1|0, 1|0,1|0,2|0,1|0,
								2|J,1|0,3|0,1|0, 1|0,1|0,2|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|0,1|0,1|0,1|0, 1|0,1|0,1|0,1|0,
								1|R,1|0,3|J,3|J, 2|0,1|0,2|0,4|J,
								1|R,1|R,3|J,2|0, 1|0,3|C,2|0,4|C,
								1|R,1|0,3|J,1|0, 2|0,1|0,2|0,1|V,
								1|R,1|0,3|J,1|0, 1|0,8|0,2|0,1|V,
								1|R,1|0,3|J,1|0, 2|0,1|0,2|0,1|V,
								1|R,1|J,3|J,1|0, 1|0,7|0,2|0,1|V,
								1|R,1|0,3|J,1|0, 2|0,1|0,2|0,1|0,
								1|R,1|0,3|J,1|0, 1|0,9|0,2|0,1|V
							};

const unsigned char isize[]	= {
								0,0,0,0, 0,0,0,0, 0,1,0,0, 0,0,0,0,
								0,0,0,0, 0,0,0,0, 0,1,0,0, 0,0,0,0,
								0,1,1,1, 0,0,0,0, 0,1,1,1, 0,0,0,0,
								0,0,0,0, 2,2,2,0, 0,1,0,0, 0,0,0,0,
								0,0,0,0, 0,0,2,0, 0,0,0,0, 0,0,2,0,
								0,0,0,0, 0,0,2,0, 0,0,0,0, 0,0,2,0,
								0,0,0,0, 1,3,2,0, 0,0,0,0, 1,3,2,0,
								2,2,2,2, 2,2,0,2, 0,0,0,0, 1,1,2,0,
								0,0,0,0, 0,0,2,0, 0,0,0,0, 0,0,2,0,
								0,0,0,0, 0,0,2,0, 0,0,0,0, 0,0,2,0,
								0,0,0,0, 0,0,2,0, 0,0,0,0, 0,0,2,0,
								0,0,0,0, 0,0,2,0, 0,0,0,0, 0,0,2,0,
								0,0,0,0, 1,0,0,0, 0,0,0,2, 1,0,0,0,
								0,0,0,0, 1,0,0,0, 0,0,0,0, 1,0,0,0,
								0,1,0,1, 1,1,0,0, 0,1,0,0, 1,0,0,0,
								0,0,0,0, 1,0,0,0, 0,1,0,1, 1,1,0,0,
							};

const unsigned char xsize[]	=	{
								3,3,1,1, 2,1,1,1, 3,3,1,1, 2,1,1,1,
								3,3,1,1, 2,1,1,1, 3,3,1,1, 2,1,1,1,
								3,3,1,1, 2,1,1,1, 3,3,1,1, 2,1,1,1,
								3,3,1,1, 2,1,1,1, 3,3,1,1, 2,1,1,1,
								2,2,2,4, 2,2,2,2, 2,2,2,4, 2,2,2,2,
								2,2,2,4, 2,1,2,2, 2,2,2,4, 2,2,2,2,
								2,2,2,4, 2,4,1,2, 2,2,2,1, 2,4,2,2,
								1,1,2,4, 3,1,2,2, 2,2,2,4, 2,1,2,1,
								1,1,2,2, 1,1,1,1, 1,1,2,2, 1,1,1,1,
								1,1,2,2, 1,1,1,1, 1,1,2,2, 1,1,1,1,
								2,2,2,2, 1,1,1,1, 2,2,2,2, 1,1,1,1,
								2,2,2,2, 1,1,1,1, 2,2,2,2, 1,1,1,1,
								1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
								1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
								1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
								1,1,1,1, 1,1,1,4, 1,1,1,1, 1,1,1,1,
							};
							
struct registers RegisterContext;
uint16	dbgbrkreq;
uint16	dbgbrkattn;
uint16	dbgreg_buf;
uint16	tc_combuffer;
uint16	tc_int_stat;
uint16	tc_comattn;
uint16	tc_watchreq;
uint16	tc_watchbuf;
uint16	tc_watchadr;
uint16	tc_keyreq;
uint16	tc_brkvect;
unsigned char tc_brkbyoff, tc_brkbyte;
uint16	tc_keybuf;
uint16	tc_hardbrk;
uint16	tc_doreset;
uint16	tc_show;
uint16	tc_hardbreakhit;
uint16	tc_debugregs;
uint16	pc_fon, pc_nest;
uint16	t_stack;
uint16	pc_init_bp;
uint16	tc_printcon;
uint16	tc_getch;
	
typedef struct {
	char	xpc;
	int	offset;
} Address24;

Address24 bios_end;

long ToPhysical(Address24 *a)
{
	auto long r;

	if(a->offset < 0xe000)
		return a->offset;
	
	r = (((a->xpc + 0xE) << 12) + (a->offset - 0xE000)) & 0x00ffffff;
	return r;
}

char GetXPC(long a)
{
	return ((char)(a >> 12) - 0xE);
}

int GetOFFSET(long a)
{
	return ((int)(a & 0xfff) + 0xE000);
}

/* returns 1 if (a > b) */
int AddrGreater(Address24 a, Address24 b)
{
	auto long aa, bb;

	aa = ToPhysical(&a);
	bb = ToPhysical(&b);

	return (aa > bb);
}
  
/* returns 1 if (a >= b) */
int AddrGreaterEq(Address24 a, Address24 b)
{
	auto long aa, bb;
	
	aa = ToPhysical(&a);
	bb = ToPhysical(&b);

	return (aa >= bb);
}

void AddrSum(Address24 *a, Address24 *b, Address24 *sum)
{
	auto long aa, bb;

	aa = ToPhysical(a);
	bb = ToPhysical(b);

	aa += bb;

	sum->xpc = GetXPC(aa);
	sum->offset = GetOFFSET(aa);
}

/*
 * list of breakpoints - maintain locally, so singlesteping works correctly
 */
typedef struct {
	uint16	offset;
	char		xpc;
	char		opcode;
} Breakpoint;
Breakpoint BreakpointList[MAXBREAKPOINTLIST];

void InitBreakpointList(void)
{
	memset((char *)BreakpointList, 0, sizeof(BreakpointList));
}

char AddBreakpoint(char xpc, uint16 offset, char opcode)
{
	auto char i;
	
	/* search if we allready have a breakpoint there */
	for(i=0; i<MAXBREAKPOINTLIST; i++) {
		if(offset == BreakpointList[i].offset)
			if(xpc == BreakpointList[i].xpc) {
				/* use the old place */
				BreakpointList[i].opcode = opcode;
				return 0;
			}
	}

	/* search for an empty slot */
	for(i=0; i<MAXBREAKPOINTLIST; i++) {
		if(0 == BreakpointList[i].offset)
			if(0 == BreakpointList[i].xpc) {
				BreakpointList[i].opcode = opcode;
				BreakpointList[i].offset = offset;
				BreakpointList[i].xpc = xpc;
				return 0;
			}
	}

	/* no empty slote - return error */
	return 1;
}

/* returns the opcode in the low byte, or -1 on error */
int QueryBreakpoint(char xpc, uint16 offset)
{
	auto char i;

	/* search for the breakpoint */
	for(i=0; i<MAXBREAKPOINTLIST; i++) {
		if(offset == BreakpointList[i].offset)
			if(xpc == BreakpointList[i].xpc) {
				return BreakpointList[i].opcode;
			}
	}

	/* breakpoint was not found - return error */
	return -1;
}

char RemoveBreakpoint(char xpc, uint16 offset)
{
	auto char i;

	/* search for the breakpoint */
	for(i=0; i<MAXBREAKPOINTLIST; i++) {
		if(offset == BreakpointList[i].offset)
			if(xpc == BreakpointList[i].xpc) {
				/* breakpoint found - remove it */
				BreakpointList[i].offset = 0;
				BreakpointList[i].xpc = 0;
				BreakpointList[i].opcode = 0;
				return 0;
			}
	}

	/* breakpoint was not found - return error */
	return 1;
}

/*
 * helper functions for single steping
 */
int16 oplen(unsigned char *code, char setBPflag)
{
   auto int16	mysize;
   
   mysize = 0;
   
	switch (mysize += (opsize[*code] & 0x0F))
   {
		case 7:
			mysize = xsize[*(code+1)];
			break;
		case 8:
		case 9:
			if (isize[*(code+1)])
				mysize = (opsize[*(code+1)] & 0x0F) + isize[*(code+1)];
			else
				mysize = 1;
			break;
	}

   // account for Rabbit opcode recursively
	if (setBPflag)
	{
		switch (code[0])
		{
			case 0xD3:	// internal IO
			case 0xDB:	// external IO
			case 0x76:	// alternate register destination
			mysize += oplen(&code[mysize], setBPflag);
				break;
			default:
				break;
		}
   }
	return (mysize);
}

int16 notcall(unsigned char *code, char *xpc, int *offset)
{
   if ((opsize[*code] & 0xF0) == V)
	   return ( 0 );
   if ((opsize[*code] & 0xF0) == C)
		return ( 0 );
	else return ( isjump ( code, xpc, offset ) );
}

// code - Code Buffer, first byte of buffer is first opcode
// bp	  - Pointer for return of call address
#define LJUMP	0xC7
#define LCALL	0xCF
#define RETI	0x4D
#define LRET	0x45
int16 isjump(unsigned char	*code, char *xpc, int *offset )
{
   auto unsigned char c,						// Distinguishing Byte of OpCode
		   		     ixy;					// Indexing Flag
	auto long tempaddr;
   auto int temp;

   // Check for Program Redirection in Extended Instructions
   // OpCode is an F80 long jump or long call
   if ((*code==LJUMP ||*code==LCALL))
   {
      *xpc = *(code+3);
      *offset = *(code + 1) + (*(code + 2) << 8); //*(int16 *)(code+1);
		return ( 1 );
   }
	if (*code == 0xED)
   {
		c = *(code+1);
		if (c != RETI && c != LRET)
			return ( 0 );
		c = 1|R;					// Treat RETI and RETN as RET
	}
   else
      // OpCode is not in Extended Set
		c = opsize[*code];

   // Check for Indexing Instructions
   if ((c & 0x0F) == 8 || (c & 0x0F) == 9)
   {
	   ixy = (c & 0x0F) - 7;
		c = opsize[*++code];
   }
   else
	   ixy = 0;

   // Determine if OpCode can Alter Program Flow (Decoded in Table)
	switch ( c & 0xF0 )
   {
      // OpCode is Computational : No Change of Flow
		case	0	:
			return (0);

      // OpCode is a Jump : Check for Register Indirect
		case	J	:
			switch ( c & 0x0F )
         {
				case	1	:
               *xpc = RegisterContext.PC_XPC;
               //*offset = bp->getOffset();
					switch ( ixy )
               {
						case	0	:
							*xpc = 0;
							*offset = RegisterContext.HL;
							break;
						case	1	:
							*xpc = 0;
							*offset = RegisterContext.IX;
							break;
						case	2	:
							*xpc = 0;
							*offset = RegisterContext.IY;
							break;
					}
					return (1);
            case	2	:								//	conditional
               *xpc = RegisterContext.PC_XPC;
               *offset = RegisterContext.PC_OFFSET + *((char *)(code+1)) + 2;
					return (1);
         }
      case	C	:
         *xpc = RegisterContext.PC_XPC;
         *offset = *(code + 1) + ((*(code + 2)) << 8); //*((uint16 *)(code+1));
			break;
		case	R	:
			if((*(code+1)==LRET && *code==0xED ))
         {
         	/*** TEST THIS!!! ***/
				ReadBlock(3, 0, RegisterContext.SP, (char *)&tempaddr);
				//&(*bp)) = tempbuf;
				*xpc = GetXPC(tempaddr);
				*offset = GetOFFSET(tempaddr);
			}
			else
         {
            *xpc = RegisterContext.PC_XPC;
            //*offset = bp->getOffset();
            temp = *offset;
				ReadBlock(2, 0, RegisterContext.SP, (char *)&temp);
            *xpc = *xpc;
            *offset = temp;
			}
			break;
      case	V	:
			if (*code == 0xf7)
         { 	//	rst 30 is a special call
            *xpc = *(code+1);
            *offset = *(code + 2) + (*(code + 3) << 8); //*(int16 *)(code+2);
         }
			else
         {
            *xpc = RegisterContext.PC_XPC;
            *offset = (*code) & 0x38;
			}
			break;
   }
	return (1);
}

void ReadRegisters(void)
{
   ReadBlock(26, 0, dbgreg_buf, (char *)&RegisterContext);
#if 0
   printf("Reg: AF=%04x, HL=%04x, DE=%04x, BC=%04x\n",
 		RegisterContext.AF, RegisterContext.HL, RegisterContext.DE, RegisterContext.BC);
#endif
   RegisterContext.PC_XPC = RegisterContext.XPC;
   RegisterContext.PC_OFFSET = RegisterContext.PC;
}

void WriteRegisters(void)
{
   RegisterContext.XPC = RegisterContext.PC_XPC;
   RegisterContext.PC  = RegisterContext.PC_OFFSET;
   WriteBlock(26, 0, dbgreg_buf, (char *)&RegisterContext);
}

void SingleStep(unsigned char opcode, char isNext)
{
	auto long time;
	auto int16         bflag;			         //	branch flag, set if next instruction is a branch
	auto int16         bpflag;			      //	break point flag, set if current instruction is a branch
	auto Address24     bpPC;				      //	break point PC, the current PC
	auto Address24     addr1;			         //	address to the following opcode
	auto Address24     addr2;			         //	address (offset) of the following opcode
	auto Address24     addr3;			         //	address of the following opcode (for special instruction
	auto unsigned char data1,data2;	         //	data1 saves the following opcode,
									            //	data2 save the opcode at branch
	auto unsigned char datar;
	auto unsigned char databp;			      //	opcode at current PC
	auto unsigned char code[16];//	holds current instruction & first opcode of next
	auto int16		     NonRet;
	auto char ch;
	auto char zero;
   auto char one;
   
   zero = 0;
   one = 1;

   ReadRegisters();
   
	ReadBlock(sizeof(code),RegisterContext.PC_XPC,RegisterContext.PC_OFFSET,code); //	get the context, this breakpt and more
	databp = *code;										   //	this is the current opcode
	bpPC.xpc = RegisterContext.PC_XPC;								// break point PC
	bpPC.offset = RegisterContext.PC_OFFSET;
	bpflag = (databp == HBREAK || databp == SBREAK);// hard soft break
	if(!isNext && databp == LBREAK)
   {				   //	next opcode is a soft breakpoint
		SingleStep(opcode, !isNext); 	   //	change that to the new opcode
		return;
	} /* ignore F7 on rst 28 */
	if (bpflag) {												   //	if the current opcode is a break
		*code = (char)QueryBreakpoint(RegisterContext.PC_XPC, RegisterContext.PC_OFFSET);   //	remove existing break (source level)
		if(*code != -1)
	      WriteBlock(1,RegisterContext.PC_XPC, RegisterContext.PC_OFFSET, code); 
	}

	//	first, assume the current opcode is "normal"
	addr2.offset = oplen(code,1);					   //	addr2 is the offset to the following opcode
	addr2.xpc = 0;

	addr1.xpc = RegisterContext.PC_XPC;
	addr1.offset = RegisterContext.PC_OFFSET;
	AddrSum(&addr1,&addr2,&addr1);

	data1 = *(code + addr2.offset);					   //	data1 is the following opcode
	//	then check for non-returning cases (for which we should not set the
	//	break following the opcode

	if((NonRet = 0) != 0)		   //	check if instruction is special (doesn't return)
																   //	addr3 returns the actual destination
	{                                    			   // set break following instruction
		if (AddrGreater(addr3, bios_end))
      {
			ReadBlock(1,addr3.xpc,addr3.offset,&data1);		     // save the new op code
			WriteBlock(1,addr1.xpc=addr3.xpc,addr1.offset=addr3.offset,&opcode); // if st4. etc
		}
	}
	else
   {
		if (AddrGreater(addr1, bios_end))
			WriteBlock(1,addr1.xpc,addr1.offset,&opcode);			//	traditional stuff, opcode already saved
	}

	//	now check for branches
	if (!NonRet)
   {           						//	if NonRet is set, the only possible
											//	location for break point is determined
											//	therefore process only if NonRet is not
											//	set
		if (isNext)										// are we skipping calls?
			//	notcall returns 1 if the instruction alters execution flow, but
			//	excludes simple function calling (special function calls are already
			//	handled above by isnonret())
			bflag = notcall ( code,&addr2.xpc,&addr2.offset );		// addr2 correct for st4. etc.
		else												//	not skipping calls
			//	isjump returns 1 if the instruction alters execution flow in anyway,
			//	including calling
			bflag = isjump ( code,&addr2.xpc,&addr2.offset );		/* returns toaddress in addr2 */
		}
	else bflag = 0;

	if (bflag && (AddrGreaterEq(addr2, bios_end)))
   {
		ReadBlock(1, addr2.xpc,addr2.offset, &data2);		//	save opcode at the branch destination
		WriteBlock(1, addr2.xpc,addr2.offset, &opcode);	// breakpoint the destination
		ReadBlock(1, addr2.xpc,addr2.offset, &datar);		//	read back breakpoint, make sure is writable
		bflag = (datar == opcode);				//	remember whether breakpoint is actually set
	}
	else bflag = 0;

	//	execute till the break point and return the register
   //ReadRegisters();
   //WriteRegisters();

   //WriteBlock(1, 0, dbgbrkreq, &one);
   WriteBlock(1, 0, dbgbrkattn, &zero);
	ch = 0;
	time = MS_TIMER;
	time += 1024;
	while(0 == ch) {
		if(MS_TIMER > time) {
			/* error */
			printf("Error executing target.\n");
			return;
		}
		ReadBlock(1, 0, dbgbrkattn, &ch); // EXECUTE
		if(ch == 0)
			sleep(100);
	}
   ReadRegisters();
	if (bpflag && (AddrGreater(bpPC, bios_end)))
		WriteBlock(1, bpPC.xpc, bpPC.offset, &databp);	// restore old breakpoint
	if (bflag && (AddrGreaterEq(addr2, bios_end)))
		WriteBlock(1, addr2.xpc, addr2.offset, &data2);	// restore op at branch to loc
	if (AddrGreater(addr1, bios_end))
		WriteBlock(1, addr1.xpc, addr1.offset, &data1);	// restore op following inst
}

void SingleStepInit(void)
{
	auto int16 j, k;
	auto uint16 table_loc, offset;
   auto unsigned short symtable;

	if(ExecStepInit) {
	
		bios_end.xpc = 0;
		bios_end.offset = (int)BIOSLength;
	
		// Fetch items from 3-table
		ReadBlock(2, 0, 3, (char *)&j);

		ReadBlock(2, 0, j + 41, (char *)&k);			   // comptr in target
		//printf("K == 0x%04x\n",k);
		ReadBlock(2, 0, k, (char *)&tc_combuffer);	// pointer to combuffer
		ReadBlock(2, 0, k + 2, (char *)&dbgreg_buf);	// register buffer pointer

		tc_int_stat = dbgreg_buf + 26;
		dbgbrkreq = tc_int_stat + 2;
		tc_comattn = k + 4;									// attention bytes
		dbgbrkattn = tc_comattn + 1;
		tc_watchreq = dbgbrkattn + 2;

		ReadBlock(2, 0, tc_watchreq + 2, (char *)&tc_watchbuf);

		tc_watchadr = tc_watchreq + 4;
		tc_keyreq = tc_watchadr + 2;
		
		ReadBlock(2, 0, j + 31, (char *)&tc_brkvect);	// address of rst28 switch
		ReadBlock(1, 0, j + 33, &tc_brkbyoff);	// c9 etc. byte to disable rst 38
		ReadBlock(1, 0, j + 34, &tc_brkbyte);	// c3 etc. byte to enable rst 38

		tc_printcon = j + 35;
		tc_getch = j + 36;
		
		tc_keybuf = tc_keyreq + 1;
		tc_hardbrk = tc_keybuf + 1;
		tc_doreset = tc_hardbrk + 1;
		tc_show = tc_doreset + 1;
		tc_hardbreakhit = tc_show + 4;
		tc_debugregs = tc_hardbreakhit + 4;

		/* Replaced with marker at the top of the BIOS
		 *
       * ReadBlock(2, 0, j + 17, (char *)&symtable);
       * // Get entry point to dkBPInit from symbol table
       * //ReadBlock(2, 0, symtable + 310, (char *)&pc_init_bp);
       * ReadBlock(2, 0, symtable + 194, (char *)&pc_init_bp);
       */
      ReadBlock(2, 0, 10, (char *)&pc_init_bp);

  	   ReadBlock(2, 0, 3, (char *)&table_loc);
		offset = table_loc + 29;
		ReadBlock(sizeof(t_stack), 0, offset, (char *)&t_stack );

		ReadRegisters();
		
		ExecStepInit = 0;

		InitBreakpointList();
	}
}

/*******
 * End of helper functions for single step
 *******/

void dtp_singlestep(void)
{
	SingleStepInit();
	SingleStep(HBREAK, packet_data[0]);

	memcpy(packet_data, (char *)&RegisterContext, 26);

	packet_data[26] = RegisterContext.PC_XPC;
	packet_data[27] = (char)(RegisterContext.PC_OFFSET & 0xff);
	packet_data[28] = (char)((RegisterContext.PC_OFFSET >> 8) & 0xff);

	StepTarget = 1;
	dtp_respondack(29);
	
	control_state = CONTROL_STEADY;
}

void SetFirstBreakpoint(void)
{
	auto DTPReadTable *pkt;
	static const uint16 one = 1;

	pkt = (DTPReadTable *)packet_data;
	packet_header.type = DTP_READTABLE;

	pkt->tc_brkreq = dbgbrkreq;
	pkt->tc_brkattn = dbgbrkattn;
	pkt->tc_reg_buf = dbgreg_buf;
	pkt->tc_combuffer = tc_combuffer;
	pkt->tc_int_stat = tc_int_stat;
	pkt->tc_comattn = tc_comattn;
	pkt->tc_watchreq = tc_watchreq;
	pkt->tc_watchbuf = tc_watchbuf;
	pkt->tc_watchadr = tc_watchadr;
	pkt->tc_keyreq = tc_keyreq;
	pkt->tc_brkvect = tc_brkvect;
	pkt->tc_brkbyoff = tc_brkbyoff;
	pkt->tc_brkbyte = tc_brkbyte;
	pkt->tc_keybuf = tc_keybuf;
	pkt->tc_hardbrk = tc_hardbrk;
	pkt->tc_doreset = tc_doreset;
	pkt->tc_show = tc_show;
	pkt->tc_hardbreakhit = tc_hardbreakhit;
	pkt->tc_debugregs = tc_debugregs;

	WriteBlock(2, 0, tc_comattn, (char *)&one);
	WriteBlock(2, 0, dbgbrkreq, (char *)&one);
	
	dtp_respondack(sizeof(DTPReadTable));
#ifdef DBG_DBGMODE
	printf("\nTarget ready for debugging!\n");
#endif
}

int PollTarget(char mask)
{
	auto int pollval, sum;
	auto long timeout;
	static int retry;

#GLOBAL_INIT {
	retry = 10;
}
	serBclose();
	serBopen(main_id.prog_baud);
	FlushSerial();
	serBputc(0xa0);
	sleep(10);

	Blink();
	pollval = serBgetc();

	if(pollval == -1) {
		if (retry-- < 0) {
			retry = 10;
			return -1;
		}		
		else {			
#ifdef DBG_DBGMODE
			printf("failed to get poll value: %d\n", retry);
#endif
			timeout = MS_TIMER;
			timeout += 100;
			while(pollval == -1){			
				if(MS_TIMER > timeout) {
					return 0;
				}
				else {
					sleep(10);
					pollval = serBgetc();
				}
			}
		}
	}

	sum = serBgetc();
	if(sum == -1) {
		if(retry-- < 0) {
			retry = 10;
			return -1;
		}		
		else {
#ifdef DBG_DBGMODE
			printf("failed to get sum: %d\n", retry);
#endif
			timeout = MS_TIMER;
			timeout += 100;
			while(sum == -1){
				if(MS_TIMER > timeout) {
					return 0;
				}
				else {
					sleep(10);
					sum = serBgetc();
				}
			}
		}		
	}
	serBclose();
	retry = 10;
	mask = pollval & mask;
	return mask;
}

/* init the polling structures */
void dtp_pollinit(void)
{
	RunTarget = 0;
	StepTarget = 0;	
	DLFlags |= DL_DEBUGMODE;
	
	poll_timeout = MS_TIMER;
	poll_timeout += POLL_INTERVAL;
	dtp_respondack(0);
}

/*
 * poll the target. return 1 if packet is to be sent, 0 otherwise
 */
int dtp_poll(void)
{
	auto int pollval;
	auto uint16	count;
	auto char	zero, ch;
	
	zero = 0;

	if(DLFlags & DL_NOPOLL)
		return 0;

	if(PollsPending > MAX_PENDING_POLLS)
		return 0;

	if(combufpending)
		return 0;
		
	if(MS_TIMER > poll_timeout) {
		/* reset the timeout - should be after the work for the poll? */
		poll_timeout = MS_TIMER;
		poll_timeout += POLL_INTERVAL;

		pollval = PollTarget(P_BREAK|P_COM|P_WATCH|P_GETKEY);
		
		if(pollval > 0) {
			/* first black our buffer */
			memset(poll_newpacket, 0, sizeof(DTPPollResults));
			
			// not sure what this flag does, but should be cleared if at breakpoint
			if(pollval & P_BREAK)
   		   if(WriteBlock(1, 0, pc_fon, &zero ))
   		   	return 0; // error
   		   
			if(pollval & P_COM)
			{
				if(ReadBlock(1, 0, tc_printcon, &ch)) // check the flag
					return 0; // error

				if(ReadBlock(2, 0, tc_combuffer, (char *)&count))
					return 0; // error
				if(count > COMBUFSIZE)
					count = COMBUFSIZE;
				if (count)
					if(ReadBlock(count, 0, tc_combuffer+4, poll_newpacket->combuf))
						return 0; // error
				// Handshake
				if(WriteBlock(1, 0, tc_comattn, &zero))
					return 0; // error

				switch(ch) {
				case 0:
					/* normal state - do nothing */
					break;
					
				case 1:
					/* normal printf stuff - send to console as well */
					memcpy(combuf, poll_newpacket->combuf, COMBUFSIZE);
					combufpending = 1;
					combufoffset = 0;
					combuflen = strlen(combuf);
					SendToGetchar = 1;
					break;

				case 2:
					/* binary data - send to console only */
					ch = poll_newpacket->combuf[0]; /* length */
					memcpy(combuf, poll_newpacket->combuf + 1, ch);
					strcpy(poll_newpacket->combuf, "[binary data]");
					combufpending = 1;
					combufoffset = 0;
					combuflen = ch;
					SendToGetchar = 1;
					break;

				default:
					/* ERROR - UNKNOWN COM TYPE! */
					/* just pass back to DC for now */
					break;
				}
			}
			
			// read registers from target
			ReadRegisters();
   		poll_newpacket->regs.AF = RegisterContext.AF;
   		poll_newpacket->regs.BC = RegisterContext.BC;
   		poll_newpacket->regs.DE = RegisterContext.DE;
   		poll_newpacket->regs.HL = RegisterContext.HL;
   		poll_newpacket->regs.SP = RegisterContext.SP;
   		poll_newpacket->regs.PC = RegisterContext.PC;
   		poll_newpacket->regs.XPC = RegisterContext.XPC;
   		poll_newpacket->regs.IX = RegisterContext.IX;
   		poll_newpacket->regs.IY = RegisterContext.IY;
   		poll_newpacket->regs.af = RegisterContext.af;
   		poll_newpacket->regs.bc = RegisterContext.bc;
   		poll_newpacket->regs.de = RegisterContext.de;
   		poll_newpacket->regs.hl = RegisterContext.hl;
			poll_newpacket->regs.PC_XPC = RegisterContext.PC_XPC;
			poll_newpacket->regs.PC_OFFSET = RegisterContext.PC_XPC;

			if(pollval & P_BREAK)
			{
				// get interrupt status from target
				if(ReadBlock(1, 0, tc_int_stat, (char *)poll_newpacket->int_stat))
					return 0; // error

		   	// read 256 bytes starting at PC for use by disassembly window
			   if(ReadBlock((char)ASMBUFSIZE, RegisterContext.PC_XPC, RegisterContext.PC_OFFSET, poll_newpacket->asmbuf))
			   	return 0; // error
	
   			// read 16 bytes starting at SP for use by stack window
	   		if(ReadBlock(STACKBUFSIZE, 0, RegisterContext.SP, poll_newpacket->stackbuf))
	   			return 0; // error
	   	}

	   	// set the flags
	   	poll_newpacket->flags = (char)pollval;
	   	
   		// Send flags to PC along with contents of register, assembly, and stack
	   	// buffer as well as anything read from combuffer.
	   	
			// send packet contents if the new packet differs from the old packet, if
			// the packet has stdio contents (P_COM set) and the target is not at a
			// breakpoint (P_BREAK cleared), if the target was running and just hit a breakpoint,
			// or if the target was single stepping and hit a breakpoint.  In other words,
			// don't send information if the target is sitting at a breakpoint.
	   	if((0 != memcmp(poll_newpacket, poll_oldpacket, sizeof(DTPPollResults))) ||
	   	   ((pollval & P_COM) && !(pollval & P_BREAK)) || (RunTarget && (pollval & P_BREAK)) ||
	   	    (StepTarget && (pollval & P_BREAK))) {
		   	if(RunTarget && (pollval & P_BREAK)) {
		   		/* target was running and hit a breakpoint */
		   		RunTarget = 0;
		   	}
		   	if(StepTarget && (pollval & P_BREAK)) {
		   		/* target was single stepping and hit a breakpoint */
		   		StepTarget = 0;
		   	}
		   	if(RunTarget && !(pollval & (P_COM)))
		   		return 0; // only send P_COM when running

	   		/* we have new data - send it, and make a backup copy for comparing later */
	   		memcpy(poll_oldpacket, poll_newpacket, sizeof(DTPPollResults));
	   		packet_header.type = DTP_POLL;

		   	/* Are we only polling in the background, for stdio?
		   	   if so, we are done - don't actually send it */
	   		if(BackgroundPoll)
	   			return 1;

	   		dtp_respondack(sizeof(DTPPollResults));
	   		PollsPending++;
	   		return 1;
	   	}		
		}
		else if(pollval == -1) {
			packet_header.type = DTP_POLL;

			if(BackgroundPoll)
				return -1;
				
			dtp_respondnak(0);
			return -1;
		}
	}
	
	return 0;
}

int dtp_targetexecute(struct registers *reg)
{
		auto char ch;
		auto char zero;
		auto long timer;
		zero = 0;
		
		// Let target execute
      // Make sure target is requesting break attention, if not error
		ReadBlock(1, 0, dbgbrkattn, &ch);
		if (ch == 0) {
			/* ERROR */
			return 1;
		}
			
		// write value of registers to target (new pc and sp)
	   WriteBlock(26, 0, dbgreg_buf, (char *)reg);
		// set brkattn to zero so that target will start running
   	WriteBlock(1, 0, dbgbrkattn, &zero);

      // give the target 1 second to execute, continually polling until the
      // target raises brkattn.
     	ch = 0;
		timer = MS_TIMER;
		timer += 1000;
		while (ch == 0)
      {
      	sleep(10);
			ReadBlock(1, 0, dbgbrkattn, &ch);
			if (ch == 0)
         {
         	if(MS_TIMER > timer) {
         		/* timeout! */
         		return 1;
         	}
			}
      }
		// read the new register values
	   ReadBlock(26, 0, dbgreg_buf, (char *)reg);

		return 0;
}

/*
 * Start the program running
 */
int dtp_startprog(void)
{
	auto DTPRunProg *pkt;
	auto char	ch;
	auto char 	PC_XPC;
	auto int		PC_OFFSET;
	auto char zero, one;
	auto struct registers ireg;

	zero = 0;
	one = 1;

	pkt = (DTPRunProg *)packet_data;

	if(pkt->run)
   {
#ifdef DBG_DBGMODE
   	printf("Starting target running freely.\n");
#endif
		PC_XPC = RegisterContext.PC_XPC;					//	memorize PC
		PC_OFFSET = RegisterContext.PC_OFFSET;
		SingleStep(HBREAK, 0); // 0 == false
		if ((PC_XPC != RegisterContext.PC_XPC) || (PC_OFFSET != RegisterContext.PC_OFFSET))
      {	//	if PC has changed, proceed
			//TCGo(&reg);
			ReadBlock(1, 0, dbgbrkattn, &ch);
			if (ch == 0) {
				/* ERROR */
				return 1;
			}
	  		WriteBlock(26, 0, dbgreg_buf, (char *)&RegisterContext);
			//TCStartRunning();
			WriteBlock(1, 0, dbgbrkattn, &zero);
		}
		RunTarget = 1;
		return 0;
	}
   else
   {
		// Clear Breakpoint List

		//ReadRegisters();
		
		// Setup to run bios breakpoint initialization routine.
      // Read current value of registers
	   //ReadBlock(26, 0, dbgreg_buf, (char *)&RegisterContext);
	   ReadBlock(26, 0, dbgreg_buf, (char *)&ireg);

		// set value of sp to temporary stack (probably does not need to be done).
		//ireg.SP = t_stack;
		// firmware will need some way of getting pc_init_bp address
		ireg.PC = pc_init_bp;

		if(dtp_targetexecute(&ireg)) {
			return 1;
		}

		// Will these need to be sent back to Dynamic C ?!?!?
      // probably not as they are used in TCStmtByLine and TCEndLine, both
      // of whose functionality will be moved to firmware.  These values will
      // be stored and used by the firmware.
		pc_fon = ireg.HL;
		pc_nest = ireg.DE;

		// Set brkreq flag so that target will stop at first breakpoint in main.
		WriteBlock(1, 0, dbgbrkreq, &one);

		RegisterContext.PC = pkt->PC;
		if(dtp_targetexecute(&RegisterContext)) {
			return 1;
		} else {
			return 0;
		}
   }	
}

void dtp_stopprog(void)
{
	auto char one;
	one = 1;
	
	WriteBlock(1, 0, tc_hardbrk, &one);
	one = tc_brkbyte;
	WriteBlock(1, 0, tc_brkvect, &one);		// force RST28

	dtp_respondack(0);
}

void dtp_csinglestep(void)
{
	auto char ch;
	auto uint16 zero, one;
	auto char PC_XPC;
	auto int PC_OFFSET;
	auto DTPCSingleStep *pkt;
	pkt = (DTPCSingleStep *)packet_data;

	zero = 0; one = 1;
	
   ReadRegisters();
	if(pkt->isNext)
   {
   	WriteBlock(2, 0, pc_nest, (char *)&zero);
      WriteBlock(2, 0, pc_fon,  (char *)&one);
   }
   WriteBlock(1, 0, dbgbrkreq, (char *)&((char)one));
   PC_OFFSET = RegisterContext.PC_OFFSET;
   PC_XPC = RegisterContext.PC_XPC;

	SingleStep(HBREAK, 0); // 0 == false
   if((PC_XPC != RegisterContext.PC_XPC) | (PC_OFFSET != RegisterContext.PC_OFFSET))
   {
   	ReadBlock(1, 0, dbgbrkattn, &ch);
      if(ch != 1) {
      	/*error*/
      	dtp_respondnak(0);
      	return;
      }
		WriteBlock(26, 0, dbgreg_buf, (char *)&RegisterContext);
      WriteBlock(1, 0, dbgbrkattn, (char *)&zero);
   }
	
	StepTarget = 1;
	dtp_respondack(0);
}

void dtp_setbreakpoint(void)
{
	auto char oldopcode;
	auto DTPSetBreakpoint *pkt;
	auto DTPAckBreakpoint *newpkt;
	auto char err;
	pkt = (DTPSetBreakpoint *)packet_data;
	newpkt = (DTPAckBreakpoint *)packet_data;

#ifdef DBG_DBGMODE
	printf("Adding breakpoint at: %02x:%04x\n", pkt->xpc, pkt->offset);
#endif
	
	/* change the old opcode to a breakpoint */
	err = ReadBlock(1, pkt->xpc, pkt->offset, &oldopcode);
	err |= WriteBlock(1, pkt->xpc, pkt->offset, &pkt->newopcode);

	/* add the breakpoint to our internal list */
	err |= AddBreakpoint(pkt->xpc, pkt->offset, oldopcode);

	/* send the old opcode back */
	newpkt->oldopcode = oldopcode;

	if(err)
		dtp_respondnak(sizeof(DTPAckBreakpoint));
	else
		dtp_respondack(sizeof(DTPAckBreakpoint));
}

void dtp_removebreakpoint(void)
{
	auto DTPRemoveBreakpoint *pkt;
	pkt = (DTPRemoveBreakpoint *)packet_data;

#ifdef DBG_DBGMODE
	printf("Removing breakpoint at: %02x:%04x\n", pkt->xpc, pkt->offset);
#endif

	RemoveBreakpoint(pkt->xpc, pkt->offset);
	if(WriteBlock(1, pkt->xpc, pkt->offset, &pkt->opcode))
		dtp_respondnak(0);
	else
		dtp_respondack(0);
}

/*
 * A list of watches
 */
typedef struct {
	char 			xpc;
	uint16		offset;
} WatchList;

WatchList Watches[TCP_MAX_WATCHES];

void InitWatchList(void)
{
#GLOBAL_INIT {
	memset((char *)&Watches[0], 0, sizeof(Watches));
}
}

void dtp_execwatch(void)
{
	auto char WatchActive, one;
	auto long timer;
	auto int bufsize;
	auto DTPExecWatch *pkt;
	pkt = (DTPExecWatch *)packet_data;

	one = 1;
	bufsize = pkt->bufsize;

	if(bufsize > MAXWATCHBUFSIZE)
		bufsize = MAXWATCHBUFSIZE;
	
	timer = MS_TIMER;
	timer += 1000;
	while(MS_TIMER < timer) {
	   if((WatchActive = PollTarget(P_WATCH)) == P_WATCH)
	   	break;
	   sleep(10);
	}

	if (WatchActive)
   {
   	//error time out
   	dtp_respondnak(0);
   	return;
   }
	else
   {
		//	watch is cleared, we can now instruct the target to execute the code
		//	tell the target where the watch code is
		WriteBlock(2, 0, tc_watchadr, (char *)&(pkt->code));
		//	set the semaphore and let the target run the code
		WriteBlock(1, 0, tc_watchreq, &one);

		timer = MS_TIMER;
		timer += 1000;
		while(MS_TIMER < timer) {
		   if((WatchActive = PollTarget(P_WATCH)) == P_WATCH)
		   	break;
		   sleep(10);
		}
		if (WatchActive)
      {
      	//error time out
      	dtp_respondnak(0);
      	return;
      }
   }
	ReadBlock(bufsize, 0, tc_watchbuf, packet_data);	//	read the result back
	dtp_respondack(bufsize);
}

void dtp_addwatch(void)
{
	auto int i;

	for(i=0; i<TCP_MAX_WATCHES; i++) {
		if((0 == Watches[i].xpc) && (0 == Watches[i].offset)) {
			Watches[i].xpc = packet_data[0];
			Watches[i].offset = packet_data[1] + (packet_data[2] << 8);
			if(WriteBlock(packet_data[3], Watches[i].xpc, Watches[i].offset, packet_data + 4))
				dtp_respondnak(0);
			else
				dtp_respondack(0);
			return;	
		}
	}

	/* no room */
	dtp_respondnak(0);
}

void dtp_deletewatch(void)
{
	auto int i,j;
	auto char xpc;
	auto uint16 offset;

	xpc = packet_data[0];
	offset = packet_data[1] + (packet_data[2] << 8);

	if((0 == xpc) && (0 == offset)) {
		/* if address == 0, delete from the end of the list */
		for(i=TCP_MAX_WATCHES - 1; i>=0; i--) {
			if((0 != Watches[i].offset) || (0 != Watches[i].xpc)) {
				Watches[i].xpc = 0;
				Watches[i].offset = 0;
				dtp_respondack(0);
				return;
			}
		}
	} else {
		for(i=0; i<TCP_MAX_WATCHES; i++) {
			if((xpc == Watches[i].xpc) && (offset == Watches[i].offset)) {
				j = i + 1;
				memcpy((char *)&Watches[i], (char *)&Watches[j], TCP_MAX_WATCHES - j);
				Watches[TCP_MAX_WATCHES - 1].xpc = 0;
				Watches[TCP_MAX_WATCHES - 1].offset = 0;

				dtp_respondack(0);
				return;
			}
		}
	}

	dtp_respondnak(0);
}

void dtp_deleteallwatches(void)
{
	auto int i, activecount;

	activecount = 0;	
	for(i = 0; i < TCP_MAX_WATCHES; i++)
	{
		if(Watches[i].offset || Watches[i].xpc)
			activecount++;
		Watches[i].xpc = 0;
		Watches[i].offset = 0;
	}
	memcpy(packet_data, &activecount, sizeof(activecount));
	dtp_respondack(sizeof(activecount));
}

int RunWatch(uint16 offset, int bufsize, char *buf)
{
	auto long timer;
	auto char WatchActive, one, zero;
	one = 1;
	zero = 0;
	
	if(bufsize > MAXWATCHBUFSIZE)
		bufsize = MAXWATCHBUFSIZE;
	
	timer = MS_TIMER;
	timer += 1000;
	while(MS_TIMER < timer) {
	   if((WatchActive = PollTarget(P_WATCH)) != P_WATCH)
	   	break;
	   sleep(10);
	}

	if (WatchActive)
   {
   	//error time out
   	return 1;
   }
	else
   {
		//	watch is cleared, we can now instruct the target to execute the code
		//	tell the target where the watch code is
		WriteBlock(2, 0, tc_watchadr, (char *)&offset);
		//	set the semaphore and let the target run the code
		WriteBlock(1, 0, tc_watchreq, &one);

		timer = MS_TIMER;
		timer += 1000;
		while(MS_TIMER < timer) {
//		   if((WatchActive = PollTarget(P_WATCH | P_COM)) != P_WATCH)
		   WatchActive = PollTarget(P_WATCH | P_COM);
			if(WatchActive == 0)
		   	break;
			if(WatchActive & P_COM == P_COM)
				if(WriteBlock(1, 0, tc_comattn, &zero))
					return 1;			
		   sleep(10);
		}
		if (WatchActive)
      {
      	//error time out
      	return 1;
      }
   }
	ReadBlock(bufsize, 0, tc_watchbuf, buf);	//	read the result back

	return 0;
}

void dtp_sendchar(void)
{
	auto char ch, zero;
	zero = 0;
	ch = (char)packet_data[0];
	if(WriteBlock(1, 0, tc_keybuf, (char *)&ch))
		dtp_respondnak(0);
	//	clear request
	if(WriteBlock(1, 0, tc_keybuf-1, &zero))
		dtp_respondnak(0);
	dtp_respondack(0);
}


/* type encoding */
#define	VAR		0
#define	ARRAY		1
#define	POINTER	2
#define	FUNC		3

#define T_CHAR		1

/* watch states */
#define WATCH_INIT	0
#define WATCH_HEADER	1
#define WATCH_COUNT	2
#define WATCH_RUN		3
#define WATCH_SEND	4
#define WATCH_ERROR	5
#define WATCH_FOOTER	6
int dtp_runallwatches(void)
{
	static int cur_watch;
	static char watch_count, watchdone;
	auto char *watchbuf;
	static int offset, length;
	auto char type;
	auto uint32 mods;
	static char send_flag;
	auto int i;

#GLOBAL_INIT {
	watch_state = 0;
}

	switch(watch_state) {
	case WATCH_INIT:
		watch_count = watchdone = 0;
		send_flag = 1;
		for(i=TCP_MAX_WATCHES-1; i>=0; i--) {
			if((Watches[i].xpc != 0) || (Watches[i].offset != 0))
				watch_count++;
		}
		watch_state = WATCH_HEADER;
		cur_watch = TCP_MAX_WATCHES - 1;
		packet_header.version = htonl(DC_ONE_PROTOCOL);
		packet_header.type = htons(DTP_RUNALLWATCH|DTP_ACK);
		packet_header.seq = htons(seq_number++);
		packet_header.length = htonl(watch_count * sizeof(TCPWatchData) + 2);
		length = sizeof(packet_header);
		offset = 0;
		/* fall through */

	case WATCH_HEADER:
		/* send the header */
		offset += sock_fastwrite(control_s, (char *)&packet_header + offset, length - offset);
		if(offset < length)
			return 1;

		packet_data[0] = watch_count;
		length = 1;
		offset = 0;
		watch_state = WATCH_COUNT;
		/* fall through */

	case WATCH_COUNT:
		/* send the number of watches */
		offset += sock_fastwrite(control_s, (char *)packet_data + offset, length - offset);
		if(offset < length)
			return 1;
			
		watch_state = WATCH_RUN;
		/* fall through */
		
	case WATCH_RUN:
WatchEmptyLoop:
		cur_watch--;
		if(cur_watch < 0) {
			packet_data[0] = 0; // flag error
			offset = 0;
			length = sizeof(packet_footer);
			watch_state = WATCH_FOOTER;
			return 1;
		}
		if((0 == Watches[cur_watch].xpc) && (0 == Watches[cur_watch].offset))
			goto WatchEmptyLoop;
			
		if(RunWatch(Watches[cur_watch].offset, sizeof(TCPWatchData), packet_data)) {
#ifdef DBG_DBGMODE
			printf("Error executing watch!\n");
#endif
			watch_state = WATCH_ERROR;
			length = (watch_count - watchdone) * sizeof(TCPWatchData); offset = 0;
			return 1;
		}

		type = packet_data[1];
		memcpy((char *)&mods,&packet_data[2],4);
		if((type == T_CHAR) && (mods == POINTER)) {
			if(ReadBlock(TCP_MAX_WATCH_STRLEN, packet_data[6], (packet_data[5] << 8) + packet_data[4], packet_data + 8)) {
				/* error */
				watch_state = WATCH_ERROR;
				length = (watch_count - watchdone) * sizeof(TCPWatchData); offset = 0;
				return 1;
			}
		}

		offset = 0;
		length = sizeof(TCPWatchData);		
		watch_state = WATCH_SEND;
		/* fall through */

	case WATCH_SEND:
		offset += sock_fastwrite(control_s, packet_data + offset, length - offset);
		if(offset < length)
			return 1;

		/* done sending watch */
		watchdone++;
		watch_state = WATCH_RUN;
		break;

	case WATCH_ERROR:
		/* send any remaining data, and flag and error at the end */
		offset += sock_fastwrite(control_s, packet_data, length - offset);
		if(offset < length)
			return 1;

		packet_data[0] = 1; // flag error
		offset = 0;
		length = sizeof(packet_footer);
		watch_state = WATCH_FOOTER;
		break;

	case WATCH_FOOTER:
		if(send_flag) {
			if(sock_fastwrite(control_s, packet_data, 1)) {
				send_flag = 0;
			}
			return 1;
		}
		offset += sock_fastwrite(control_s, (char *)&packet_footer + offset, length - offset);
		if(offset < length)
			return 1;

		/* all done! */
		watch_state = WATCH_INIT;
		return 0; // signal we are done
	}

	return 1;
}

/*
 * Receive a packet, and queue the processing of it
 * returns non-zero when a packet has arrived
 */
int dtp_getpacket(void)
{
	static DTPacketHeader packetheader;
	static DTPacketFooter packetfooter;
	static char packetdata[MAX_RECEIVE_SIZE];
	
	switch(rx_state) {
	case RX_INIT:
		rx_offset = 0;
		rx_length = sizeof(packetheader);
		rx_state = RX_HEADER;
		/* fall through to start receiving */

	case RX_HEADER:
		rx_offset += sock_fastread(control_s, (char *)&packetheader + rx_offset, rx_length - rx_offset);
		if(rx_offset < rx_length)
			break;

		packetheader.version = ntohl(packetheader.version);
		packetheader.type = ntohs(packetheader.type);
		packetheader.length = ntohl(packetheader.length);
		packetheader.seq = ntohs(packetheader.seq);

		rx_offset = 0;
		rx_length = (int)packetheader.length;
		rx_state = RX_BODY;
		/* fall through again */
		
	case RX_BODY:
		rx_offset += sock_fastread(control_s, (char *)packetdata + rx_offset, rx_length - rx_offset);
		if(rx_offset < rx_length)
			break;

		rx_offset = 0;
		rx_length = sizeof(packetfooter);
		rx_state = RX_FOOTER;
		/* fall through again */
		
	case RX_FOOTER:
		rx_offset += sock_fastread(control_s, (char *)&packetfooter + rx_offset, rx_length - rx_offset);
		if(rx_offset < rx_length)
			break;

		rx_state = RX_INIT;
		/* memcpy the packet from the temp storage into the real places */
		memcpy((char *)&packet_header, (char *)&packetheader, sizeof(DTPacketHeader));
		memcpy((char *)packet_data, (char *)packetdata, (int)packetheader.length);
		memcpy((char *)&packet_footer, (char *)&packetfooter, sizeof(DTPacketFooter));
		return 1;
	}

	return 0;
}

/*
 * Run the control code - NON-Blockable!
 */
#ifdef DBG_GEN
#define SEND_ERROR	packet_pending = 0; control_state = CONTROL_RESET; printf("Send timeout!\n"); DLFlags = 0; return;
#else
#define SEND_ERROR	packet_pending = 0; control_state = CONTROL_RESET; DLFlags = 0; return;
#endif
void dtp_controlhandler(void)
{
	static long send_timeout, control_timeout;
	auto int retval;
	
	if(0 == tcp_tick(control_s)) {
#ifdef DBG_GEN
		printf("Connection dropped!\n");
#endif
		if((DLFlags & DL_DEBUGMODE) && (DLFlags & DL_PROGSTARTED) &&
			(control_state == CONTROL_STEADY)) {
				if(main_id.run_background_poll)
					BackgroundPoll = 1; // clean quit - turn on background polling
		}
		packet_pending = 0;
		control_state = CONTROL_RESET;
		serial_state = SERIAL_NOTHING;
		DLFlags = 0;
	}

	/* run the serial write-port */
	dtp_serialhandler();

	/* run the watches */
	if(control_state == CONTROL_RUNWATCHES) {
		if(dtp_runallwatches()) {
			return;
		}
	}
		
	if(packet_pending) {
		/* send the packet */
		switch(send_state) {
		case SEND_INIT:
			if((ntohs(packet_header.type) & 0x0fff) > 0x0020) {
#ifdef DBG_GEN
				printf("ERROR! packet_header.type == 0x%04x\n",packet_header.type);
#endif
				/* Should kill the connection here? */
			}
			
			send_offset = 0;
			send_length = sizeof(packet_header);
			send_state = SEND_HEADER;
			send_timeout = MS_TIMER;
			send_timeout += SEND_TIMEOUT;
			/* fall through to start sending the header */
			
		case SEND_HEADER:
			retval = sock_fastwrite(control_s,
				(char *)&packet_header + send_offset,
				send_length - send_offset);
			send_offset += retval;
			if(retval > 0) {
				send_timeout = MS_TIMER;
				send_timeout += SEND_TIMEOUT;
			}
			if(chk_timeout(send_timeout)) {
				/* TIMEOUT */
				SEND_ERROR;
			}
			if(send_offset < send_length)
				break;
				
			send_state = SEND_DATA;
			send_offset = 0;
			send_length = (int)ntohl(packet_header.length);
			/* fall through again */
			
		case SEND_DATA:
			retval = sock_fastwrite(control_s,
				(char *)packet_data + send_offset,
				send_length - send_offset);
			send_offset += retval;
			if(retval > 0) {
				send_timeout = MS_TIMER;
				send_timeout += SEND_TIMEOUT;
			}
			if(chk_timeout(send_timeout)) {
				/* TIMEOUT */
				SEND_ERROR;
			}
			if(send_offset < send_length)
				break;
				
			send_state = SEND_FOOTER;
			send_offset = 0;
			send_length = sizeof(packet_footer);
			/* fall through again */
			
		case SEND_FOOTER:
			retval = sock_fastwrite(control_s,
				(char *)&packet_footer + send_offset,
				send_length - send_offset);
			send_offset += retval;
			if(retval > 0) {
				send_timeout = MS_TIMER;
				send_timeout += SEND_TIMEOUT;
			}
			if(chk_timeout(send_timeout)) {
				/* TIMEOUT */
				SEND_ERROR;
			}
			if(send_offset < send_length)
				break;
				
			/* all done */
			packet_pending = 0;
			send_state = SEND_INIT;
		}
		return;
	}

	/*** TIMEOUT CONTROL STATES! (mainly steady-state) ***/
	if(chk_timeout(control_timeout) && (control_state != CONTROL_LISTEN)
		&& (serial_state == SERIAL_NOTHING) && (control_state != CONTROL_RUNTARGET)) {
		/* too long without a timeout - reset */
		control_state = CONTROL_RESET;
		DLFlags = 0;
#ifdef DBG_GEN
		printf("Control steady-state timeout! (state == %d)\n",control_state);
#endif
	}
	
	if(LastPoll == -1) {
		if(BackgroundPoll) {
			BackgroundPoll = 0; // shut off polling on error
		} else {
			control_state = CONTROL_RESET;
		}
	}
	
	if(((DLFlags & DL_DEBUGMODE) && (DLFlags & DL_PROGSTARTED) &&
		(control_state == CONTROL_STEADY))
		|| BackgroundPoll ) {
		/* only run the poll if we didn't last time through */
		if(RunPoll) {
			retval = dtp_poll();
			if(retval > 0) {
				control_timeout = MS_TIMER;
				control_timeout += CONTROL_TIMEOUT;
				RunPoll = 0;
				return;
			}
			else if(retval == -1) {
				LastPoll = -1;
			}
		}
	}
	RunPoll = 1;

	switch(control_state) {
	case CONTROL_LISTEN:		/* wait for a connection to be established */		
		if(sock_established(control_s)) {
			ExecStepInit = 1;
				
#ifdef DBG_GEN
			printf("Connection established!\n");
#endif
			
			/* send a CHALLENGE to the incoming host */
			dtp_respondchallenge();

			GlobalStatus = DTPS_PROGRAMMED;

			control_timeout = set_timeout(CONTROL_TIMEOUT);
		}
		break;

	case CONTROL_AUTH:		/* wait for the response to our challenge */
		if(dtp_getpacket()) {
			/* verify challenge */
			if(dtp_verifychallenge()) {
				/* challenge-response was good send ack */
				dtp_ackauth();
				control_state = CONTROL_STEADY;

				dtp_controllinitpolling(); // finish init of polling data

			} else {
				/* error in auth - drop them on the floor */
				dtp_respondnak(0);
				control_state = CONTROL_RESET;
			}
		}
		break;

	case CONTROL_STEADY:
		if(dtp_getpacket()) {
			/* check seq number? */

			switch(packet_header.type) {
			case (DTP_POLL|DTP_ACK):
				if(PollsPending < 1) {
#ifdef DBG_DBGMODE
					printf("Error! More POLL ACKs received than POLLs sent!\n");
#endif
					PollsPending = 0;
				} else {
					PollsPending--;
				}
				break;
				
			case DTP_RESET:
				dtp_reset();
				break;

			case DTP_COLDLOAD:
				dtp_coldload();
				break;

			case DTP_DOWNLOADSTART:
				dtp_download();
				break;

			case DTP_DOWNLOADDATA:
				dtp_downloaddata();
				break;

			case (DTP_STATUS|DTP_ACK):
				if(StatusPending < 1) {
#ifdef DBG_DL
					printf("Error! More STATUS ACKs received than STATUS packets sent!\n");
#endif
					StatusPending = 0;
				} else {
					StatusPending--;
				}				
				break;

			case DTP_READ:
				control_state = CONTROL_READ;
				dtp_read();
				break;

			case DTP_WRITE:
				control_state = CONTROL_WRITE;
				dtp_write();
				break;

			case DTP_QUIT:
#ifdef DBG_GEN
				printf("QUIT received!\n");
#endif
				if((DLFlags & DL_DEBUGMODE) && (DLFlags & DL_PROGSTARTED) &&
					(control_state == CONTROL_STEADY)) {
						if(main_id.run_background_poll)
							BackgroundPoll = 1; // clean quit - turn on background polling
				}
				control_state = CONTROL_RESET;
				DLFlags = 0;
				break;

			case DTP_SINGLESTEP:
				control_state = CONTROL_SINGLESTEP;
				dtp_singlestep();
				break;

			case DTP_DEBUGMODE:
				dtp_pollinit();
				break;

			case DTP_STARTPROG:
#ifdef DBG_DBGMODE
				printf("Attempting to start the program...\n");
#endif
				if(dtp_startprog()) {
#ifdef DBG_DBGMODE
					printf("Error starting program!\n");
#endif
					dtp_respondnak(0);
				} else {
					DLFlags |= DL_PROGSTARTED;
#ifdef DBG_DBGMODE
					printf("Program started successfully!\n");
#endif
					// send registers back with ack
					memcpy(packet_data, (char *)&RegisterContext, 26);
					packet_data[26] = RegisterContext.PC_XPC;
					packet_data[27] = (char)(RegisterContext.PC_OFFSET & 0xff);
					packet_data[28] = (char)((RegisterContext.PC_OFFSET >> 8) & 0xff);
					dtp_respondack(29);
				}
				break;

			case DTP_STOPPROG:
				dtp_stopprog();
				break;
				
			case DTP_CSINGLESTEP:
				dtp_csinglestep();
				break;
				
			case DTP_EXECWATCH:
				dtp_execwatch();
				break;

			case DTP_SETBRKPT:
				dtp_setbreakpoint();
				break;

			case DTP_REMOVEBRKPT:
				dtp_removebreakpoint();
				break;

			case DTP_NOOP:
				/* no response to a noop - just reset the timeout */
				break;
				
			case DTP_ADDWATCH:
				dtp_addwatch();
				break;

			case DTP_DELETEWATCH:
				dtp_deletewatch();
				break;
							
			case DTP_RUNALLWATCH:
				control_state = CONTROL_RUNWATCHES;
				break;
				
			case DTP_DELETEALLWATCH:
				dtp_deleteallwatches();
				break;
							
			case DTP_SENDCHAR:
				dtp_sendchar();
				break;
				
			default:
#ifdef DBG_GEN
				printf("Unknown command! (packet_header.type == 0x%04x)\n",packet_header.type);
#endif
				dtp_respondnak(0);
				break;
			}

			/* something happened - reset the timeout */
			control_timeout = MS_TIMER;
			control_timeout += CONTROL_TIMEOUT;
		}
		break;

	case CONTROL_READ:
		dtp_read();
		break;

	case CONTROL_WRITE:
		dtp_write();
		break;

	case CONTROL_SINGLESTEP:
		dtp_singlestep();
		break;

	case CONTROL_RUNTARGET:
		SingleStepInit();
		SetFirstBreakpoint();
		control_state = CONTROL_STEADY;
		control_timeout = MS_TIMER;
		control_timeout += CONTROL_TIMEOUT;
		break;

	case CONTROL_RUNWATCHES:
		/* we are all done with the watches - reset back to normal */
		control_state = CONTROL_STEADY;
		break;
				
	case CONTROL_RESET:
		/* shut down everything and reset for a new connection */
		if(!BackgroundPoll)
			bc_usrledoff();
		sock_close(control_s);
		serial_state = SERIAL_NOTHING;
		DLFlags = 0;
		dtp_controlinit();
		InitWatchList();
		watch_state = WATCH_INIT;
		break;
						
	default:
		/* ERROR - reset it */
		bc_usrledoff();
		sock_close(control_s);
		serial_state = SERIAL_NOTHING;
		DLFlags = 0;
		dtp_controlinit();
	}
}

/************************
 * End control section *
 ************************/

 
/**************************
 * Serial console section *
 **************************/

#ifdef SERIAL_CONSOLE

/* macros for which serial port we are using */
#ifdef SERIAL_PORT_C
	#define SERopen			serCopen
	#define SERclose			serCclose
	#define SERwrFlush		serCwrFlush
	#define SERrdFlush		serCrdFlush
	#define SERpeek			serCpeek
	#define SERWRAPputs		serCputs
	#define SERWRAPputc		serCputc
	#define SERWRAPrdUsed	serCrdUsed
	#define SERwrUsed			serCwrUsed
	#define SERWRAPread		serCread
	#define SERWRAPwrite		serCwrite
#else
	#define SERopen			serAopen
	#define SERclose			serAclose
	#define SERwrFlush		serAwrFlush
	#define SERrdFlush		serArdFlush
	#define SERpeek			serApeek
	#define SERWRAPputs		serAputs
	#define SERWRAPputc		serAputc
	#define SERWRAPrdUsed	serArdUsed
	#define SERwrUsed			serAwrUsed
	#define SERWRAPread		serAread
	#define SERWRAPwrite		serAwrite
#endif

/*
 * Wrapers for the write functions, to send to
 * the target as well.
 */

int SERputHandler(void)
{
	auto char ch, zero, sendchar;
	zero = 0;
	
	if(GetcharLength < 1)
		return 0; /* nothing to do */

	if((control_state != CONTROL_STEADY) && !BackgroundPoll)
		return 0; /* only if we are doing nothing else */

	/* test to see if the target is ready */	
	if(ReadBlock(1, 0, tc_keybuf-1, &ch))
		return 0;

	/* verify ch == 0xff */
	if(ch != 0xff) {
		/* target is not ready yet */
		return 0;
	}

	/* get a new character out of the buffer */
	xmem2root(&sendchar, GetcharStart, 1);
	GetcharStart++;
	GetcharLength--;
	if(GetcharStart >= (GetcharBuf + GETCHAR_BUFSIZE)) {
		/* wrapped around - reset to the beginning */
		GetcharStart = GetcharBuf;
	}

	if(WriteBlock(1, 0, tc_keybuf, (char *)&sendchar))
		return 0;
	//	clear request
	if(WriteBlock(1, 0, tc_keybuf-1, &zero))
		return 0;

	if((GetcharLength < 1) && (0 == SendToGetchar)) {
		/* no more data - clear the Pending flag on the target */
		if(WriteBlock(1, 0, tc_getch, &zero))
			return 0;
	}
	
	return 1;	
}
 
int SERputc(char c)
{
	auto long current;
	auto char one;
	one = 1;
	
	if (0 == SERWRAPputc(c)) {
		/* Error in serial port putc! */
		return 0;
	}

	if(!SendToGetchar)
		return 1; /* no request was made - only send to the serial port */
	
	if(GetcharLength >= GETCHAR_BUFSIZE) {
		printf("Getchar overflow!\n");
		return 0;
	}

	/* compute the position in the circular buffer for
	   our next character */
	current = GetcharStart + GetcharLength;
	if(current >= (GetcharBuf + GETCHAR_BUFSIZE)) {
		current -= GETCHAR_BUFSIZE;
	}

	if(0 == GetcharLength) {
		/* first byte to send - set the pending flag on the target */
		if(WriteBlock(1, 0, tc_getch, &one))
			return 0;
	}
	
	/* store the character in the buffer */
	root2xmem(current, &c, 1);
	GetcharLength += 1;

	return 1;
}

int SERputs(char *str)
{
	int cnt;

	cnt = 0;
	while(*str) {
		if(0 == SERputc(*str))
			return cnt;
		cnt++;
		str++;
	}
	return cnt;
}

int SERwrite(char *data, int length)
{
	while(length > 0) {
		if(0 == SERputc(*data))
			return 0;
		length--;
		data++;
	}
}
 
/*
 * Wrapers for the read functions, to receive
 * from the target as well.
 */
 
int SERrdUsed(void)
{
	if(combufpending) {
		return (combuflen - combufoffset);
	} else {
		return SERWRAPrdUsed();
	}
}
 
int SERread(void *data, int length, unsigned long tmout)
{
	int len;
	if(combufpending) {
		len = combuflen - combufoffset;
		if(len > length)
			len = length;

		memcpy(data, combuf + combufoffset, len);
		combufoffset += len;

		if(combufoffset >= combuflen) {
			/* all done! */
			combufpending = 0;
		}

		return len;
	} else {
		return SERWRAPread(data,length,tmout);
	}
}

/*
 * Global defines for the serial console
 */
#define SER_INIT 					0
#define SER_READY					1
#define SER_GETREQ 				2
#define SER_PARSECMD 			3
#define SER_ECHO					4
#define SER_PUT					5
#define SER_PUT_READING			6
#define SER_PUT_B1				7
#define SER_PUT_B2				8
#define SER_GET					9
#define SER_GET_WRITING			10
#define SER_GET_B1				11
#define SER_GET_B2				12
#define SER_LIST					13
#define SER_LIST_FILES			14
#define SER_LIST_VARIABLES		15
#define SER_SET					16
#define SER_SET_IP				17
#define SER_SET_NETMASK			18
#define SER_SET_HOSTNAME		19
#define SER_SET_GATEWAY			20
#define SER_SET_PORT				21
#define SER_SET_MAIL				22
#define SER_SET_MAIL_FROM		23
#define SER_SET_MAIL_SERVER	24
#define SER_MAIL					25
#define SER_MAIL_READING		26
#define SER_MAIL_SENDING		27
#define SER_SHOW					28
#define SER_PASSWORD				29
#define SER_PASSWORD_GET		30
#define SER_PASSWORD_AGAIN		31
#define SER_PASSWORD_FINISH	32
#define SER_BAUD					33
#define SER_RESET					34
#define SER_RESET_ANSWER		35
#define SER_RESET_CHECK			36
#define SER_RESET_ALL			37
#define SER_RESET_VARIABLES	38
#define SER_RESET_FILES			39
#define SER_HELP					40
#define SER_HELP_WRITING		41
#define SER_HELP_FINISH			42
//#define SER_HELP_MAIL			41
#define SER_DELETE				43
#define SER_CONS_BAUD			44
#define SER_CREATEV				45
#define SER_PUTV					46
#define SER_GETV					47
#define SER_BACKGROUND_POLL	48

#define SER_MAIN_TIMEOUT		60
#define SER_PUT_TIMEOUT			10

#define CMD_SIZE 101
#define BUFFER_SIZE 1024
#define PASSWORD_SIZE	256

typedef struct {
	char* bufferend;
	FileNumber filenum;
	File f;

	char command[CMD_SIZE];
	char buffer[BUFFER_SIZE];
	char passbuf[PASSWORD_SIZE];
	char mailfrom[MAIL_FROM_SIZE];
	char mailserv[MAIL_SERV_SIZE];
	int buffernum;
	char* cmdptr;
	char* bufptr;
	int numparams;

	int spec;
	long num;
	long total;
	int getsawcr;

	char name[SSPEC_MAXNAME];

	char* mail_to;
	char* mail_subject;
	char* mail_body;
	
	int state;
	int nextstate;
	int laststate;
	int substate;
	int sawcr;
	int sawesc;
	int echo;

	long timeout;

	long varbuf;
	long varbuflen;
} SerialState;

/* The main state structure */
static SerialState sstate;


const HttpType http_types[] =
{
   { ".shtml", "text/html", shtml_handler}, // ssi
   { ".html", "text/html", NULL},           // html
   { ".cgi", "", NULL},                     // cgi
   { ".gif", "image/gif", NULL},
   { ".jpg", "image/jpeg", NULL},
   { ".jpeg", "image/jpeg", NULL},
   { ".txt", "text/plain", NULL}
};

/*
 * Init everything
 */
void dtp_consoleinit(void)
{
	sstate.state = SER_INIT;
	tcp_reserveport(80);
	http_init();	
}

/*
 * Helper functions
 */
void DISABLE_NET(void)
{
	/* kill any open TCP connection */
	sock_close(control_s);
	tcp_tick(NULL);
}

void ENABLE_NET(void)
{
	/* nothing right now */
}
 
void ser_ok(SerialState* state) {
	SERputs("OK\r\n");
	state->state = SER_READY;

	/* stop sending messages to the target */
	SendToGetchar = 0;
}

#define SER_ERR_TIMEOUT			1
#define SER_ERR_BADCOMMAND		2
#define SER_ERR_BADPARAMETER	3
#define SER_ERR_NAMETOOLONG	4
#define SER_ERR_DUPLICATE		5
#define SER_ERR_BADFILESIZE	6
#define SER_ERR_SAVINGFILE		7
#define SER_ERR_READINGFILE	8
#define SER_ERR_FILENOTFOUND	9
#define SER_ERR_MSGTOOLONG		10
#define SER_ERR_SMTPERROR		11
#define SER_ERR_BADPASSPHRASE	12
#define SER_ERR_CANCELRESET	13
#define SER_ERR_BADVARTYPE		14
#define SER_ERR_BADVARVALUE	15
#define SER_ERR_NOVARSPACE		16
#define SER_ERR_VARNOTFOUND	17
#define SER_ERR_STRINGTOOLONG	18
#define SER_ERR_NOTAFILE		19
#define SER_ERR_NOTAVAR			20

void ser_error(SerialState* state, int errnum) {
	auto char errstr[7];

	SERputs("ERROR ");
	sprintf(errstr, "%04d  ", errnum);
	SERputs(errstr);

	if (errnum == SER_ERR_TIMEOUT)
		SERputs("Timed out.");
	else if (errnum == SER_ERR_BADCOMMAND)
		SERputs("Unknown command.");
	else if (errnum == SER_ERR_BADPARAMETER)
		SERputs("Bad or missing parameter.");
	else if (errnum == SER_ERR_NAMETOOLONG)
		SERputs("Filename too long.");
	else if (errnum == SER_ERR_DUPLICATE)
		SERputs("Duplicate object found.");
	else if (errnum == SER_ERR_BADFILESIZE)
		SERputs("Bad file size.");
	else if (errnum == SER_ERR_SAVINGFILE)
		SERputs("Error saving file.");
	else if (errnum == SER_ERR_READINGFILE)
		SERputs("Error reading file.");
	else if (errnum == SER_ERR_FILENOTFOUND)
		SERputs("File not found.");
	else if (errnum == SER_ERR_MSGTOOLONG)
		SERputs("Mail message too long.");
	else if (errnum == SER_ERR_SMTPERROR)
		SERputs("SMTP server error.");
	else if (errnum == SER_ERR_BADPASSPHRASE)
		SERputs("Passphrases do not match!");
	else if (errnum == SER_ERR_CANCELRESET)
		SERputs("Reset cancelled.");
	else if (errnum == SER_ERR_BADVARTYPE)
		SERputs("Bad variable type.");
	else if (errnum == SER_ERR_BADVARVALUE)
		SERputs("Bad variable value.");
	else if (errnum == SER_ERR_NOVARSPACE)
		SERputs("Out of variable space.");
	else if (errnum == SER_ERR_VARNOTFOUND)
		SERputs("Variable not found.");
	else if (errnum == SER_ERR_STRINGTOOLONG)
		SERputs("String too long.");
	else if (errnum == SER_ERR_NOTAFILE)
		SERputs("Not a file.");
	else if (errnum == SER_ERR_NOTAVAR)
		SERputs("Not a variable.");
	SERputs("\r\n");

	state->state = SER_READY;

	/* stop sending messages to the target */
	SendToGetchar = 0;
}

int nullify(char* buffer) {
	auto int i;
	auto int looking;
	auto int quoted;
	auto int num;

	looking = 0;
	quoted = 0;
	num = 0;
	for (i = 0; buffer[i] != '\0'; i++) {
		if ((looking == 0) && (buffer[i] == ' ')) {
			buffer[i] = '\0';
		} else if ((looking == 0) && (buffer[i] != ' ')) {
			if (buffer[i] == '\"') {
				quoted = 1;
				buffer[i] = '\0';
			}
			looking = 1;
			num++;
		} else if ((looking == 1) && (quoted == 0) &&
		           ((buffer[i] == ' ') || (buffer[i] == '\"'))) {
		   if (buffer[i] == '\"') {
		   	// Quote in an unquoted param; start new param
		   	quoted = 1;
		   	looking = 1;
		   	num++;
		   } else {
		   	looking = 0;
		   }
			buffer[i] = '\0';
		} else if ((looking == 1) && (quoted == 1) && (buffer[i] == '\"')) {
			// Ending a quoted param
			looking = 0;
			quoted = 0;
			buffer[i] = '\0';
		}
		// Handle escaped characters
		if (buffer[i] == '\\') {
			strcpy(&buffer[i], &buffer[i+1]);
		}
	}
	return num;
}

char* get_param(char* buffer, int num) {
	auto int numnulls;
	auto int i;

	numnulls = 0;
	i = 0;
	// Skip past initial nulls
	while (buffer[i] == '\0') {
		i++;
	}
	// Skip to the right parameter
	while (numnulls < num) {
		if ((buffer[i] == '\0') && (buffer[i+1] != '\0')) {
			numnulls++;
		}
		i++;
	}
	return (&buffer[i]);
}

int match_names(char* name1, char* name2) {
	if ((name1 == NULL) && (name2 == NULL)) {
		return 1;
	} else if (strcmpi(name1, name2) == 0) {
		return 1;
	} else {
		return 0;
	}
}

long lookup_help(char* cmd1, char* cmd2, char* cmd3) {
	auto int i;
	for (i = 0; i < (sizeof(helpindex) / sizeof(struct HelpIndex)); i++) {
		if ((match_names(cmd1, helpindex[i].cmd1)) &&
		    (match_names(cmd2, helpindex[i].cmd2)) &&
		    (match_names(cmd3, helpindex[i].cmd3))) {
			return helpindex[i].addr;
		}
	}
	return -1;
}

/*
 * The main handler
 */
void dtp_consolehandler(void) {
	auto char data;
	auto long len;
	auto int bytes;
	auto char* name;
	auto char* ptr;
	static SerialState* state;
	auto unsigned long	fileSize;
	auto char s[20];
	auto int sspec;
	auto long location;
	auto int tempchar;
	auto int tempint;
	auto long templong;
	auto float tempfloat;
	auto char* tailptr;
	auto int retval;
	auto word varkind;

	#GLOBAL_INIT {
		state = &sstate;
	}

	/* send console data to the target if necessary */
	SERputHandler();
	
	if (state->state != state->laststate) {
		if ((state->state != SER_PUT_B1) && (state->state != SER_PUT_B2)) {
			// state changed; reset timeout
			state->laststate = state->state;
			state->timeout = set_timeout(SER_MAIN_TIMEOUT);
		}
	}

	if (chk_timeout(state->timeout)) {
		if (state->state <= SER_GETREQ) {
			// never mind--we're waiting for a command or initializing
			state->timeout = set_timeout(SER_MAIN_TIMEOUT);
		} else {
			// we timed out in one state for too long
			if (state->filenum != 0) {
				fclose(&state->f);
				fdelete(state->filenum);
			}
			ser_error(state, SER_ERR_TIMEOUT);
		}
	}
	
	if ((state->state == SER_GETREQ) || (state->state == SER_READY)) {
		http_handler();
	}
	switch (state->state) {
	case SER_INIT:
		state->varbuf = xalloc(VAR_BUFFER);
		state->varbuflen = 0;

		state->echo = main_id.echo;
		state->sawcr = 0;

		SERopen(main_id.console_baud);
		SERwrFlush();
		SERrdFlush();
		SERputs("RabbitLink Console Version ");
		SERputs(CONSOLE_VERSION);
		SERputs(" Ready\r\n");
		state->state = SER_READY;
		break;

	case SER_READY:
		state->command[0] = '\0';
		state->cmdptr = state->command;
		state->numparams = 0;
		state->sawesc = 0;
		state->state = SER_GETREQ;
		state->filenum = 0;
		break;

	case SER_GETREQ:
		if (SERrdUsed() > 0) {
			SERread(&data, 1, 0);
			state->timeout = set_timeout(SER_MAIN_TIMEOUT);
		} else {
			break;
		}

		if (state->sawesc > 0) {
			// Skip past escaped characters (starting with 1B, such as cursor
			// movement keys)
			state->sawesc--;
			break;
		} else if ((data == '\r') || ((data == '\n') && (state->sawcr == 0))) {
			if (data == '\r') {
				state->sawcr = 1;
			}
			if (state->echo) {
				SERputs("\r\n");
			}
			if ((state->cmdptr - state->command) < CMD_SIZE) {
				*(state->cmdptr) = '\0';
			} else {
				ser_error(state, SER_ERR_BADCOMMAND);
				break;
			}

			// find the command
			state->state = SER_PARSECMD;
			break;
		} else if ((data == '\n') && (state->sawcr == 1)) {
			state->sawcr = 0;
			break;
		} else if (state->sawcr == 1) {
			state->sawcr = 0;
		}
		// Handle the character we just got
		if (data == 0x08) {
			// Backspace character
			if (state->cmdptr > state->command) {
				state->cmdptr--;
				if (state->echo) {
					//SERwrite(&data, 1);
					SERputs("\x08 \x08");
				}
			}
		} else if (data == 0x1B) {
			// Escape character--skip past the next two characters
			state->sawesc = 2;
		} else if (data == 0x15) {
			// CTRL-U -- linefeed and clear the command buffer
			if (state->echo) {
				SERputs("\r\n");
			}
			state->cmdptr = state->command;
			state->command[0] = '\0';
		} else if (data >= 0x20) {
			if ((state->cmdptr - state->command) < CMD_SIZE) {
				*(state->cmdptr) = data;
			}
			state->cmdptr++;
			if (state->echo) {
				SERwrite(&data, 1);
			}
		}
		break;

	case SER_PARSECMD:
		state->numparams = nullify(state->command);
		if (state->numparams == 0) {
			state->state = SER_READY;
		} else if (strcmpi(get_param(state->command, 0), "echo") == 0) {
			state->state = SER_ECHO;
		} else if (strcmpi(get_param(state->command, 0), "put") == 0) {
			state->state = SER_PUT;
		} else if (strcmpi(get_param(state->command, 0), "get") == 0) {
			state->state = SER_GET;
		} else if (strcmpi(get_param(state->command, 0), "list") == 0) {
			state->state = SER_LIST;
		} else if (strcmpi(get_param(state->command, 0), "set") == 0) {
			state->state = SER_SET;
		} else if (strcmpi(get_param(state->command, 0), "mail") == 0) {
			state->state = SER_MAIL;
		} else if (strcmpi(get_param(state->command, 0), "show") == 0) {
			state->state = SER_SHOW;
		} else if (strcmpi(get_param(state->command, 0), "reset") == 0) {
			state->state = SER_RESET;
		} else if (strcmpi(get_param(state->command, 0), "help") == 0) {
			state->state = SER_HELP;
		} else if (strcmpi(get_param(state->command, 0), "delete") == 0) {
			state->state = SER_DELETE;
		} else if (strcmpi(get_param(state->command, 0), "createv") == 0) {
			state->state = SER_CREATEV;
		} else if (strcmpi(get_param(state->command, 0), "getv") == 0) {
			state->state = SER_GETV;
		} else if (strcmpi(get_param(state->command, 0), "putv") == 0) {
			state->state = SER_PUTV;
		} else {
			ser_error(state, SER_ERR_BADCOMMAND);
		}
		break;

	case SER_ECHO:
		if ((state->numparams == 2) &&
		    (strcmpi(get_param(state->command, 1), "on") == 0)) {
			ser_ok(state);
			state->echo = 1;
			dtp_backup_id();
		} else if ((state->numparams == 2) &&
		           (strcmpi(get_param(state->command, 1), "off") == 0)) {
			ser_ok(state);
			state->echo = 0;
			dtp_backup_id();
		} else {
			ser_error(state, SER_ERR_BADPARAMETER);
		}
		state->state = SER_READY;
		break;

	case SER_PUT:
		if ((state->numparams == 2) || (state->numparams == 3)) {
			if (strlen(get_param(state->command, 1)) < SSPEC_MAXNAME) {
				strcpy(state->name, get_param(state->command, 1));
			} else {
				// Not enough room to put the page name
				ser_error(state, SER_ERR_NAMETOOLONG);
				break;
			}
			if (sspec_findname(state->name, SERVER_HTTP) != -1) {
				ser_error(state, SER_ERR_DUPLICATE);
				break;
			}
			state->bufferend = state->buffer;
			if (state->numparams == 2) {
				if ((state->filenum = fcreate_unused(&state->f)) == 0) {
					ser_error(state, SER_ERR_SAVINGFILE);
					break;
				}
				state->state = SER_PUT_READING;			
			} else {
				state->num = strtol(get_param(state->command, 2), &ptr, 10);
				if ((*ptr == '\0') && (state->num < LONG_MAX) && (state->num > LONG_MIN)) {
					if ((state->filenum = fcreate_unused(&state->f)) == 0) {
						ser_error(state, SER_ERR_SAVINGFILE);
						break;
					}
					state->total = 0;
					state->timeout = set_timeout(SER_PUT_TIMEOUT);
					state->state = SER_PUT_B1;
				} else {
					ser_error(state, SER_ERR_BADFILESIZE);
				}
			}
		} else {
			ser_error(state, SER_ERR_BADPARAMETER);
		}
		break;

	case SER_PUT_READING:
		if (SERrdUsed() > 0) {
			state->timeout = set_timeout(SER_MAIN_TIMEOUT);
			SERread(&data, 1, 0);
		} else {
			break;
		}
		if ((data == 0x04) || (data == 0x1A)) {
			// Done getting the page (CTRL-D or CTRL-Z)
			if ((state->bufferend - state->buffer) > 0) {
				if (fwrite(&state->f, state->buffer, state->bufferend - state->buffer) < (state->bufferend - state->buffer)) {
					fclose(&state->f);
					fdelete(state->filenum);
					ser_error(state, SER_ERR_SAVINGFILE);
					break;
				}
			}
			fclose(&state->f);
			if (sspec_addfsfile(state->name, state->filenum, SERVER_HTTP) >= 0) {
				state->filenum = 0;
				dtp_backup_id();
				ser_ok(state);
				break;
			} else {
				fdelete(state->filenum);
				state->filenum = 0;
				ser_error(state, SER_ERR_SAVINGFILE);
				break;
			}
		} else if (data == 0x03) {
			// Bail out (CTRL-C)
			fclose(&state->f);
			fdelete(state->filenum);
			state->filenum = 0;
			ser_ok(state);
			break;
		} else if ((data == '\r') || ((data == '\n') && (state->sawcr == 0))) {
			*(state->bufferend) = data;
			state->bufferend++;
			if (data == '\r') {
				state->sawcr = 1;
			}
			if (state->echo) {
				SERputs("\r\n");
			}
		} else if ((data == '\n') && (state->sawcr == 1)) {
			*(state->bufferend) = data;
			state->bufferend++;
			state->sawcr = 0;
		} else {
			*(state->bufferend) = data;
			state->bufferend++;
			if (state->echo) {
				SERwrite(&data, 1);
			}
		}
		if ((state->bufferend - state->buffer) == BUFFER_SIZE) {
			if (fwrite(&state->f, state->buffer, BUFFER_SIZE) < BUFFER_SIZE) {
				fclose(&state->f);
				fdelete(state->filenum);
				ser_error(state, SER_ERR_SAVINGFILE);
				break;
			}
		}
		break;

	case SER_PUT_B1:
		if (SERrdUsed() > 0) {
			state->timeout = set_timeout(SER_PUT_TIMEOUT);
			if ((state->sawcr == 1) && (SERpeek() == '\n')) {
				SERread(&data, 1, 0);
			}
			state->state = SER_PUT_B2;
		}
		break;

	case SER_PUT_B2:
		if (SERrdUsed() > 0) {
			state->timeout = set_timeout(SER_PUT_TIMEOUT);
			len = state->bufferend - state->buffer;
			state->buffernum = SERread(state->bufferend, (((BUFFER_SIZE - (int)len) < 256)?(BUFFER_SIZE - (int)len):256), 0);
			if ((len + state->buffernum) == BUFFER_SIZE) {
				if (fwrite(&state->f, state->buffer, BUFFER_SIZE) < BUFFER_SIZE) {
					fclose(&state->f);
					fdelete(state->filenum);
					ser_error(state, SER_ERR_SAVINGFILE);
					break;
				}
				state->bufferend = state->buffer;
			} else {
				state->bufferend += state->buffernum;
			}
			state->total += state->buffernum;
			
			if (state->total >= state->num) {
				if (state->bufferend > state->buffer) {
					if (fwrite(&state->f, state->buffer, state->bufferend - state->buffer) != (state->bufferend - state->buffer)) {
						fclose(&state->f);
						fdelete(state->filenum);
						ser_error(state, SER_ERR_SAVINGFILE);
						break;
					}
				}
				fclose(&state->f);
				if (sspec_addfsfile(state->name, state->filenum, SERVER_HTTP) >= 0) {
					state->filenum = 0;
					dtp_backup_id();
					ser_ok(state);
					break;
				} else {
					fdelete(state->filenum);
					state->filenum = 0;
					ser_error(state, SER_ERR_SAVINGFILE);
					break;
				}
			}
		}
		break;

	case SER_GET:
		if ((state->numparams == 2) || (state->numparams == 3)) {
			if (strlen(get_param(state->command, 1)) < SSPEC_MAXNAME) {
				strcpy(state->name, get_param(state->command, 1));
			} else {
				// Not enough room to put the page name
				ser_error(state, SER_ERR_NAMETOOLONG);
				break;
			}
			if ((state->spec = sspec_findname(state->name, SERVER_HTTP)) == -1) {
				// Didn't find the file
				ser_error(state, SER_ERR_READINGFILE);
				break;
			}
			if (sspec_gettype(state->spec) != SSPEC_FILE) {
				// Not a file
				ser_error(state, SER_ERR_NOTAFILE);
				break;
			}
			state->num = 0;
			state->getsawcr = 0;
			if (state->numparams == 2) {
				state->state = SER_GET_WRITING;
			} else {
				state->num = strtol(get_param(state->command, 2), &ptr, 10);
				if ((*ptr == '\0') && (state->num < LONG_MAX) && (state->num > LONG_MIN)) {
					state->total = 0;
					sprintf(state->buffer, "%ld\r\n", sspec_getlength(state->spec));
					SERputs("LENGTH ");
					SERputs(state->buffer);
					state->state = SER_GET_B1;
				} else {
					ser_error(state, SER_ERR_BADFILESIZE);
				}
			}
		} else {
			ser_error(state, SER_ERR_BADPARAMETER);
		}
		break;

	case SER_GET_WRITING:
		if (SERwrUsed() == 0) {
			if (!state->echo) {
				bytes = sspec_readfile(state->spec, state->buffer, state->num, 200);
				if (bytes <= 0) {
					ser_error(state, SER_ERR_READINGFILE);
					break;
				}
				state->timeout = set_timeout(SER_MAIN_TIMEOUT);
				bytes = SERwrite(state->buffer, bytes);
				state->num += bytes;
				if (state->num >= sspec_getlength(state->spec)) {
					ser_ok(state);
				}
			} else {
				if (sspec_readfile(state->spec, &data, state->num, 1) == 1) {
					if ((data == '\r') || ((data == '\n') && (state->getsawcr == 0))) {
						SERputs("\r\n");
						if (data == '\r') {
							state->getsawcr = 1;
						}
					} else if ((data == '\n') && (state->getsawcr == 1)) {
						state->getsawcr = 0;
					} else {
						SERwrite(&data, 1);
					}
					state->num++;
				} else {
					ser_error(state, SER_ERR_READINGFILE);
					break;
				}
				if (state->num == sspec_getlength(state->spec)) {
					ser_ok(state);
				}
			}
		}
		break;

	case SER_GET_B1:
		if (SERwrUsed() == 0) {
			if ((state->sawcr == 1) && (SERpeek() == '\n')) {
				SERread(&data, 1, 0);
			}
			state->num = strtol(get_param(state->command, 2), &ptr, 10);
			if ((*ptr == '\0') && (state->num < LONG_MAX) && (state->num > LONG_MIN)) {
				state->total = 0;
				state->state = SER_GET_B2;
			} else {
				ser_error(state, SER_ERR_BADFILESIZE);
			}
		}
		break;

	case SER_GET_B2:
		if (SERwrUsed() == 0) {
			bytes = sspec_readfile(state->spec, state->buffer, state->total, 200);
			if (bytes <= 0) {
				ser_error(state, SER_ERR_READINGFILE);
				break;
			}
			state->timeout = set_timeout(SER_MAIN_TIMEOUT);
			if ((state->total + bytes) <= state->num) {
				SERwrite(state->buffer, bytes);
			} else if (state->total < state->num) {
				SERwrite(state->buffer, (int)(state->num - state->total));
			} else {
				ser_ok(state);
				break;
			}
			state->total += bytes;
			if (state->total >= sspec_getlength(state->spec)) {
				ser_ok(state);
			}
		}
		break;

	case SER_LIST:
		if (state->numparams != 2) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		if (strcmpi(get_param(state->command, 1), "files") == 0) {
			state->num = 0;
			state->state = SER_LIST_FILES;
		} else if (strcmpi(get_param(state->command, 1), "variables") == 0) {
			state->num = 0;
			state->state = SER_LIST_VARIABLES;
		} else {
			ser_error(state, SER_ERR_BADPARAMETER);
		}
		break;

	case SER_LIST_FILES:
		if (SERwrUsed() == 0) {
			if ((state->num = sspec_findnextfile((int)state->num, SERVER_HTTP)) != -1) {
				name = sspec_getname((int)state->num);
				if ((name != NULL) && (sspec_gettype((int)state->num) == SSPEC_FILE)) {
					fileSize = sspec_getlength((int)state->num);
					sprintf(s, "%8d  ", fileSize);
					SERputs(s);					
					SERputs(name);
					SERputs("\r\n");
					state->timeout = set_timeout(SER_MAIN_TIMEOUT);
				}
				state->num++;
			} else {
				ser_ok(state);
			}
		}
		break;

	case SER_LIST_VARIABLES:
		if (SERwrUsed() == 0) {
			if ((state->num = sspec_findnextfile((int)state->num, SERVER_HTTP)) != -1) {
				name = sspec_getname((int)state->num);
				if ((name != NULL) && (sspec_gettype((int)state->num) == SSPEC_VARIABLE)) {
					SERputs(name);
					varkind = sspec_getvarkind((int)state->num);
					switch (varkind) {
					case INT8:
						SERputs(" INT8");
						break;
					case INT16:
						SERputs(" INT16");
						break;
					case INT32:
						SERputs(" INT32");
						break;
					case FLOAT32:
						SERputs(" FLOAT32");
						break;
					case PTR16:
						SERputs(" STRING ");
						sprintf(s, "%d", main_id.varstrlen[(int)state->num] - 1);
						SERputs(s);
						break;
					}
					SERputs("\r\n");
					state->timeout = set_timeout(SER_MAIN_TIMEOUT);
				}
				state->num++;
			} else {
				ser_ok(state);
			}
		}
		break;

	case SER_SET:
		if (state->numparams <= 1) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		if (strcmpi(get_param(state->command, 1), "ip") == 0) {
			state->state = SER_SET_IP;
		} else if (strcmpi(get_param(state->command, 1), "netmask") == 0) {
			state->state = SER_SET_NETMASK;
		} else if (strcmpi(get_param(state->command, 1), "hostname") == 0) {
			state->state = SER_SET_HOSTNAME;
		} else if (strcmpi(get_param(state->command, 1), "gateway") == 0) {
			state->state = SER_SET_GATEWAY;
		} else if (strcmpi(get_param(state->command, 1), "port") == 0) {
			state->state = SER_SET_PORT;
		} else if (strcmpi(get_param(state->command, 1), "mail") == 0) {
			state->state = SER_SET_MAIL;
		} else if (strcmpi(get_param(state->command, 1), "passphrase") == 0) {
			state->state = SER_PASSWORD;
		} else if (strcmpi(get_param(state->command, 1), "progbaud") == 0) {
			state->state = SER_BAUD;
		} else if (strcmpi(get_param(state->command, 1), "consbaud") == 0) {
			state->state = SER_CONS_BAUD;
		} else if (strcmpi(get_param(state->command, 1), "poll") == 0) {
			state->state = SER_BACKGROUND_POLL;
		} else {
			ser_error(state, SER_ERR_BADCOMMAND);
		}
		break;

	case SER_SET_IP:
		if (state->numparams != 3) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		DISABLE_NET();
		tcp_config("MY_IP_ADDRESS",get_param(state->command,2));
		SERputs("IP address set to: ");
		SERputs(inet_ntoa(state->buffer,my_ip_addr));
		SERputs("\r\n");
		dtp_backup_id();
		ENABLE_NET();
		ser_ok(state);
		break;

	case SER_SET_NETMASK:
		if (state->numparams != 3) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		DISABLE_NET();
		tcp_config("MY_NETMASK",get_param(state->command,2));
		SERputs("Netmask set to: ");
		SERputs(inet_ntoa(state->buffer,sin_mask));
		SERputs("\r\n");
		dtp_backup_id();
		ENABLE_NET();
		ser_ok(state);
		break;

	case SER_SET_HOSTNAME:
		if (state->numparams != 3) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		DISABLE_NET();
		strcpy(main_id.name,get_param(state->command,2));
		SERputs("Hostname set to: ");
		SERputs(main_id.name);
		SERputs("\r\n");
		dtp_backup_id();
		ENABLE_NET();
		ser_ok(state);
		break;

	case SER_SET_GATEWAY:
		if (state->numparams != 3) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		tcp_config("MY_GATEWAY",get_param(state->command,2));
		SERputs("Gateway set to: ");
		SERputs(inet_ntoa(state->buffer,_arp_gate_data[0].gate_ip));
		SERputs("\r\n");
		dtp_backup_id();
		ser_ok(state);
		break;

	case SER_SET_PORT:
		if (state->numparams != 3) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		DISABLE_NET();
		main_id.control_port = atoi(get_param(state->command,2));
		sprintf(state->buffer,"Control port set to : %d\r\n",main_id.control_port);
		SERputs(state->buffer);
		dtp_backup_id();
		ENABLE_NET();
		ser_ok(state);
		break;

	case SER_BACKGROUND_POLL:
		if (state->numparams != 3) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		if(0 == strncmpi(get_param(state->command, 2), "on", 4)) {
			main_id.run_background_poll = 1;
			dtp_backup_id();
			ser_ok(state);
		} else if(0 == strncmpi(get_param(state->command, 2), "off", 5)) {
			main_id.run_background_poll = 0;
			dtp_backup_id();
			ser_ok(state);
		} else {
			ser_error(state, SER_ERR_BADPARAMETER);
		}
		break;

	case SER_SET_MAIL:
		if (state->numparams != 4) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		if (strcmpi(get_param(state->command, 2), "from") == 0) {
			state->state = SER_SET_MAIL_FROM;
		} else if (strcmpi(get_param(state->command, 2), "server") == 0) {
			state->state = SER_SET_MAIL_SERVER;
		} else {
			ser_error(state, SER_ERR_BADCOMMAND);
		}
		break;

	case SER_SET_MAIL_FROM:
		if (strlen(get_param(state->command, 3)) < MAIL_FROM_SIZE) {
			strcpy(main_id.mail_from, get_param(state->command, 3));
			dtp_backup_id();
			ser_ok(state);
		} else {
			ser_error(state, SER_ERR_BADPARAMETER);
		}
		break;

	case SER_SET_MAIL_SERVER:
		if (strlen(get_param(state->command, 3)) < MAIL_SERV_SIZE) {
			if (smtp_setserver(get_param(state->command, 3)) == SMTP_OK) {
				strcpy(main_id.mail_serv, get_param(state->command, 3));
				dtp_backup_id();
				ser_ok(state);
			} else {
				ser_error(state, SER_ERR_BADPARAMETER);
			}
		} else {
			ser_error(state, SER_ERR_BADPARAMETER);
		}
		break;

	case SER_MAIL:
		if (state->numparams != 2) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		state->bufptr = state->buffer;
		state->state = SER_MAIL_READING;
		break;

	case SER_MAIL_READING:
		if (SERrdUsed() > 0) {
			SERread(&data, 1, 0);
			state->timeout = set_timeout(SER_MAIN_TIMEOUT);
		} else {
			break;
		}
		if ((data == 0x04) || (data == 0x1A)) {
			if ((state->bufptr - state->buffer) >= (BUFFER_SIZE - 1)) {
				ser_error(state, SER_ERR_MSGTOOLONG);
				break;
			}
			*state->bufptr = '\0';
			if ((ptr = strchr(state->buffer, '\r')) != NULL) {
				*ptr = '\0';
				if (((ptr + 1 - state->buffer) < BUFFER_SIZE) && (*(ptr+1) == '\n')) {
					state->mail_body = ptr + 2;
				} else {
					state->mail_body = ptr + 1;
				}
			} else if ((ptr = strchr(state->buffer, '\n')) != NULL) {
				*ptr = '\0';
				state->mail_body = ptr + 1;
			} else {
				// No newline, so only subject, with no body
				state->mail_body = state->bufptr;
			}
			smtp_sendmail(get_param(state->command, 1), main_id.mail_from,
			              state->buffer, state->mail_body);
			state->state = SER_MAIL_SENDING;
			break;
		} else if (data == 0x03) {
			// Bail out (CTRL-C)
			ser_ok(state);
			break;
		}
		if ((data == '\r') || ((data == '\n') && (state->sawcr == 0))) {
			if (data == '\r') {
				state->sawcr = 1;
				// Put \r\n into the message
				if ((state->bufptr - state->buffer) < BUFFER_SIZE) {
					*state->bufptr = data;
				}
				state->bufptr++;
				data = '\n';
			}
			if (state->echo) {
				SERputs("\r\n");
			}
		} else if ((data == '\n') && (state->sawcr == 1)) {
			state->sawcr = 0;
			break;
		} else {
			if (state->sawcr == 1) {
				state->sawcr = 0;
			}
			if (state->echo) {
				SERwrite(&data, 1);
			}
		}
		if ((state->bufptr - state->buffer) < BUFFER_SIZE) {
			*state->bufptr = data;
		}
		state->bufptr++;
		break;

	case SER_MAIL_SENDING:
		if (smtp_mailtick() == SMTP_PENDING) {
			break;
		} else if (smtp_status() == SMTP_SUCCESS) {
			ser_ok(state);
		} else {
			ser_error(state, SER_ERR_SMTPERROR);
		}
		break;

	case SER_SHOW:
		SERputs("Current configuration:");
		SERputs("\r\n\tIP address:\t");
		SERputs(inet_ntoa(state->buffer,my_ip_addr));
		SERputs("\r\n\tNetmask:\t");
		SERputs(inet_ntoa(state->buffer,sin_mask));
		SERputs("\r\n\tGateway:\t");
		SERputs(inet_ntoa(state->buffer, _arp_gate_data[0].gate_ip));
		SERputs("\r\n\tHostname:\t");
		SERputs(main_id.name);
		SERputs("\r\n\tControl port:\t");
		sprintf(state->buffer,"%d",main_id.control_port);
		SERputs(state->buffer);
		SERputs("\r\n\tProg baud:\t");
		sprintf(state->buffer,"%ld",main_id.prog_baud);
		SERputs(state->buffer);
		SERputs("\r\n\tConsole baud:\t");
		sprintf(state->buffer,"%ld",main_id.console_baud);
		SERputs(state->buffer);
		SERputs("\r\n\tMail from:\t");
		SERputs(main_id.mail_from);
		SERputs("\r\n\tMail server:\t");
		SERputs(main_id.mail_serv);
		SERputs("\r\n\tBkgnd polling:\t");
		if(main_id.run_background_poll)
			SERputs("on");
		else
			SERputs("off");
		SERputs("\r\n");
		ser_ok(state);
		break;

	case SER_PASSWORD:
		if (state->numparams != 2) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		SERputs("Enter new passphrase: ");

		state->bufptr = state->buffer;
		state->state = SER_PASSWORD_GET;
		state->nextstate = SER_PASSWORD_AGAIN;
		break;

	case SER_PASSWORD_GET:
		if (SERrdUsed() > 0) {
			SERread(&data, 1, 0);
			state->timeout = set_timeout(SER_MAIN_TIMEOUT);
		} else {
			break;
		}
		if(state->sawcr == 1) {
			state->sawcr = 0;

			if ('\n' == data) {
				/* nothing - drop it */
				data = '\0';
			} else {
				/* character is ok */
			}
		}
		if('\0' == data) {
			/* empty char - drop it */
			break;
		} else if(('\r' == data) || ('\n' == data)) {
			/* end of line reached */
			if('\r' == data) {
				state->sawcr = 1;
			}
			if((state->bufptr - state->buffer) < BUFFER_SIZE) {
				*state->bufptr = '\0';
			}
			state->buffer[BUFFER_SIZE - 1] = '\0'; /* make sure we end in null */

			/* state->buffer has the password at this point */
			state->state = state->nextstate;
		} else {
			/* normal character - store it */
			if ((state->bufptr - state->buffer) < BUFFER_SIZE) {
				*state->bufptr = data;
			}
			state->bufptr++;
		}
		break;

	case SER_PASSWORD_AGAIN:
		memcpy(state->passbuf,state->buffer,PASSWORD_SIZE);
		state->passbuf[PASSWORD_SIZE - 1] = '\0';

		SERputs("\r\nEnter passphrase again: ");
		
		state->bufptr = state->buffer;
		state->state = SER_PASSWORD_GET;
		state->nextstate = SER_PASSWORD_FINISH;
		break;

	case SER_PASSWORD_FINISH:
		if(0 == strncmp(state->passbuf,state->buffer,strlen(state->passbuf))) {
			dtp_hash(state->passbuf, strlen(state->passbuf), main_id.passwd);
			dtp_backup_id();
			SERputs("\r\nPassphrase set.\r\n");
			ser_ok(state);
		} else {
//			SERputs("\r\nPassphrases do not match!\r\n");
			ser_error(state,SER_ERR_BADPASSPHRASE);
		}
		break;

	case SER_BAUD:
		if (state->numparams != 3) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		DISABLE_NET();
		main_id.prog_baud = atol(get_param(state->command,2));
		sprintf(state->buffer,"Programming baud set to : %ld\r\n",main_id.prog_baud);
		SERputs(state->buffer);
		dtp_backup_id();
		ENABLE_NET();
		ser_ok(state);
		break;

	case SER_CONS_BAUD:
		if (state->numparams != 3) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		main_id.console_baud = atol(get_param(state->command,2));
		sprintf(state->buffer,"Console baud being set to : %ld\r\n",main_id.console_baud);
		SERputs(state->buffer);
		dtp_backup_id();
		SERclose();
		SERopen(main_id.console_baud);
		SERwrFlush();
		SERrdFlush();
		ser_ok(state);
		break;

	case SER_RESET:
		if (state->numparams != 2) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		if (strcmpi(get_param(state->command, 1), "all") == 0) {
			state->nextstate = SER_RESET_ALL;
			SERputs("Are you sure you want to reset to factory defaults? (y/n): ");
		} else if (strcmpi(get_param(state->command, 1), "variables") == 0) {
			state->nextstate = SER_RESET_VARIABLES;
			SERputs("Are you sure you want to reset the variables? (y/n): ");
		} else if (strcmpi(get_param(state->command, 1), "files") == 0) {
			state->nextstate = SER_RESET_FILES;
			SERputs("Are you sure you want to reset the files? (y/n): ");
		} else {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}

		state->bufptr = state->buffer;
		state->state = SER_RESET_ANSWER;
		break;

	case SER_RESET_ANSWER:
		if (SERrdUsed() > 0) {
			SERread(&data, 1, 0);
			state->timeout = set_timeout(SER_MAIN_TIMEOUT);
		} else {
			break;
		}
		if(state->sawcr == 1) {
			state->sawcr = 0;

			if ('\n' == data) {
				/* nothing - drop it */
				data = '\0';
			} else {
				/* character is ok */
			}
		}
		if('\0' == data) {
			/* empty char - drop it */
			break;
		} else if(('\r' == data) || ('\n' == data)) {
			/* end of line reached */
			if('\r' == data) {
				state->sawcr = 1;
			}
			if((state->bufptr - state->buffer) < BUFFER_SIZE) {
				*state->bufptr = '\0';
			}
			state->buffer[BUFFER_SIZE - 1] = '\0'; /* make sure we end in null */

			state->state = SER_RESET_CHECK;
		} else {
			/* normal character - store it */
			if ((state->bufptr - state->buffer) < BUFFER_SIZE) {
				SERputc(data);
				*state->bufptr = data;
			}
			state->bufptr++;
		}
		break;

	case SER_RESET_CHECK:
		SERputs("\r\n");
		if(1 != strlen(state->buffer)) {
			ser_error(state, SER_ERR_BADPARAMETER);		// ??
			break;
		}
		if(('y' != state->buffer[0]) && ('Y' != state->buffer[0])) {
			ser_error(state, SER_ERR_CANCELRESET);
			break;
		}
		SERputs("reset!\r\n");
		state->state = state->nextstate;
		break;

	case SER_RESET_ALL:
		/* reset it all! */
		InitAllState();
		sspec_init();
		fs_format(0, FS_NUM_BLOCKS, 1);

		fs_reserve_blocks(((sizeof(main_id) + sizeof(server_spec) + sizeof(server_auth)) /
		                  ((int)FS_BLOCK_SIZE - FS_HEADER) + 1) * 2);

		/* backup the reset data to flash */		
		dtp_backup_id();
		
		/* reset serial console baud rate */
		SERclose();
		SERopen(main_id.console_baud);
		SERwrFlush();
		SERrdFlush();

		ser_ok(state);
		break;

	case SER_RESET_VARIABLES:
		sspec = 0;
		state->varbuflen = 0;
		while ((sspec = sspec_findnextfile(sspec, SERVER_HTTP)) != -1) {
			if (sspec_gettype(sspec) == SSPEC_VARIABLE) {
				sspec_remove(sspec);
			}
			sspec++;
		}
		dtp_backup_id();
		ser_ok(state);
		break;

	case SER_RESET_FILES:
		sspec = 0;
		state->varbuflen = 0;
		while ((sspec = sspec_findnextfile(sspec, SERVER_HTTP)) != -1) {
			if (sspec_gettype(sspec) == SSPEC_FILE) {
				templong = sspec_getfileloc(sspec);
				fdelete((FileNumber)templong);
				sspec_remove(sspec);
			}
			sspec++;
		}
		dtp_backup_id();
		ser_ok(state);
		break;

	case SER_HELP:
		if (state->numparams == 1) {
			SERputs("RabbitLink Console Version ");
			SERputs(CONSOLE_VERSION);
			SERputs("\r\n");
			location = lookup_help(NULL, NULL, NULL);
		} else if (state->numparams == 2) {
			location = lookup_help(get_param(state->command, 1),
			                       NULL, NULL);
		} else if (state->numparams == 3) {
			location = lookup_help(get_param(state->command, 1),
			                       get_param(state->command, 2),
			                       NULL);
		} else if (state->numparams == 4) {
			location = lookup_help(get_param(state->command, 1),
			                       get_param(state->command, 2),
			                       get_param(state->command, 3));
		}
		if (location != -1) {
			xmem2root(&state->total, location, 4);
			xmem2root(state->buffer, location+4, (int)state->total);
			state->buffer[(int)state->total] = '\0';
			state->num = 0;
		} else {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		state->state = SER_HELP_WRITING;
		break;

	case SER_HELP_WRITING:
		if (state->num < state->total) {
			state->num += SERputs(state->buffer + (int)state->num);
		} else {
			state->state = SER_HELP_FINISH;
		}
		break;

	case SER_HELP_FINISH:
		if (SERwrUsed() == 0) {
			ser_ok(state);
		}
		break;

	case SER_DELETE:
		if (state->numparams != 2) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		sspec = sspec_findname(get_param(state->command, 1), SERVER_HTTP);
		if (sspec >= 0) {
			if (sspec_getfiletype(sspec) == SSPEC_FSFILE) {
				location = sspec_getfileloc(sspec);
				if (location == -1) {
					ser_error(state, SER_ERR_FILENOTFOUND);
					break;
				}
				fdelete((FileNumber)location);
				sspec_remove(sspec);
			} else {
				// Not a flash file
				ser_error(state, SER_ERR_FILENOTFOUND);
				break;
			}
		} else {
			// File not found
			ser_error(state, SER_ERR_FILENOTFOUND);
			break;
		}
		dtp_backup_id();
		ser_ok(state);
		break;

	case SER_CREATEV:
		if ((state->numparams != 5) && (state->numparams != 6)) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		if (sspec_findname(get_param(state->command, 1), SERVER_HTTP) != -1) {
			ser_error(state, SER_ERR_DUPLICATE);
			break;
		}
		// Get the type of variable
		ptr = get_param(state->command, 2);
		if (strcmpi(ptr, "INT16") == 0) {
			// Check number of parameters
			if (state->numparams != 5) {
				ser_error(state, SER_ERR_BADPARAMETER);
				break;
			}
			// Check for space in our variable buffer
			if ((state->varbuflen + 2) > VAR_BUFFER) {
				ser_error(state, SER_ERR_NOVARSPACE);
				break;
			}
			templong = strtol(get_param(state->command, 4), &tailptr, 10);
			if (*tailptr != '\0') {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			if ((templong > INT_MAX) || (templong < INT_MIN)) {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			} else {
				tempint = (int)templong;
			}
			root2xmem(state->varbuf + state->varbuflen, &tempint, 2);
			retval = sspec_addxmemvar(get_param(state->command, 1),
			                          state->varbuf + state->varbuflen, INT16,
			                          get_param(state->command, 3), SERVER_HTTP);
			if (retval == -1) {
				ser_error(state, SER_ERR_NOVARSPACE);
				break;
			} else {
				state->varbuflen += 2;
			}
		} else if (strcmpi(ptr, "INT8") == 0) {
			// Check number of parameters
			if (state->numparams != 5) {
				ser_error(state, SER_ERR_BADPARAMETER);
				break;
			}
			// Check for space in our variable buffer
			if ((state->varbuflen + 1) > VAR_BUFFER) {
				ser_error(state, SER_ERR_NOVARSPACE);
				break;
			}
			templong = strtol(get_param(state->command, 4), &tailptr, 10);
			if (*tailptr != '\0') {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			if ((templong > CHAR_MAX) || (templong < 0)) {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			} else {
				tempchar = (char)templong;
			}
			root2xmem(state->varbuf + state->varbuflen, &tempchar, 1);
			retval = sspec_addxmemvar(get_param(state->command, 1),
			                          state->varbuf + state->varbuflen, INT8,
			                          get_param(state->command, 3), SERVER_HTTP);
			if (retval == -1) {
				ser_error(state, SER_ERR_NOVARSPACE);
				break;
			} else {
				state->varbuflen += 1;
			}
		} else if (strcmpi(ptr, "INT32") == 0) {
			// Check number of parameters
			if (state->numparams != 5) {
				ser_error(state, SER_ERR_BADPARAMETER);
				break;
			}
			// Check for space in our variable buffer
			if ((state->varbuflen + 4) > VAR_BUFFER) {
				ser_error(state, SER_ERR_NOVARSPACE);
				break;
			}
			templong = strtol(get_param(state->command, 4), &tailptr, 10);
			if (*tailptr != '\0') {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			if ((templong >= LONG_MAX) || (templong <= LONG_MIN)) {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			root2xmem(state->varbuf + state->varbuflen, &templong, 4);
			retval = sspec_addxmemvar(get_param(state->command, 1),
			                          state->varbuf + state->varbuflen, INT32,
			                          get_param(state->command, 3), SERVER_HTTP);
			if (retval == -1) {
				ser_error(state, SER_ERR_NOVARSPACE);
				break;
			} else {
				state->varbuflen += 4;
			}
		} else if (strcmpi(ptr, "FLOAT32") == 0) {
			// Check number of parameters
			if (state->numparams != 5) {
				ser_error(state, SER_ERR_BADPARAMETER);
				break;
			}
			// Check for space in our variable buffer
			if ((state->varbuflen + 4) > VAR_BUFFER) {
				ser_error(state, SER_ERR_NOVARSPACE);
				break;
			}
			tempfloat = strtod(get_param(state->command, 4), &tailptr);
			if (*tailptr != '\0') {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			root2xmem(state->varbuf + state->varbuflen, &tempfloat, 4);
			retval = sspec_addxmemvar(get_param(state->command, 1),
			                          state->varbuf + state->varbuflen, FLOAT32,
			                          get_param(state->command, 3), SERVER_HTTP);
			if (retval == -1) {
				ser_error(state, SER_ERR_NOVARSPACE);
				break;
			} else {
				state->varbuflen += 4;
			}
		} else if (strcmpi(ptr, "STRING") == 0) {
			// Check number of parameters
			if (state->numparams != 6) {
				ser_error(state, SER_ERR_BADPARAMETER);
				break;
			}
			templong = strtol(get_param(state->command, 5), &tailptr, 10);
			if (*tailptr != '\0') {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			if ((templong >= (LONG_MAX - 1)) || (templong <= 0)) {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			templong += 1;		// Add 1 for the null terminator
			if ((state->varbuflen + templong) > VAR_BUFFER) {
				ser_error(state, SER_ERR_NOVARSPACE);
				break;
			}
			if (strlen(get_param(state->command, 4)) > templong) {
				ser_error(state, SER_ERR_STRINGTOOLONG);
				break;
			}
			root2xmem(state->varbuf + state->varbuflen,
			          get_param(state->command, 4),
			          strlen(get_param(state->command, 4)) + 1);
			retval = sspec_addxmemvar(get_param(state->command, 1),
			                          state->varbuf + state->varbuflen,
			                          PTR16,
			                          get_param(state->command, 3), SERVER_HTTP);
			if (retval == -1) {
				ser_error(state, SER_ERR_NOVARSPACE);
				break;
			}
			main_id.varstrlen[retval] = (uint)templong;
			state->varbuflen += templong;
		} else {
			ser_error(state, SER_ERR_BADVARTYPE);
			break;
		}
		dtp_backup_id();
		ser_ok(state);
		break;

	case SER_GETV:
		if (state->numparams != 2) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		retval = sspec_findname(get_param(state->command, 1), SERVER_HTTP);
		if (retval == -1) {
			ser_error(state, SER_ERR_VARNOTFOUND);
			break;
		}
		if (sspec_gettype(retval) != SSPEC_VARIABLE) {
			ser_error(state, SER_ERR_NOTAVAR);
			break;
		}
		sspec_readvariable(retval, state->buffer);
		SERputs(state->buffer);
		SERputs("\r\n");
		ser_ok(state);
		break;

	case SER_PUTV:
		if (state->numparams != 3) {
			ser_error(state, SER_ERR_BADPARAMETER);
			break;
		}
		sspec = sspec_findname(get_param(state->command, 1), SERVER_HTTP);
		if (sspec == -1) {
			ser_error(state, SER_ERR_VARNOTFOUND);
			break;
		}
		if (sspec_gettype(sspec) != SSPEC_VARIABLE) {
			ser_error(state, SER_ERR_VARNOTFOUND);
			break;
		}
		varkind = sspec_getvarkind(sspec);
		if (varkind == INT8) {
			templong = strtol(get_param(state->command, 2), &tailptr, 10);
			if (*tailptr != '\0') {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			if ((templong > CHAR_MAX) || (templong < 0)) {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			} else {
				tempchar = (char)templong;
			}
			root2xmem(sspec_getxvaraddr(sspec), &tempchar, 1);
		} else if (varkind == INT16) {
			templong = strtol(get_param(state->command, 2), &tailptr, 10);
			if (*tailptr != '\0') {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			if ((templong > INT_MAX) || (templong < INT_MIN)) {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			} else {
				tempint = (int)templong;
			}
			root2xmem(sspec_getxvaraddr(sspec), &tempint, 2);
		} else if (varkind == INT32) {
			templong = strtol(get_param(state->command, 2), &tailptr, 10);
			if (*tailptr != '\0') {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			if ((templong >= LONG_MAX) || (templong <= LONG_MIN)) {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			root2xmem(sspec_getxvaraddr(sspec), &templong, 4);
		} else if (varkind == FLOAT32) {
			tempfloat = strtod(get_param(state->command, 2), &tailptr);
			if (*tailptr != '\0') {
				ser_error(state, SER_ERR_BADVARVALUE);
				break;
			}
			root2xmem(sspec_getxvaraddr(sspec), &tempfloat, 4);
		} else if (varkind == PTR16) {
			//xmem2root(&templong, sspec_getxvaraddr(sspec) - 4, 4);
			if ((strlen(get_param(state->command, 2)) + 1) > main_id.varstrlen[sspec]) {
				ser_error(state, SER_ERR_STRINGTOOLONG);
				break;
			}
			root2xmem(sspec_getxvaraddr(sspec),
			          get_param(state->command, 2),
			          strlen(get_param(state->command, 2)) + 1);
		} else {
			ser_error(state, SER_ERR_BADVARTYPE);
			break;
		}
		ser_ok(state);
		break;

	default:
		/* ERROR! */
		state->state = SER_INIT;
		break;
	}
}
		
#endif

/******************************
 * End serial console section *
 ******************************/

/**********************
 * Everything else... *
 **********************/

void dtp_hash(char *data, int length, char *digest)
{
	auto md5_state_t	md5_state;

	md5_init(&md5_state);
	md5_append(&md5_state, (md5_byte_t *)data, length);
	md5_finish(&md5_state, (md5_byte_t *)digest);
}

/*
 * This should be called to backup our identification + IP into flash
 */
void dtp_backup_id(void)
{
	auto int offset, len, retval;
	auto unsigned long temp;
	auto File f, *F;
	F = &f;
	
	main_id.top = 0xa5;
	main_id.my_ip_addr = my_ip_addr;
	main_id.sin_mask = sin_mask;
	main_id.gate_ip = _arp_gate_data[0].gate_ip;

	/* backup: main_id */
#ifdef DBG_BACKUP
	printf("dtp_backup_id()...\n");
#endif
	if(fcreate(F, NextBackupFile)) {
		/* Error! File allready exists? */
		goto backup_error;
	}
	
	/* write out the new version number */
	BackupVersion++;
#ifdef DBG_BACKUP
	printf("\tBackupVersion == %ld\n",BackupVersion);
#endif
	if(4 != fwrite(F, (char *)&BackupVersion, 4)) {
		goto backup_error;
	}
	
	/* write out the length of the config table */
	temp = sizeof(main_id);
#ifdef DBG_BACKUP
	printf("\tsizeof(main_id) == %ld\n",temp);
#endif
	if(4 != fwrite(F, (char *)&temp, 4)) {
		goto backup_error;
	}

	/* write out the actual config table */
	offset = 0;
	len = sizeof(main_id);
	while(offset < len) {
		retval = fwrite(F, (char *)&(main_id.top) + offset, len - offset);
		if(retval < 1) {
			/* error - no room */
			goto backup_error;
		}
		offset += retval;
	}

	/* write out the length of the spec table */
	temp = SSPEC_MAXSPEC * sizeof(ServerSpec);
#ifdef DBG_BACKUP
	printf("\tsizeof(ServerSpec[]) == %ld\n",temp);
#endif
	if(4 != fwrite(F, (char *)&temp, 4)) {
		goto backup_error;
	}

	/* write the actual spec table */
	offset = 0;
	len = SSPEC_MAXSPEC * sizeof(ServerSpec);
	while(offset < len) {
		retval = fwrite(F, (char *)&(server_spec[0]) + offset, len - offset);
		if(retval < 1) {
			/* error - no room */
			goto backup_error;
		}
		offset += retval;
	}

	fclose(F);
	/* remove the old file, if it exists */
	if(BACKUP_FILE1 == NextBackupFile) {
		fdelete(BACKUP_FILE2);
		NextBackupFile = BACKUP_FILE2;
	} else {
		fdelete(BACKUP_FILE1);
		NextBackupFile = BACKUP_FILE1;
	}
	
	return;	

backup_error:
	fclose(F);
	
	/* should we format the fs here? */
#ifdef DBG_BACKUP
	printf("ERROR: No room to backup config table in flash FS!\n");
#endif
	return; 
}

/* load the tables out of the file, returning 0 on success, 1 on error */
int load_table(File *F)
{
	auto unsigned long config_len, spec_len;
	auto unsigned long offset;
	auto int retval;	

#ifdef DBG_BACKUP
	printf("Reading old config table...\n");
#endif
	/* read out the version number */
	if(4 != fread(F, (char *)&BackupVersion, 4)) {
		/* error */
		return 1;
	}
#ifdef DBG_BACKUP
	printf("\tBackupVersion == %ld\n",BackupVersion);
#endif

	/* read out the length of the main config region */
	if(4 != fread(F, (char *)&config_len, 4)) {
		/* error */
		return 1;
	}
#ifdef DBG_BACKUP
	printf("\tsizeof(main_id) == %ld\n",config_len);
#endif
	if(config_len != (unsigned long)sizeof(main_id)) {
#ifdef DBG_BACKUP
		printf("ERROR: Filesystem contains config struct of incorrect version!\n");
		printf("\tsizeof(main_id) == 0x%08lx\tconfig_len == 0x%08lx\n",(unsigned long)sizeof(main_id),config_len);
#endif
		return 1;
	}
	
	/* read out the main configuration */
	offset = 0;
	while(offset < config_len) {
		retval = fread(F, (char *)&(main_id.top) + (int)offset, (int)(config_len - offset));
		if(retval < 1) {
			/* not enough data in file! */
			return 1;
		}
		offset += retval;
	}

	/* read out the length of the spec table */
	if(4 != fread(F, (char *)&spec_len, 4)) {
		/* error */
		return 1;
	}
#ifdef DBG_BACKUP
	printf("\tsizeof(ServerSpec[]) == %ld\n",spec_len);
#endif
	if(spec_len != (SSPEC_MAXSPEC * sizeof(ServerSpec))) {
#ifdef DBG_BACKUP
		printf("ERROR: Server spec table is of incorrect size!\n");
#endif
		return 1;
	}

	/* read the spec table */
	offset = 0;
	while(offset < spec_len) {
		retval = fread(F, (char *)&(server_spec[0]) + (int)offset, (int)(spec_len - offset));
		if(retval < 1) {
			/* not enough data in file! */
			return 1;
		}
		offset += retval;
	}

	/* all done! */
	my_ip_addr = main_id.my_ip_addr;
	sin_mask = main_id.sin_mask;
	_arp_gate_data[0].gate_ip = main_id.gate_ip;
	smtp_setserver(main_id.mail_serv);
	return 0;
}

void load_tables(void)
{
	auto FileNumber fname1, fname2;
	auto File *F1, *F2;
	auto File f1, f2;
	auto unsigned long v1, v2;

	F1 = &f1;
	F2 = &f2;
	fname1 = BACKUP_FILE1;
	fname2 = BACKUP_FILE2;

	if(fopen_rd(F1, fname1)) {
		/* no file 1 */
		if(fopen_rd(F2, fname2)) {
			/* we have neither file! Error! format the fs! */
			fs_format(0, FS_NUM_BLOCKS, 1);
			
		} else {
			if(load_table(F2)) {
				goto format_fs;
			}
			NextBackupFile = BACKUP_FILE1;
			fclose(F2);
			
		}
	} else {
		if(fopen_rd(F2, fname2)) {
			/* have file 1, but no file 2 */
			if(load_table(F1)) {
				goto format_fs;
			}
			NextBackupFile = BACKUP_FILE2;
			fclose(F1);
			
		} else {
			/* both exist! check the version numbers */
			if(4 != fread(F1, (char *)&v1, 4)) {
				/* error in read! try the other one */
				if(load_table(F2)) {
					/* error in send file as well - format everything! */
					goto format_fs;
				}
				fclose(F1);
				fclose(F2);
				fdelete(fname1); /* kill the first file - it was incorrect */
				NextBackupFile = BACKUP_FILE1;
				return;
			}
			if(4 != fread(F2, (char *)&v2, 4)) {
				/* error in read! try the other one */
				if(load_table(F1)) {
					/* error in send file as well - format everything! */
					goto format_fs;
				}
				fclose(F1);
				fclose(F2);
				fdelete(fname2); /* kill the first file - it was incorrect */
				NextBackupFile = BACKUP_FILE2;
				return;
			}

			/* got two version numbers - compare them */
			if(v1 < v2) {
				/* v1 is older - keep it */
				fseek(F1,0,SEEK_SET);
				if(load_table(F1)) {
					goto format_fs;
				}
				fclose(F1);
				NextBackupFile = BACKUP_FILE2;

				/* delete the incorrect one */
				fclose(F2);
				fdelete(fname2);
			} else {
				/* v2 is older - keep it */
				fseek(F2,0,SEEK_SET);
				if(load_table(F2)) {
					goto format_fs;
				}
				fclose(F2);
				NextBackupFile = BACKUP_FILE1;

				/* delete the incorrect one */
				fclose(F1);
				fdelete(fname1);
			}			
		}
	}
	/* all done */
	return;
	
format_fs:
	fclose(F1); /* make sure the files are closed */
	fclose(F2);
	fs_format(0, FS_NUM_BLOCKS, 1);
}

int TryTarget(void)
{
	auto int count;
	auto char temp[4];
	auto DTPRunProg *pkt;

	pkt = (DTPRunProg *)packet_data;

	WrPortI ( PCFR,  &PCFRShadow, PCFRShadow|0x10 );
	WrPortI ( TAT1R, &TAT1RShadow, 0 );
   WrPortI ( TACR,  &TACRShadow, TACRShadow|0x20 );   
	WrPortI ( TAT5R, &TAT5RShadow, 5 );

	serBopen(main_id.prog_baud);
	serBclose();

	/* try several times to poll the target */
	count = 5;
	while(count > 0) {
		count--;
		
		if(0 == ReadBlock(2,0,3,temp)) {
			/* the poll succeded */
#ifdef DBG_GEN
			printf("Succesfully started polling target!\n");
#endif

			/* prepare the target for debugging */
			ExecStepInit = 1;
			SingleStepInit();
			SetFirstBreakpoint();
			packet_pending = 0;
			
			packet_header.type = 0;
			dtp_pollinit();

			pkt->run = 0;
			if(ReadBlock(2,0,6,(char *)&(pkt->PC))) {
#ifdef DBG_GEN
				printf("ERROR: Couldn't read initial PC from target!\n");
#endif
				return 0;
			}
			//printf("---> calling dtp_startprog(0); (initial PC=0x%04x)\n",pkt->PC);
			dtp_startprog();
			packet_pending = 0;
			
			pkt->run = 1;
			dtp_startprog();
			packet_pending = 0;

			packet_header.type = 0;
			packet_pending = 0;
			
			BackgroundPoll = 1;
#ifdef DBG_GEN
			printf("All done starting target!\n");
#endif
			return 1;
		}

		HIT_WATCHDOG;
		sleep(POLL_INTERVAL);
	}

	return 0;
}

void StartTarget(void)
{
	if(TryTarget()) {
		return;
	}

	serBclose();
	
	/* no response */
#ifdef DBG_GEN
	printf("Unable to start polling on target - reseting board.\n");
#endif

	WrPortI ( PCFR,  &PCFRShadow, PCFRShadow|0x10 );
	WrPortI ( TAT1R, &TAT1RShadow, 1 );
   WrPortI ( TACR,  &TACRShadow, TACRShadow|0x20 );   
	WrPortI ( TAT5R, &TAT5RShadow, 144 );
	WrPortI ( SBCR, &SBCRShadow, 0 );
	WrPortI ( PBDR, &PBDRShadow, PBDRShadow | 0xC0 );		// Set SMODE[0,1] high
	WrPortI ( SPCR, &SPCRShadow, 0x84 );						// Enable /RESET line   
	WrPortI ( PADR, &PADRShadow, 0xFF );

	/* reset target */
#asm
		ld a, 0x01
ioi   ld (PADR), a			; take reset pin high		
	call ResetClone
#endasm
	sleep(300);
#asm
	call UnResetClone
#endasm

	sleep(500);

#asm
	ld	a,0x80
	ld	d,a
	ld	c,a
	ld	a,0x24
	ld	e,a
	call SendTriplet
#endasm
	
	sleep(3000); // wait for the target BIOS to start
	
	if(TryTarget()) {
		return;
	}

	/* no response */
#ifdef DBG_GEN
	printf("Unable to start polling on target!\n");
#endif	
}

void InitAllState(void)
{
	/* init everything to basic values */
	BackupVersion = 1;
	NextBackupFile = BACKUP_FILE1;
	main_id.control_port = CONTROL_PORT;
	main_id.prog_baud = PROG_BAUD;
	main_id.console_baud = CONSOLE_BAUD;
	strcpy(main_id.name,MY_NAME);
	dtp_hash("",0,main_id.passwd);
	dtp_hash("",0,main_id.consolepasswd);
	main_id.echo = SERIAL_ECHO;
	main_id.run_background_poll = 1;
	strcpy(main_id.mail_from, "nobody@nowhere.com");
	strcpy(main_id.mail_serv, "10.10.6.1");
	smtp_setserver(main_id.mail_serv);
}

main()
{
	ProgramBuffer = xalloc(PROGRAM_BUFFER_SIZE);
	/* init sub-buffer in ProgramBuffer, for after the download */
	GetcharBuf = ProgramBuffer;
	// NextBuf = GetcharBuf + GETCHAR_BUFSIZE
	
	poll_oldpacket = &poll_oldpacketDATA;
	poll_newpacket = (DTPPollResults *)packet_data;

	/* init the watchdog timer */
	wd = VdGetFreeWd(WATCHDOG_TIMEOUT);

	InitAllState();

   WrPortI ( PCFR,  &PCFRShadow, PCFRShadow|0x10 );
   WrPortI ( SBCR, &SBCRShadow, 0 );
   WrPortI ( SPCR, &SPCRShadow, 0x84 );						// Enable /RESET line   
   WrPortI ( PBDR, &PBDRShadow, 0x00 );						// Set SMODE[0,1] low
#asm
	call UnResetClone
#endasm

	/* init everything */
	sock_init();
	bc_usrledon();

	/* load from flash main_id and my_ip_addr here */
	/* (opposite of dtp_backup_id() ) */

	/* do we have a filesystem in flash? if so, try to mount it */
	if(fs_init(0, FS_NUM_BLOCKS)) {
		/* problem initializing the filesystem - format it? */
	}

	/* check for duplicate files for our ID stuff, and load the tables */
	load_tables();

	fs_reserve_blocks(((sizeof(main_id) + sizeof(server_spec) + sizeof(server_auth)) /
	                  ((int)FS_BLOCK_SIZE - FS_HEADER) + 1) * 2);
	                  
	/* init the main daemons */
	dtp_broadcastinit();
 	dtp_controlinit();
#ifdef SERIAL_CONSOLE
	dtp_consoleinit();
#endif

	/* try and regain communication with the target */
	if(main_id.run_background_poll)
		StartTarget();
	
	/* run everything in a big loop */
	for(;;) {
		HIT_WATCHDOG;
		
		dtp_broadcasthandler();
		dtp_controlhandler();
#ifdef SERIAL_CONSOLE
		dtp_consolehandler();
#endif
		tcp_tick(NULL);
	}
}
