/************************************************************************\
 * File Version Information
 * $Header: /Altair32v2/Debugger.c 32    11/08/06 9:21p Racini $
 ************************************************************************/
/************************************************************************\
  MITS Altair Emulator
  Integrated debugger support code

  Modifications for the Solace Sol-20 emulator
  Copyright (c) Jim Battle, 2000, 2001

  Modifications for the Altair32 Emulator
  Copyright (c) 2001-2006 Richard A. Cini

  Debugger commands:

   Memory commands:
	DB/DU(mp)      ssss eeee           dump (bytes)
	DW             ssss eeee           dump words
	DA             ssss eeee           dump ascii
	EB/EN(ter)     ssss bb bb bb ...   enter (bytes)
	EA             ssss bb bb bb ...   enter ascii
	F(ill)         ssss eeee bb        fill bytes
	LB             ssss eeee bb bb ... locate byte string
	LA             ssss eeee abcde ... locate ascii string
	MM             ssss eeee dddd      move memory
	CM             ssss eeee dddd      compare memory
	SM             ssss eeee bb bb ... show matching bytes
	DASM           ssss eeee           disassemble
	LOAD           <fname>             load .ent or .hex file
	SAVE           ssss eeee <fname>   dump to .ent file
	SAVEH          ssss eeee <fname>   dump to .hex file

   Execution:
	RESET                         reset processor
	SE(T) <reg> nnnn              set register value, <reg>=PC/SP/BC/DE/HL/AF
	SE(T) <reg> bb                set register value, <reg>=B/C/D/E/H/L/A/F
	N(ext)         nnnn           step over (nnnn instructions)
	S(tep)         nnnn           step in (nnnn instructions)
	EX(ecute)      aaaa           continue execution (at nnnn)
	INC(LUDE)      <fname>        perform a script of commands
                                    
   Breakpoint commands:            
	TO        aaaa                temporary break at PC=aaaa
	BP(c)     aaaa                break at PC=aaaa
	BR(B)     aaaa [dd [mm]]      break at read address=aaaa,  (data & mm)   = dd
	BRW       aaaa [dddd [mmmm]]  break at read address=aaaa,  (data & mmmm) = dddd
	BW(B)     aaaa [dd [mm]]      break at write address=aaaa, (data & mm)   = dd
	BWW       aaaa [dddd [mmmm]]  break at write address=aaaa, (data & mmmm) = dddd
	BI        nn   [dd [mm]]      break at in  port=aa, (data & mm) == dd
	BO        nn   [dd [mm]]      break at out port=aa, (data & mm) == dd
	BD       [nn]                 breakpoint disable (#n)
	BE       [nn]                 breakpoint enable (#n)
	BK       [nn]                 breakpoint kill (#n)
	BL(ist)  [nn]                 breakpoint list (#n)

   Misc:
	OV(erlay) [<file>] [<mode>]   manage source code overlays
	KEY  nn                       force a keystroke
	H(elp) [<cmd>]                help message
	?                              "      "
	EXIT/X(it)/Q(uit)             continue & close window


Change Log:
  2001/12/02  RAC -- Began coding.
  2001/12/14  RAC -- RELEASE MARKER -- v2.1
  2001/12/27  RAC -- Changes to sync with Solace 3.0
  2002/01/16  RAC -- Removed support for single stepping and step_over
  						from within the debugger. Requires timeslicing to
  						work properly.
  2002/01/31  RAC -- RELEASE MARKER -- v2.2
  2002/05/17  RAC -- Mods to accommodate OVerlay files; sync with Solace v3.1
  2002/07/07  RAC -- RELEASE MARKER -- v2.3
  2002/08/23  RAC -- RELEASE MARKER -- v2.30.10
  2002/10/30  RAC -- Re-enabled Next and Step commands since timeslicer works.
  2002/11/15  RAC -- RELEASE MARKER -- v2.40.2100
  2003/04/26  RAC -- RELEASE MARKER -- v2.50.2045
  2004/01/20  RAC -- Diff'ed changes from FJS (Fred J. Scipione)
  2004/03/09  RAC -- Diff'ed more changes from FJS (Fred J. Scipione)
  2004/07/30  RAC -- RELEASE MARKER -- v3.00.0135
  2006/05/12  RAC -- RELEASE MARKER -- v3.10.0200
  2006/06/13  RAC -- Minor change to SAVE params to sync with Solace 3.2
  2006/11/15  RAC -- RELEASE MARKER -- v3.20.0400
\************************************************************************/

#include <windows.h>
#include <stdlib.h>
#include <stdio.h>		// C-lib
#include <stdarg.h>		// for varargs
#include <string.h>
#include "altair32.h"
#include "i8080.h"
#include "windbg.h"
#include "srcdbg.h"
#include "hexfile.h"


#define BREAK_DATA16 1 // 1=allow 16b data breakpoints

// this is a bitmap of all 64K memory locations.  a bit is set to '1'
// if this address is involved in any type of breakpoint.
uint32 brkpt_map[2048];
byte brkpt_iomap[256];	 // simple 1 per byte


// forward declarations
static void dbg_db(int args, uint32 arg[]);
static void dbg_dw(int args, uint32 arg[]);
static void dbg_da(int args, uint32 arg[]);
static void dbg_dasm(int args, uint32 arg[]);
static void dbg_load(int args, uint32 arg[]);
static void dbg_save(int args, uint32 arg[]);
static void dbg_en(int args, uint32 arg[]);
static void dbg_fill(int args, uint32 arg[]);
static void dbg_lb(int args, uint32 arg[]);
static void dbg_cm(int args, uint32 arg[]);
static void dbg_mm(int args, uint32 arg[]);
static void dbg_reset(int args, uint32 arg[]);
static void dbg_next(int args, uint32 arg[]);
static void dbg_step(int args, uint32 arg[]);
static void dbg_run(int args, uint32 arg[]);
static void dbg_set(int args, uint32 arg[]);
//static void dbg_include(int args, uint32 arg[]);	//FJS
static void dbg_help(int args, uint32 arg[]);
static void dbg_exit(int args, uint32 arg[]);
static void dbg_blist(int args, uint32 arg[]);
static void dbg_benable(int args, uint32 arg[]);
static void dbg_badd(int args, uint32 arg[]);
static void dbg_to(int args, uint32 arg[]);
static void dbg_key(int args, uint32 arg[]);
static void dbg_overlay(int args, uint32 arg[]);

static int is_a_file(char *fname);

static breakpoint_t breakpoints[NUM_BREAKPOINTS];
static int breaknum;		// # of defined breakpoints
static int breaknxt;		// breakpoint serial number

// this routine builds a bitmap of all addresses that are involved
// in any type of breakpoint.  it is used to accelerate the typical
// handling of memory reads & writes.
//
// it should be called just before execution of the simulator resumes.
// !SLOW!
static void
breakpoint_clearmap(void)
{
    memset(brkpt_map,   0, sizeof(brkpt_map));
    memset(brkpt_iomap, 0, sizeof(brkpt_iomap));
}

// mark a given address as having a breakpoint of some kind at that address
static void 
breakpoint_setaddr(word addr)
{
    brkpt_map[addr>>5] |= (1 << (addr & 31));
}

// remove breakpoint hit bit for a given address
static void 
breakpoint_clearaddr(word addr)
{
    brkpt_map[addr>>5] &= ~(1 << (addr & 31));
}

// go through all active breakpoints and set the corresponding bit in the map
static void 
breakpoint_updatemap(void)
{
    int i;

    // set bit to '1' if it is involved in an operation
    for(i=0; i<breaknum; i++) {
	int addr  = breakpoints[i].addr;
	int addr2 = addr + 1;
	if (!breakpoints[i].enabled)
	    continue;
	switch (breakpoints[i].btype) {
	    case BRK_INVALID:
		break;
	    case BRK_IN:
	    case BRK_OUT:
		brkpt_iomap[addr] = 1;
		break;
	    case BRK_RD16:
	    case BRK_WR16:
		breakpoint_setaddr((word)addr2);
		// .. fall through
	    case BRK_TEMP:
	    case BRK_OP:
	    case BRK_RD:
	    case BRK_WR:
		breakpoint_setaddr((word)addr);
		break;
	    default:
		ASSERT(0);
		break;
	}
    }
}


// this is called after each breakpoint to double check the rd16/wr16
// type of breakpoints.  it returns 1 if it really is a breakpoint,
// and 0 if there wasn't a valid breakpoint.
static void 
breakpoint_clearflags(void)
{
    int i;
    for(i=0; i<breaknum; i++) {
	breakpoints[i].triggered = 0;
	breakpoints[i].dlo       = -1;
	breakpoints[i].dhi       = -1;
    }
}


// regenerate breakpoint map
void 
breakpoint_genmap(void)
{
    breakpoint_clearmap(); 	// wipe out any indication of breakpoints
    breakpoint_updatemap();	// set 1 bits where addresses have breakpoints
    breakpoint_clearflags();	// get rid of 'triggered' flags
}


void 
breakpoint_init(void)
{
    int i;

    breaknum = 1;	// skip 0
    breaknxt = 1;

    for(i=0; i<NUM_BREAKPOINTS; i++) {
	breakpoints[i].btype   = BRK_INVALID;	// invalid
	breakpoints[i].enabled = 0;
	breakpoints[i].sernum  = 0;
	breakpoints[i].addr    = 0x0000;
	breakpoints[i].data    = 0x0000;
	breakpoints[i].mask    = 0x0000;
    }
}


// remove temporary breakpoint; incrementally update breakpoint map
// quickly because it may be called frequently in realtime by the
// "next nnnn" stepping.
void 
breakpoint_temp_kill(void)
{
    int i;

    if (breakpoints[0].enabled) {
	breakpoints[0].enabled = 0;
	// we can't just clear the bit corresponding to the tmp breakpoint
	// in case there is an active permanent breakpoint there too
	for(i=1; i<breaknum; i++) {
	    if ((breakpoints[0].addr == breakpoints[i].addr) &&
	        (breakpoints[i].enabled))
		return;
	}
	// it is the only breakpoint at that address; clear it
	breakpoint_clearaddr((word)breakpoints[0].addr);
    }
}


// add a temporary breakpoint; always uses sernum=0, entry 0
// incrementally update breakpoint map quickly because it may
// be called frequently in realtime by "next nnnn" stepping.
void 
breakpoint_temp_add(word addr)
{
    if (breakpoints[0].enabled)
	breakpoint_temp_kill();	// remove old one (if it exists)

    breakpoints[0].btype   = BRK_TEMP;
    breakpoints[0].enabled = 1;
    breakpoints[0].addr    = addr;
    breakpoints[0].sernum  = 0;

    breakpoint_setaddr(addr);
}


// return index to breakpoint if successful, otherwise return 0.
static int 
breakpoint_add(break_t type, word addr, word data, word mask)
{
    int i;
    int idx = -1;
    int brk;

    if (type == BRK_TEMP) {
	breakpoint_temp_add(addr);
	return -1;	// non-zero, but not valid
    }

    if (breaknum >= NUM_BREAKPOINTS)
	return 0;

    // see if we already have a breakpoint on that address of the same type
    for(i=1; i<breaknum; i++) {
	if ((breakpoints[i].addr  == addr) &&
	    (breakpoints[i].btype == type)) {
	    idx = i;
	    break;
	}
    }

    brk = (idx < 0) ? breaknum : idx;

    breakpoints[brk].btype   = type;
    breakpoints[brk].enabled = 1;
    breakpoints[brk].addr    = addr;
    breakpoints[brk].data    = data;
    breakpoints[brk].mask    = mask;

    if (idx < 0) {
	breakpoints[brk].sernum = breaknxt;
	idx = brk;
	breaknum++;
	breaknxt++;
    }

    return idx;
}


// check all breakpoints to see if the specified  address
// has a breakpoint on it of the specified type.  return
//
//    0 if no breakpoint
//   +n if breakpoint, serial number is n
//   -n if disabled breakpoint, serial number is n
int 
check_brk(word addr, break_t type)
{
    int i;

    for(i=1; i<breaknum; i++) {
	int sernum = breakpoints[i].sernum;
	if ((breakpoints[i].btype == type) &&
	    (breakpoints[i].addr  == addr))
	    return (breakpoints[i].enabled) ? sernum : -sernum;
    } // for

    return 0;	// no breakpoint
}


// returns 1 if there are any active breakpoints of the specified type
int 
any_breakpoints(break_t btype)
{
    int i;
    int active = 0;

    for(i=0; i<breaknum; i++)
	if ((breakpoints[i].btype == btype) &&
	    (breakpoints[i].enabled))
	    active = 1;

    return active;
}


// see if a given access matches any of the breakpoints
int 
breakpoint_test(break_t btype, word addr, word data, int *num)
{
    int i;


    switch (btype) {

	case BRK_TEMP:
	case BRK_OP:
	    for(i=0; i<breaknum; i++) {
		if ((breakpoints[i].btype == BRK_OP ||
		     breakpoints[i].btype == BRK_TEMP) &&
			 breakpoints[i].enabled &&
			(breakpoints[i].addr == addr)) {
		    breakpoints[i].triggered = 1;
		    *num = breakpoints[i].sernum;
		    return 1;
		} // if same type, enabled
	    } // for
	    break;

	case BRK_RD:
	    for(i=0; i<breaknum; i++) {

		if ((breakpoints[i].btype == BRK_RD) &&
		     breakpoints[i].enabled          &&
		    (breakpoints[i].addr == addr)    &&
		    (((breakpoints[i].data ^ data) & breakpoints[i].mask) == 0x00)) {
		    breakpoints[i].triggered = 1;
		    *num = breakpoints[i].sernum;
		    return 1;

		} else if ((breakpoints[i].btype == BRK_RD16) &&
			    breakpoints[i].enabled) {
		    if (breakpoints[i].addr   == addr) {
			breakpoints[i].triggered = 1;
			breakpoints[i].dlo       = data;
		      *num = breakpoints[i].sernum;
			return 1;
		    }
		    if (breakpoints[i].addr+1 == addr) {
			breakpoints[i].triggered = 1;
			breakpoints[i].dhi       = data;
			*num = breakpoints[i].sernum;
			return 1;
		    }
		}

	    } // for
	    break;

	case BRK_WR:
	    for(i=0; i<breaknum; i++) {

		if ((breakpoints[i].btype == BRK_WR) &&
		     breakpoints[i].enabled          &&
		    (breakpoints[i].addr == addr)    &&
		    (((breakpoints[i].data ^ data) & breakpoints[i].mask) == 0x00)) {
		    breakpoints[i].triggered = 1;
		    *num = breakpoints[i].sernum;
		    return 1;

		} else if ((breakpoints[i].btype == BRK_WR16) &&
			    breakpoints[i].enabled) {
		    if (breakpoints[i].addr   == addr) {
			breakpoints[i].triggered = 1;
			breakpoints[i].dlo       = data;
			*num = breakpoints[i].sernum;
			return 1;
		    }
		    if (breakpoints[i].addr+1 == addr) {
			breakpoints[i].triggered = 1;
			breakpoints[i].dhi       = data;
			*num = breakpoints[i].sernum;
			return 1;
		    }
		}

	    } // for
	    break;

	case BRK_IN:
	case BRK_OUT:
	    for(i=0; i<breaknum; i++) {
		if ((breakpoints[i].btype == btype) &&
		     breakpoints[i].enabled         &&
		    (breakpoints[i].addr == addr)   &&
		    (((breakpoints[i].data ^ data) & breakpoints[i].mask) == 0x00)) {
		    breakpoints[i].triggered = 1;
		    *num = breakpoints[i].sernum;
		    return 1;
		} // if
	    } // for
	    break;

	default:
	    ASSERT(0);
	    break;

    } // switch

    return 0;	// no breakpoint
}


// goes through all breakpoints and returns the number of the first one
// that matches.  it also validates the RD16 and WR16 breakpoint types.
int 
breakpoint_verify(int *num)
{
    int i;
    int dlo, dhi;
    word data;

    for(i=1; i<breaknum; i++) {	// skip the TEMP breakpoint at 0
	if (breakpoints[i].triggered) {
	    switch (breakpoints[i].btype) {

		// these are don't need qualification
		case BRK_OP:
		case BRK_IN:
		case BRK_OUT:
		case BRK_RD:
		case BRK_WR:
		    breakpoint_clearflags();
		    *num = breakpoints[i].sernum;
		    return 1;

		case BRK_RD16:
		case BRK_WR16:
		    dlo = breakpoints[i].dlo;
		    dhi = breakpoints[i].dhi;
		    if ((dlo >= 0) && (dhi >= 0)) {
			// both bytes of operand were touched.  now test.
			data = (dhi << 8) | dlo;

			if (((breakpoints[i].data ^ data) & breakpoints[i].mask) == 0x00) {
			    breakpoint_clearflags();
			    *num = breakpoints[i].sernum;
			    return 1;
			}
		    }
		    break;

		default:
		    ASSERT(0);
		    break;

	    } // switch
	} // if triggered
    } // for

    breakpoint_clearflags();
    return 0;
}


// ---------- parsing routines for command line -----------

static void 
SkipWhite(char **cp)
{
    char ch;
    for(;;) {
	ch = **cp;
	if (ch != ' ' && ch != '\t')
	    break;
	(*cp)++;
    }
}


// scan character string pointed at by cp.
// return a pointer to the start of a word.
// leave cp pointing to one char past end of word.
static int 
NextArg(char **cp, char **start)
{
    char *p = *cp, ch;

    SkipWhite(&p);
    *start = p;

    ch = *p;
    if (ch == 0) {
	*start = NULL;
	*cp = p;
	return 0;	// no arguments
    }	

    while (ch && (ch != ' ') && (ch != '\t')) {
	p++;
	ch = *p;
    }
    *p = '\0';	// null terminate this portion of the string
    if (ch)
	p++;
    
    *cp = p;
    return 1;
}


static void 
uppercase(char *str)
{
    char ch, *p = str;

	while ( (ch = *p) != '\0' /* EOS */) { // FJS
//	while (ch = *p) {
	if ((ch >= 'a') && (ch <= 'z'))
	    *p = ch - 'a' + 'A';
	p++;
    }
}


// parse and return a 16b number.
// assumed to be hex, but if it starts with '#', it is decimal.
// return a negative number if an arg is found but isn't a number.
// returns:
//     0 if no argument is found
//     1 if valid argument is found
//    -1 if invalid argument is found
static int
NumArg(char **cp, uint32 /* FJS */ *val)	// CHANGED
//NumArg(char **cp, int *val)
{
    char *p, ch;
    int v, arg = NextArg(cp, &p);
    int base = 16;	// hex by default

    if (!arg)
	return 0;

    uppercase(p);

    if (*p == '#') {
	base = 10;
	p++;
	if (!*p)
	    return -1;	// argument is only '#'
    }

    v = 0;
   	while ( (ch = *p) != '\0' /* EOS */) { // FJS
	//while (ch = (*p)) {	// FJS
	if (ch >= '0' && ch <= '9')
	    v = base*v + (ch - '0');
	else if ((base == 16) && (ch >= 'A' && ch <= 'F'))
	    v = base*v + (ch - 'A' + 10);
	else
	    return -1;	// not a hex char
	p++;
    }
	if (ch /* != EOS */)
	return -1;	// didn't end properly
    
    *val = (v & 0xFFFF);
    return 1;
}


typedef enum tagARG {
    ARG_NONE,		// no argument expected
    ARG_16,		// 16b word expected
    ARG_16_OPT,		// optional 16b word, defaults to previous arg
    ARG_16_OPT1,	// optional 16b word, defaults to 1
    ARG_8,		//  8b word expected
    ARG_8_LIST,		// list of 8b integers
    ARG_A_LIST,		// list of ascii chars
    ARG_STR,		// pointer to space-delimited word
    ARG_STR_OPT,	// pointer to optional space-delimited word
    ARG_STOP,		// stop parsing -- let routine finish parsing
} arg_t;

typedef void (*dbg_fcn)(int args, uint32 arg[]);

typedef struct tagCMDENTRY {
    char      *cmd;		// command
    int        cmdlen;		// required # of matching chars
    char      *syntax;		// command syntax string
    char      *help;		// detailed command summary
    int        arg0;		// invisible argument
    arg_t      arg1_type;	// argument 1 type
    arg_t      arg2_type;	// argument 2 type
    arg_t      arg3_type;	// argument 3 type
    dbg_fcn    fcn;		// debugger function
} cmdentry_t;

cmdentry_t cmdtable[] = {

    {
	NULL, 99,					// summary comment
	"memory commands:",
	NULL, 0,0,0,0, NULL	// unused args
    },

    // this one is a synonym for DB, but is here because this is a
    // SOLOS command and might be expected
    {
	"DUMP", 2,					// command, len
	NULL,						// suppress help summary

"DUmp <start_address> [<end_address>] --\n\
\n\
    The DUmp command is identical to the SOLOS DUMP command,\n\
    and is an alias of the debugger DB command.\n\
\n\
    It produces a listing of hex bytes in the inclusive memory\n\
    range specified.  If the optional second address isn't\n\
    supplied, it assumes the value of the first argument, and\n\
    only one byte is printed.\n\
\n\
    Sixteen bytes are printed per line.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT,					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_db,						// debugger function
    },

    {
	"DB", 2,					// command, len
	"    DB   <start> [<end>]          dump bytes",	// help string

"DB <start_address> [<end_address>] --\n\
\n\
    The Dump Bytes command is identical to the SOLOS DUmp\n\
    command, and is an alias of the debugger DUmp command.\n\
\n\
    It produces a listing of hex bytes in the inclusive memory\n\
    range specified.  If the optional second address isn't\n\
    supplied, it assumes the value of the first argument, and\n\
    only one byte is printed.\n\
\n\
    Sixteen bytes are printed per line.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT,					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_db,						// debugger function
    },

    {
	"DW", 2,					// command, len
	"    DW   <start> [<end>]          dump words",	// help string
"DW <start_address> [<end_address>] --\n\
\n\
    The Dump Words command produces a listing of 16b hex\n\
    byte pairs in the inclusive memory range specified.\n\
    If the optional second address isn't supplied, it assumes\n\
    the value of the first argument, and only one word is\n\
    printed.\n\
\n\
    Note that words are printed in little endian order.\n\
\n\
    Eight words are printed per line.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT,					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_dw,						// debugger function
    },

    {
	"DA", 2,					// command, len
	"    DA   <start> [<end>]          dump ascii",	// help string
"DA <start_address> [<end_address>] --\n\
\n\
    The Dump Ascii command produces an ASCII listing of\n\
    the inclusive memory range specified.  If the optional\n\
    second address isn't supplied, it assumes the value of\n\
    the first argument, and only one byte is printed.\n\
\n\
    Only bytes in the range of 32-127 are printed; lower-\n\
    valued characters are printed as '.'.  The msb of each\n\
    byte is ignored.\n\
\n\
    Thirty-two bytes are printed per line.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT,					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_da,						// debugger function
    },

    // this is a synonym for EB, and is similar to SOLOS' command
    {
	"ENTER", 2,					// command, len
	NULL,						// suppress help summary

"ENter <start_address> [ aa bb cc dd ... ] --\n\
\n\
    The ENter command is patterned on the SOLOS ENter\n\
    command, but isn't quite the same.  After the initial\n\
    memory address, the command takes a string of bytes\n\
    which are to be stored starting at the specified address\n\
    into consecutive addresses.\n\
\n\
    It is synonymous with the debugger EB command.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_8_LIST,					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_en,						// debugger function
    },

    {
	"EB", 2,					// command, len
	"    EB   <start> aa bb ...        enter bytes",	// help string

"EB <start_address> [ aa bb cc dd ... ] --\n\
\n\
    The Enter Bytes command is patterned on the SOLOS ENter\n\
    command, but isn't quite the same.  After the initial\n\
    memory address, the command takes a string of bytes\n\
    which are to be stored starting at the specified address\n\
    into consecutive addresses.\n\
\n\
    It is synonymous with the debugger ENter command.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_8_LIST,					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_en,						// debugger function
    },

    {
	"EA", 2,					// command, len
	"    EA   <start> abcde...         enter ascii",	// help string

"EA <start_address> abcde... --\n\
\n\
    The Enter Ascii command is patterned on the SOLOS ENter\n\
    command, but isn't quite the same.  After the initial\n\
    memory address, the command takes a string of bytes in\n\
    the form of a character string, which are to be stored\n\
    starting at the specified address into consecutive\n\
    addresses.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_A_LIST,					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_en,						// debugger function
    },

    {
	"FILL", 1,					// command, len
	"    FILL <start> <end> <bb>       fill block",	// help string

"F(ill) <start_address> <end_address> <value> --\n\
\n\
    The Fill command fills the specified inclusive address\n\
    range with the specified byte value.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16, 					// arg2 type
	ARG_8,						// arg3 type
	dbg_fill,					// debugger function
    },

    {
	"LB", 2,					// command, len
	"    LB   <start> <end> aa bb ...  locate bytes",	// help string

"LB <start_address> <end_address> aa bb cc dd...\n\
\n\
    The Locate Bytes command searches the specified\n\
    inclusive address range for the supplied list of\n\
    bytes appearing consecutively in that range.  The\n\
    string of bytes must be located entirely within the\n\
    address range.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16,						// arg2 type
	ARG_8_LIST,					// arg3 type
	dbg_lb,						// debugger function
    },

    {
	"LA", 2,					// command, len
	"    LA   <start> <end> abcde...   locate ascii",	// help string

"LA <start_address> <end_address> abcde...\n\
\n\
    The Locate Ascii command searches the specified\n\
    inclusive address range for the supplied ASCII\n\
    string appearing consecutively in that range.  The\n\
    string of bytes must be located entirely within the\n\
    address range.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16,						// arg2 type
	ARG_A_LIST,					// arg3 type
	dbg_lb,						// debugger function
    },

    {
	"SM", 2,					// command, len
	"    SM   <start> <end> aa bb ...  show matching bytes",	// help string

"SM <start_address> <end_address> aa bb cc dd...\n\
\n\
    The Show Matching bytes command is very similar to the\n\
    LB command, except that while it searches for a string\n\
    of bytes between <start_address> and <end_address>,\n\
    it reports all matches, not just the first.",

	1,						// arg 0
	ARG_16,						// arg1 type
	ARG_16,						// arg2 type
	ARG_8_LIST,					// arg3 type
	dbg_lb,						// debugger function
    },

    {
	"CM", 2,					// command, len
	"    CM   <start> <end> <dst>      compare memory",	// help string

"CM <start_address> <end_address> <dest_address>\n\
\n\
    The Compare Memory command compares the bytes of the\n\
    block of memory between <start_address> and <end_address>\n\
    against the block beginning at <dest_address>.  Any bytes\n\
    that don't match are reported as\n\
         <src_address>:<src_byte> != <dst_address>:<dst_byte>",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16,						// arg2 type
	ARG_16,						// arg3 type
	dbg_cm,						// debugger function
    },

    {
	"MM", 2,					// command, len
	"    MM   <start> <end> <dst>      move memory",	// help string

"MM <start_address> <end_address> <dest_address>\n\
\n\
    The Move Memory command moves the block of data that\n\
    lies between <start_address> and <end_address> to the\n\
    block beginning at <dest_address>.  The MM command will\n\
    move the block intelligently; that is, if the source and\n\
    destination blocks overlap, the move will occur from the\n\
    end of the block towards the start.\n",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16,						// arg2 type
	ARG_16,						// arg3 type
	dbg_mm,						// debugger function
    },

    {
	"DASM", 4,					// command, len
	"    DASM <start> [<end>]          disassemble",	// help string

"DASM <start_address> [<end_address>] --\n\
\n\
    The DASM command disassembled memory starting at the\n\
    <start_address> and ending at the optional <end_address>.\n\
    If <end_address> is not provided, a single instruction\n\
    is disassembled.",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_dasm,					// debugger function
    },

// FIXME -- should remove support for the .ent format
    {
	"LOAD", 4,					// command, len
	"    LOAD  <filename>              load .ent or .hex file",	// help string

"LOAD <file_name> --\n\
\n\
    The LOAD command reads a file in either Intel .hex format\n\
    or SOLOS .ent format.  Solace automatically figures out\n\
    the file type from the file contents, not the file name.\n",

	0,						// arg 0
	ARG_STR,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_load,					// debugger function
    },
// END FIXME --

    {
	"SAVEH", 5,					// command, len
	"    SAVEH <start> <end> <name>    .hex file dump",	// help string

"SAVEH <start_address> <end_address> <file_name> --\n\
\n\
    The SAVEH command dumps a range of memory from <start_address>\n\
    to <end_address>, inclusively, to the disk file given by\n\
    <file_name>.  The generated file is in Intel hex format.\n",

	0,						// arg 0
	ARG_16,						// arg1 type
	ARG_16, 					// arg2 type
	ARG_STR,					// arg3 type
	dbg_save,					// debugger function
    },

// FIXME -- Should probably change SAVE to saving in binary mode.
    {
	"SAVE", 3,					// command, len
	"    SAVE  <start> <end> <name>    .ent file dump",	// help string

"SAVE <start_address> <end_address> <file_name> --\n\
\n\
    The SAVe command dumps a range of memory from <start_address>\n\
    to <end_address>, inclusively, to the disk file given by\n\
    <file_name>.  The generated file is in ENter format,\n\
    which can be loaded into a real Sol computer.",

	1,						// arg 0
	ARG_16,						// arg1 type
	ARG_16, 					// arg2 type
	ARG_STR,					// arg3 type
	dbg_save,					// debugger function
    },

    {
	NULL, 99,					// summary comment
	"\nexecution commands:",
	NULL, 0,0,0,0, NULL	// unused args
    },
// END FIXME --

    {
	"RESET", 5,					// command, len
	"    RESET                         reset uP",	// help string

"RESET --\n\
\n\
    The RESET command causes the 8080 state to be changed\n\
    back to its reset state.  The other computer state,\n\
    such as memory contents, is unaffected.",

	0,						// arg 0
	ARG_NONE,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_reset,					// debugger function
    },

    {
	"NEXT", 1,					// command, len
	"    N(ext)    [<nnnn>]            step over n ops",	// help string

"Next [<count>] --\n\
\n\
    The Next command single steps the processor by one\n\
    instruction by default, or by the number of instructions\n\
    supplied by the optional parameter.\n\
\n\
    If any CALL-type instructions are encountered, the\n\
    subroutine is executed in its entirety and counts\n\
    for only one instruction.  This is said to be\n\
    \"stepping over\" CALL instructions.",

	0,						// arg 0
	ARG_16_OPT1,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_next,					// debugger function
    },

    {
	"STEP", 1,					// command, len
	"    S(tep)    [<nnnn>]            step in n ops",	// help string

"Step [<count>] --\n\
\n\
    The Step command single steps the processor by one\n\
    instruction by default, or by the number of instructions\n\
    supplied by the optional parameter.\n\
\n\
    If any CALL-type instructions are encountered, the\n\
    subroutine is entered and each instruction there\n\
    counts for one instruction.  This is said to be\n\
    \"stepping into\" CALL instructions.",

	0,						// arg 0
	ARG_16_OPT1,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_step,					// debugger function
    },

    {
	"EXECUTE", 2,					// command, len
	"    EX(ecute) [<nnnn>]            continue execution",	// help string

"EXecute [<address>] --\n\
\n\
    The EXecute command is patterned after the SOLOS\n\
    EXecute command.  Performing this command causes\n\
    the PC to be set to the supplied address and the\n\
    emulated CPU is put back into the RUN state.\n\
\n\
    If the optional argument is not supplied, emulation\n\
    continues from the current PC.\n\
\n\
    This command has an alias named Cont.",

	0,						// arg 0
	ARG_16_OPT,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_run,					// debugger function
    },

    // alias for EXecute
    {
	"CONT", 1,					// command, len
	NULL,						// suppress help summary

"Cont [<address>] --\n\
\n\
    The Cont command is patterned after the SOLOS\n\
    EXecute command.  Performing this command causes\n\
    the PC to be set to the supplied address and the\n\
    emulated CPU is put back into the RUN state.\n\
\n\
    If the optional argument is not supplied, emulation\n\
    continues from the current PC.\n\
\n\
    This command has an alias named EXecute.",

	0,						// arg 0
	ARG_16_OPT,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_run,					// debugger function
    },

    {
	"SET", 3,					// command, len
	"    SET <reg> <nnnn>              modify register",	// help string

"SET <reg> <nnnn> --\n\
\n\
    The SET command is used to modify the contents of the\n\
    8080 register set.\n\
\n\
    The valid 8-bit register names are:\n\
        A, F, B, C, D, E, H, L\n\
\n\
    The valid 16-bit register names are:\n\
        AF, BC, DE, HL, PC, SP",

	0,						// arg 0
	ARG_STR,					// arg1 type
	ARG_16, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_set,					// debugger function
    },

/*
    {
	"INCLUDE", 3,					// command, len
	"    INC(LUDE) <filename>          run script",	// help string

"INClude <filename> --\n\
\n\
    The INClude command reads in a series of debugger\n\
    commands from a file and executes them as if they had\n\
    been typed from the keyboard.  Scripts can be nested\n\
    three deep.  It is useful for setting up initial\n\
    conditions for repeated debugging runs.",

	0,						// arg 0
	ARG_STR,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_include,					// debugger function
    },
*/

    {
	NULL, 99,					// summary comment
	"\nbreakpoint commands:",
	NULL, 0,0,0,0, NULL	// unused args
    },

    {
	"TO", 2,					// command, len
	"    TO    aaaa                    run to PC=aaaa",	// help string

"TO <address> --\n\
\n\
    The TO command sets a temporary breakpoint on the\n\
    specified memory location and immediately continues\n\
    execution.  The target address is ignored if another\n\
    breakpoint is triggered first.  In any event, the\n\
    temporary breakpoint is automatically removed when the\n\
    next breakpoint (of any kind) is triggered.",

	BRK_TEMP,					// arg 0
	ARG_16,						// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_to,						// debugger function
    },

    {
	"BPC", 2,					// command, len
	"    BP(c) aaaa                    break  PC=aaaa",	// help string

"BPc <address> --\n\
\n\
    The Break on PC command sets a breakpoint on the\n\
    specified memory location.  During later emulation,\n\
    if the PC ever matches the supplied value at the start\n\
    of an instruction, emulation stops and the debugger\n\
    is invoked.",

	BRK_OP,						// arg 0
	ARG_16,						// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_badd,					// debugger function
    },

    {
	"BRB", 2,					// command, len
	"    BR(b) aaaa [dd [mm]]          break read  byte addr=aaaa, data=dd,   mask=mm",	// help string

"BRb <address> [ <data> [ <mask> ] ]--\n\
\n\
    The Break Read Byte command sets a data breakpoint on\n\
    the specified memory location.  During later emulation,\n\
    if any instruction reads the memory location specified by\n\
    argument 1, emulation stops and the debugger is invoked.\n\
    At that point, the PC will be pointing to the next\n\
    instruction after the one causing the breakpoint.\n\
\n\
    The breakpoint can be made more specific by supplying\n\
    the optional second argument, <data>.  To trigger the\n\
    breakpoint, not only does the address have to match,\n\
    but the data value must also match the one supplied.\n\
\n\
    Finally, an optional third parameter can be supplied,\n\
    <mask>.  This value is used to qualify the <data>\n\
    comparison.  Any bit position whose corresponding bit\n\
    in the mask is '0' will be ignored during the comparison.",

	BRK_RD,						// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT, 					// arg2 type
	ARG_16_OPT,					// arg3 type
	dbg_badd,					// debugger function
    },

#if BREAK_DATA16 // turn on/off 16b data breakpoints
    {
	"BRW", 3,					// command, len
	"    BRW   aaaa [dddd [mmmm]]      break read  word addr=aaaa, data=dddd, mask=mmmm",	// help string

"BRW <address> [ <data> [ <mask> ] ]--\n\
\n\
    The Break Read Word command sets a data breakpoint on\n\
    the specified memory location.  During later emulation,\n\
    if any instruction reads both memory byte locations\n\
    specified by argument 1, emulation stops and the debugger\n\
    is invoked.  At that point, the PC will be pointing to the\n\
    next instruction after the one causing the breakpoint.\n\
\n\
    The breakpoint can be made more specific by supplying\n\
    the optional second argument, <data>.  To trigger the\n\
    breakpoint, not only does the address have to match,\n\
    but the data value must also match the one supplied.\n\
\n\
    Finally, an optional third parameter can be supplied,\n\
    <mask>.  This value is used to qualify the <data>\n\
    comparison.  Any bit position whose corresponding bit\n\
    in the mask is '0' will be ignored during the comparison.",

	BRK_RD16,					// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT, 					// arg2 type
	ARG_16_OPT,					// arg3 type
	dbg_badd,					// debugger function
    },
#endif

    {
	"BWB", 2,					// command, len
	"    BW(b) aaaa [dd [mm]]          break write byte addr=aaaa, data=dd,   mask=mm",	// help string

"BWb <address> [ <data> [ <mask> ] ]--\n\
\n\
    The Break Write Byte command sets a data breakpoint on\n\
    the specified memory location.  During later emulation,\n\
    if any instruction writes the memory location specified by\n\
    argument 1, emulation stops and the debugger is invoked.\n\
    At that point, the PC will be pointing to the next\n\
    instruction after the one causing the breakpoint.\n\
\n\
    The breakpoint can be made more specific by supplying\n\
    the optional second argument, <data>.  To trigger the\n\
    breakpoint, not only does the address have to match,\n\
    but the data value must also match the one supplied.\n\
\n\
    Finally, an optional third parameter can be supplied,\n\
    <mask>.  This value is used to qualify the <data>\n\
    comparison.  Any bit position whose corresponding bit\n\
    in the mask is '0' will be ignored during the comparison.",

	BRK_WR,						// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT, 					// arg2 type
	ARG_16_OPT,					// arg3 type
	dbg_badd,					// debugger function
    },

#if BREAK_DATA16 // turn on/off 16b data breakpoints
    {
	"BWW", 3,					// command, len
	"    BWW   aaaa [dddd [mmmm]]      break write word addr=aaaa, data=dddd, mask=mmmm",	// help string

"BWW <address> [ <data> [ <mask> ] ]--\n\
\n\
    The Break Write Word command sets a data breakpoint on\n\
    the specified memory location.  During later emulation,\n\
    if any instruction writes both memory byte locations\n\
    specified by argument 1, emulation stops and the debugger\n\
    is invoked.  At that point, the PC will be pointing to the\n\
    next instruction after the one causing the breakpoint.\n\
\n\
    The breakpoint can be made more specific by supplying\n\
    the optional second argument, <data>.  To trigger the\n\
    breakpoint, not only does the address have to match,\n\
    but the data value must also match the one supplied.\n\
\n\
    Finally, an optional third parameter can be supplied,\n\
    <mask>.  This value is used to qualify the <data>\n\
    comparison.  Any bit position whose corresponding bit\n\
    in the mask is '0' will be ignored during the comparison.",

	BRK_WR16,					// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT, 					// arg2 type
	ARG_16_OPT,					// arg3 type
	dbg_badd,					// debugger function
    },
#endif

    {
	"BIN", 2,					// command, len
	"    BI(n)   aa [dd [mm]]          break in  port=aa, data=dd, mask=mm",	// help string

"BIn <address> [ <data> [ <mask> ] ]--\n\
\n\
    The Break In command sets a data breakpoint on the\n\
    specified input port location.  During later emulation,\n\
    if any IN reads the port location specified by argument\n\
    one, emulation stops and the debugger is invoked.\n\
    At that point, the PC will be pointing to the next\n\
    instruction after the one causing the breakpoint.\n\
\n\
    The breakpoint can be made more specific by supplying\n\
    the optional second argument, <data>.  To trigger the\n\
    breakpoint, not only does the port address have to match,\n\
    but the data value must also match the one supplied.\n\
\n\
    Finally, an optional third parameter can be supplied,\n\
    <mask>.  This value is used to qualify the <data>\n\
    comparison.  Any bit position whose corresponding bit\n\
    in the mask is '0' will be ignored during the comparison.",

	BRK_IN,						// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT, 					// arg2 type
	ARG_16_OPT,					// arg3 type
	dbg_badd,					// debugger function
    },

    {
	"BOUT", 2,					// command, len
	"    BO(ut)  aa [dd [mm]]          break out port=aa, data=dd, mask=mm",	// help string

"BOut <address> [ <data> [ <mask> ] ]--\n\
\n\
    The Break Out command sets a data breakpoint on the\n\
    specified output port location.  During later emulation,\n\
    if any OUT writes the port location specified by argument\n\
    one, emulation stops and the debugger is invoked.\n\
    At that point, the PC will be pointing to the next\n\
    instruction after the one causing the breakpoint.\n\
\n\
    The breakpoint can be made more specific by supplying\n\
    the optional second argument, <data>.  To trigger the\n\
    breakpoint, not only does the port address have to match,\n\
    but the data value must also match the one supplied.\n\
\n\
    Finally, an optional third parameter can be supplied,\n\
    <mask>.  This value is used to qualify the <data>\n\
    comparison.  Any bit position whose corresponding bit\n\
    in the mask is '0' will be ignored during the comparison.",

	BRK_OUT,					// arg 0
	ARG_16,						// arg1 type
	ARG_16_OPT, 					// arg2 type
	ARG_16_OPT,					// arg3 type
	dbg_badd,					// debugger function
    },

    {
	"BLIST", 2,					// command, len
	"    BL(ist)    [<nn>]             breakpoint list    all/nn",	// help string

"BList [nn] --\n\
\n\
    The BList command lists all of the defined breakpoints\n\
    of all types.  If the optional argument is given, it\n\
    specifies a particular breakpoint to list.\n\
\n\
    Note that while the breakpoint serial nubmers are\n\
    printed in decimal, default argument input is hex.\n\
    To specify a decimal number, precede it with a '#'.",

	0,						// arg 0
	ARG_16_OPT,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_blist,					// debugger function
    },

    {
	"BDISABLE", 2,					// command, len
	"    BD(isable) [<nn>]             breakpoint disable all/nn",	// help string

"BDisable [nn] --\n\
\n\
    The BDisable command changes the state of all defined\n\
    breakpoints to be disabled.  If the optional argument is\n\
    given, it specifies a particular breakpoint to disable.\n\
\n\
    Note that while the breakpoint serial nubmers are\n\
    printed in decimal, default argument input is hex.\n\
    To specify a decimal number, precede it with a '#'.",

	0,						// arg 0
	ARG_16_OPT,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_benable,					// debugger function
    },

    {
	"BENABLE", 2,					// command, len
	"    BE(nable)  [<nn>]             breakpoint enable  all/nn",	// help string

"BEnable [nn] --\n\
\n\
    The BEnable command changes the state of all defined\n\
    breakpoints to be enabled.  If the optional argument is\n\
    given, it specifies a particular breakpoint to enable.\n\
\n\
    Note that while the breakpoint serial nubmers are\n\
    printed in decimal, default argument input is hex.\n\
    To specify a decimal number, precede it with a '#'.",

	1,						// arg 0
	ARG_16_OPT,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_benable,					// debugger function
    },

    {
	"BKILL", 2,					// command, len
	"    BK(ill)    [<nn>]             breakpoint remove  all/nn",	// help string

"BKill [nn] --\n\
\n\
    The BKill command removes all of the defined\n\
    breakpoints.  If the optional argument is given,\n\
    it specifies a particular breakpoint to remove.\n\
\n\
    Note that while the breakpoint serial nubmers are\n\
    printed in decimal, default argument input is hex.\n\
    To specify a decimal number, precede it with a '#'.",

	2,						// arg 0
	ARG_16_OPT,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_benable,					// debugger function
    },

    {
	NULL, 99,					// summary comment
	"\nmisc commands:",
	NULL, 0,0,0,0, NULL	// unused args
    },

    {
    "OVERLAY", 2,					// command, len
	"    OV(erlay) [<file[.prn]>|<id>] [OFF|ON|BOTH|REFRESH|KILL] manage overlays",	// help string

	"OV(erlay) [<file[.prn]>|<id>] [OFF|ON|BOTH|REFRESH|KILL] --\n"
	"\n"
	"    The OVERLAY command is used to manage source code overlays\n"
	"    that are used for source-level debugging.  An overlay is\n"
	"    simply the .PRN output of the CP/M ASM or MAC programs.\n"
	"    Up to ten overlays can be managed at the same time.  This\n"
	"    command takes two optional parameters and has many forms.\n"
	"    Although the second parameter is shown in uppercase, it may\n"
	"    be typed in either upper or lower case.\n"
	"\n"
	"    At the time an overlay is created, it is given a numeric\n"
	"    shortand, the id.  In any of the commands below that take\n"
	"    a filename, the id may be used in place of the filename.\n"
	"\n"
	"    OV(erlay)\n"
	"        This form simply lists which overlays are resident, as\n"
	"        well as the display mode for each overlay.  Each overlay\n"
	"        has an ID number, which can be used in subsequent commands\n"
	"        to identify the overlay instead of the filename.\n"
	"\n"
	"    OV(erlay) <filename[.prn]|id>\n"
	"        This form reads in the specified filename and parses it.\n"
	"        If the filename as specified can't be found, an extention\n"
	"        of \".PRN\" is assumed and the file access is tried again.\n"
	"        If this file isn't already in the overlay table, the mode\n"
	"        of the overlay is assumed to be ON since none is specified.\n"
	"        If the file is already in the table, it retains its current\n"
	"        mode and the second argument is assumed to be REFRESH.\n"
	"\n"
	"    OV(erlay) <filename[.prn]|id> <OFF|ON|BOTH>\n"
	"        This form creates an overlay for the specified file if it\n"
	"        doesn't already exist, and associates one of the display\n"
	"        modes with the overlay.   If the overlay already exists,\n"
	"        the 2nd parameter changes its display mode.\n"
	"           ON    means that the debugger disassembly window will\n"
	"                 show original source lines when possible.\n"
	"           BOTH  means show both the original source lines and also\n"
	"                 mechanically disassemble each address.\n"
	"           OFF   means just mechanically disassemble each address.\n"
	"\n"
	"    OV(erlay) <filename[.prn]|id> REFRESH\n"
	"        This form reparses an existing overlay.  It is useful after\n"
	"        an edit/assemble cycle.  As a shorthand, if a filename is\n"
	"        given and it is already in the table, but no 2nd parameter\n"
	"        is supplied, REFRESH is assumed.\n"
	"\n"
	"    OV(erlay) <filename[.prn]|id> KILL\n"
	"        This form deletes the specified overlay from the table.\n"
	"\n"
	"    OV(erlay) <OFF|ON|BOTH|REFRESH|KILL>\n"
	"        This form doesn't specify a filename, so the command is\n"
	"        applied to all resident overlays.\n"
	"\n",

	0,						// arg 0
	ARG_STR_OPT,					// arg1 type
	ARG_STR_OPT, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_overlay,					// debugger function
    },

    {
	"KEY", 3,					// command, len
	"    KEY nn                        force keystroke",	// help string

"KEY nn --\n\
\n\
    The KEY command forces a simulated keystroke into the\n\
    status latch that holds keystroke information.  The\n\
    numeric argument is the ascii value of the keystroke that\n\
    is to be simulated.",

	0,						// arg 0
	ARG_8,						// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_key,					// debugger function
    },

    {
	"HELP", 1,					// command, len
	"    H(elp) [<cmd>]                command summary",	// help string

"HELP [<command_name>] --\n\
\n\
    The Help command generates a terse debugger command\n\
    summary.  If the optional second argument is supplied,\n\
    it specifies the name of a particular debugger command.\n\
    In this case, much more detailed information is supplied.",

	0,						// arg 0
	ARG_STR_OPT,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_help,					// debugger function
    },

    // this one is a synonym for HELP, but is here because until
    // the user sees the help message, they don't know to type HELP,
    // and '?' is a common help command in many apps.
    {
	"?", 1,						// command, len
	NULL,						// suppress help summary

"? [<command_name>] --\n\
\n\
    The Help command generates a terse debugger command\n\
    summary.  If the optional second argument is supplied,\n\
    it specifies the name of a particular debugger command.\n\
    In this case, much more detailed information is supplied.",

	0,						// arg 0
	ARG_STR_OPT,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_help,					// debugger function
    },

    {
	"EXIT", 4,					// command, len
	"    EXIT | X(it)                  close debugger",	// help string

"EXIT | Xit --\n\
\n\
    Either of these commands can be used to close the\n\
    debugger window and resume emulation.",

	0,						// arg 0
	ARG_NONE,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_exit,					// debugger function
    },

    {
	"XIT", 1,					// command, len
	NULL,						// suppress summary

"EXIT | Xit --\n\
\n\
    Either of these commands can be used to close the\n\
    debugger window and resume emulation.",

	0,						// arg 0
	ARG_NONE,					// arg1 type
	ARG_NONE, 					// arg2 type
	ARG_NONE,					// arg3 type
	dbg_exit,					// debugger function
    },
};


typedef struct tagBYTELIST {
    int    elems;	// size of array
    uint8 *arr;		// pointer to array of 8b
} bytelist_t;


// given a command string, find which table entry matches.
// partial matches are allowed.  returns -1 if no match.
static int
cmd_idx_lookup(char *str)
{
    int i, idx;
    unsigned int len = strlen(str);

    idx = -1;

    for(i=0; i<sizeof(cmdtable)/sizeof(cmdentry_t); i++) {
	if (!cmdtable[i].cmd)
	    continue;
	else if (len < (unsigned int)cmdtable[i].cmdlen)
	    continue;
	else if (len < strlen(cmdtable[i].cmd)) {
	    if (!strncmp(cmdtable[i].cmd, str, len)) {
		idx = i;
		break;
	    }
	} else {
	    // must be an exact match
	    if (!strcmp(cmdtable[i].cmd, str)) {
		idx = i;
		break;
	    }
	}
    }

    return idx;
}


static void 
dbg_log(char *fmt, ...)
{
    char buff[1000];
    va_list args;

    va_start(args, fmt);
    _vsnprintf(buff, sizeof(buff), fmt, args);
    va_end(args);

    UI_DbgWinLog(buff, FALSE);
}


void 
dbg_interp(char *cmd)
{
    char *op;
//	char buf[300];	//FJS
//	int buflen;		//FJS
    int i, idx, flag;
    arg_t argt[4];	// copy of command argument type
    int args;		// # of arguments
    uint32 arg[200];	// allow lots of args, more than we'll need

    // extract command name
    flag = NextArg(&cmd, &op);
    if (!flag)
	return;

    uppercase(op);

//	buflen = 0;		//FJS
//	buf[0] = '\0';	//FJS

    // find matching command in table
    idx = cmd_idx_lookup(op);
    if (idx < 0) {
	dbg_log("  Error: command not understood; try the 'help' command");
	return;
    }

    // we know which command it is, now parse args
    arg[0]  = cmdtable[idx].arg0;		// invisible parameter
    argt[1] = cmdtable[idx].arg1_type;
    argt[2] = cmdtable[idx].arg2_type;
    argt[3] = cmdtable[idx].arg3_type;
    args = 0;

    for(i=1; i<4; i++) {
	switch (argt[i]) {

	case ARG_NONE:
	    flag = NextArg(&cmd, &op);
	    if (flag) {
		dbg_log("  Error: extra characters at end of command");
		return;
	    }
	    break;

	case ARG_16:
	    flag = NumArg(&cmd, &arg[i]);
	    if (!flag) {
		dbg_log("  Error: missing expected 16b argument %d", i);
		return;
	    } else if (flag < 0) {
		dbg_log("  Error: non-numeric argument %d", i);
		return;
	    }
	    args++;
	    break;

	case ARG_16_OPT:
	    flag = NumArg(&cmd, &arg[i]);
	    if (!flag)
		arg[i] = arg[i-1];
	    else if (flag < 0) {
		dbg_log("  Error: non-numeric optional argument %d", i);
		return;
	    } else
		args++;
	    break;

	case ARG_16_OPT1:
	    flag = NumArg(&cmd, &arg[i]);
	    if (!flag)
		arg[i] = 1;
	    else if (flag < 0) {
		dbg_log("  Error: non-numeric optional argument %d", i);
		return;
	    } else
		args++;
	    break;

	case ARG_8:
	    flag = NumArg(&cmd, &arg[i]);
	    if (!flag) {
		dbg_log("  Error: missing expected 8b argument %d", i);
		return;
	    } else if (flag < 0) {
		dbg_log("  Error: non-numeric argument %d", i);
		return;
	    }
	    arg[i] &= 0xFF;
	    args++;
	    break;

	case ARG_8_LIST:
	    // scan a list of byte values
	    do {
		flag = NumArg(&cmd, &arg[args+1]);
		if (!flag)
		    break;
		else if (flag < 0) {
		    dbg_log("  Error: non-numeric argument %d", args+1);
		    return;
		}
		arg[args+1] &= 0xFF;
		args++;
	    } while (args < sizeof(arg)/sizeof(uint32));
	    break;

	case ARG_A_LIST:
	    // scan a list of ascii bytes
	    do {
		arg[args+1] = *cmd;
		if (!arg[args+1])
		    break;
		args++;
		cmd++;
	    } while (args < sizeof(arg)/sizeof(uint32));
	    break;

	case ARG_STR_OPT:
	case ARG_STR:
	    // scan a word
	    flag = NextArg(&cmd, (char **)(&arg[i]));
	    if (flag)
		args++;
	    else if (argt[i] == ARG_STR) {
		dbg_log("  Error: missing arg%d token", i);
		return;
	    }
	    break;

	default:
	    ASSERT(0);
	    break;

	} // switch
    } // for i


    // call the function
    (*cmdtable[idx].fcn)(args, arg);
}


// dump bytes
static void 
dbg_db(int args, uint32 arg[])
{
    uint32 addr1 = arg[1];
    uint32 addr2 = arg[2];
    uint32 addr;
    int v, mod, buflen;
    char buf[300];

    buflen = 0;
    buf[0] = '\0';
    mod = 0;

    for(addr=addr1; addr == addr1 || addr <= addr2; addr++, mod++) {

	if (mod == 16) {
	    dbg_log(buf);
	    mod = 0;
	}
	if (mod == 0) {
	    int len = sprintf(buf, "%04X:", addr);
	    buflen = len;
	}

	v = Di80((word)addr);
	sprintf(&buf[buflen], " %02X", v); buflen += 3;
    }

    dbg_log(buf);
}


// dump words
static void 
dbg_dw(int args, uint32 arg[])
{
    uint32 addr1 = arg[1];
    uint32 addr2 = arg[2];
    uint32 addr;
    int v, mod, buflen;
    char buf[300];

    buflen = 0;
    buf[0] = '\0';
    mod = 0;

    for(addr=addr1; addr == addr1 || addr <= addr2; addr+=2, mod+=2) {

	if (mod == 16) {
	    dbg_log(buf);
	    mod = 0;
	}
	if (mod == 0) {
	    int len = sprintf(buf, "%04X:", addr);
	    buflen = len;
	}

	v = Di80((word)addr) + 256*Di80((byte)(addr+1));
	sprintf(&buf[buflen], " %04X", v); buflen += 5;
    }

    dbg_log(buf);
}


// dump ascii
static void 
dbg_da(int args, uint32 arg[])
{
    uint32 addr1 = arg[1];
    uint32 addr2 = arg[2];
    uint32 addr;
    int v, mod, buflen;
    char buf[300];

    mod = 0;

    for(addr=addr1; addr == addr1 || addr <= addr2; addr++, mod++) {

	if (mod == 32) {
	    buf[buflen] = '\0';
	    dbg_log(buf);
	    mod = 0;
	}
	if (mod == 0) {
	    int len = sprintf(buf, "%04X:", addr);
	    buflen = len;
	}

	v = Di80((word)addr);
	v &= 0x7F;	// strip off inverse video
	if (v < 32)
	    buf[buflen] = '.';
	else
	    buf[buflen] = v;
	buflen += 1;
    }

    buf[buflen] = '\0';
    dbg_log(buf);
}


// enter bytes/ascii
static void 
dbg_en(int args, uint32 arg[])
{
    uint32 addr1 = arg[1];
    int i;

    for(i=0; i < args-1; i++) {
	Wr80((word)(addr1+i),(byte)arg[2+i]);
    }

    // redraw of debugger window since we might
    // have diddled state which is displayed
    UI_DbgWin(DW_Update, UPDATE_REFRESH);
}


// fill (bytes)
static void 
dbg_fill(int args, uint32 arg[])
{
    uint32 addr1 = arg[1];
    uint32 addr2 = arg[2];
    uint8  val   = arg[3];
    uint32 addr;

    if (addr1 > addr2) {
	dbg_log("  Error: arg1 > arg2");
	return;
    }

    for(addr=addr1; addr <= addr2; addr++) {
	Wr80((word)addr,(byte)val);
    }

    dbg_log("  OK.  %d bytes filled.", addr2-addr1+1);

    // redraw of debugger window since we might
    // have diddled state which is displayed
    UI_DbgWin(DW_Update, UPDATE_REFRESH);
}


// disassemble
static void
dbg_dasm(int args, uint32 arg[])
{
    uint32 addr1 = arg[1];
    uint32 addr2 = arg[2];
    uint32 addr;
    int oplen;
    char buf[300];

    if (addr1 > addr2) {
	dbg_log("  Error: arg1 > arg2");
	return;
    }

    for(addr=addr1; addr == addr1 || addr <= addr2; addr+=oplen) {
	int j;

	sprintf(buf, "  %04X ", addr);
	oplen = OpLen((word)addr);	// get instruction length

	for(j=0; j<3; j++) {
	    if (j < oplen)
		sprintf(&buf[7+3*j], "%02X ", Di80((word)(addr+j)));
	    else
		sprintf(&buf[7+3*j], "   ");
	}

	(void)DAsm(&buf[16], (word)addr, 0);
	dbg_log(buf);
    }
}


// locate bytes
// show matching
static void 
dbg_lb(int args, uint32 arg[])
{
    uint32 sm_cmd = (arg[0] != 0);
    uint32 addr1 = arg[1];
    uint32 addr2 = arg[2];
    uint32 addr, v;
    int len = args - 2;		// # bytes in search string
    int i, miss, found;
    char buf[300];

    found = 0;
    buf[0] = '\0';
    
    for(addr=addr1; addr <= addr2-len+1; addr++) {

	miss = 0;

	for(i=0; i<len; i++) {
	    v = Di80((word)(addr+i));
	    if (v != arg[3+i]) {
		miss = 1;
		break;
	    }
	}
	if (!miss) {
	    found++;
	    if (!sm_cmd)
		break;
	    sprintf(&buf[strlen(buf)], "%04X ", addr);
	    if ((found % 6) == 0) {
		dbg_log(buf);
		buf[0] = '\0';
	    }
	}
    }


    if (found > 0) {
	if (!sm_cmd)
	    sprintf(buf, "  Found: string starts at %04X", addr);
	else if ((found % 6) == 0)
	    return;
    } else
	sprintf(buf, "  Not found.");
    dbg_log(buf);
}

// compare memory
static void
dbg_cm(int args, uint32 arg[])
{
    uint32 addr  = arg[1];
    uint32 addr2 = arg[2];
    uint32 daddr = arg[3];
    int s, d;
    int miss = 0;
    //char buf[300];

    //buf[0] = '\0';

    while (addr <= addr2) {
	s = Di80((word)(addr));
	d = Di80((word)(daddr));
	if (s != d) {
	    dbg_log("  %04X:%02X != %04X:%02X", addr,s,daddr,d);
	    miss = 1;
	}
	addr++;
	daddr++;
    }

    if (!miss) {
	dbg_log("  Matched exactly.");
    }
}

// move memory
static void
dbg_mm(int args, uint32 arg[])
{
    int addr1 = arg[1];
    int addr2 = arg[2];
    int addr3 = arg[3];
    //char buf[300];

    if (addr2 < addr1) {
	dbg_log("  Error: addr2 must not be less than addr1.");
	return;
    }

    if ((addr3 >= addr1) && (addr3 <= addr2)) {
	// backwards move due to overlapping range
	addr3 += (addr2 - addr1);
	while (addr2 >= addr1)
	    Mem[addr3--] = Mem[addr2--];
    } else {
	// non-overlapping: forward move
	while (addr1 <= addr2)
	    Mem[addr3++] = Mem[addr1++];
    }
}


// reset processor
static void 
dbg_reset(int args, uint32 arg[])
{
    Reset8080();
    UI_DbgWin(DW_Update, UPDATE_ALL);
}


// step over
static void 
dbg_next(int args, uint32 arg[])
{
    int count = arg[1];
    Sys_DbgNotify(STEP_KEY, count);
}

// step into
static void 
dbg_step(int args, uint32 arg[])
{
    int count = arg[1];
    Sys_DbgNotify(STEPI_KEY, count);
}


// continue running
static void 
dbg_run(int args, uint32 arg[])
{
    uint32 newpc = arg[1];

    if (args == 1)
	I80Regs.nPC.W = (newpc & 0xFFFF);

    Sys_DbgNotify(RUN_KEY, 0);
}


// set register value
static void 
dbg_set(int args, uint32 arg[])
{
    char *regname = (char*)arg[1];
    uint32 val    = arg[2];

    typedef struct {
	char *regname;
	int   regsize;
	void *preg;
    } regtbl_t;

    static regtbl_t d_regs[] =
    {
	{ "A",   8, (void*)&I80Regs.nAF.B.h },
	{ "F",   8, (void*)&I80Regs.nAF.B.l },
	{ "B",   8, (void*)&I80Regs.nBC.B.h },
	{ "C",   8, (void*)&I80Regs.nBC.B.l },
	{ "D",   8, (void*)&I80Regs.nDE.B.h },
	{ "E",   8, (void*)&I80Regs.nDE.B.l },
	{ "H",   8, (void*)&I80Regs.nHL.B.h },
	{ "L",   8, (void*)&I80Regs.nHL.B.l },
	{ "AF", 16, (void*)&I80Regs.nAF.W },
	{ "BC", 16, (void*)&I80Regs.nBC.W },
	{ "DE", 16, (void*)&I80Regs.nDE.W },
	{ "HL", 16, (void*)&I80Regs.nHL.W },
	{ "PC", 16, (void*)&I80Regs.nPC.W },
	{ "SP", 16, (void*)&I80Regs.nSP.W },
    };

    const int entries = sizeof(d_regs)/sizeof(regtbl_t);
    int i;

    uppercase(regname);

    for(i=0; i<entries; i++) {
	int flags = UPDATE_REFRESH;
	if (!strcmp(d_regs[i].regname, regname)) {
	    // match
	    if (d_regs[i].regsize == 8) {
		*(byte*)d_regs[i].preg = (val & 0xFF);
	    } else {
		*(word*)d_regs[i].preg = (val & 0xFFFF);
	    }
	    if (!strcmp(regname,"PC"))
		flags |= HOME_DASM;
	    else if (!strcmp(regname,"SP"))
		flags |= HOME_MEM;
	    UI_DbgWin(DW_Update, flags);
	    return;
	}
    }

    dbg_log("  Error: no such register");
}

// FJS
#if 0
// set register value
static void
dbg_include(int args, uint32 arg[])
{
/*
    static int inuse = 0;
    char *filename = (char*)arg[1];
    char cmd[1024];
    int sh;

    // detect if a script tries doing an include command
    if (inuse)
	return;

    sh = script_open(filename, SCRIPT_META_INC,
			 3);	// max nesting depth
    switch (sh) {
	case SCRIPT_OK:
	    break;
	case SCRIPT_MALLOC_ERROR:
	    dbg_log("  Error: couldn't malloc enough memory.");
	    return;
	case SCRIPT_BAD_FILE:
	    dbg_log("  Error: couldn't open file.");
	    return;
	default:
	    dbg_log("  Error: couldn't run script.");
	    return;
    }

    inuse = 1;
    for(;;) {
	int r = script_next_line(sh, cmd, sizeof(cmd)-1);
	if (r == SCRIPT_OK) {
	    UI_DbgWinLog(cmd, TRUE);
	    dbg_interp(cmd);
	} else
	    break;
    }
    script_close(sh);
    inuse = 0;
	*/
}
#endif

// split a string separated by newlines into individual strings
// and send them to dbg_log(), since dbg_log() can't handle it.
static void 
LogMultistring(char *str)
{
    char *cp = str;
    char buf[300], *bp;

    while (*cp) {
	// grab the next string
	bp = buf;
	while (*cp && (*cp != '\n')) {
	    *bp++ = *cp++;
	}
	if (*cp == '\n')
	    cp++;
	*bp = '\0';
	dbg_log(buf);
    }
}


// command summary
static void 
dbg_help(int args, uint32 arg[])
{
    int i;
    char *cmd = (char*)arg[1];

    if (args == 1) {
	// help for a specific command
	int idx;

	uppercase(cmd);

	idx = cmd_idx_lookup(cmd);
	if (idx < 0) {
	    dbg_log("  Error: command not found.");
	    return;
	}
	if (cmdtable[idx].help == NULL) {
	    dbg_log("  No detailed help is available for this command.");
	    return;
	}

	LogMultistring(cmdtable[idx].help);
	return;
    }

    // help summary for all commands
    for(i=0; i<sizeof(cmdtable)/sizeof(cmdentry_t); i++) {
	if (cmdtable[i].syntax != NULL)
	    LogMultistring(cmdtable[i].syntax);
    }
}


// close debugger
static void 
dbg_exit(int args, uint32 arg[])
{
    Sys_DbgNotify(CLOSE_WIN, 0);
}


static void 
append_data_mask8(char *buf, int i)
{
    uint32 mask = breakpoints[i].mask;

    if (mask != 0x00) {
	sprintf(&buf[strlen(buf)],", data=  %02X", breakpoints[i].data);
    }

    if ((mask != 0xFF) && (mask != 0x00)) {
	sprintf(&buf[strlen(buf)],", mask=  %02X", breakpoints[i].mask);
    }
}

static void 
append_data_mask16(char *buf, int i)
{
    uint32 mask = breakpoints[i].mask;

    if (mask != 0x0000) {
	sprintf(&buf[strlen(buf)],", data=%04X", breakpoints[i].data);
    }

    if ((mask != 0xFFFF) && (mask != 0x0000)) {
	sprintf(&buf[strlen(buf)],", mask=%04X", breakpoints[i].mask);
    }
}

static void 
bp_to_str(char *buf, int num)
{
    int len;
    sprintf(buf,"  brk #%d:", breakpoints[num].sernum);
    len = strlen(buf);

    switch (breakpoints[num].btype) {
    case BRK_OP:
	sprintf(&buf[len],"  BPC          PC=%04X", breakpoints[num].addr);
	break;
    case BRK_IN:
	sprintf(&buf[len],"  BIN     in addr=  %02X", breakpoints[num].addr);
	append_data_mask8(buf, num);
	break;
    case BRK_OUT:
	sprintf(&buf[len],"  BOUT   out addr=  %02X", breakpoints[num].addr);
	append_data_mask8(buf, num);
	break;
    case BRK_RD:
	sprintf(&buf[len],"  BRB   read addr=%04X", breakpoints[num].addr);
	append_data_mask8(buf, num);
	break;
    case BRK_RD16:
	sprintf(&buf[len],"  BRW   read addr=%04X", breakpoints[num].addr);
	append_data_mask16(buf, num);
	break;
    case BRK_WR:
	sprintf(&buf[len],"  BWB  write addr=%04X", breakpoints[num].addr);
	append_data_mask8(buf, num);
	break;
    case BRK_WR16:
	sprintf(&buf[len],"  BWW  write addr=%04X", breakpoints[num].addr);
	append_data_mask16(buf, num);
	break;
    default:
	ASSERT(0);
	break;
    } // switch

    if (!breakpoints[num].enabled) {
	len = strlen(buf);
	while (len < 50)
	    buf[len++] = ' ';
	sprintf(&buf[len]," (disabled)");
    }
}


// arg[0]: 0=disable, 1=enable, 2=kill
static void 
dbg_benable(int args, uint32 arg[])
{
    int brknum = (int)arg[1];
    int do_all = (args == 0);
    int found;
    int i,j;
    char buf[100];

    if (breaknum == 1) {
	dbg_log("  No breakpoints defined.");
	return;
    }

    // special case -- clear all breakpoints
    if (do_all && (arg[0] == 2)) {
	// clear
	dbg_log("  OK.  %d breakpoint%s cleared", breaknum-1, (breaknum==2)?"":"s");
	breakpoint_init();
	bkptchange_coresim();
	UI_DbgWin(DW_Update, UPDATE_REFRESH);
	return;
    }

    found = 0;

    for(i=1; i<breaknum; i++) {
	if (do_all || (breakpoints[i].sernum == brknum)) {

	    found = 1;

	    switch (arg[0]) {
		case 0: // disable
		case 1: // enable
		    breakpoints[i].enabled = arg[0];
		    bp_to_str(buf, i);
		    dbg_log(buf);
		    break;
		case 2: // remove breakpoint
		    for(j=i; j<breaknum-1; j++)
			breakpoints[j] = breakpoints[j+1];
		    breaknum--;
		    dbg_log("  Removed breakpoint %d", brknum);
		    UI_DbgWin(DW_Update, UPDATE_REFRESH);
		    return;
		default:
		    ASSERT(0);
		    break;
	    } // switch

	} // if
    } // for

    if (!do_all && !found)
	dbg_log("  Error: that breakpoint is not defined");

    // redraw of debugger window since we might be displaying
    // a line which has had breakpoint state change
    bkptchange_coresim();
    UI_DbgWin(DW_Update, UPDATE_REFRESH);
}


static void 
dbg_blist(int args, uint32 arg[])
{
    int brknum = (int)arg[1];
    int do_all = (args == 0);
    int i, found;
    char buf[100];

    if (breaknum == 1) {
	dbg_log("  No breakpoints defined.");
	return;
    }

    found = 0;

    for(i=1; i<breaknum; i++) {
	if (do_all || (breakpoints[i].sernum == brknum)) {
	    found = 1;
	    bp_to_str(buf, i);
	    dbg_log(buf);
	}
    }

    if (!found)
	dbg_log("  Error: that breakpoint is not defined");
}


static void 
dbg_badd(int args, uint32 arg[])
{
    uint32 addr = arg[1];
    uint32 data = arg[2];
    uint32 mask = arg[3];
    int idx;
    char buf[100];

    // fill in missing arguments
    switch (args) {
	case 1: mask = 0x0000; data = 0x0000; break;	// eg BR 1234
	case 2: mask = 0xFFFF; break;			// eg BR 1234 05
	case 3: break;					// eg BR 1234 05 0F
	default:
	    ASSERT(0);
	    break;
    }

    // mask fields to match breakpoint type
    switch (arg[0]) {
	case BRK_IN:
	case BRK_OUT:
	    // 8b op
	    addr &= 0xFF;
	    data &= 0xFF;
	    mask &= 0xFF;
	    break;
	case BRK_RD:
	case BRK_WR:
	    // 8b op
	    addr &= 0xFFFF;
	    data &= 0xFF;
	    mask &= 0xFF;
	    break;
	case BRK_OP:
	case BRK_RD16:
	case BRK_WR16:
	case BRK_TEMP:
	    // 16b op
	    addr &= 0xFFFF;
	    data &= 0xFFFF;
	    mask &= 0xFFFF;
	    break;
	default:
	    ASSERT(0);
	    break;
    }

    // add the breakpoint
    idx = breakpoint_add((break_t)arg[0], (word)addr, (word)data, (word)mask);
    if (!idx) {
	dbg_log("  Error: too many breakpoints defined.");
	return;
    }
    
    if (idx > 0)
	bp_to_str(buf, idx);
    else
	strcpy(buf, "  OK.  Setting temporary breakpoint.");
    dbg_log(buf);

    bkptchange_coresim();

    // redraw of debugger window since we might be displaying
    // a line which has had breakpoint state change
    UI_DbgWin(DW_Update, UPDATE_REFRESH);
}


// add temporary breakpoint at specified address
static void 
dbg_to(int args, uint32 arg[])
{
    breakpoint_temp_kill();	// ignore single step or earlier "to" command

    // add the temporary breakpoint
    dbg_badd(args, arg);

    // change back to run state
    Sys_DbgNotify(FORCERUN_KEY, 0);
}



static void 
dbg_save(int args, uint32 arg[])
{
	// save a range of memory to a file. HEX or .ent
	
    uint32 addr1 = arg[1];
    uint32 addr2 = arg[2];
    char *fname = (char*)arg[3];
    int rslt;

	// 2001/11/13 -- changed memory array name
	rslt = hexfile_hexdump(fname, arg[0], addr1, addr2, Mem);

    if (rslt != HFILE_OK)
	dbg_log("    Error: problem creating dump file.");
    else
	dbg_log("    OK; dump file created.");

	
}


static void 
dbg_load(int args, uint32 arg[])
{
	// load a hex file into memory. autodetect format
	
    int stat;

    char *fname = (char*)arg[1];

    stat = hexfile_read(fname, 1, Mem);
    if (stat != HFILE_OK)
	dbg_log("    Error reading file.");
    else
	dbg_log("    OK. File loaded.");
	
}


// key -- force keystroke
static void 
dbg_key(int args, uint32 arg[])
{
    byte  key = arg[1];

    SysKeyPress(key);

    if (key >=32 && key < 128)
	dbg_log("  OK, pressed key 0x%02x ('%c')", key, key);
    else
	dbg_log("  OK, pressed key 0x%02x", key);
}

// overlay -- the most complicated command to parse

enum tagOVERLAY { mod_nomatch, mod_off, mod_on, mod_both, mod_refresh, mod_kill };

// scan the word to see if it maches one of the modifiers
static int
dbg_overlay_helper(char *str)
{
    typedef struct {
	char *modstr;
	int   code;
    } modtable_t;

    static const modtable_t modtable[] = {
	{ "off",     mod_off     },
	{ "on",      mod_on      },
	{ "both",    mod_both    },
	{ "refresh", mod_refresh },
	{ "kill",    mod_kill    },
    };

    int i;

    ASSERT(str != NULL);

    for(i=0; i<sizeof(modtable)/sizeof(modtable_t); i++) {
	//if (!strcasecmp(str, modtable[i].modstr))
	if (!_stricmp(str, modtable[i].modstr))
	    return modtable[i].code;
    }

    return mod_nomatch;
}

static int
doparse(char *filename)
{
    int status = OvlParse(filename, 0);	// not autoloaded
    if (status != OVLERR_OK) {
	dbg_log("  Error: failed to parse '%s'", filename);
	dbg_log("         turning off overlay");
    }
    return status;
}

static void
domodcmd(int id, int modcmd, char *filename)
{
    switch (modcmd) {
	case mod_off:  OvlSetMode(id, OVERLAY_OFF);  break;
	case mod_on:   OvlSetMode(id, OVERLAY_ON);   break;
	case mod_both: OvlSetMode(id, OVERLAY_BOTH); break;
	case mod_kill: OvlRemove(id); break;
	case mod_refresh:
	    {
		// dup in case filename points to overlay struct,
		// in which case doparse() trashes the name
		char *dupfile = strdup(filename);
		if (dupfile != NULL) {
		    (void)doparse(dupfile);
		    free(dupfile);
		}
	    }
	    break;
	default:
	    ASSERT(0);
	    break;
    }

    OvlHitInit();	// FIXME: is this the best place for this?
    UI_DbgWin(DW_Update, UPDATE_REFRESH);
}


static void
dbg_overlay(int args, uint32 arg[])
{
    char *arg1 = (char*)arg[1];	// filename
    char *arg2 = (char*)arg[2];	// OFF|ON|BOTH|REFRESH|KILL
    char *filename = NULL;	// overlay filename
    int  ovlid = -1;		// overlay ID
    int  modcmd = mod_nomatch;	// modifier argument

    // no arguments?  just list the table.
    if (args == 0) {
	int num = OvlNumOverlays();
	int i;
	if (num == 0) {
	    dbg_log("  No overlays.");
	    return;
	}
	dbg_log("  min   max   mode  id   auto  filename");
	dbg_log("  ----  ----  ----  ---  ----  ----------------------");
	for(i=0; i<num; i++) {
	    char *filename;
	    int   id, autoload, minaddr, maxaddr;
	    ovr_stat_t mode;
	    OvlInfo(i, &filename, &id, &autoload, &mode, &minaddr, &maxaddr);
	    dbg_log("  %04X  %04X  %-4s  %3d  %4s  %s",
		    minaddr, maxaddr,
		    (mode==OVERLAY_BOTH) ? "both" :
		    (mode==OVERLAY_OFF)  ? "off"  :
					   "on",
		    id, (autoload) ? "auto" : "", filename);
	}
	return;
    }


    // see if the first arg a filename?  an index?
    if (is_a_file(arg1)) {
	filename = strdup(arg1);
    } else {
	filename = malloc(strlen(arg1) + 4 + 1);
	strcpy(filename, arg1);
	strcat(filename, ".prn");
	if (!is_a_file(filename)) {
	    free(filename);
	    filename = NULL;
	    // check for a number
	    if (isdigit((int)arg1[0])) {
		ovlid = atoi(arg1);
		// see if it is valid, and covert to a filename
		filename = OvlID2File(ovlid);
		if (filename == NULL) {
		    dbg_log("    Error: non-existant overlay ID: %d", ovlid);
		    return;
		}
	    } else {
		// check if it is a modifier argument
		modcmd = dbg_overlay_helper(arg1);
		if (modcmd == mod_nomatch) {
		    dbg_log("    Error: can't open file '%s'", arg1);
		    return;
		}
	    }
	}
    }

    // figure out the implied 2nd argument
    if ((args == 1) && (filename != NULL)) {
	// "OVERLAY <filename>"
	ovlid = OvlFile2ID(filename);
	modcmd = (ovlid < 0) ? mod_on : mod_refresh;
	arg2 = "on";
	args = 2;
    }

    if (args == 1) {
	// "OVERLAY <OFF|ON|BOTH|REFRESH|KILL>"
	// apply command to all overlays in the table.
	// work backwards in case it's KILL.
	int num, i;
	ASSERT(modcmd != mod_nomatch);
	num = OvlNumOverlays();
	if (num == 0) {
	    dbg_log("  No overlays present.");
	    return;
	}
	for(i=num-1; i>=0; i--) {
	    char *filename;
	    int   id;
	    ovr_stat_t mode;
	    OvlInfo(i, &filename, &id, NULL, &mode, NULL, NULL);
	    domodcmd(id, modcmd, filename);
	}
	dbg_log("  Done.");
	return;
    }


    // the command must have two arguments by this point.
    // verify the second argument.
    if (filename == NULL) {
	// eg, "OVERLAY ON ON"
	dbg_log("  Error: the first argument must be a filename or overlay id");
	return;
    }
    modcmd = dbg_overlay_helper(arg2);
    if (modcmd == mod_nomatch) {
	dbg_log("  Error: unknown second parameter: '%s'", arg2);
	return;
    }

    // "OVERLAY <filename|id> <OFF|ON|BOTH|REFRESH|KILL>" form
    ovlid = OvlFile2ID(filename);
    if (ovlid < 0) {
	// it is a new overlay
	if (doparse(filename) != OVLERR_OK)
	    return;
	ovlid = OvlFile2ID(filename);
	ASSERT(ovlid >= 0);
	if (modcmd == mod_refresh)
	    modcmd = OVERLAY_ON;	// refresh doens't make sense for a new overlay
    }
    // now perform modifier command
    domodcmd(ovlid, modcmd, filename);
}


// helper function
// FIXME: this should be part of the UI_* function set

static int
is_a_file(char *fname)
{
    FILE *fp;

    if (fname != NULL) {
	fp = fopen(fname, "r");
	if (fp != NULL) {
	    fclose(fp);
	    return 1;
	}
    }

    return 0;
}

