/* Julian H. Stacey's Portable Monitor <jhs@	*/
/* frozen copy just about still usable for CPM, 
	but leaving all the CPM stuff in was too painful,
	so all references to CPM removed,
	 & program converted for msdos & unix,
	not yet good enough to use on msdos or unix
	(even though it was fully debugged when used on cpm)

	This copy is _only_ for cpm, use my more modern publicly 
	available copy for anything else.
*/
#ifdef MSDOS	/* { */
#include	"grep_msd.h"
#endif		/* } */

char	sccsID[] = "\n@(#) Monitor V2.0 : 22nd August 1987 Version.\n";

/* Copyright, Julian H. Stacey, January 1984 Canterbury, England.
*  '96 Munich Germany.
*
* Facilities Included (in order):
* 	1 Designed for NSC 16032 CPU ( & runable on Z80 also ) .
*	1.5 Converted for MSDOS 8086
* 	2 Help List.
* 	3 Intel Object IO
* 	4 Shortform hex input.
* 	5 Chained access (can use '+' & '-' as combined '+' & 'CR' or '-' & 'CR'
* 	6 Relative + Base addressing.
* 	7 Interrupt.
* 	8 To simplify data entry typing during sequential data entry,
* 	  instead of terminating data with 'white space' (the normal way),
* 	  a '+', '-', or '.' can be used to terminate input, the monitor
* 	  then doesn't need white space until you wish to end chain data entry.
* 	9 Developed on Xerox CP/M system using C80 compiler.  Then MSDOS
*	10 Uses minimum number of library/(macro ?) calls in order to
*	   ease transportation to ROM.
*
*
* Limitations (in order):
* 	1 Needs ram for variables & stack frame, not a 'ROM only' monitor.
* 	2 No dis-assembler.
* 	3 Not relocatable (using CPM C80 C to 8080 compiler).
* 	4 No machine specific features such as
* 		data sectors for normal/system, data/stack/code.
* 	5 Object IO : only data & EOF records implemented,
*	  no relocatable records.
* 	6 No Backspace (delete a numeric character by shifting it off the end,
* 	  with 6 or less zeros.
* 	7 No symbolic labels.
*
* Pending Improvement (in order):
* 	1 Suppres print of some CRs
*  	2 SIO Driver
* 	3 Validate/test case needs to generatethe dram time-out
*	  chain branch code.
* 	4 SWI needs code to vector to & execute.
* 	5 + CR + CR + CR squences are tedious, could a CR be assumed
* 	  until terminating dot.
*
* Design Aims (in order):
* 	1 To have a portable monitor usable on the NS16000  & 8086 series.
* 	2 To have a 'rich' list of facilities.
* 	3 Portability: to be usable for other 32 bit & lesser processors
* 	  with a minimum of modification. (Hence written in 'C' but with
* 	  more explicit entity sizes than C normally provides).
* 	4 To optimise for minimum code size rather than maximum speed (except
* 	  lengthy ram validate case).
*
* Assumptions:
* 	1 VDU does a CR LF if sent a CR.
*
* Notes:
*	1 signal(10,1) & signal(11,1) should override segmentation
* 	  violations if debugging on a proper unix(though untried !)
* 	2 JJ & ?? = edit search key implying needs attention later
*
* Bugs:	interrupt, doesnt
* 	disc_rw() sets drive to A, but shouldnt
*/

/*----------------------------------------------------------------------*/

#ifndef	CPM
#include	<stdio.h>
#else
extern	char	Cmode;	/* CPM C80 unbuffered io flag */
extern	int 	CtlB;	/* address of a procedure to be excuted on a
				Control B interrupt */
#endif	CPM

/*----------------------------------------------------------------------*/

/* Programming entities to aid portability */
#define	s32bit	long
#define	x32bit	long
/*	u32bit	CPM C80 compiler doesn't provide this */

#define	u16bit	unsigned
#define	x16bit	int
#define	s16bit	int

/*	u8bit	unused */
#define	x8bit	char
/*	s8bit	unused */
#define	u8more	unsigned
#define	x8more	char
#define	s8more	int

/*----------------------------------------------------------------------*/

#ifdef	CPM
#define	pointer	long
	/* should be 'char *', but CPM C80 only gives 16 bits for that */
#else
#define	pointer	char *
#endif

#ifdef	CPM
int	*pc80w; long	*pc80l, *pc81l;
/* Pointers for intermediate evaluation solely for use by CPM C80
* C to 8080 compiler, as CPM C80 fails to convert longs to
* [pointers to ints or longs] properly,
* note this only needs to be here whilst developing the monitor,
* on the 8080, when monitor is running on a 32bit machine, theyre redundant
*/
#endif	CPM

#define	BEL	7

/*----------------------------------------------------------------------*/

/* Flags to define mode of operation of the monitor */

u8more	size;		/* Entity Type */
#define	STRING		0
#define	BYTE		1
#define	WORD		2
#define	LONG		3

u8more	mod_access;		/* Access Modes */
#define	READ		1
#define	WRITE		2
#define	MODIFY		READ | WRITE

x8more	verbose ;	/* if flag is off monitor gives no prompts
			   (useful for remote control of monitor
			   by another program */
x8more	ascii ;		/* if set & also in byte read mode,
			   prints text as well as hex, else prints just hex */
x8more	intrupt ;	/* used to indicate when keyboard has interrupted
			   a sequence. */
x8more	dump ;		/* if 1 write to disc (& possibly screen),
			   if 0 write to screen */
x8more	load ;		/* if 1 [read from disc & display on screen]
			   if 0 read from keyboard */
x8more	supres;		/* used to suppress multiple CR LFs
			   coming in from keyboard */

/*----------------------------------------------------------------------*/

/* 			Current address monitor is looking at.
*			'relative' & 'base' are the masters, 'total' is
*			a derived dependant, these pointers are used to
*			specify beginning of block operations, as well as
*			discrete operations 
*/
long	relative ;

/*			define b_ent for use everywhere for typecasting,
			depending if we are using CPM 8080 or MSDOS 8086 */
#ifdef	MSDOS
#define	b_ent	pointer	
#endif	MSDOS
#ifndef	MSDOS
#define	b_ent	long	
#endif	MSDOS
b_ent	base;

pointer total;
pointer	stop;		/* end of source block & scratch pointer */
pointer	dest ;		/* destination block & scratch pointer */
pointer	mem_p ;		/* yet another scratch pointer */

			/* temporary variable used within individual cases,
			& to ensure CPM C80 deals with corect sizes */
union 	cludge
	{
	char	s1;
	unsigned s2;
	long	s4;
	} data ;

u8more	cksum ;		/* Checksum for intel loader format*/
#ifndef	CPM
FILE	*chan;		/* used by object io */
#else
int	chan ;
#endif
x8more	hexcom;

/*----------------------------------------------------------------------*/

#define	FILE_LN	15		/* Max length name is a:monitor_.obj + null */
char	filnam[FILE_LN];	/* filename for object IO (in RAM) */
char	initnam[] = "DUMP.OBJ"; /* initial filename (in EPROM) */
char	*name_p ;		/* pointer to current filename */

char	menu[] = "(For menu type \'?\')";

/*----------------------------------------------------------------------*/

#ifdef	CPM
/* Called by CPM C80 environment when interrupt has been noticed
	(can only be noticed by calling CtlCk periodically */
interupt() { putchar(BEL); intrupt = 1; }
x8more	intj() { CtlCk(); return(intrupt); }
#else
/* JJLATER hack this to signals */
interupt() { putchar(BEL); intrupt = 1; }
x8more	intj() { return(0); }
#endif

/*----------------------------------------------------------------------*/

x8more isletter(x) char x;
	{ if ((x >= 0x20) && (x <= 0x7E)) return(1) ; else return(0); }

/* Put a character to screen after seeing if a control B
		keyboard interrupt has occured */
putjch(ch_x)
	char	ch_x;
	{
#ifdef	CPM
	CtlCk();	/* call 'interupt' if one has occured */
#else	CPM
	/* JJLATER add something */
#endif	CPM
	ch_x &= 0x7F ;	
/* 			make sure I put out no parity, also
*			make it a litle bit hard to modify the
*			copyright notice if the recipient only has a binary
*/
#define	BS	8
#define	HT	9
#define	NL	0xA
#define	CR	0xD
#define	NULL	0
	if (isletter(ch_x) || ch_x == BEL || ch_x == BS || ch_x == HT
		|| ch_x == NL || ch_x == CR || ch_x == NULL )
/*		mask out chars that will cause cursor addressing screen
*		disturbances, but allow nulls, as they may be used to
*		implement time delays 
*/
		{
		if (!dump) putchar(ch_x);
		else if (verbose) putchar(ch_x);
		}
	if (dump) putc(ch_x, chan);
	}

/*----------------------------------------------------------------------*/

/* Error messages defined so they are consistently the same,
& can be recognised by a remote control program */

error(x)
	char	*x; { putjch(BEL); prints( "\nERROR : ") ; prints(x); }

/*----------------------------------------------------------------------*/

/* Ensure same spacing on screen for reads & writes */
#define	prompt		prints(" ? ")
#define	space		prints("   ")

/*----------------------------------------------------------------------*/

/*The output procedures in this section could be defined by macros, but not
using CPM C80, as the pre-processor refuses parameters */

/* Output Routines */

put1h(x)  char	 x; { if (x < 10) putjch(x + '0'); else putjch(x -10 + 'A'); }
put2h(x)  x8bit  x; { cksum += x ; put1h((x >>4 ) & 0xF) ; put1h(x & 0xF); }
put4h(x)  x16bit x; { put2h((x >> 8)  & 0xFF   ); put2h(x & 0xFF)  ; }
put8h(x)  x32bit x; { put4h((x >> 16) & 0xFFFF ); put4h(x & 0xFFFF); }
putadr(x) x32bit x; { put2h(x >> 16   & 0xFF   ); put4h(x & 0xFFFF); }
out1h(x)  char	 x; { putjch(' '); put1h(x);  }
out2h(x)  x8bit  x; { putjch(' '); put2h(x);  }
out4h(x)  x16bit x; { putjch(' '); put4h(x);  }
out8h(x)  x32bit x; { putjch(' '); put8h(x);  }
outadr(x) x32bit x; { putjch(' '); putadr(x); }

/*----------------------------------------------------------------------*/

#define	ADDER	total = relative + base ;

/*----------------------------------------------------------------------*/

/* Set 'total' to be consistent with 'relative' & 'base' masters, then display*/
setloc(lf)
	x8more	lf;	/* line feed flag */
	{
	ADDER
	outloc((x8more)lf) ;
	}

/* Display current location addresses */
outloc(lf)
	x8more	lf;
	{
	if (lf) vputjch('\n');
	if (base != (b_ent)0)
		{
		vprints( "B:" );
		putadr((x32bit)base);
		vprints( "+R:" );
		}
	putadr((x32bit)relative);
	if (base != (b_ent)0)
		{
		vprints( "=T:" );
		putadr((x32bit)total) ;
		}
	putjch(' ');
	}

put_byte(x)
	x8bit x;
	{
	put2h(x);
	put_letr(x);
	}

put_letr(x)
	x8bit x;
	{
	putjch(' ');
	if ( ascii && isletter(x)) putjch(x) ;
	else putjch(' ');
	}

vputjch(x)
	char	x;
	{
	if (verbose) putjch(x);
	}

/*-------------------------------------------------------------------------*/

/* String procedures */
vprints(x)
	char	*x;
	{
	if (verbose) prints(x);
	}

jgets(name,len)	/* Puts a null terminated string into array *'name',
		where array is of max. size (inc null) 'len') */
	char	*name ;
	x8more	len;
	{
	s8more	c ;
	char	*limit ;
	limit = name + len -1 ;
#ifdef	CPM
#define	EOF	-1
#endif
			/* End of file (CPM C80 defines this as -1) */
	while ( name < limit && (c = getjch()) != '\n' && c != EOF)
		{ *name = (char)c ; name++ ; }
	*name = '\0';
	}

prints(x)
	char	*x;
	{
	while((*x) && !intrupt)
		{
		/* if necessary for VDU
			if (*x == '\n') putjch('\r');	*/
		putjch(*x);
		x++;
		}
	}

/*----------------------------------------------------------------------*/

/* Input Routines */

/* Gets a charater from disc or keyboard */
	s8more	
getjjch()
	{
	s8more	x ;
	x = ( load ? getc(chan) :
#ifdef	CPM
				 getchar()
#endif	CPM
#ifdef	MSDOS
				 getche()
#endif	MSDOS
#ifdef	UNIX
				 getchar() /* something else needed */
#endif	UNIX
						 ) ;
	if ( x != EOF)
		{
		x &= 0x7F ;	/* strip parity bit */
		if (load && verbose) putchar((char)x); 
			/* show whats coming from disc */
		}
	return(x);
	}

/* Strips multiple CR LF NULL strings to a single LF,
	to deal with CPM and nasty terminals.*/
s8more	
getjch()
	{
	s8more	x;
	if (supres)	/* ignore 'CR's etc */
		{
		do x = getjjch() ;
			while (x == '\r' || x == '\n' || x == '\0');
		supres = 0;	/* allow next time to return a CR */
		}
	else	{	/* may get a 'CR' etc */
		x = getjjch() ;
		if ( x == '\r' || x == '\n' || x == '\0')
			{
			supres = 1 ;
			x = '\n';
			}
		}
	return(x);
	}

/* Gets one hex from keyboard returns -1 if a 'white space' character
	or comma, Assumptions : ASCII */
s8more	
geth()
	{
	s8more	data ;
	hexcom = 0;
	for(;;)
		{
		data = getjch() ;
		if (data >= '0' && data <= '9') return(data - '0');
		if (data >= 'A' && data <= 'F') return(data - 'A' + 10 );
		if (data >= 'a' && data <= 'f') return(data - 'a' + 10 );
		if (data == ' ' || data == '\t' || data == ',' ||
			data == '\n' || data == EOF ) return((s8more)-1);
		if (data == '+' || data == '-' || data == '.')
			{
			hexcom = data;
			vprints("\b \n");
			return((s8more)-1);
			}
		error( "\nHex characters only please.\n" );
		}
	}

/* The hex input routines below allow number correction by allowing continuous
input up to a 'white space' - excess most significant nibbles
fall of the left end */

x8bit 
get2h()	/* Get a byte containing 2 hex chars */
	{
	int	temp1, temp2;
	while (( temp1 = geth()) == -1);
	while (( temp2 = geth() ) != -1 ) temp1 = (temp1 << 4) | temp2 ;
	return((x8bit)temp1);
	}

u16bit 
get4h()
	{
	int	temp; u16bit result;
	while ((result = geth())  == -1);
	while ( ( temp = geth() ) != -1 )
		result = (result << 4) | temp ;
	return(result);
	}

x32bit 
get8h()
	{
	int	temp; s32bit result;
	while (( result = geth()) == -1 ) ;
	while ( ( temp = geth() ) != -1 )
		result = (result << 4) | temp ;
	return(result);
	}

/* 'getadr' jgets an address, it is separate from 'get8h' for 2 reasons :
	1	in case fixed format input is one day required.
	2	Current NS16K family adresses are 24 bits (= 6 hex) */
#ifdef	CPM
x32bit 
#else
pointer
#endif
getadr()
	{
	x32bit temp;
	temp = get8h();
	return(
#ifndef	CPM
		(pointer)
#endif
			(0xFFFFFF & temp));
	}

/*----------------------------------------------------------------------*/

/* Gets 2 hex chars for object loader, no more, no less,
	no terminating white space required */
u8more	
take2h()
	{
	x8bit temp;
	temp = geth() << 4 ;
	temp |= geth() ;
	cksum += temp ;
	return(temp);
	}

/*----------------------------------------------------------------------*/

/* Display [current, previous, or next] : [byte, word, long, or string] */
present(disp)
	s8more	disp ;	/* Displacement of next entity to be accessed
			with case '.' , '+' , or '-' */
	{
	unsigned count ;
	x8bit tmp;
	if ( size == BYTE)
		{
		relative += disp;
		setloc(0); /* 'outloc' would be a bit faster, but less safe */
		if (mod_access & READ)
			{
			space;
			put_byte(*(char *)total);
			}
		if (mod_access & WRITE)
			{
			prompt;
			*(char *)total = get2h();
			}
		}
	else if ( size == WORD)
		{
		relative += 2 * disp;
		setloc(0);
#ifdef	CPM
		pc80w = (int *)total;
#endif
		if (mod_access & READ)
			{
			space;
#ifdef	CPM
			out4h(*pc80w);
#else
			out4h(*(int *)total);
#endif
			}
		if (mod_access & WRITE)
			{
			prompt;
#ifdef	CPM
			*pc80w = get4h();
#else
			*(int *)total = get4h();
#endif
			}
		}
	else if ( size == LONG )
	 	{
		relative += 4 * disp;
		setloc(0);
#ifdef	CPM
		pc80l = (long *)total;
#endif
		if (mod_access & READ)
			{
			space;
#ifdef	CPM
			out8h(*pc80l) ;
#else
			out8h(*(long *)total);
#endif
			}
		if (mod_access & WRITE)
			{
			prompt;
#ifdef	CPM
			*pc80l = get8h();
#else
			*(long *)total = get8h();
#endif
			}
		}
	else	/* STRING */
		{
		relative += disp;
		if (mod_access & READ)
			{
			setloc(0);
			count = 300;	/* limit printout to give a feel of
					where you are, without printing
					kilobytes (in case the string
					you are printing is a source file ! */
			space;
			while ((*(char *)total) && count-- && !( intrupt))
				{
				putjch(*(char *)total);
				total += 1;
				}
			}
		if (mod_access & WRITE)
			{
			setloc(0);
			prompt;
			while ((tmp = getjch()) != '\n' && tmp != EOF)
				{
				*(char *)total = tmp;
				total += 1 ;
				}
			/* note no null appended to string */
			}
		}
	if (!(mod_access & WRITE)) vputjch('\n');
	}

/*----------------------------------------------------------------------*/

/* Procedures used by the ram block validate case */

tstrfrsh()	/* test dram refresh (see separate note in hardware document) */
	{
/* #define	CHAIN_LN	10000
	u8bit jumpchain[CHAIN_LN];
	pointer ptr;
 	for (ptr = jumpchain,  ;  ;  ) ;
	*/
	}

int
initblk(x)	/* Initialise Block to x (normally 0 or 0xFF),
		Returns: 1=success, 0=fail */
	x8bit x;
	{
	for (relative = dest; relative <= stop; relative++)
		{
		ADDER
		*(x8bit *)total = x;
		}
	tstrfrsh();
	for (relative = dest; relative <= stop; relative++)
		{
		ADDER
		if (*(x8bit *)total != x)
			{
			checkerr(x);
			return(0);
			}
		}
	return(1);
	}


x8bit rolb(x)	/* rotate byte 1 bit to left */
	x8bit x;
	{ if ( x & 0x80 ) return((x << 1 ) | 1); else return(x<<1); }

x8bit rorb(x)	/* rotate byte 1 bit to right */
	x8bit x;
	{if (x&1) return( ( x >> 1 ) | 0x80); else return( ( x >> 1 ) & 0x7F); }

checkerr(x)
	x8bit x;
	{
	error("\nData lost at ");	outloc((x8more)0);
	prints("Data is ");		put2h(*(x8bit *)total);
	prints(", but should be ");	put2h(x); putjch('\n');
	}

checblok(dir,bits)	/* check block for correctly set rotating 1s or 0s
			(return 1 if ok, 0 if not) */
	s8more	dir;
	{
	x8bit data;
	tstrfrsh();
	if (dir == 1) /* increment through block */
		{
		for (relative = dest, data = bits;
			relative <= stop;
			relative++, data = rolb(data))
			{
			ADDER
			if ( *(x8bit *)total != data )
				{
				checkerr(data);
				return(0);
				}
			}
		}
	else /* decrement through block */
		{
		for (relative = stop, data = bits;
			relative >= dest;
			relative--, data = rorb(data))
			{
			ADDER
			if ( *(x8bit *)total != data )
				{
				checkerr(data);
				return(0);
				}
			}
		}
	return(1);
	}

/* Prints error message for ram validation case.
	For when main case validate code has detected either :
		incompletely decoded address common with a
		previous address in the ram block under test,
	OR interference between the block & this monitor's ram workspace */
shorterr(dir,initial)
	s8more	dir;
	x8bit initial;
	{
	error("Possible incomplete decoding with a ");
	if (dir == 1) prints("lower"); else prints("higher");
	prints(" address");
	checkerr(initial);
	}


/*----------------------------------------------------------------------*/

/* Set disc to r/w (if you have changed discs while the monitor is running
cpm will have made it read only, so make it now read/write.
Side effect: logged disc becomes drive A/0  */
disc_rw()
	{
#ifndef	CPM
/*
#endif
#asm
	PUSH	B
	PUSH	D
	PUSH	H
	MVI	C,0DH
	CALL	5
	POP	H
	POP	D
	POP	B
#endasm
#ifndef CPM
*/
#endif
	}

/*----------------------------------------------------------------------*/

s8more	out_open()
	{
	ADDER
 	disc_rw();
	if ((chan = fopen(name_p, "w")) == 0)
		{
		error("Cant open ") ;prints(name_p);
		return(0);
		}
	dump = 1;
	return(1);
	}

s8more	
in_open()
	{
	if (relative ==0 && base == (b_ent)0)
		{
		error("Probable overwrite of monitor - so aborted");
		return(0);
		}
	ADDER
	if ((chan = fopen(name_p,"r")) == 0)
		{
		error("Cant open ") ; prints(name_p);
		return(0);
		}
	load = 1;
	return(1);
	}

sort()
	{
	if (stop < total)
		{	/* dont chage mem_p to another temporary variable,
			as 'main' kows it is set */
		mem_p = total ;
		total = stop ;
		stop = mem_p ;
		}
	}

upto()
	{
	prints("Up to, ");
	}

status()	/* Print Monitor Status */
	{
	vprints("Status: ");
	setloc(0);
	if (mod_access & READ ) prints("Read, ");
	if (mod_access & WRITE ) prints("Write, ");
	if (( size) == BYTE ) prints("Byte");
	if (( size) == WORD ) prints("Word");
	if (( size) == LONG ) prints("Long");
	if (( size) == STRING ) prints("Text");
	prints(", Hex");
	if ( ascii ) prints("+Ascii,");
	else prints(" Only,");
	if ( verbose ) prints(" Verbose,");
		else prints(" Quiet,") ;
	prints(" \'");
	prints(name_p);
	prints("\'\n");
	}

/*----------------------------------------------------------------------*/

main()
	{
	s8more	command;	/* command character */
	int	(*procptr)();	/* procedure pointer */
	x8more	count1,count2;	/* general count variable */
	x8more	forever;	/* loop forever flag */
	s8more	validir;	/* validate case: values 1, 0, -1, represent
				whether incrementing, or decrementing through
				block, or finished */
#define	FINDSTR 30
	char	findstr[FINDSTR];
	x8bit	bits1,bits2,bits3;	/* ram validate case: bit patterns of least
				significant byte in the block */
	x8more	init0F;		/* ram validate case: 0 or FF block
					initialise data */
	s8more	chint;		/* gen. purpose char declared as int
					so can get EOF */
	u16bit	load_tmp;	/* used by load object case */
	x8more	eof_flg;	/* used by load object case,
				0 until an EOF record encountered, 1 after */

colds:		/* cold start point */

/* initialise target micro port here */

#ifdef CPM
	Cmode = 0;		/* set single char IO (unbuffered mode) */
	CtlB = (int)interupt;	/* load the CPM C80 interupt vector
				with the address of my routine */
#endif
#ifdef UNIX
	/* add something */
#endif
/* ifdef MSDOS nothing necessary */

	printf("This program is being ported, even though it compiles, it is not ready for use.\n");
	prints(sccsID);
#define	DEFENSE	0x80
	putjch('C'+DEFENSE);
	putjch('o'+DEFENSE);
	putjch('p'+DEFENSE);
	putjch('y'+DEFENSE);
	putjch('r'+DEFENSE);
	putjch('i'+DEFENSE);
	putjch('g'+DEFENSE);
	putjch('h'+DEFENSE);
	putjch('t'+DEFENSE);
	putjch(' '+DEFENSE);
	putjch('J'+DEFENSE);
	putjch('a'+DEFENSE);
	putjch('n'+DEFENSE);
	putjch('.'+DEFENSE);
	putjch(' '+DEFENSE);
	putjch('8'+DEFENSE);
	putjch('4'+DEFENSE);
	putjch(' '+DEFENSE);
	putjch('J'+DEFENSE);
	putjch('u'+DEFENSE);
	putjch('l'+DEFENSE);
	putjch('i'+DEFENSE);
	putjch('a'+DEFENSE);
	putjch('n'+DEFENSE);
	putjch(' '+DEFENSE);
	putjch('S'+DEFENSE);
	putjch('t'+DEFENSE);
	putjch('a'+DEFENSE);
	putjch('c'+DEFENSE);
	putjch('e'+DEFENSE);
	putjch('y'+DEFENSE);
	putjch('.'+DEFENSE);
	prints(menu);

	relative = 0;
#ifdef	DEBUG	/* { */
#define	BASE	0x4000
			/* This so that whilst debuging the monitor in ram
			under CPM you dont inadvertently stamp over the binary*/
	base = (b_ent)BASE ;
#endif	DEBUG	/* } */
	size = BYTE ; mod_access = READ ;
	verbose = 1; ascii = 1; supres = 0;
	name_p = &initnam[0] ;

/*----------------------------------------------------------------------*/

warms:		/* Warm Start Point */
hexcom = 0;
putchar('\n');
for (;;) 	/* Main Command Loop Of Monitor */
	{
	if ((hexcom) && ( command == '+' || command == '.' || command == '-' ))
		{	/* user wants to chain write, he has previously
			typed a '+', '-', or '.' */
		command = hexcom ;
		hexcom = 0 ;
		}
	else	{
		intrupt = 0;	/* ensure prompt string will appear */
		/* vputjch('\n'); only needed if keyboard doesnt have local echo */
		vprints( "> " );
		command=getjch();
		vprints("\b\b\b   \b\b\b");
		}
	intrupt = 0 ;	/* flush any interrupts */
	if (command <= 'Z' && command >= 'A') command += 'a' - 'A' ;
	switch(command) {
		case EOF:error("End of file read.\n");
			break ;
		case'\n':/* NULL :ignore carriage returns & line feeds */
			break ;
		case'0':/* Set monitor back to initial mode */
			relative = 0 ;
#ifdef	DEBUG	/* { */
			base = (b_ent)BASE ;
#endif	DEBUG	/* } */
			size = BYTE ; mod_access = READ ;
			verbose = 1; ascii = 1;
			name_p = &initnam[0] ;
			/* no break */
		case's':status();
			break;
		case'r':vprints("Read mode." );vputjch('\n');
			mod_access = READ ;
			break;
		case'w':vprints( "Write mode." );vputjch('\n');
			mod_access = WRITE ;
			break;
		case'm':vprints( "Modify mode (= read then write)." ); 
			vputjch('\n') ;
			mod_access = MODIFY ;
			break;
		case'\"':vprints( "Text mode." );vputjch('\n');
			size = STRING ;
			break;
		case'1':vprints("Byte mode." ); vputjch('\n') ;
			size = BYTE ;
			break;
		case'2':vprints("Word mode." ); vputjch('\n') ;
			size = WORD ;
			break;
		case'4':vprints("Long mode." ); vputjch('\n') ;
			size = LONG ;
			break;
		case'v':prints( "Verbose mode." ); vputjch('\n') ;
			verbose = 1;
			break;
		case'q':vprints( "Quiet mode." ); vputjch('\n') ;
			/* Tell user before you stop talking to them */
			verbose = 0;
			break;
		case'\'':vprints("Ascii+hex mode."); vputjch('\n');
			ascii = 1;
			break;
		case'h':vprints("Hex only mode." ); vputjch('\n') ;
			ascii = 0;
			break;
		case'a':vprints("Relative address = ? ");
			relative = getadr();
			setloc(0);
			break ;
		case'b':vprints( "Base address = ? ");
			base=(b_ent)getadr();
			setloc(0);
			break;
		case'n':vprints("File name = ? ");
			jgets(&filnam[0],FILE_LN) ;
			name_p = &filnam[0] ;
			break ;
		case'e':prints( "Exiting Monitor.\n" );
			exit();	/* This will do a return to a CP/M monitor
				or Unix shell if called from there (probably
				whilst testing this program */
			break;
		case'g':vprints("Goto where ? " );
			relative = getadr();
			prints("\nCAN\'T GOTO ");	
				/* C wont allow a changeable goto
				(exept by using machine dependant
				self modifying code). */
			setloc(0);
			break ;
		case'x':prints( "Call where ? " );
			relative = getadr();
			vprints( "Calling");
			setloc(0);
			procptr = total;
			(*procptr)();
			break;
		case'.':/* Access at same address, (useful if you have
				just changed modes, or wish to
				repeatedly access a port */
			present(0) ;
			break;
		case'+':/* advance & display */
			present(1);
			break;
		case'-':/* back & display */
			present(-1);
			break;
		case'!':prints( "SWI set at " );
			setloc(0);
#define	BREAKPOINT	0xF2
			/* set as appropriate for machine
			& remember to type cast for the correct size */
			/* 0xF2 for NS16K */
			*(char *)total = BREAKPOINT;
			break;
		/* Processor status */
		case'p':error( "Processor Status Not Implemented\n");
			/* use #ASM, do a SWI (Software Interrupt),
			then do a stack unwind */
			break;
		case'd':vprints( "Display");	/* a block of text or string */
			ADDER
			if (size != STRING)
				{
				mem_p = relative ;
				prints(": LENGTH ? ");
				stop = getadr() ;
/* 					length of display from current position
*					to lower address. This is a cludge
*					based on the fact I known
*					'getadr' can actually return negatives,
*					even though it is defined as a pointer
*					JJLATER is this true for MSDOS & UNIX
*/
				if (size == WORD) stop = 
					(pointer)((long)stop * 2) ;
				if (size == LONG) stop =
					(pointer)((long)stop * 4) ;
				stop = stop + (long)total - 1 ;
				sort();
				count1 = 0 ;	/* count of items on line */
				while ((total <= stop) && !( intrupt))
					{
					if (!count1) outloc((x8more)1) ;
					if (size == BYTE)
						{
#define	DISP_LEN	0x10
						count1 = DISP_LEN;
						dest = total;
						while (count1)
							{
							if (!((base != (b_ent)0)
								&& (ascii) &&
								((count1%4) || (count1 == DISP_LEN) ))) putjch(' ');
							if (total <= stop)
								{
								put2h(*(char *)total);
								total += 1 ;
								relative += 1;	/* needs to do this else
									address display is inconsistent */
								}
							else 	/* space out to start position for ascii */
								vprints("  ");
							count1--;
							}
						/* now print ascii equivalent of the previous hex */
						if ( ascii)
							{
							putjch(' ');
							total = dest ;
							count1 = DISP_LEN ;
							while(count1 && (total <= stop))
								{
								if (isletter(*(char *)total))
									putjch(*(char *)total);
								else putjch('.');
								total += 1 ;
								count1-- ;
								/* if (count1 == 8) putjch(' '); */
								}
							}
						}
					else if ( size == WORD)
						{
						if (!count1) count1 = 0x8 ;
#ifdef	CPM
						pc80w = (int *)total ;
						out4h(*pc80w);
#else
						out4h(*(int *)total);
#endif
						total += 2 ;
						relative += 2;
						count1--;
						}
					else if ( size == LONG )
						{
						if (!count1) count1 = 0x4;
#ifdef	CPM
						pc80l = total; out8h(*pc80l) ;
#else
						out8h(*(pointer)total) ;
#endif	CPM
						total += 4 ; relative += 4;
						count1--;
						}
					}
				relative = mem_p ;
				}
			else /* (size == STRING) */
				{
				/* monitor ends at null after displaying */
				while (!intrupt && (chint = *(char *)total) )
					{
					if (isletter((char)chint))
						putjch((char)chint);
					total += 1; relative += 1;
					}
				vprints("\nSTART WAS"); /* where string ends */
				outadr(mem_p);
				}
			vputjch('\n') ;
			break;
		case')':prints( "Dump Text\n" );
			if (!out_open()) goto warms;
			while (!intrupt)
				{
				chint = *(char *)total;
				if (chint) vputjch(chint); else break;
				total += 1 ;
				}
#define	CPM_EOF 0x1A
			putjch(CPM_EOF); /* CPM end of text file marker */
			fclose(chan) ; dump = 0;
			vputjch('\n');
			break;
		case'(':prints( "Loading Text\n" );
			if (!in_open()) goto warms;
			while(!intrupt && (chint = getjch() ) != EOF)
				{
				*(char *)total = chint;
				total++ ;
				}
			*(char *)total = '\0';
			fclose(chan);
			load = 0;
			vputjch('\n');
			break;
		case'>':prints( "Dump Object : Stop Address ? " );
			dest = relative ;/* save 'relative'; note 'relative'
				is used as well as 'total' otherwise addresses 
				printed & written to disc would be wrong */
			if (relative > stop )
				{
				dest = stop ;
				stop = relative ;
				relative = dest ;
				}
			stop = getadr();
			if (!out_open()) goto warms;
			count1 = 0 ;	/* count of binary data bytes */
			while ((relative <= stop) && !intrupt)
				{
				if (count1 == 0)
					{
					putjch(':');
					cksum = 0 ;
#define	OBJ_OUT	0xf
					/* max spec allows is 0x20 */
					if (stop - relative < OBJ_OUT)
						put2h((x8bit)(stop - relative +
						 1 ) & 0xFF) ;
					else put2h((x8bit)OBJ_OUT);
					put4h((u16bit)relative);
						/* Address of start of record
						(spec is 16 bit address) */
					put2h((x8bit)0); /* record type : data*/
					}
				ADDER
				put2h(*(char *)total );
				count1 += 1 ;
				if ((count1 == OBJ_OUT) || (relative == stop))
					{
					cksum = ~cksum + 1 ;
					put2h(cksum & 0xFF);
					putjch('\r'); putjch('\n');
					count1 =0;
					}
				relative += 1 ;
				}
			/* append an EOF record */
			putjch(':');
			cksum = 0;
			put2h(0);
			put4h((int)base);
			put2h(1);	/* record type = data */
			cksum = ~cksum + 1 ;
			put2h(cksum) ;
			putjch('\r'); putjch('\n');
			putjch(CPM_EOF);
			fclose(chan) ;
			dump = 0;
			relative = dest;	/* restore 'relative' */
			break;
		case'<':prints( "Loading Object\n" );
			/* note 'relative' & 'total' are both incremented
				for same reason as dump object case */
			/* exit by giving a non data record type ( != 0 ).
			*  slight bug : if a checksum error occurs,
			*  data record is written to ram regardless,
			*  checksum error is not detected until end of line
			*  Note 'relative' is used as well as 'total'
			*  otherwise addresses printed & written to disc
			*  would be wrong */
			dest = relative ;
			eof_flg = 0;
			if (!in_open()) goto warms;
			while(!eof_flg && !intrupt)
				{
				/* get beginning of record delimiter */
				while((chint = getjch() ) != ':')
					if ( chint == EOF )
						{
						prints("End of file.");
						load = 0;
						fclose(chan);
						goto warms;
						}
				cksum = 0 ;
				count1 = take2h();	/* get data byte count */
#define	OBJ_IN	0x20
			/* max allowed by mostek definition */
				if(count1 > OBJ_IN && count1 != 0)
					{
					error("Bad Count");
					break;
					}
				/* get record start address */
				relative = take2h() ;
				relative <<= 8 ;
				data.s4 = take2h() ;
				relative |= data.s4 ;
				ADDER
				if ((load_tmp = take2h()) == 1) eof_flg = 1 ;
				/* load ram with data */
				while (count1--)
					{
					*(char *)total = take2h();
					total += 1 ;
					}
				/* calculate checksum from data */
				data.s1 = ~cksum +1 ;
				/* compare with checksum from record */
				if ( data.s1 != take2h())
					{
					error("Bad Checksum");
					break;
					}
				}
			/* have we received an EOF record ? */
			if (!eof_flg) error("No EOF Record.");
			else
				{
				vprints("\nTHE EOF ADDRESS WAS ");
				putadr((x32bit)load_tmp);
				putjch('\n');
				/* note cannot output this number earlier
				as it would corrupt checksum */
				}
			fclose(chan);
			load = 0;
			relative = dest ;
			break;
		case'c':vprints("Copy: ");
			ADDER
			if (size != STRING) upto() ;
			prints("Destination = ? ");
			if (size != STRING)	/* get top of source block */
				{
				stop = getadr();
				stop = stop + (long)base;
				}
			dest = getadr() ;	/* get base of destination */
			dest = dest + (long)base;
			/* now process */
			if ((size == WORD) && ((stop - total) % 2 )
				|| (size == LONG) && ((stop - total) % 4))
				{
				error( "Length quantisation" );
				break ;
				}
			if (size == STRING )
				{ /* set 'stop' so we can
					treat it as a block */
				stop = total;
				while (*(char *)stop++) ;
				/* note the null will be copied */
				}
			else if (size == WORD) stop += 1 ;
				/* note we will be copying the byte
					above 'stop' as well. */
			else if (size == LONG) stop += 3 ;
				/* ditto 3 bytes */
			sort() ;
			/* Now do the copy, note this has not been written
			to copy through zero */
			if (dest < total)
				{
				do	{
					*(char *)dest = *(char *)total;
					dest += 1 ; total += 1 ;
					} while ( total <= stop ) ;
				}
			else 	{
				dest += stop - total ;
				do	{
					*(char *)dest = *(char *)stop ;
					stop-- ; dest-- ;
					} while ( stop >= total ) ;
				}
			break;
		case'f':vprints("Fill : ");
			upto() ;
			prints("Data ? " );
			ADDER
			if (size != STRING)
				{
				stop = getadr();
				stop = stop + (long)base ;
				}
			else	{
				stop = relative + base ;
				while (*(char *)stop++) ;
				stop -- ; /* dont include nul */
				}
			if (( size == BYTE) || ( size== STRING ))
				{
				data.s1 = get2h();
				while(( total <= stop) && !intj())
					{
					*(char *)total = data.s1;
				total += 1;
					}
				}
			else if ( size == WORD)
				/* Note the byte beyond 'stop'
					may be affected*/
				{
				data.s2 = get4h();
			while(( total <= stop) && !intj())
				{
					*(int *)total = data.s2;
					total += 2;
					}
			}
			else if (size == LONG )
				{
				/* Note up to 3 bytes beyond 'stop'
				may be affected */
				data.s4 = get8h();
				while(( total <= stop) && !intj())
					{
#ifdef	CPM
					pc80l = (long *)total;
					*pc80l = data.s4 ;
#else
					*(long *)total = data.s4 ;
#endif	CPM
					total += 4;
					}
				}
			break;
		case'/':prints( "Search : Data ? " );
			ADDER
			dest = total ;	/* to detect unsuccesful searches */
			if ( size == BYTE)
				{
				data.s1 = get2h();
				do	{
					if (*(char *)total == data.s1) break;
					total += 1;
#define	TOP	0x1000000
					/* NS 16000 is 24 bit address, not 32 so cut short the search */
					if (total == TOP) total = 0;
					}
					while ((total != dest) && !intj());
				}
			else if ( size == WORD)
				{
				data.s2 = get4h();
				do	{
					if (*(int *)total == data.s2) break;
					total += 1;
					if (total == TOP) total = 0;
					}
					while ((total != dest) && !intj());
				}
			else if ( size == LONG)
				{
				data.s4 = get8h();
				do	{
#ifdef	CPM
					pc80l = (long *)total ;
					if (*pc80l == data.s4) break;
#else
					if (*(long *)total == data.s4) break;
#endif	CPM
					total += 1;
					if (total== TOP) total = 0;
					} while ((total != dest) && !intj());
				}
			else /* String */
				{
				jgets(findstr,FINDSTR);
				prints(" LOOKING FOR \'");
				prints(findstr);
				putjch('\'');
				/* total == dest */
				do	{
/* JJ is this an error */		if( *(char *)total == *findstr) 
						{
						for (mem_p = findstr + 1 ,
							stop = total + 1;
							*(char *)stop ==
							*(char *)mem_p &&
							*(char *)stop != '\0' ;)
							{
							stop++ ;
							if (stop == TOP) stop
								= 0;
							mem_p++ ;
							}
						if (*(char *)mem_p == '\0' )
							break ;
						}
					total++ ;
					if (total == TOP) total = 0;
					} while ( total != dest && !intj()) ;
				}
			if (total == dest) error("Not found.\n");
			else relative = total - base ;
			outloc((x8more)0);
			vputjch('\n');
			break;
		case't':vprints("Test RAM: up to ? ");
/*			Algorithm enhanced from an article by
*				Edward J Milner, NASA
*				Lewis Research Center,
*				Published Electronic Design News
*				October 13th 1983.
*			Synopsis of article :
*				slide a 1 thru succesive incrementing bytes,
*					  ( having initialised block to 0 )
*					1		dec		0
*					1		inc		FF
*					1		dec		FF
*				repeat all previous 8 times, shifting start
*					bit one bit each time,
*				repeat all with a sliding 0 instead of a 1.
*			Thus the first test is : Sliding one, Zero initial,
*				Incrementing addresses, rotate a bit to the left
*				through succesive bytes in the block,
*				first testing each byte to see if
*				a previous cycle of the loop (to
*				access a lower address) has also erroneously
*				set the contents of the current address
*/
	dest = relative; stop = getadr();
	/* insert code here to generate data for 'tstrfrsh' */
	prints("Number of laps (0=infinite) ? ");
	count1 = get2h();
	forever = (count1 != 0) ? 0 : 1 ;
	while (count1 || forever)
		{
		count1--;
		/* First a sliding 1 test, then a sliding 0 */
		for (bits1=1; ; bits1 = ~1)
			{
			/*For each of the 8 bits within a byte*/
			for (bits2 = bits1, count2 = 0;
				count2 < 8 ;
				bits2 = rolb(bits2), count2++)
				{
				/* first initialise block low,
					then high */
				for (init0F = 0; ;
					init0F = 0xFF)
					{
					/* Sweep block first
					incrementing addresses,
					then decrementing */
					for (validir = 1; ;
						validir = -1)
						{
						if (verbose)
						{
						putchar('\n');
						putadr((x32bit)
							(dest+(long)base));
						putchar(':');
						putadr((x32bit)
							(stop+(long)base));
						if (forever) prints(" FOREVER");
						else	{
							prints(" LAP=");
							put2h(count1);
							}
						prints(", sliding ");
						if (bits1 == 1) putchar('1');
						else putchar('0');
						prints(", bit "); 
						putchar('0'+count2);
						prints(", block start ");
						if(!init0F) prints("00");
						else prints("FF");
						prints(", sweep ");
						if (validir == 1) putchar('+');
						else putchar('-');
						}
						/* initialise block */
						if (!initblk(init0F) )
							goto warms;
						/* set bit pattern in block */
						for ( bits3 = bits2,
							relative = dest;
							relative <= stop;
							bits3 = rolb(bits3),
							relative++)
							{
							if (intj()) goto warms;
							ADDER

							if (*(x8bit *)total !=
								init0F)
								{
								shorterr(validir
								,init0F);
								goto warms;
								}
							*(x8bit *)total = bits3 ;
							}
						/* check bit pattern in block */
						if (validir == 1)
							{
							if (!checblok(1,bits2))
							goto warms;
							}
						else	{
							if (!checblok(-1,
								rorb(bits3)))
								goto warms;
							}
						if (validir != 1) break ;
						}
					if (init0F != 0) break ;
					}
				}
			if (bits1 != 1) break ;
			}
		}
	relative = dest ;
	putchar('\n');
	break;
case'?':prints(  "Set  Address : A Relative B Base      / Search");
	prints("\nSet  R/W     : R Read     W Write     M Modify=R+W");
	prints("\nSet  Size    : 1 Byte     2 Word      4 Double     \" Text");
	prints("\nSet  Mode    : \' Ascii    H Hex       V Verbose    Q Quiet");
	prints("\nEntity Access: + Next     . Same      - Previous   ! SWI Set");
	prints("\nBlock Access : D Display  C Copy      F Fill       T Test");
	prints("\nFile  Access : N Name     > Dump Obj  < Load Obj   ) Dump Text ( Load Text");
	prints("\nStatus Print : S Monitor  P Processor");
	prints("\nControl      : E Exit    ^B Interrupt 0 Restart    X Execute   G Goto\n");
	status();
	break;
default:error("BAD COMMAND, TRY \'?\'\n");
	break;
	}
	}
}
