/*
 * monitor.c - Implements a builtin system monitor for debugging
 *
 * Copyright (C) 1995-1998 David Firth
 * Copyright (C) 1998-2005 Atari800 development team (see DOC/CREDITS)
 *
 * This file is part of the Atari800 emulator project which emulates
 * the Atari 400, 800, 800XL, 130XE, and 5200 8-bit computers.
 *
 * Atari800 is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Atari800 is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Atari800; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "antic.h"
#include "atari.h"
#include "cpu.h"
#include "gtia.h"
#include "memory.h"
#include "monitor.h"
#include "pia.h"
#include "pokey.h"
#include "util.h"
#ifdef STEREO_SOUND
#include "pokeysnd.h"
#endif

#ifdef __PLUS

#include <stdarg.h>
#include "misc_win.h"

FILE *mon_output, *mon_input;

void monitor_printf(const char *format, ...)
{
	va_list args;
	va_start(args, format);
	vfprintf(mon_output, format, args);
	va_end(args);
}

#define printf            monitor_printf
#define puts(s)           fputs(s, mon_output)
#define putchar(c)        fputc(c, mon_output)
#define perror(filename)  printf("%s: %s\n", filename, strerror(errno))

#undef stdout
#define stdout mon_output

#undef stdin
#define stdin mon_input

#define PLUS_EXIT_MONITOR Misc_FreeMonitorConsole(mon_output, mon_input)

#else /* __PLUS */

#define PLUS_EXIT_MONITOR

#endif /* __PLUS */

#ifdef MONITOR_TRACE
FILE *trace_file = NULL;
#endif

#ifdef MONITOR_HINTS

typedef struct {
	char *name;
	UWORD addr;
} symtable_rec;

 /* Symbol names taken from atari.equ - part of disassembler by Erich Bacher
    and from antic.h, gtia.h, pia.h and pokey.h.
    Symbols must be sorted by address. If the adress has different names
    when reading/writing to it, put the read name first. */

static const symtable_rec symtable_builtin[] = {
	{"NGFLAG",  0x0001}, {"CASINI",  0x0002}, {"CASINI+1",0x0003}, {"RAMLO",   0x0004},
	{"RAMLO+1", 0x0005}, {"TRAMSZ",  0x0006}, {"CMCMD",   0x0007}, {"WARMST",  0x0008},
	{"BOOT",    0x0009}, {"DOSVEC",  0x000a}, {"DOSVEC+1",0x000b}, {"DOSINI",  0x000c},
	{"DOSINI+1",0x000d}, {"APPMHI",  0x000e}, {"APPMHI+1",0x000f}, {"POKMSK",  0x0010},
	{"BRKKEY",  0x0011}, {"RTCLOK",  0x0012}, {"RTCLOK+1",0x0013}, {"RTCLOK+2",0x0014},
	{"BUFADR",  0x0015}, {"BUFADR+1",0x0016}, {"ICCOMT",  0x0017}, {"DSKFMS",  0x0018},
	{"DSKFMS+1",0x0019}, {"DSKUTL",  0x001a}, {"DSKUTL+1",0x001b}, {"ABUFPT",  0x001c},
	{"ABUFPT+1",0x001d}, {"ABUFPT+2",0x001e}, {"ABUFPT+3",0x001f},
	{"ICHIDZ",  0x0020}, {"ICDNOZ",  0x0021}, {"ICCOMZ",  0x0022}, {"ICSTAZ",  0x0023},
	{"ICBALZ",  0x0024}, {"ICBAHZ",  0x0025}, {"ICPTLZ",  0x0026}, {"ICPTHZ",  0x0027},
	{"ICBLLZ",  0x0028}, {"ICBLHZ",  0x0029}, {"ICAX1Z",  0x002a}, {"ICAX2Z",  0x002b},
	{"ICAX3Z",  0x002c}, {"ICAX4Z",  0x002d}, {"ICAX5Z",  0x002e}, {"ICAX6Z",  0x002f},
	{"STATUS",  0x0030}, {"CHKSUM",  0x0031}, {"BUFRLO",  0x0032}, {"BUFRHI",  0x0033},
	{"BFENLO",  0x0034}, {"BFENHI",  0x0035}, {"LTEMP",   0x0036}, {"LTEMP+1", 0x0037},
	{"BUFRFL",  0x0038}, {"RECVDN",  0x0039}, {"XMTDON",  0x003a}, {"CHKSNT",  0x003b},
	{"NOCKSM",  0x003c}, {"BPTR",    0x003d}, {"FTYPE",   0x003e}, {"FEOF",    0x003f},
	{"FREQ",    0x0040}, {"SOUNDR",  0x0041}, {"CRITIC",  0x0042}, {"FMSZPG",  0x0043},
	{"FMSZPG+1",0x0044}, {"FMSZPG+2",0x0045}, {"FMSZPG+3",0x0046}, {"FMSZPG+4",0x0047},
	{"FMSZPG+5",0x0048}, {"FMSZPG+6",0x0049}, {"ZCHAIN",  0x004a}, {"ZCHAIN+1",0x004b},
	{"DSTAT",   0x004c}, {"ATRACT",  0x004d}, {"DRKMSK",  0x004e}, {"COLRSH",  0x004f},
	{"TEMP",    0x0050}, {"HOLD1",   0x0051}, {"LMARGN",  0x0052}, {"RMARGN",  0x0053},
	{"ROWCRS",  0x0054}, {"COLCRS",  0x0055}, {"COLCRS+1",0x0056}, {"DINDEX",  0x0057},
	{"SAVMSC",  0x0058}, {"SAVMSC+1",0x0059}, {"OLDROW",  0x005a}, {"OLDCOL",  0x005b},
	{"OLDCOL+1",0x005c}, {"OLDCHR",  0x005d}, {"OLDADR",  0x005e}, {"OLDADR+1",0x005f},
	{"FKDEF",   0x0060}, {"FKDEF+1", 0x0061}, {"PALNTS",  0x0062}, {"LOGCOL",  0x0063},
	{"ADRESS",  0x0064}, {"ADRESS+1",0x0065}, {"MLTTMP",  0x0066}, {"MLTTMP+1",0x0067},
	{"SAVADR",  0x0068}, {"SAVADR+1",0x0069}, {"RAMTOP",  0x006a}, {"BUFCNT",  0x006b},
	{"BUFSTR",  0x006c}, {"BUFSTR+1",0x006d}, {"BITMSK",  0x006e}, {"SHFAMT",  0x006f},
	{"ROWAC",   0x0070}, {"ROWAC+1", 0x0071}, {"COLAC",   0x0072}, {"COLAC+1", 0x0073},
	{"ENDPT",   0x0074}, {"ENDPT+1", 0x0075}, {"DELTAR",  0x0076}, {"DELTAC",  0x0077},
	{"DELTAC+1",0x0078}, {"KEYDEF",  0x0079}, {"KEYDEF+1",0x007a}, {"SWPFLG",  0x007b},
	{"HOLDCH",  0x007c}, {"INSDAT",  0x007d}, {"COUNTR",  0x007e}, {"COUNTR+1",0x007f},
	{"LOMEM",   0x0080}, {"LOMEM+1", 0x0081}, {"VNTP",    0x0082}, {"VNTP+1",  0x0083},
	{"VNTD",    0x0084}, {"VNTD+1",  0x0085}, {"VVTP",    0x0086}, {"VVTP+1",  0x0087},
	{"STMTAB",  0x0088}, {"STMTAB+1",0x0089}, {"STMCUR",  0x008a}, {"STMCUR+1",0x008b},
	{"STARP",   0x008c}, {"STARP+1", 0x008d}, {"RUNSTK",  0x008e}, {"RUNSTK+1",0x008f},
	{"TOPSTK",  0x0090}, {"TOPSTK+1",0x0091}, {"MEOLFLG", 0x0092}, {"POKADR",  0x0095},
	{"POKADR+1",0x0096}, {"DATAD",   0x00b6}, {"DATALN",  0x00b7}, {"DATALN+1",0x00b8},
	{"STOPLN",  0x00ba}, {"STOPLN+1",0x00bb}, {"SAVCUR",  0x00be}, {"IOCMD",   0x00c0},
	{"IODVC",   0x00c1}, {"PROMPT",  0x00c2}, {"ERRSAVE", 0x00c3}, {"COLOUR",  0x00c8},
	{"PTABW",   0x00c9}, {"LOADFLG", 0x00ca}, {"FR0",     0x00d4}, {"FR0+1",   0x00d5},
	{"FR0+2",   0x00d6}, {"FR0+3",   0x00d7}, {"FR0+4",   0x00d8}, {"FR0+5",   0x00d9},
	{"FRE",     0x00da}, {"FRE+1",   0x00db}, {"FRE+2",   0x00dc}, {"FRE+3",   0x00dd},
	{"FRE+4",   0x00de}, {"FRE+5",   0x00df}, {"FR1",     0x00e0}, {"FR1+1",   0x00e1},
	{"FR1+2",   0x00e2}, {"FR1+3",   0x00e3}, {"FR1+4",   0x00e4}, {"FR1+5",   0x00e5},
	{"FR2",     0x00e6}, {"FR2+1",   0x00e7}, {"FR2+2",   0x00e8}, {"FR2+3",   0x00e9},
	{"FR2+4",   0x00ea}, {"FR2+5",   0x00eb}, {"FRX",     0x00ec}, {"EEXP",    0x00ed},
	{"NSIGN",   0x00ee}, {"ESIGN",   0x00ef}, {"FCHRFLG", 0x00f0}, {"DIGRT",   0x00f1},
	{"CIX",     0x00f2}, {"INBUFF",  0x00f3}, {"INBUFF+1",0x00f4}, {"ZTEMP1",  0x00f5},
	{"ZTEMP1+1",0x00f6}, {"ZTEMP4",  0x00f7}, {"ZTEMP4+1",0x00f8}, {"ZTEMP3",  0x00f9},
	{"ZTEMP3+1",0x00fa}, {"RADFLG",  0x00fb}, {"FLPTR",   0x00fc}, {"FLPTR+1", 0x00fd},
	{"FPTR2",   0x00fe}, {"FPTR2+1", 0x00ff},

	{"VDSLST",  0x0200}, {"VDSLST+1",0x0201}, {"VPRCED",  0x0202}, {"VPRCED+1",0x0203},
	{"VINTER",  0x0204}, {"VINTER+1",0x0205}, {"VBREAK",  0x0206}, {"VBREAK+1",0x0207},
	{"VKEYBD",  0x0208}, {"VKEYBD+1",0x0209}, {"VSERIN",  0x020a}, {"VSERIN+1",0x020b},
	{"VSEROR",  0x020c}, {"VSEROR+1",0x020d}, {"VSEROC",  0x020e}, {"VSEROC+1",0x020f},
	{"VTIMR1",  0x0210}, {"VTIMR1+1",0x0211}, {"VTIMR2",  0x0212}, {"VTIMR2+1",0x0213},
	{"VTIMR4",  0x0214}, {"VTIMR4+1",0x0215}, {"VIMIRQ",  0x0216}, {"VIMIRQ+1",0x0217},
	{"CDTMV1",  0x0218}, {"CDTMV1+1",0x0219}, {"CDTMV2",  0x021a}, {"CDTMV2+1",0x021b},
	{"CDTMV3",  0x021c}, {"CDTMV3+1",0x021d}, {"CDTMV4",  0x021e}, {"CDTMV4+1",0x021f},
	{"CDTMV5",  0x0220}, {"CDTMV5+1",0x0221}, {"VVBLKI",  0x0222}, {"VVBLKI+1",0x0223},
	{"VVBLKD",  0x0224}, {"VVBLKD+1",0x0225}, {"CDTMA1",  0x0226}, {"CDTMA1+1",0x0227},
	{"CDTMA2",  0x0228}, {"CDTMA2+1",0x0229}, {"CDTMF3",  0x022a}, {"SRTIMR",  0x022b},
	{"CDTMF4",  0x022c}, {"INTEMP",  0x022d}, {"CDTMF5",  0x022e}, {"SDMCTL",  0x022f},
	{"SDLSTL",  0x0230}, {"SDLSTH",  0x0231}, {"SSKCTL",  0x0232}, {"SPARE",   0x0233},
	{"LPENH",   0x0234}, {"LPENV",   0x0235}, {"BRKKY",   0x0236}, {"BRKKY+1", 0x0237},
	{"VPIRQ",   0x0238}, {"VPIRQ+1", 0x0239}, {"CDEVIC",  0x023a}, {"CCOMND",  0x023b},
	{"CAUX1",   0x023c}, {"CAUX2",   0x023d}, {"TMPSIO",  0x023e}, {"ERRFLG",  0x023f},
	{"DFLAGS",  0x0240}, {"DBSECT",  0x0241}, {"BOOTAD",  0x0242}, {"BOOTAD+1",0x0243},
	{"COLDST",  0x0244}, {"RECLEN",  0x0245}, {"DSKTIM",  0x0246}, {"PDVMSK",  0x0247},
	{"SHPDVS",  0x0248}, {"PDMSK",   0x0249}, {"RELADR",  0x024a}, {"RELADR+1",0x024b},
	{"PPTMPA",  0x024c}, {"PPTMPX",  0x024d}, {"CHSALT",  0x026b}, {"VSFLAG",  0x026c},
	{"KEYDIS",  0x026d}, {"FINE",    0x026e}, {"GPRIOR",  0x026f}, {"PADDL0",  0x0270},
	{"PADDL1",  0x0271}, {"PADDL2",  0x0272}, {"PADDL3",  0x0273}, {"PADDL4",  0x0274},
	{"PADDL5",  0x0275}, {"PADDL6",  0x0276}, {"PADDL7",  0x0277}, {"STICK0",  0x0278},
	{"STICK1",  0x0279}, {"STICK2",  0x027a}, {"STICK3",  0x027b}, {"PTRIG0",  0x027c},
	{"PTRIG1",  0x027d}, {"PTRIG2",  0x027e}, {"PTRIG3",  0x027f}, {"PTRIG4",  0x0280},
	{"PTRIG5",  0x0281}, {"PTRIG6",  0x0282}, {"PTRIG7",  0x0283}, {"STRIG0",  0x0284},
	{"STRIG1",  0x0285}, {"STRIG2",  0x0286}, {"STRIG3",  0x0287}, {"HIBYTE",  0x0288},
	{"WMODE",   0x0289}, {"BLIM",    0x028a}, {"IMASK",   0x028b}, {"JVECK",   0x028c},
	{"NEWADR",  0x028e}, {"TXTROW",  0x0290}, {"TXTCOL",  0x0291}, {"TXTCOL+1",0x0292},
	{"TINDEX",  0x0293}, {"TXTMSC",  0x0294}, {"TXTMSC+1",0x0295}, {"TXTOLD",  0x0296},
	{"TXTOLD+1",0x0297}, {"TXTOLD+2",0x0298}, {"TXTOLD+3",0x0299}, {"TXTOLD+4",0x029a},
	{"TXTOLD+5",0x029b}, {"CRETRY",  0x029c}, {"HOLD3",   0x029d}, {"SUBTMP",  0x029e},
	{"HOLD2",   0x029f}, {"DMASK",   0x02a0}, {"TMPLBT",  0x02a1}, {"ESCFLG",  0x02a2},
	{"TABMAP",  0x02a3}, {"TABMAP+1",0x02a4}, {"TABMAP+2",0x02a5}, {"TABMAP+3",0x02a6},
	{"TABMAP+4",0x02a7}, {"TABMAP+5",0x02a8}, {"TABMAP+6",0x02a9}, {"TABMAP+7",0x02aa},
	{"TABMAP+8",0x02ab}, {"TABMAP+9",0x02ac}, {"TABMAP+A",0x02ad}, {"TABMAP+B",0x02ae},
	{"TABMAP+C",0x02af}, {"TABMAP+D",0x02b0}, {"TABMAP+E",0x02b1}, {"LOGMAP",  0x02b2},
	{"LOGMAP+1",0x02b3}, {"LOGMAP+2",0x02b4}, {"LOGMAP+3",0x02b5}, {"INVFLG",  0x02b6},
	{"FILFLG",  0x02b7}, {"TMPROW",  0x02b8}, {"TMPCOL",  0x02b9}, {"TMPCOL+1",0x02ba},
	{"SCRFLG",  0x02bb}, {"HOLD4",   0x02bc}, {"DRETRY",  0x02bd}, {"SHFLOC",  0x02be},
	{"BOTSCR",  0x02bf}, {"PCOLR0",  0x02c0}, {"PCOLR1",  0x02c1}, {"PCOLR2",  0x02c2},
	{"PCOLR3",  0x02c3}, {"COLOR0",  0x02c4}, {"COLOR1",  0x02c5}, {"COLOR2",  0x02c6},
	{"COLOR3",  0x02c7}, {"COLOR4",  0x02c8}, {"RUNADR",  0x02c9}, {"RUNADR+1",0x02ca},
	{"HIUSED",  0x02cb}, {"HIUSED+1",0x02cc}, {"ZHIUSE",  0x02cd}, {"ZHIUSE+1",0x02ce},
	{"GBYTEA",  0x02cf}, {"GBYTEA+1",0x02d0}, {"LOADAD",  0x02d1}, {"LOADAD+1",0x02d2},
	{"ZLOADA",  0x02d3}, {"ZLOADA+1",0x02d4}, {"DSCTLN",  0x02d5}, {"DSCTLN+1",0x02d6},
	{"ACMISR",  0x02d7}, {"ACMISR+1",0x02d8}, {"KRPDER",  0x02d9}, {"KEYREP",  0x02da},
	{"NOCLIK",  0x02db}, {"HELPFG",  0x02dc}, {"DMASAV",  0x02dd}, {"PBPNT",   0x02de},
	{"PBUFSZ",  0x02df}, {"RUNAD",   0x02e0}, {"RUNAD+1", 0x02e1}, {"INITAD",  0x02e2},
	{"INITAD+1",0x02e3}, {"RAMSIZ",  0x02e4}, {"MEMTOP",  0x02e5}, {"MEMTOP+1",0x02e6},
	{"MEMLO",   0x02e7}, {"MEMLO+1", 0x02e8}, {"HNDLOD",  0x02e9}, {"DVSTAT",  0x02ea},
	{"DVSTAT+1",0x02eb}, {"DVSTAT+2",0x02ec}, {"DVSTAT+3",0x02ed}, {"CBAUDL",  0x02ee},
	{"CBAUDH",  0x02ef}, {"CRSINH",  0x02f0}, {"KEYDEL",  0x02f1}, {"CH1",     0x02f2},
	{"CHACT",   0x02f3}, {"CHBAS",   0x02f4}, {"NEWROW",  0x02f5}, {"NEWCOL",  0x02f6},
	{"NEWCOL+1",0x02f7}, {"ROWINC",  0x02f8}, {"COLINC",  0x02f9}, {"CHAR",    0x02fa},
	{"ATACHR",  0x02fb}, {"CH",      0x02fc}, {"FILDAT",  0x02fd}, {"DSPFLG",  0x02fe},
	{"SSFLAG",  0x02ff},

	{"DDEVIC",  0x0300}, {"DUNIT",   0x0301}, {"DCOMND",  0x0302}, {"DSTATS",  0x0303},
	{"DBUFLO",  0x0304}, {"DBUFHI",  0x0305}, {"DTIMLO",  0x0306}, {"DUNUSE",  0x0307},
	{"DBYTLO",  0x0308}, {"DBYTHI",  0x0309}, {"DAUX1",   0x030a}, {"DAUX2",   0x030b},
	{"TIMER1",  0x030c}, {"TIMER1+1",0x030d}, {"ADDCOR",  0x030e}, {"CASFLG",  0x030f},
	{"TIMER2",  0x0310}, {"TIMER2+1",0x0311}, {"TEMP1",   0x0312}, {"TEMP1+1", 0x0313},
	{"TEMP2",   0x0314}, {"TEMP3",   0x0315}, {"SAVIO",   0x0316}, {"TIMFLG",  0x0317},
	{"STACKP",  0x0318}, {"TSTAT",   0x0319}, {"HATABS",  0x031a}, /* HATABS 1-34 */
	{"PUTBT1",  0x033d}, {"PUTBT2",  0x033e}, {"PUTBT3",  0x033f},
	{"B0-ICHID",0x0340}, {"B0-ICDNO",0x0341}, {"B0-ICCOM",0x0342}, {"B0-ICSTA",0x0343},
	{"B0-ICBAL",0x0344}, {"B0-ICBAH",0x0345}, {"B0-ICPTL",0x0346}, {"B0-ICPTH",0x0347},
	{"B0-ICBLL",0x0348}, {"B0-ICBLH",0x0349}, {"B0-ICAX1",0x034a}, {"B0-ICAX2",0x034b},
	{"B0-ICAX3",0x034c}, {"B0-ICAX4",0x034d}, {"B0-ICAX5",0x034e}, {"B0-ICAX6",0x034f},
	{"B1-ICHID",0x0350}, {"B1-ICDNO",0x0351}, {"B1-ICCOM",0x0352}, {"B1-ICSTA",0x0353},
	{"B1-ICBAL",0x0354}, {"B1-ICBAH",0x0355}, {"B1-ICPTL",0x0356}, {"B1-ICPTH",0x0357},
	{"B1-ICBLL",0x0358}, {"B1-ICBLH",0x0359}, {"B1-ICAX1",0x035a}, {"B1-ICAX2",0x035b},
	{"B1-ICAX3",0x035c}, {"B1-ICAX4",0x035d}, {"B1-ICAX5",0x035e}, {"B1-ICAX6",0x035f},
	{"B2-ICHID",0x0360}, {"B2-ICDNO",0x0361}, {"B2-ICCOM",0x0362}, {"B2-ICSTA",0x0363},
	{"B2-ICBAL",0x0364}, {"B2-ICBAH",0x0365}, {"B2-ICPTL",0x0366}, {"B2-ICPTH",0x0367},
	{"B2-ICBLL",0x0368}, {"B2-ICBLH",0x0369}, {"B2-ICAX1",0x036a}, {"B2-ICAX2",0x036b},
	{"B2-ICAX3",0x036c}, {"B2-ICAX4",0x036d}, {"B2-ICAX5",0x036e}, {"B2-ICAX6",0x036f},
	{"B3-ICHID",0x0370}, {"B3-ICDNO",0x0371}, {"B3-ICCOM",0x0372}, {"B3-ICSTA",0x0373},
	{"B3-ICBAL",0x0374}, {"B3-ICBAH",0x0375}, {"B3-ICPTL",0x0376}, {"B3-ICPTH",0x0377},
	{"B3-ICBLL",0x0378}, {"B3-ICBLH",0x0379}, {"B3-ICAX1",0x037a}, {"B3-ICAX2",0x037b},
	{"B3-ICAX3",0x037c}, {"B3-ICAX4",0x037d}, {"B3-ICAX5",0x037e}, {"B3-ICAX6",0x037f},
	{"B4-ICHID",0x0380}, {"B4-ICDNO",0x0381}, {"B4-ICCOM",0x0382}, {"B4-ICSTA",0x0383},
	{"B4-ICBAL",0x0384}, {"B4-ICBAH",0x0385}, {"B4-ICPTL",0x0386}, {"B4-ICPTH",0x0387},
	{"B4-ICBLL",0x0388}, {"B4-ICBLH",0x0389}, {"B4-ICAX1",0x038a}, {"B4-ICAX2",0x038b},
	{"B4-ICAX3",0x038c}, {"B4-ICAX4",0x038d}, {"B4-ICAX5",0x038e}, {"B4-ICAX6",0x038f},
	{"B5-ICHID",0x0390}, {"B5-ICDNO",0x0391}, {"B5-ICCOM",0x0392}, {"B5-ICSTA",0x0393},
	{"B5-ICBAL",0x0394}, {"B5-ICBAH",0x0395}, {"B5-ICPTL",0x0396}, {"B5-ICPTH",0x0397},
	{"B5-ICBLL",0x0398}, {"B5-ICBLH",0x0399}, {"B5-ICAX1",0x039a}, {"B5-ICAX2",0x039b},
	{"B5-ICAX3",0x039c}, {"B5-ICAX4",0x039d}, {"B5-ICAX5",0x039e}, {"B5-ICAX6",0x039f},
	{"B6-ICHID",0x03a0}, {"B6-ICDNO",0x03a1}, {"B6-ICCOM",0x03a2}, {"B6-ICSTA",0x03a3},
	{"B6-ICBAL",0x03a4}, {"B6-ICBAH",0x03a5}, {"B6-ICPTL",0x03a6}, {"B6-ICPTH",0x03a7},
	{"B6-ICBLL",0x03a8}, {"B6-ICBLH",0x03a9}, {"B6-ICAX1",0x03aa}, {"B6-ICAX2",0x03ab},
	{"B6-ICAX3",0x03ac}, {"B6-ICAX4",0x03ad}, {"B6-ICAX5",0x03ae}, {"B6-ICAX6",0x03af},
	{"B7-ICHID",0x03b0}, {"B7-ICDNO",0x03b1}, {"B7-ICCOM",0x03b2}, {"B7-ICSTA",0x03b3},
	{"B7-ICBAL",0x03b4}, {"B7-ICBAH",0x03b5}, {"B7-ICPTL",0x03b6}, {"B7-ICPTH",0x03b7},
	{"B7-ICBLL",0x03b8}, {"B7-ICBLH",0x03b9}, {"B7-ICAX1",0x03ba}, {"B7-ICAX2",0x03bb},
	{"B7-ICAX3",0x03bc}, {"B7-ICAX4",0x03bd}, {"B7-ICAX5",0x03be}, {"B7-ICAX6",0x03bf},
	{"PRNBUF",  0x03c0}, /* PRNBUF 1-39 */
	{"SUPERF",  0x03e8}, {"CKEY",    0x03e9}, {"CASSBT",  0x03ea}, {"CARTCK",  0x03eb},
	{"DERRF",   0x03ec}, {"ACMVAR",  0x03ed}, /* ACMVAR 1-10 */
	{"BASICF",  0x03f8}, {"MINTLK",  0x03f9}, {"GINTLK",  0x03fa}, {"CHLINK",  0x03fb},
	{"CHLINK+1",0x03fc}, {"CASBUF",  0x03fd},

	{"M0PF",  0xd000}, {"HPOSP0",0xd000}, {"M1PF",  0xd001}, {"HPOSP1",0xd001},
	{"M2PF",  0xd002}, {"HPOSP2",0xd002}, {"M3PF",  0xd003}, {"HPOSP3",0xd003},
	{"P0PF",  0xd004}, {"HPOSM0",0xd004}, {"P1PF",  0xd005}, {"HPOSM1",0xd005},
	{"P2PF",  0xd006}, {"HPOSM2",0xd006}, {"P3PF",  0xd007}, {"HPOSM3",0xd007},
	{"M0PL",  0xd008}, {"SIZEP0",0xd008}, {"M1PL",  0xd009}, {"SIZEP1",0xd009},
	{"M2PL",  0xd00a}, {"SIZEP2",0xd00a}, {"M3PL",  0xd00b}, {"SIZEP3",0xd00b},
	{"P0PL",  0xd00c}, {"SIZEM", 0xd00c}, {"P1PL",  0xd00d}, {"GRAFP0",0xd00d},
	{"P2PL",  0xd00e}, {"GRAFP1",0xd00e}, {"P3PL",  0xd00f}, {"GRAFP2",0xd00f},
	{"TRIG0", 0xd010}, {"GRAFP3",0xd010}, {"TRIG1", 0xd011}, {"GRAFM", 0xd011},
	{"TRIG2", 0xd012}, {"COLPM0",0xd012}, {"TRIG3", 0xd013}, {"COLPM1",0xd013},
	{"PAL",   0xd014}, {"COLPM2",0xd014}, {"COLPM3",0xd015}, {"COLPF0",0xd016},
	{"COLPF1",0xd017},
	{"COLPF2",0xd018}, {"COLPF3",0xd019}, {"COLBK", 0xd01a}, {"PRIOR", 0xd01b},
	{"VDELAY",0xd01c}, {"GRACTL",0xd01d}, {"HITCLR",0xd01e}, {"CONSOL",0xd01f},

	{"POT0",  0xd200}, {"AUDF1", 0xd200}, {"POT1",  0xd201}, {"AUDC1", 0xd201},
	{"POT2",  0xd202}, {"AUDF2", 0xd202}, {"POT3",  0xd203}, {"AUDC2", 0xd203},
	{"POT4",  0xd204}, {"AUDF3", 0xd204}, {"POT5",  0xd205}, {"AUDC3", 0xd205},
	{"POT6",  0xd206}, {"AUDF4", 0xd206}, {"POT7",  0xd207}, {"AUDC4", 0xd207},
	{"ALLPOT",0xd208}, {"AUDCTL",0xd208}, {"KBCODE",0xd209}, {"STIMER",0xd209},
	{"RANDOM",0xd20a}, {"SKREST",0xd20a}, {"POTGO", 0xd20b},
	{"SERIN", 0xd20d}, {"SEROUT",0xd20d}, {"IRQST", 0xd20e}, {"IRQEN", 0xd20e},
	{"SKSTAT",0xd20f}, {"SKCTL", 0xd20f},

	{"PORTA", 0xd300}, {"PORTB", 0xd301}, {"PACTL", 0xd302}, {"PBCTL", 0xd303},

	{"DMACTL",0xd400}, {"CHACTL",0xd401}, {"DLISTL",0xd402}, {"DLISTH",0xd403},
	{"HSCROL",0xd404}, {"VSCROL",0xd405}, {"PMBASE",0xd407}, {"CHBASE",0xd409},
	{"WSYNC", 0xd40a}, {"VCOUNT",0xd40b}, {"PENH",  0xd40c}, {"PENV",  0xd40d},
	{"NMIEN", 0xd40e}, {"NMIST", 0xd40f}, {"NMIRES",0xd40f},

	{"AFP",   0xd800}, {"FASC",  0xd8e6}, {"IFP",   0xd9aa}, {"FPI",   0xd9d2},
	{"ZPRO",  0xda44}, {"ZF1",   0xda46}, {"FSUB",  0xda60}, {"FADD",  0xda66},
	{"FMUL",  0xdadb}, {"FDIV",  0xdb28}, {"PLYEVL",0xdd40}, {"FLD0R", 0xdd89},
	{"FLD0R", 0xdd8d}, {"FLD1R", 0xdd98}, {"FLD1P", 0xdd9c}, {"FST0R", 0xdda7},
	{"FST0P", 0xddab}, {"FMOVE", 0xddb6}, {"EXP",   0xddc0}, {"EXP10", 0xddcc},
	{"LOG",   0xdecd}, {"LOG10", 0xded1},

	{"DSKINV",0xe453}, {"CIOV",  0xe456}, {"SIOV",  0xe459}, {"SETVBV",0xe45c},
	{"SYSVBV",0xe45f}, {"XITVBV",0xe462}, {"SIOINV",0xe465}, {"SENDEV",0xe468},
	{"INTINV",0xe46b}, {"CIOINV",0xe46e}, {"SELFSV",0xe471}, {"WARMSV",0xe474},
	{"COLDSV",0xe477}, {"RBLOKV",0xe47a}, {"CSOPIV",0xe47d}, {"PUPDIV",0xe480},
	{"SELFTSV",0xe483},{"PENTV", 0xe486}, {"PHUNLV",0xe489}, {"PHINIV",0xe48c},
	{"GPDVV", 0xe48f},

	{NULL,    0x0000}
};

static int symtable_builtin_enable = TRUE;

static symtable_rec *symtable_user = NULL;
static int symtable_user_size = 0;

static const char *find_label_name(UWORD addr, int write)
{
	int i;
	for (i = 0; i < symtable_user_size; i++) {
		if (symtable_user[i].addr == addr)
			return symtable_user[i].name;
	}
	if (symtable_builtin_enable) {
		const symtable_rec *p;
		for (p = symtable_builtin; p->name != NULL; p++) {
			if (p->addr == addr) {
				if (write && p[1].addr == addr)
					p++;
				return p->name;
			}
		}
	}
	return NULL;
}

static symtable_rec *find_user_label(const char *name)
{
	int i;
	for (i = 0; i < symtable_user_size; i++) {
		if (Util_stricmp(symtable_user[i].name, name) == 0)
			return &symtable_user[i];
	}
	return NULL;
}

static int find_label_value(const char *name)
{
	const symtable_rec *p = find_user_label(name);
	if (p != NULL)
		return p->addr;
	if (symtable_builtin_enable) {
		for (p = symtable_builtin; p->name != NULL; p++) {
			if (Util_stricmp(p->name, name) == 0)
				return p->addr;
		}
	}
	return -1;
}

static void free_user_labels(void)
{
	if (symtable_user != NULL) {
		while (symtable_user_size > 0)
			free(symtable_user[--symtable_user_size].name);
		free(symtable_user);
		symtable_user = NULL;
	}
}

static void add_user_label(const char *name, UWORD addr)
{
#define SYMTABLE_USER_INITIAL_SIZE 128
	if (symtable_user == NULL)
		symtable_user = (symtable_rec *) Util_malloc(SYMTABLE_USER_INITIAL_SIZE * sizeof(symtable_rec));
	else if (symtable_user_size >= SYMTABLE_USER_INITIAL_SIZE
	      && (symtable_user_size & (symtable_user_size - 1)) == 0) {
		/* symtable_user_size is a power of two: allocate twice as much */
		symtable_user = (symtable_rec *) Util_realloc(symtable_user,
			2 * symtable_user_size * sizeof(symtable_rec));
	}
	symtable_user[symtable_user_size].name = Util_strdup(name);
	symtable_user[symtable_user_size].addr = addr;
	symtable_user_size++;
}

static void load_user_labels(const char *filename)
{
	FILE *fp;
	char line[256];
	if (filename == NULL) {
		printf("You must specify a filename\n");
		return;
	}
	/* "rb" and not "r", because we strip EOLs ourselves
	   - this is better, because we can use CR/LF files on Unix */
	fp = fopen(filename, "rb");
	if (fp == NULL) {
		perror(filename);
		return;
	}
	free_user_labels();
	while (fgets(line, sizeof(line), fp)) {
		char *p;
		unsigned int value = 0;
		int digits = 0;
		/* Find first 4 hex digits or more. */
		/* We don't support "Cafe Assembler", "Dead Assembler" or "C0de Assembler". ;-) */
		for (p = line; *p != '\0'; p++) {
			if (*p >= '0' && *p <= '9') {
				value = (value << 4) + *p - '0';
				digits++;
			}
			else if (*p >= 'A' && *p <= 'F') {
				value = (value << 4) + *p - 'A' + 10;
				digits++;
			}
			else if (*p >= 'a' && *p <= 'f') {
				value = (value << 4) + *p - 'a' + 10;
				digits++;
			}
			else if (digits >= 4)
				break;
			else if (*p == '-')
				break; /* ignore labels with negative values */
			else {
				/* note that xasm can put "2" before the label value and mads puts "00" */
				value = 0;
				digits = 0;
			}
		}
		if (*p != ' ' && *p != '\t')
			continue;
		if (value > 0xffff || digits > 8)
			continue;
		do
			p++;
		while (*p == ' ' || *p == '\t');
		Util_chomp(p);
		if (*p == '\0')
			continue;
		add_user_label(p, (UWORD) value);
	}
	fclose(fp);
	printf("Loaded %d labels\n", symtable_user_size);
}

#endif /* MONITOR_HINTS */

static const char instr6502[256][10] = {
	"BRK", "ORA (1,X)", "CIM", "ASO (1,X)", "NOP 1", "ORA 1", "ASL 1", "ASO 1",
	"PHP", "ORA #1", "ASL", "ANC #1", "NOP 2", "ORA 2", "ASL 2", "ASO 2",

	"BPL 0", "ORA (1),Y", "CIM", "ASO (1),Y", "NOP 1,X", "ORA 1,X", "ASL 1,X", "ASO 1,X",
	"CLC", "ORA 2,Y", "NOP !", "ASO 2,Y", "NOP 2,X", "ORA 2,X", "ASL 2,X", "ASO 2,X",

	"JSR 2", "AND (1,X)", "CIM", "RLA (1,X)", "BIT 1", "AND 1", "ROL 1", "RLA 1",
	"PLP", "AND #1", "ROL", "ANC #1", "BIT 2", "AND 2", "ROL 2", "RLA 2",

	"BMI 0", "AND (1),Y", "CIM", "RLA (1),Y", "NOP 1,X", "AND 1,X", "ROL 1,X", "RLA 1,X",
	"SEC", "AND 2,Y", "NOP !", "RLA 2,Y", "NOP 2,X", "AND 2,X", "ROL 2,X", "RLA 2,X",


	"RTI", "EOR (1,X)", "CIM", "LSE (1,X)", "NOP 1", "EOR 1", "LSR 1", "LSE 1",
	"PHA", "EOR #1", "LSR", "ALR #1", "JMP 2", "EOR 2", "LSR 2", "LSE 2",

	"BVC 0", "EOR (1),Y", "CIM", "LSE (1),Y", "NOP 1,X", "EOR 1,X", "LSR 1,X", "LSE 1,X",
	"CLI", "EOR 2,Y", "NOP !", "LSE 2,Y", "NOP 2,X", "EOR 2,X", "LSR 2,X", "LSE 2,X",

	"RTS", "ADC (1,X)", "CIM", "RRA (1,X)", "NOP 1", "ADC 1", "ROR 1", "RRA 1",
	"PLA", "ADC #1", "ROR", "ARR #1", "JMP (2)", "ADC 2", "ROR 2", "RRA 2",

	"BVS 0", "ADC (1),Y", "CIM", "RRA (1),Y", "NOP 1,X", "ADC 1,X", "ROR 1,X", "RRA 1,X",
	"SEI", "ADC 2,Y", "NOP !", "RRA 2,Y", "NOP 2,X", "ADC 2,X", "ROR 2,X", "RRA 2,X",


	"NOP #1", "STA (1,X)", "NOP #1", "SAX (1,X)", "STY 1", "STA 1", "STX 1", "SAX 1",
	"DEY", "NOP #1", "TXA", "ANE #1", "STY 2", "STA 2", "STX 2", "SAX 2",

	"BCC 0", "STA (1),Y", "CIM", "SHA (1),Y", "STY 1,X", "STA 1,X", "STX 1,Y", "SAX 1,Y",
	"TYA", "STA 2,Y", "TXS", "SHS 2,Y", "SHY 2,X", "STA 2,X", "SHX 2,Y", "SHA 2,Y",

	"LDY #1", "LDA (1,X)", "LDX #1", "LAX (1,X)", "LDY 1", "LDA 1", "LDX 1", "LAX 1",
	"TAY", "LDA #1", "TAX", "ANX #1", "LDY 2", "LDA 2", "LDX 2", "LAX 2",

	"BCS 0", "LDA (1),Y", "CIM", "LAX (1),Y", "LDY 1,X", "LDA 1,X", "LDX 1,Y", "LAX 1,X",
	"CLV", "LDA 2,Y", "TSX", "LAS 2,Y", "LDY 2,X", "LDA 2,X", "LDX 2,Y", "LAX 2,Y",


	"CPY #1", "CMP (1,X)", "NOP #1", "DCM (1,X)", "CPY 1", "CMP 1", "DEC 1", "DCM 1",
	"INY", "CMP #1", "DEX", "SBX #1", "CPY 2", "CMP 2", "DEC 2", "DCM 2",

	"BNE 0", "CMP (1),Y", "ESCRTS #1", "DCM (1),Y", "NOP 1,X", "CMP 1,X", "DEC 1,X", "DCM 1,X",
	"CLD", "CMP 2,Y", "NOP !", "DCM 2,Y", "NOP 2,X", "CMP 2,X", "DEC 2,X", "DCM 2,X",


	"CPX #1", "SBC (1,X)", "NOP #1", "INS (1,X)", "CPX 1", "SBC 1", "INC 1", "INS 1",
	"INX", "SBC #1", "NOP", "SBC #1 !", "CPX 2", "SBC 2", "INC 2", "INS 2",

	"BEQ 0", "SBC (1),Y", "ESCAPE #1", "INS (1),Y", "NOP 1,X", "SBC 1,X", "INC 1,X", "INS 1,X",
	"SED", "SBC 2,Y", "NOP !", "INS 2,Y", "NOP 2,X", "SBC 2,X", "INC 2,X", "INS 2,X"
};

/* Opcode type:
   bits 1-0 = instruction length
   bit 2    = instruction reads from memory (without stack-manipulating instructions)
   bit 3    = instruction writes to memory (without stack-manipulating instructions)
   bits 7-4 = adressing type:
     0 = NONE (implicit)
     1 = ABSOLUTE
     2 = ZPAGE
     3 = ABSOLUTE_X
     4 = ABSOLUTE_Y
     5 = INDIRECT_X
     6 = INDIRECT_Y
     7 = ZPAGE_X
     8 = ZPAGE_Y
     9 = RELATIVE
     A = IMMEDIATE
     B = STACK 2 (RTS)
     C = STACK 3 (RTI)
     D = INDIRECT (JMP () )
     E = ESCRTS
     F = ESCAPE */
const UBYTE optype6502[256] = {
	0x01, 0x56, 0x01, 0x5e, 0x22, 0x26, 0x2e, 0x2e, 0x01, 0xa2, 0x01, 0xa2, 0x13, 0x17, 0x1f, 0x1f,
	0x92, 0x66, 0x01, 0x6e, 0x72, 0x76, 0x7e, 0x7e, 0x01, 0x47, 0x01, 0x4f, 0x33, 0x37, 0x3f, 0x3f,
	0x13, 0x56, 0x01, 0x5e, 0x26, 0x26, 0x2e, 0x2e, 0x01, 0xa2, 0x01, 0xa2, 0x17, 0x17, 0x1f, 0x1f,
	0x92, 0x66, 0x01, 0x6e, 0x72, 0x76, 0x7e, 0x7e, 0x01, 0x47, 0x01, 0x4f, 0x33, 0x37, 0x3f, 0x3f,
	0xc1, 0x56, 0x01, 0x5e, 0x22, 0x26, 0x2e, 0x2e, 0x01, 0xa2, 0x01, 0xa2, 0x13, 0x17, 0x1f, 0x1f,
	0x92, 0x66, 0x01, 0x6e, 0x72, 0x76, 0x7e, 0x7e, 0x01, 0x47, 0x01, 0x4f, 0x33, 0x37, 0x3f, 0x3f,
	0xb1, 0x56, 0x01, 0x5e, 0x22, 0x26, 0x2e, 0x2e, 0x01, 0xa2, 0x01, 0xa2, 0xd3, 0x17, 0x1f, 0x1f,
	0x92, 0x66, 0x01, 0x6e, 0x72, 0x76, 0x7e, 0x7e, 0x01, 0x47, 0x01, 0x4f, 0x33, 0x37, 0x3f, 0x3f,
	0xa2, 0x5a, 0x01, 0x5a, 0x2a, 0x2a, 0x2a, 0x2a, 0x01, 0xa2, 0x01, 0xa2, 0x1b, 0x1b, 0x1b, 0x1b,
	0x92, 0x6a, 0x01, 0x6a, 0x7a, 0x7a, 0x8a, 0x8a, 0x01, 0x4b, 0x01, 0x4b, 0x3b, 0x3b, 0x4b, 0x4b,
	0xa2, 0x56, 0xa2, 0x56, 0x26, 0x26, 0x26, 0x26, 0x01, 0xa2, 0x01, 0xa2, 0x17, 0x17, 0x17, 0x17,
	0x92, 0x66, 0x01, 0x66, 0x76, 0x76, 0x86, 0x86, 0x01, 0x47, 0x01, 0x47, 0x37, 0x37, 0x47, 0x47,
	0xa2, 0x56, 0xa2, 0x5e, 0x26, 0x26, 0x2e, 0x2e, 0x01, 0xa2, 0x01, 0xa2, 0x17, 0x17, 0x1f, 0x1f,
	0x92, 0x66, 0xe2, 0x6e, 0x72, 0x76, 0x7e, 0x7e, 0x01, 0x47, 0x01, 0x4f, 0x33, 0x37, 0x3f, 0x3f,
	0xa2, 0x56, 0xa2, 0x5e, 0x26, 0x26, 0x2e, 0x2e, 0x01, 0xa2, 0x01, 0xa2, 0x17, 0x17, 0x1f, 0x1f,
	0x92, 0x66, 0xf2, 0x6e, 0x72, 0x76, 0x7e, 0x7e, 0x01, 0x47, 0x01, 0x4f, 0x33, 0x37, 0x3f, 0x3f
};


static void safe_gets(char *buffer, size_t size)
{
#ifdef HAVE_FFLUSH
	fflush(stdout);
#endif
	fgets(buffer, size, stdin);
	Util_chomp(buffer);
}

static int pager(void)
{
	char buf[100];
	printf("Press Return to continue ('q' to quit): ");
	safe_gets(buf, sizeof(buf));
	return buf[0] == 'q' || buf[0] == 'Q';
}

static char *token_ptr;

static char *get_token(void)
{
	char *p = token_ptr;
	while (*p == ' ')
		p++;
	if (*p == '\0')
		return NULL;
	token_ptr = p;
	do {
		token_ptr++;
		if (*token_ptr == ' ') {
			*token_ptr++ = '\0';
			break;
		}
	} while (*token_ptr != '\0');
	return p;
}

#if defined(MONITOR_BREAK) || !defined(NO_YPOS_BREAK_FLICKER)
static int get_dec(int *decval)
{
	const char *t;
	t = get_token();
	if (t != NULL) {
		int x = Util_sscandec(t);
		if (x < 0)
			return FALSE;
		*decval = x;
		return TRUE;
	}
	return FALSE;
}
#endif

static int parse_hex(const char *s, UWORD *hexval)
{
	int x = Util_sscanhex(s);
#ifdef MONITOR_HINTS
	int y = find_label_value(s);
	if (y >= 0) {
		if (x < 0 || x > 0xffff || x == y) {
			*hexval = (UWORD) y;
			return TRUE;
		}
		/* s can be a hex number or a label name */
		printf("%s is ambiguous. Use 0%X or %X instead.\n", s, x, y);
		return FALSE;
	}
#endif
	if (x < 0 || x > 0xffff)
		return FALSE;
	*hexval = (UWORD) x;
	return TRUE;
}

static int get_hex(UWORD *hexval)
{
	const char *t;
	t = get_token();
	if (t != NULL)
		return parse_hex(t, hexval);
	return FALSE;
}

static int get_hex2(UWORD *hexval1, UWORD *hexval2)
{
	return get_hex(hexval1) && get_hex(hexval2);
}

static int get_hex3(UWORD *hexval1, UWORD *hexval2, UWORD *hexval3)
{
	return get_hex(hexval1) && get_hex(hexval2) && get_hex(hexval3);
}

static void get_uword(UWORD *val)
{
	if (!get_hex(val))
		printf("Invalid argument!\n");
}

static void get_ubyte(UBYTE *val)
{
	UWORD uword;
	if (!get_hex(&uword) || uword > 0xff)
		printf("Invalid argument!\n");
	else
		*val = (UBYTE) uword;
}

static int get_bool(void)
{
	const char *t;
	t = get_token();
	if (t != NULL) {
		int result = Util_sscanbool(t);
		if (result >= 0)
			return result;
	}
	printf("Invalid argument (should be 0 or 1)!\n");
	return -1;
}

static int get_attrib_range(UWORD *addr1, UWORD *addr2)
{
	if (get_hex2(addr1, addr2) && *addr1 <= *addr2) {
#ifdef PAGED_ATTRIB
		if ((*addr1 & 0xff) != 0 || (*addr2 & 0xff) != 0xff) {
			printf("This is PAGED_ATTRIB version of Atari800.\n"
			       "You can only change attributes of full memory pages.\n");
			return FALSE;
		}
#endif
		return TRUE;
	}
	printf("Missing or bad argument!\n");
	return FALSE;
}

static UWORD show_instruction(FILE *fp, UWORD pc)
{
	UWORD addr = pc;
	UBYTE insn;
	const char *mnemonic;
	const char *p;
	int value = 0;
	int nchars = 0;

	insn = dGetByte(pc++);
	mnemonic = instr6502[insn];
	for (p = mnemonic + 3; *p != '\0'; p++) {
		if (*p == '1') {
			value = dGetByte(pc++);
			nchars = fprintf(fp, "%04X: %02X %02X     " /*"%Xcyc  "*/ "%.*s$%02X%s",
			                 addr, insn, value, /*cycles[insn],*/ (int) (p - mnemonic), mnemonic, value, p + 1);
			break;
		}
		if (*p == '2') {
			value = dGetWord(pc);
			nchars = fprintf(fp, "%04X: %02X %02X %02X  " /*"%Xcyc  "*/ "%.*s$%04X%s",
			                 addr, insn, value & 0xff, value >> 8, /*cycles[insn],*/ (int) (p - mnemonic), mnemonic, value, p + 1);
			pc += 2;
			break;
		}
		if (*p == '0') {
			UBYTE op = dGetByte(pc++);
			value = (UWORD) (pc + (SBYTE) op);
			nchars = fprintf(fp, "%04X: %02X %02X     " /*"3cyc  "*/ "%.4s$%04X", addr, insn, op, mnemonic, value);
			break;
		}
	}
	if (*p == '\0') {
		fprintf(fp, "%04X: %02X        " /*"%Xcyc  "*/ "%s\n", addr, insn, /*cycles[insn],*/ mnemonic);
		return pc;
	}
#ifdef MONITOR_HINTS
	if (p[-1] != '#') {
		/* different names when reading/writing memory */
		const char *label = find_label_name((UWORD) value, (optype6502[insn] & 0x08) != 0);
		if (label != NULL) {
			fprintf(fp, "%*s;%s\n", 28 - nchars, "", label);
			return pc;
		}
	}
#endif
	fputc('\n', fp);
	return pc;
}

void show_state(FILE *fp, UWORD pc, UBYTE a, UBYTE x, UBYTE y, UBYTE s,
                char n, char v, char z, char c)
{
	fprintf(fp, "%3d %3d A=%02X X=%02X Y=%02X S=%02X P=%c%c*-%c%c%c%c PC=",
		ypos, xpos, a, x, y, s,
		n, v, (regP & D_FLAG) ? 'D' : '-', (regP & I_FLAG) ? 'I' : '-', z, c);
	show_instruction(fp, pc);
}

static void monitor_show_state(void)
{
	show_state(stdout, regPC, regA, regX, regY, regS,
		(char) ((regP & N_FLAG) ? 'N' : '-'), (char) ((regP & V_FLAG) ? 'V' : '-'),
		(char) ((regP & Z_FLAG) ? 'Z' : '-'), (char) ((regP & C_FLAG) ? 'C' : '-'));
}

static UWORD disassemble(UWORD addr)
{
	int count = 24;
	do
		addr = show_instruction(stdout, addr);
	while (--count > 0);
	return addr;
}

#ifdef MONITOR_ASSEMBLER
static UWORD assembler(UWORD addr)
{
	printf("Simple assembler (enter empty line to exit)\n");
	for (;;) {
		char s[128];  /* input string */
		char c[128];  /* converted input */
		char *sp;     /* input pointer */
		char *cp;     /* converted input pointer */
		char *vp;     /* value pointer (the value is stored in s) */
		char *tp;     /* type pointer (points at type character '0', '1' or '2' in converted input) */
		int i;
		int isa;      /* the operand is "A" */
		UWORD value = 0;

		printf("%04X: ", (int) addr);
		safe_gets(s, sizeof(s));
		if (s[0] == '\0')
			return addr;

		Util_strupper(s);

		sp = s;
		cp = c;
		/* copy first three characters */
		for (i = 0; i < 3 && *sp != '\0'; i++)
			*cp++ = *sp++;
		/* insert space before operands */
		*cp++ = ' ';

		tp = NULL;
		isa = FALSE;

		/* convert input to format of instr6502[] table */
		while (*sp != '\0') {
			switch (*sp) {
			case ' ':
			case '\t':
			case '$':
			case '@':
				sp++;
				break;
			case '#':
			case '(':
			case ')':
			case ',':
				isa = FALSE;
				*cp++ = *sp++;
				break;
			default:
				if (tp != NULL) {
					if (*sp == 'X' || *sp == 'Y') {
						*cp++ = *sp++;
						break;
					}
					goto invalid_instr;
				}
				vp = s;
				do
					*vp++ = *sp++;
				while (strchr(" \t$@#(),", *sp) == NULL && *sp != '\0');
				/* If *sp=='\0', strchr() should return non-NULL,
				   but we do an extra check to be on safe side. */
				*vp++ = '\0';
				tp = cp++;
				*tp = '0';
				isa = (s[0] == 'A' && s[1] == '\0');
				break;
			}
		}
		if (cp[-1] == ' ')
			cp--;    /* no arguments (e.g. NOP or ASL @) */
		*cp = '\0';

		/* if there's an operand, get its value */
		if (tp != NULL && !parse_hex(s, &value)) {
			printf("Invalid operand!\n");
			continue;
		}

		for (;;) {
			/* search table for instruction */
			for (i = 0; i < 256; i++) {
				if (strcmp(instr6502[i], c) == 0) {
					if (tp == NULL) {
						dPutByte(addr, (UBYTE) i);
						addr++;
					}
					else if (*tp == '0') {
						value -= (addr + 2);
						if ((SWORD) value < -128 || (SWORD) value > 127)
							printf("Branch out of range!\n");
						else {
							dPutByte(addr, (UBYTE) i);
							addr++;
							dPutByte(addr, (UBYTE) value);
							addr++;
						}
					}
					else if (*tp == '1') {
						c[3] = '\0';
						if (isa && (strcmp(c, "ASL") == 0 || strcmp(c, "LSR") == 0 ||
						            strcmp(c, "ROL") == 0 || strcmp(c, "ROR") == 0)) {
							printf("\"%s A\" is ambiguous.\n"
							       "Use \"%s\" for accumulator mode or \"%s 0A\" for zeropage mode.\n", c, c, c);
						}
						else {
							dPutByte(addr, (UBYTE) i);
							addr++;
							dPutByte(addr, (UBYTE) value);
							addr++;
						}
					}
					else { /* *tp == '2' */
						dPutByte(addr, (UBYTE) i);
						addr++;
						dPutWord(addr, value);
						addr += 2;
					}
					goto next_instr;
				}
			}
			/* not found */
			if (tp == NULL || *tp == '2')
				break;
			if (++*tp == '1' && value > 0xff)
				*tp = '2';
		}
	invalid_instr:
		printf("Invalid instruction!\n");
	next_instr:
		;
	}
}
#endif /* MONITOR_ASSEMBLER */

#ifdef MONITOR_BREAK
UWORD break_addr = 0xd000;
UBYTE break_step = FALSE;
static UBYTE break_over = FALSE;
UBYTE break_ret = FALSE;
UBYTE break_brk = FALSE;
int ret_nesting = 0;
#endif

#ifdef MONITOR_BREAKPOINTS

breakpoint_cond breakpoint_table[BREAKPOINT_TABLE_MAX];
int breakpoint_table_size = 0;
int breakpoints_enabled = TRUE;

static void breakpoint_print_flag(int flagmask)
{
	switch (flagmask) {
	case N_FLAG:
		putchar('N');
		break;
	case V_FLAG:
		putchar('V');
		break;
	case D_FLAG:
		putchar('D');
		break;
	case I_FLAG:
		putchar('I');
		break;
	case Z_FLAG:
		putchar('Z');
		break;
	case C_FLAG:
		putchar('C');
		break;
	}
}

static int breakpoint_scan_flag(char c)
{
	switch (c) {
	case 'N':
		return N_FLAG;
	case 'V':
		return V_FLAG;
	case 'D':
		return D_FLAG;
	case 'I':
		return I_FLAG;
	case 'Z':
		return Z_FLAG;
	case 'C':
		return C_FLAG;
	default:
		return -1;
	}
}

static void breakpoints_set(int enabled)
{
	int i;
	if (get_dec(&i)) {
		do {
			if (/*i >= 0 &&*/ i < breakpoint_table_size)
				breakpoint_table[i].enabled = (UBYTE) enabled;
		} while (get_dec(&i));
	}
	else
		breakpoints_enabled = enabled;
}

static void monitor_breakpoints(void)
{
	char *t = get_token();
	if (t == NULL) {
		int i;
		if (breakpoint_table_size == 0) {
			printf("No breakpoints defined\n");
			return;
		}
		printf("Breakpoints are %sabled\n", breakpoints_enabled ? "en" : "dis");
		for (i = 0; i < breakpoint_table_size; i++) {
			printf("%2d: ", i);
			if (!breakpoint_table[i].enabled)
				printf("OFF ");
			switch (breakpoint_table[i].condition) {
			case BREAKPOINT_OR:
				printf("OR");
				break;
			case BREAKPOINT_FLAG_CLEAR:
				printf("CLR");
				breakpoint_print_flag(breakpoint_table[i].value);
				break;
			case BREAKPOINT_FLAG_SET:
				printf("SET");
				breakpoint_print_flag(breakpoint_table[i].value);
				break;
			default:
				{
					const char *op;
					switch (breakpoint_table[i].condition & 7) {
					case BREAKPOINT_LESS:
						op = "<";
						break;
					case BREAKPOINT_EQUAL:
						op = "=";
						break;
					case BREAKPOINT_LESS | BREAKPOINT_EQUAL:
						op = "<=";
						break;
					case BREAKPOINT_GREATER:
						op = ">";
						break;
					case BREAKPOINT_GREATER | BREAKPOINT_EQUAL:
						op = ">=";
						break;
					case BREAKPOINT_LESS | BREAKPOINT_GREATER:
						op = "!=";
						break;
					default:
						op = "?";
						break;
					}
					switch (breakpoint_table[i].condition >> 3) {
					case BREAKPOINT_PC >> 3:
						printf("PC%s%04X", op, breakpoint_table[i].value);
						break;
					case BREAKPOINT_A >> 3:
						printf("A%s%02X", op, breakpoint_table[i].value);
						break;
					case BREAKPOINT_X >> 3:
						printf("X%s%02X", op, breakpoint_table[i].value);
						break;
					case BREAKPOINT_Y >> 3:
						printf("A%s%02X", op, breakpoint_table[i].value);
						break;
					case BREAKPOINT_S >> 3:
						printf("S%s%02X", op, breakpoint_table[i].value);
						break;
					case BREAKPOINT_READ >> 3:
						printf("READ%s%04X", op, breakpoint_table[i].value);
						break;
					case BREAKPOINT_WRITE >> 3:
						printf("WRITE%s%04X", op, breakpoint_table[i].value);
						break;
					case BREAKPOINT_ACCESS >> 3:
						printf("ACCESS%s%04X", op, breakpoint_table[i].value);
						break;
					default:
						printf("???");
						break;
					}
				}
			}
			putchar('\n');
		}
		return;
	}
	if (strcmp(t, "?") == 0) {
		printf(
			"B                - print breakpoint table\n"
			"B ?              - this help\n"
			"B C              - clear breakpoint table\n"
			"B D pos          - delete one entry\n"
			"B ON             - enable breakpoints\n"
			"B OFF            - disable breakpoints\n"
			"B ON pos1...     - enable specified breakpoints\n"
			"B OFF pos1...    - disable specified breakpoints\n"
			"B [pos] cond1... - insert breakpoints (at the specified position)\n"
			"    cond is: TYPE OPERATOR VALUE (without spaces)\n"
			"         or: SETFLAG, CLRFLAG where FLAG is: N, V, D, I, Z, C\n");
		printf(
			"    TYPE is: PC, A, X, Y, S, READ, WRITE, ACCESS (read or write)\n"
			"OPERATOR is: <, <=, =, ==, >, >=, !=, <>\n"
			"   VALUE is a hex number\n"
			"Breakpoint conditions are connected by AND operator\n"
			"unless you explicitly use OR.\n"
			"Examples:\n"
			"B PC>=203f A<3a OR PC=3a7f X<>0 - creates 5 new entries\n"
			"B 2 Y<5                         - adds a new entry at position 2\n"
			"B D 1                           - deletes the entry at position 1\n"
			"B OR SETD                       - appends 2 new entries\n");
		return;
	}
	Util_strupper(t);
	if (strcmp(t, "C") == 0) {
		breakpoint_table_size = 0;
		printf("Breakpoint table cleared\n");
	}
	else if (strcmp(t, "D") == 0) {
		int i;
		if (get_dec(&i) && /*i >= 0 &&*/ i < breakpoint_table_size) {
			breakpoint_table_size--;
			while (i < breakpoint_table_size) {
				breakpoint_table[i] = breakpoint_table[i + 1];
				i++;
			}
			printf("Entry deleted\n");
		}
		else
			printf("Missing or bad argument\n");
	}
	else if (strcmp(t, "ON") == 0) {
		breakpoints_set(TRUE);
	}
	else if (strcmp(t, "OFF") == 0) {
		breakpoints_set(FALSE);
	}
	else {
		int i;
		if (t[0] >= '0' && t[0] <= '9') {
			i = Util_sscandec(t);
			if (i < 0 || i > breakpoint_table_size) {
				printf("Bad argument\n");
				return;
			}
			t = get_token();
			if (t == NULL) {
				printf("Missing arguments\n");
				return;
			}
		}
		else
			i = breakpoint_table_size;
		while (breakpoint_table_size < BREAKPOINT_TABLE_MAX) {
			UBYTE condition;
			int value;
			int j;
			if (strcmp(t, "OR") == 0) {
				condition = BREAKPOINT_OR;
				value = 0;
			}
			else if (strncmp(t, "CLR", 3) == 0) {
				condition = BREAKPOINT_FLAG_CLEAR;
				value = breakpoint_scan_flag(t[3]);
			}
			else if (strncmp(t, "SET", 3) == 0) {
				condition = BREAKPOINT_FLAG_SET;
				value = breakpoint_scan_flag(t[3]);
			}
			else {
				condition = 0;
				switch (t[0]) {
				case 'A':
					if (strncmp(t, "ACCESS", 6) == 0) {
						condition = BREAKPOINT_ACCESS;
						t += 6;
					}
					else {
						condition = BREAKPOINT_A;
						t++;
					}
					break;
				case 'X':
					condition = BREAKPOINT_X;
					t++;
					break;
				case 'Y':
					condition = BREAKPOINT_Y;
					t++;
					break;
				case 'P':
					if (t[1] == 'C') {
						condition = BREAKPOINT_PC;
						t += 2;
					}
					break;
				case 'R':
					if (strncmp(t, "READ", 4) == 0) {
						condition = BREAKPOINT_READ;
						t += 4;
					}
					break;
				case 'S':
					condition = BREAKPOINT_S;
					t++;
					break;
				case 'W':
					if (strncmp(t, "WRITE", 5) == 0) {
						condition = BREAKPOINT_WRITE;
						t += 5;
					}
					break;
				default:
					break;
				}
				if (t[0] == '!' && t[1] == '=') {
					condition += BREAKPOINT_LESS | BREAKPOINT_GREATER;
					t += 2;
				}
				else {
					if (*t == '<') {
						condition += BREAKPOINT_LESS;
						t++;
					}
					if (*t == '>') {
						condition += BREAKPOINT_GREATER;
						t++;
					}
					if (*t == '=') {
						condition += BREAKPOINT_EQUAL;
						t++;
						if (*t == '=')
							t++;
					}
				}
				if ((condition >> 3) == 0 || (condition & 7) == 0) {
					printf("Bad argument\n");
					return;
				}
				{
					UWORD tmp;
					if (parse_hex(t, &tmp))
						value = tmp;
					else
						value = -1;
				}
			}
			if (value < 0 || value > 0xffff) {
				printf("Bad argument\n");
				return;
			}
			for (j = breakpoint_table_size; j > i; j--)
				breakpoint_table[j] = breakpoint_table[j - 1];
			breakpoint_table[i].enabled = TRUE;
			breakpoint_table[i].condition = condition;
			breakpoint_table[i].value = (UWORD) value;
			i++;
			breakpoint_table_size++;
			t = get_token();
			if (t == NULL) {
				printf("Breakpoint(s) added\n");
				return;
			}
			Util_strupper(t);
		}
		printf("Breakpoint table full\n");
	}
}

#endif /* MONITOR_BREAKPOINTS */

int monitor(void)
{
	UWORD addr;

#ifdef __PLUS
	if (!Misc_AllocMonitorConsole(&mon_output, &mon_input))
		return TRUE;
#endif

	addr = regPC;

	CPU_GetStatus();

	if (cim_encountered) {
		printf("(CIM encountered)\n");
		cim_encountered = FALSE;
	}

#ifdef MONITOR_BREAK
	if (break_over) {
		/* "O" command was active */
		break_addr = 0xd000;
		break_over = FALSE;
	}
	else if (regPC == break_addr)
		printf("(breakpoint at %04X)\n", (unsigned int) break_addr);
	else if (ypos == break_ypos)
		printf("(breakpoint at scanline %d)\n", break_ypos);
	else if (break_ret && ret_nesting <= 0)
		printf("(returned)\n");
	break_step = FALSE;
	break_ret = FALSE;
#endif /* MONITOR_BREAK */

	monitor_show_state();

	for (;;) {
		char s[128];
		static char old_s[128] = "";
		char *t;

		printf("> ");
		safe_gets(s, sizeof(s));
		if (s[0] != '\0')
			strcpy(old_s, s);
		else {
			/* if no command is given, restart the last one, but remove all
			 * arguments, so after a 'm 600' we will see 'm 700' ... */
			int i;
			strcpy(s, old_s);
			for (i = 0; i < (int) sizeof(s); i++)
				if (s[i] == ' ') {
					s[i] = '\0';
					break;
				}
		}
#ifdef HAVE_SYSTEM
		if (s[0] == '!') {
			system(s + 1);
			continue;
		}
#endif
		token_ptr = s;
		t = get_token();
		if (t == NULL)
			continue;

		/* uppercase the command */
		Util_strupper(t);

		if (strcmp(t, "CONT") == 0) {
#ifdef MONITOR_PROFILE
			memset(instruction_count, 0, sizeof(instruction_count));
#endif
			PLUS_EXIT_MONITOR;
			return TRUE;
		}
#ifdef MONITOR_BREAK
		else if (strcmp(t, "BBRK") == 0) {
			t = get_token();
			if (t == NULL)
				printf("Break on BRK is %sabled\n", break_brk ? "en" : "dis");
			else if (Util_stricmp(t, "ON") == 0)
				break_brk = TRUE;
			else if (Util_stricmp(t, "OFF") == 0)
				break_brk = FALSE;
			else
				printf("Invalid argument. Usage: BBRK ON or OFF\n");
		}
		else if (strcmp(t, "BPC") == 0) {
			get_hex(&break_addr);
			if (break_addr >= 0xd000 && break_addr <= 0xd7ff)
				printf("PC breakpoint disabled\n");
			else
				printf("Breakpoint at PC=%04X\n", break_addr);
		}
		else if (strcmp(t, "HISTORY") == 0 || strcmp(t, "H") == 0) {
			int i;
			for (i = 0; i < REMEMBER_PC_STEPS; i++) {
				int j;
				j = remember_xpos[(remember_PC_curpos + i) % REMEMBER_PC_STEPS];
				printf("%3d %3d ", j >> 8, j & 0xff);
				show_instruction(stdout, remember_PC[(remember_PC_curpos + i) % REMEMBER_PC_STEPS]);
			}
		}
		else if (strcmp(t, "JUMPS") == 0) {
			int i;
			for (i = 0; i < REMEMBER_JMP_STEPS; i++)
				show_instruction(stdout, remember_JMP[(remember_jmp_curpos + i) % REMEMBER_JMP_STEPS]);
		}
#endif
#if defined(MONITOR_BREAK) || !defined(NO_YPOS_BREAK_FLICKER)
		else if (strcmp(t, "BLINE") == 0) {
			get_dec(&break_ypos);
			if (break_ypos >= 1008 && break_ypos <= 1247)
				printf("Blinking scanline %d\n", break_ypos - 1000);
#ifdef MONITOR_BREAK
			else if (break_ypos >= 0 && break_ypos <= 311)
				printf("Breakpoint set at scanline %d\n", break_ypos);
#endif
			else
				printf("BLINE disabled\n");
		}
#endif
		else if (strcmp(t, "DLIST") == 0) {
#if 0
/* one instruction per line */
			UWORD tdlist = dlist;
			int nlines = 0;
			get_hex(&tdlist);
			for (;;) {
				UBYTE IR;

				printf("%04X: ", tdlist);
				IR = ANTIC_GetDLByte(&tdlist);

				if (IR & 0x80)
					printf("DLI ");

				if ((IR & 0x0f) == 0)
					printf("%c BLANK\n", ((IR >> 4) & 0x07) + '1');
				else if ((IR & 0x0f) == 1) {
					tdlist = ANTIC_GetDLWord(&tdlist);
					if (IR & 0x40) {
						printf("JVB %04X\n", tdlist);
						break;
					}
					printf("JMP %04X\n", tdlist);
				}
				else {
					if (IR & 0x40)
						printf("LMS %04X ", ANTIC_GetDLWord(&tdlist));
					if (IR & 0x20)
						printf("VSCROL ");
					if (IR & 0x10)
						printf("HSCROL ");
					printf("MODE %X\n", IR & 0x0f);
				}

				if (++nlines == 24) {
					if (pager())
						break;
					nlines = 0;
				}
			}
#else
/* group identical instructions */
			UWORD tdlist = dlist;
			UWORD new_tdlist;
			UBYTE IR;
			int scrnaddr = -1;
			int nlines = 0;
			get_hex(&tdlist);
			new_tdlist = tdlist;
			IR = ANTIC_GetDLByte(&new_tdlist);
			for (;;) {
				printf("%04X: ", tdlist);
				if ((IR & 0x0f) == 0) {
					UBYTE new_IR;
					tdlist = new_tdlist;
					new_IR = ANTIC_GetDLByte(&new_tdlist);
					if (new_IR == IR) {
						int count = 1;
						do {
							count++;
							tdlist = new_tdlist;
							new_IR = ANTIC_GetDLByte(&new_tdlist);
						} while (new_IR == IR && count < 240);
						printf("%dx ", count);
					}
					if (IR & 0x80)
						printf("DLI ");
					printf("%c BLANK\n", ((IR >> 4) & 0x07) + '1');
					IR = new_IR;
				}
				else if ((IR & 0x0f) == 1) {
					tdlist = ANTIC_GetDLWord(&new_tdlist);
					if (IR & 0x80)
						printf("DLI ");
					if (IR & 0x40) {
						printf("JVB %04X\n", tdlist);
						break;
					}
					printf("JMP %04X\n", tdlist);
					new_tdlist = tdlist;
					IR = ANTIC_GetDLByte(&new_tdlist);
				}
				else {
					UBYTE new_IR;
					int new_scrnaddr;
					int count;
					if ((IR & 0x40) && scrnaddr < 0)
						scrnaddr = ANTIC_GetDLWord(&new_tdlist);
					for (count = 1; ; count++) {
						tdlist = new_tdlist;
						new_IR = ANTIC_GetDLByte(&new_tdlist);
						if (new_IR != IR || count >= 240) {
							new_scrnaddr = -1;
							break;
						}
						if (IR & 0x40) {
							new_scrnaddr = ANTIC_GetDLWord(&new_tdlist);
							if (new_scrnaddr != scrnaddr)
								break;
						}
					}
					if (count > 1)
						printf("%dx ", count);
					if (IR & 0x80)
						printf("DLI ");
					if (IR & 0x40)
						printf("LMS %04X ", scrnaddr);
					if (IR & 0x20)
						printf("VSCROL ");
					if (IR & 0x10)
						printf("HSCROL ");
					printf("MODE %X\n", IR & 0x0f);
					scrnaddr = new_scrnaddr;
					IR = new_IR;
				}

				if (++nlines == 24) {
					if (pager())
						break;
					nlines = 0;
				}
			}
#endif
		}
		else if (strcmp(t, "SETPC") == 0)
			get_uword(&regPC);
		else if (strcmp(t, "SETS") == 0)
			get_ubyte(&regS);
		else if (strcmp(t, "SETA") == 0)
			get_ubyte(&regA);
		else if (strcmp(t, "SETX") == 0)
			get_ubyte(&regX);
		else if (strcmp(t, "SETY") == 0)
			get_ubyte(&regY);
		else if (strcmp(t, "SETN") == 0) {
			int val = get_bool();
			if (val == 0)
				ClrN;
			else if (val == 1)
				SetN;
		}
		else if (strcmp(t, "SETV") == 0) {
			int val = get_bool();
			if (val == 0)
				ClrV;
			else if (val == 1)
				SetV;
		}
		else if (strcmp(t, "SETD") == 0) {
			int val = get_bool();
			if (val == 0)
				ClrD;
			else if (val == 1)
				SetD;
		}
		else if (strcmp(t, "SETI") == 0) {
			int val = get_bool();
			if (val == 0)
				ClrI;
			else if (val == 1)
				SetI;
		}
		else if (strcmp(t, "SETZ") == 0) {
			int val = get_bool();
			if (val == 0)
				ClrZ;
			else if (val == 1)
				SetZ;
		}
		else if (strcmp(t, "SETC") == 0) {
			int val = get_bool();
			if (val == 0)
				ClrC;
			else if (val == 1)
				SetC;
		}
#ifdef MONITOR_TRACE
		else if (strcmp(t, "TRACE") == 0) {
			const char *filename = get_token();
			if (trace_file != NULL) {
				fclose(trace_file);
				printf("Trace file closed\n");
				trace_file = NULL;
			}
			if (filename != NULL) {
				trace_file = fopen(filename, "w");
				if (trace_file != NULL)
					printf("Trace file open\n");
				else
					perror(filename);
			}
		}
#endif /* MONITOR_TRACE */
#ifdef MONITOR_PROFILE
		else if (strcmp(t, "PROFILE") == 0) {
			int i;
			for (i = 0; i < 24; i++) {
				int max, instr;
				int j;
				max = instruction_count[0];
				instr = 0;
				for (j = 1; j < 256; j++) {
					if (instruction_count[j] > max) {
						max = instruction_count[j];
						instr = j;
					}
				}
				if (max <= 0)
					break;
				instruction_count[instr] = 0;
				printf("Opcode %02X: %-9s has been executed %d times\n",
					   instr, instr6502[instr], max);
			}
		}
#endif /* MONITOR_PROFILE */
		else if (strcmp(t, "SHOW") == 0)
			monitor_show_state();
		else if (strcmp(t, "STACK") == 0) {
			int ts;
			for (ts = 0x101 + regS; ts < 0x200; ) {
				if (ts < 0x1ff) {
					UWORD ta = (UWORD) (dGetWord(ts) - 2);
					if (dGetByte(ta) == 0x20) {
						printf("%04X: %02X %02X  %04X: JSR %04X\n",
							ts, dGetByte(ts), dGetByte(ts + 1), ta,
							dGetWord(ta + 1));
						ts += 2;
						continue;
					}
				}
				printf("%04X: %02X\n", ts, dGetByte(ts));
				ts++;
			}
		}
		else if (strcmp(t, "ROM") == 0) {
			UWORD addr1;
			UWORD addr2;
			if (get_attrib_range(&addr1, &addr2)) {
				SetROM(addr1, addr2);
				printf("Changed memory from %04X to %04X into ROM\n",
					   addr1, addr2);
			}
		}
		else if (strcmp(t, "RAM") == 0) {
			UWORD addr1;
			UWORD addr2;
			if (get_attrib_range(&addr1, &addr2)) {
				SetRAM(addr1, addr2);
				printf("Changed memory from %04X to %04X into RAM\n",
					   addr1, addr2);
			}
		}
#ifndef PAGED_ATTRIB
		else if (strcmp(t, "HARDWARE") == 0) {
			UWORD addr1;
			UWORD addr2;
			if (get_attrib_range(&addr1, &addr2)) {
				SetHARDWARE(addr1, addr2);
				printf("Changed memory from %04X to %04X into HARDWARE\n",
					   addr1, addr2);
			}
		}
#endif
		else if (strcmp(t, "COLDSTART") == 0) {
			Coldstart();
			PLUS_EXIT_MONITOR;
			return TRUE;	/* perform reboot immediately */
		}
		else if (strcmp(t, "WARMSTART") == 0) {
			Warmstart();
			PLUS_EXIT_MONITOR;
			return TRUE;	/* perform reboot immediately */
		}
#ifndef PAGED_MEM
		else if (strcmp(t, "READ") == 0) {
			const char *filename;
			filename = get_token();
			if (filename != NULL) {
				UWORD nbytes;
				if (get_hex2(&addr, &nbytes) && addr + nbytes <= 0x10000) {
					FILE *f = fopen(filename, "rb");
					if (f == NULL)
						perror(filename);
					else {
						if (fread(&memory[addr], 1, nbytes, f) == 0)
							perror(filename);
						fclose(f);
					}
				}
			}
		}
		else if (strcmp(t, "WRITE") == 0) {
			UWORD addr1;
			UWORD addr2;
			if (get_hex2(&addr1, &addr2) && addr1 <= addr2) {
				const char *filename;
				FILE *f;
				filename = get_token();
				if (filename == NULL)
					filename = "memdump.dat";
				f = fopen(filename, "wb");
				if (f == NULL)
					perror(filename);
				else {
					size_t nbytes = addr2 - addr1 + 1;
					if (fwrite(&memory[addr1], 1, addr2 - addr1 + 1, f) < nbytes)
						perror(filename);
					fclose(f);
				}
			}
		}
#endif
		else if (strcmp(t, "SUM") == 0) {
			UWORD addr1;
			UWORD addr2;
			if (get_hex2(&addr1, &addr2)) {
				int sum = 0;
				int i;
				for (i = addr1; i <= addr2; i++)
					sum += dGetByte(i);
				printf("SUM: %X\n", sum);
			}
		}
		else if (strcmp(t, "M") == 0) {
			int count = 16;
			get_hex(&addr);
			do {
				int i;
				printf("%04X: ", addr);
				for (i = 0; i < 16; i++)
					printf("%02X ", GetByte((UWORD) (addr + i)));
				putchar(' ');
				for (i = 0; i < 16; i++) {
					UBYTE c;
					c = GetByte(addr);
					addr++;
					putchar((c >= ' ' && c <= 'z' && c != '\x60') ? c : '.');
				}
				putchar('\n');
			} while (--count > 0);
		}
#ifndef PAGED_MEM
		else if (strcmp(t, "F") == 0) {
			UWORD addr1;
			UWORD addr2;
			UWORD hexval;
			if (get_hex3(&addr1, &addr2, &hexval)) {
				/* use int to avoid endless loop with addr2==0xffff */
				int addr;
				for (addr = addr1; addr <= addr2; addr++)
					dPutByte(addr, (UBYTE) hexval);
			}
		}
#endif
		else if (strcmp(t, "S") == 0) {
			/* static, so "S" without arguments repeats last search */
			static int n = 0;
			static UWORD addr1;
			static UWORD addr2;
			static UBYTE tab[64];
			UWORD hexval;
			if (get_hex3(&addr1, &addr2, &hexval)) {
				n = 0;
				do {
					tab[n++] = (UBYTE) hexval;
					if (hexval > 0xff && n < 64)
						tab[n++] = (UBYTE) (hexval >> 8);
				} while (n < 64 && get_hex(&hexval));
			}
			if (n > 0) {
				int addr;
				for (addr = addr1; addr <= addr2; addr++) {
					int i = 0;
					while (GetByte((UWORD) (addr + i)) == tab[i]) {
						i++;
						if (i >= n) {
							printf("Found at %04X\n", addr);
							break;
						}
					}
				}
			}
		}
#ifndef PAGED_MEM
		else if (strcmp(t, "C") == 0) {
			UWORD temp = 0;
			get_hex(&addr);
			while (get_hex(&temp)) {
#ifdef PAGED_ATTRIB
				if (writemap[addr >> 8] != NULL && writemap[addr >> 8] != ROM_PutByte)
					(*writemap[addr >> 8])(addr, (UBYTE) temp);
#else
				if (attrib[addr] == HARDWARE)
					Atari800_PutByte(addr, (UBYTE) temp);
#endif
				else /* RAM, ROM */
					dPutByte(addr, (UBYTE) temp);
				addr++;
				if (temp > 0xff) {
#ifdef PAGED_ATTRIB
					if (writemap[addr >> 8] != NULL && writemap[addr >> 8] != ROM_PutByte)
						(*writemap[addr >> 8])(addr, (UBYTE) (temp >> 8));
#else
					if (attrib[addr] == HARDWARE)
						Atari800_PutByte(addr, (UBYTE) (temp >> 8));
#endif
					else /* RAM, ROM */
						dPutByte(addr, (UBYTE) (temp >> 8));
					addr++;
				}
			}
		}
#endif
#ifdef MONITOR_BREAK
		else if (strcmp(t, "G") == 0) {
			break_step = TRUE;
			PLUS_EXIT_MONITOR;
			return TRUE;
		}
		else if (strcmp(t, "R") == 0 ) {
			break_ret = TRUE;
			ret_nesting = 1;
			PLUS_EXIT_MONITOR;
			return TRUE;
		}
		else if (strcmp(t, "O") == 0) {
			UBYTE opcode = dGetByte(regPC);
			if ((opcode & 0x1f) == 0x10 || opcode == 0x20) {
				/* branch or JSR: set breakpoint after it */
				break_addr = regPC + (optype6502[dGetByte(regPC)] & 0x3);
				break_over = TRUE;
			}
			else
				break_step = TRUE;
			PLUS_EXIT_MONITOR;
			return TRUE;
		}
#endif /* MONITOR_BREAK */
#ifdef MONITOR_BREAKPOINTS
		else if (strcmp(t, "B") == 0)
			monitor_breakpoints();
#endif
		else if (strcmp(t, "D") == 0) {
			get_hex(&addr);
			addr = disassemble(addr);
		}
		else if (strcmp(t, "LOOP") == 0) {
			int caddr;
			get_hex(&addr);
			caddr = addr;
			for (;;) {
				UBYTE opcode;
				if (caddr > (UWORD) (addr + 0x7e)) {
					printf("Conditional loop containing instruction at %04X not detected\n", addr);
					break;
				}
				opcode = dGetByte(caddr);
				if ((opcode & 0x1f) == 0x10) {
					/* branch */
					UWORD target = caddr + 2 + (SBYTE) dGetByte(caddr + 1);
					if (target <= addr) {
						addr = disassemble(target);
						break;
					}
				}
				caddr += optype6502[opcode] & 3;
			}
		}
#ifdef MONITOR_HINTS
		else if (strcmp(t, "LABELS") == 0) {
			char *cmd = get_token();
			if (cmd == NULL) {
				printf("Built-in labels are %sabled.\n", symtable_builtin_enable ? "en" : "dis");
				if (symtable_user_size > 0)
					printf("Using %d user-defined label%s.\n",
						symtable_user_size, (symtable_user_size > 1) ? "s" : "");
				else
					printf("There are no user-defined labels.\n");
				printf(
					"Labels are displayed in disassembly listings.\n"
					"You may also use them as command arguments"
#ifdef MONITOR_ASSEMBLER
						" and in the built-in assembler"
#endif
						".\n"
					"Usage:\n"
					"LABELS OFF            - no labels\n"
					"LABELS BUILTIN        - use only built-in labels\n"
					"LABELS LOAD filename  - use only labels loaded from file\n"
					"LABELS ADD filename   - use built-in and loaded labels\n"
					"LABELS SET name value - define a label\n"
					"LABELS LIST           - list user-defined labels\n"
				);
			}
			else {
				Util_strupper(cmd);
				if (strcmp(cmd, "OFF") == 0) {
					symtable_builtin_enable = FALSE;
					free_user_labels();
				}
				else if (strcmp(cmd, "BUILTIN") == 0) {
					symtable_builtin_enable = TRUE;
					free_user_labels();
				}
				else if (strcmp(cmd, "LOAD") == 0) {
					symtable_builtin_enable = FALSE;
					load_user_labels(get_token());
				}
				else if (strcmp(cmd, "ADD") == 0) {
					symtable_builtin_enable = TRUE;
					load_user_labels(get_token());
				}
				else if (strcmp(cmd, "SET") == 0) {
					const char *name = get_token();
					if (name != NULL && get_hex(&addr)) {
						symtable_rec *p = find_user_label(name);
						if (p != NULL) {
							if (p->addr != addr) {
								printf("%s redefined (previous value: %04X)\n", name, p->addr);
								p->addr = addr;
							}
						}
						else
							add_user_label(name, addr);
					}
					else
						printf("Missing or bad arguments\n");
				}
				else if (strcmp(cmd, "LIST") == 0) {
					int i;
					int nlines = 0;
					for (i = 0; i < symtable_user_size; i++) {
						if (++nlines == 24) {
							if (pager())
								break;
							nlines = 0;
						}
						printf("%04X %s\n", symtable_user[i].addr, symtable_user[i].name);
					}
				}
				else
					printf("Invalid command, type \"LABELS\" for help\n");
			}
		}
#endif
		else if (strcmp(t, "ANTIC") == 0) {
			printf("DMACTL=%02X    CHACTL=%02X    DLISTL=%02X    "
				   "DLISTH=%02X    HSCROL=%02X    VSCROL=%02X\n",
				   DMACTL, CHACTL, dlist & 0xff, dlist >> 8, HSCROL, VSCROL);
			printf("PMBASE=%02X    CHBASE=%02X    VCOUNT=%02X    "
				   "NMIEN= %02X    ypos=%4d\n",
				   PMBASE, CHBASE, ANTIC_GetByte(_VCOUNT), NMIEN, ypos);
		}
		else if (strcmp(t, "PIA") == 0) {
			printf("PACTL= %02X    PBCTL= %02X    PORTA= %02X    "
				   "PORTB= %02X\n", PACTL, PBCTL, PORTA, PORTB);
		}
		else if (strcmp(t, "GTIA") == 0) {
			printf("HPOSP0=%02X    HPOSP1=%02X    HPOSP2=%02X    HPOSP3=%02X\n",
				   HPOSP0, HPOSP1, HPOSP2, HPOSP3);
			printf("HPOSM0=%02X    HPOSM1=%02X    HPOSM2=%02X    HPOSM3=%02X\n",
				   HPOSM0, HPOSM1, HPOSM2, HPOSM3);
			printf("SIZEP0=%02X    SIZEP1=%02X    SIZEP2=%02X    SIZEP3=%02X    SIZEM= %02X\n",
				   SIZEP0, SIZEP1, SIZEP2, SIZEP3, SIZEM);
			printf("GRAFP0=%02X    GRAFP1=%02X    GRAFP2=%02X    GRAFP3=%02X    GRAFM= %02X\n",
				   GRAFP0, GRAFP1, GRAFP2, GRAFP3, GRAFM);
			printf("COLPM0=%02X    COLPM1=%02X    COLPM2=%02X    COLPM3=%02X\n",
				   COLPM0, COLPM1, COLPM2, COLPM3);
			printf("COLPF0=%02X    COLPF1=%02X    COLPF2=%02X    COLPF3=%02X    COLBK= %02X\n",
				   COLPF0, COLPF1, COLPF2, COLPF3, COLBK);
			printf("PRIOR= %02X    VDELAY=%02X    GRACTL=%02X\n",
				   PRIOR, VDELAY, GRACTL);
		}
		else if (strcmp(t, "POKEY") == 0) {
			printf("AUDF1= %02X    AUDF2= %02X    AUDF3= %02X    AUDF4= %02X    AUDCTL=%02X    KBCODE=%02X\n",
				   AUDF[CHAN1], AUDF[CHAN2], AUDF[CHAN3], AUDF[CHAN4], AUDCTL[0], KBCODE);
			printf("AUDC1= %02X    AUDC2= %02X    AUDC3= %02X    AUDC4= %02X    IRQEN= %02X    IRQST= %02X\n",
				   AUDC[CHAN1], AUDC[CHAN2], AUDC[CHAN3], AUDC[CHAN4], IRQEN, IRQST);
#ifdef STEREO_SOUND
			if (stereo_enabled) {
				printf("Second chip:\n");
				printf("AUDF1= %02X    AUDF2= %02X    AUDF3= %02X    AUDF4= %02X    AUDCTL=%02X\n",
					   AUDF[CHAN1 + CHIP2], AUDF[CHAN2 + CHIP2], AUDF[CHAN3 + CHIP2], AUDF[CHAN4 + CHIP2], AUDCTL[1]);
				printf("AUDC1= %02X    AUDC2= %02X    AUDC3= %02X    AUDC4= %02X\n",
					   AUDC[CHAN1 + CHIP2], AUDC[CHAN2 + CHIP2], AUDC[CHAN3 + CHIP2], AUDC[CHAN4 + CHIP2]);
			}
#endif
		}
#ifdef MONITOR_ASSEMBLER
		else if (strcmp(t, "A") == 0) {
			get_hex(&addr);
			addr = assembler(addr);
		}
#endif
		else if (strcmp(t, "HELP") == 0 || strcmp(t, "?") == 0) {
			printf(
				"CONT                           - Continue emulation\n"
				"SHOW                           - Show registers\n"
				"STACK                          - Show stack\n"
				"SET{PC,A,X,Y,S} hexval         - Set register value\n"
				"SET{N,V,D,I,Z,C} 0 or 1        - Set flag value\n"
				"C startaddr hexval...          - Change memory\n"
				"D [startaddr]                  - Disassemble memory\n"
				"F startaddr endaddr hexval     - Fill memory\n"
				"M [startaddr]                  - Memory list\n"
				"S startaddr endaddr hexval...  - Search memory\n");
			/* split into several printfs to avoid gcc -pedantic warning: "string length 'xxx'
			   is greater than the length '509' ISO C89 compilers are required to support" */
			printf(
				"LOOP [inneraddr]               - Disassemble a loop that contains inneraddr\n"
				"RAM startaddr endaddr          - Convert memory block into RAM\n"
				"ROM startaddr endaddr          - Convert memory block into ROM\n"
				"HARDWARE startaddr endaddr     - Convert memory block into HARDWARE\n"
				"READ filename startaddr nbytes - Read file into memory\n"
				"WRITE startaddr endaddr [file] - Write memory block to a file (memdump.dat)\n"
				"SUM startaddr endaddr          - Print sum of specified memory range\n");
#ifdef MONITOR_TRACE
			printf(
				"TRACE [filename]               - Output 6502 trace on/off\n");
#endif
#ifdef MONITOR_BREAK
			printf(
				"BPC [addr]                     - Set breakpoint at address\n"
				"BLINE [ypos] or [1000+ypos]    - Break at scanline or blink scanline\n"
				"BBRK ON or OFF                 - Breakpoint on BRK on/off\n"
				"HISTORY or H                   - List last %d executed instructions\n", REMEMBER_PC_STEPS);
			printf(
				"JUMPS                          - List last %d executed JMP/JSR\n", REMEMBER_JMP_STEPS);
			printf("Press return to continue: ");
			{
				char buf[100];
				safe_gets(buf, sizeof(buf));
			}
			printf(
				"G                              - Execute one instruction\n"
				"O                              - Step over the instruction\n"
				"R                              - Execute until return\n");
#elif !defined(NO_YPOS_BREAK_FLICKER)
			printf(
				"BLINE [1000+ypos]              - Blink scanline (8<=ypos<=247)\n");
#endif
			printf(
#ifdef MONITOR_BREAKPOINTS
				"B [argument...]                - Manage breakpoints (\"B ?\" for help)\n"
#endif
#ifdef MONITOR_ASSEMBLER
				"A [startaddr]                  - Start simple assembler\n"
#endif
				"ANTIC, GTIA, PIA, POKEY        - Display hardware registers\n"
				"DLIST [startaddr]              - Show Display List\n");
			printf(
#ifdef MONITOR_PROFILE
				"PROFILE                        - Display profiling statistics\n"
#endif
#ifdef MONITOR_HINTS
				"LABELS [command] [filename]    - Configure labels\n"
#endif
				"COLDSTART, WARMSTART           - Perform system coldstart/warmstart\n"
#ifdef HAVE_SYSTEM
				"!command                       - Execute shell command\n"
#endif
				"QUIT or EXIT                   - Quit emulator\n"
				"HELP or ?                      - This text\n");
		}
		else if (strcmp(t, "QUIT") == 0 || strcmp(t, "EXIT") == 0) {
			PLUS_EXIT_MONITOR;
			return FALSE;
		}
		else
			printf("Invalid command!\n");
	}
}
