/*
* UAE - The Un*x Amiga Emulator
*
* Debugger
*
* (c) 1995 Bernd Schmidt
* (c) 2006 Toni Wilen
*
*/

#include "sysconfig.h"
#include "sysdeps.h"

#include "main.h"
#include "hatari-glue.h"

#include <ctype.h>

#include "options_cpu.h"
//#include "uae.h"
#include "memory.h"
#include "custom.h"
#include "newcpu.h"
#include "cpu_prefetch.h"
#include "debug.h"
#include "disasm.h"
#include "debugmem.h"
//#include "cia.h"
//#include "xwin.h"
//#include "identify.h"
//#include "audio.h"
//#include "sounddep/sound.h"
//#include "disk.h"
#include "savestate.h"
//#include "autoconf.h"
//#include "akiko.h"
//#include "inputdevice.h"
//#include "crc32.h"
#include "cpummu.h"
//#include "rommgr.h"
//#include "inputrecord.h"
//#include "calc.h"
#include "cpummu.h"
#include "cpummu030.h"
//#include "ar.h"
//#include "pci.h"
//#include "ppc/ppcd.h"
//#include "uae/io.h"
//#include "uae/ppc.h"
//#include "drawing.h"
//#include "devices.h"
//#include "blitter.h"
//#include "ini.h"
#include "readcpu.h"
#include "cputbl.h"
//#include "keybuf.h"

static int trace_mode;
static uae_u32 trace_param[3];

#ifdef WINUAE_FOR_HATARI
#include "stMemory.h"
int debugger_active;
static int debug_mmu_mode;
int debugging;
int exception_debugging;

void debug ( void ) { }						/* Not used in Hatari for now */

#else
int debugger_active;
static int debug_rewind;
static int memwatch_triggered;
static int inside_debugger;
int debugger_used;
int memwatch_access_validator;
int memwatch_enabled;
int debugging;
int exception_debugging;
int no_trace_exceptions;
int debug_copper = 0;
int debug_dma = 0, debug_heatmap = 0;
int debug_sprite_mask = 0xff;
int debug_illegal = 0;
uae_u64 debug_illegal_mask;
static int debug_mmu_mode;
static bool break_if_enforcer;
static uaecptr debug_pc;

static int trace_cycles;
static int last_hpos1, last_hpos2;
static int last_vpos1, last_vpos2;
static int last_frame = -1;
static uae_u32 last_cycles1, last_cycles2;

static uaecptr processptr;
static uae_char *processname;

static uaecptr debug_copper_pc;

extern int audio_channel_mask;
extern int inputdevice_logging;

static void debug_cycles(int mode)
{
	trace_cycles = mode;
	last_cycles2 = get_cycles();
	last_vpos2 = vpos;
	last_hpos2 = current_hpos();
}

void deactivate_debugger (void)
{
	inside_debugger = 0;
	debugger_active = 0;
	debugging = 0;
	exception_debugging = 0;
	processptr = 0;
	xfree (processname);
	processname = NULL;
	debugmem_enable();
	debug_pc = 0xffffffff;
	keybuf_ignore_next_release();
}

void activate_debugger (void)
{
	disasm_init();
	if (isfullscreen() > 0)
		return;

	debugger_load_libraries();

	debugger_used = 1;
	inside_debugger = 1;
	debug_pc = 0xffffffff;
	trace_mode = 0;
	if (debugger_active) {
		// already in debugger but some break point triggered
		// during disassembly etc..
		return;
	}
	debug_cycles(1);
	debugger_active = 1;
	set_special (SPCFLAG_BRK);
	debugging = 1;
	mmu_triggered = 0;
	debugmem_disable();
}

void activate_debugger_new(void)
{
	activate_debugger();
	debug_pc = M68K_GETPC;
}

void activate_debugger_new_pc(uaecptr pc, int len)
{
	activate_debugger();
	trace_mode = TRACE_RANGE_PC;
	trace_param[0] = pc;
	trace_param[1] = pc + len;
}

bool debug_enforcer(void)
{
	if (!break_if_enforcer)
		return false;
	activate_debugger();
	return true;
}


int firsthist = 0;
int lasthist = 0;
struct cpuhistory {
	struct regstruct regs;
	int fp, vpos, hpos;
};
static struct cpuhistory history[MAX_HIST];

static const TCHAR help[] = {
	_T("          HELP for UAE Debugger\n")
	_T("         -----------------------\n\n")
	_T("  g [<address>]         Start execution at the current address or <address>.\n")
	_T("  c                     Dump state of the CIA, disk drives and custom registers.\n")
	_T("  r                     Dump state of the CPU.\n")
	_T("  r <reg> <value>       Modify CPU registers (Dx,Ax,USP,ISP,VBR,...).\n")
	_T("  rc[d]                 Show CPU instruction or data cache contents.\n")
	_T("  m <address> [<lines>] Memory dump starting at <address>.\n")
	_T("  a <address>           Assembler.\n")
	_T("  d <address> [<lines>] Disassembly starting at <address>.\n")
	_T("  t [instructions]      Step one or more instructions.\n")
	_T("  z                     Step through one instruction - useful for JSR, DBRA etc.\n")
	_T("  f                     Step forward until PC in RAM (\"boot block finder\").\n")
	_T("  f <address>           Add/remove breakpoint.\n")
	_T("  fa <address> [<start>] [<end>]\n")
	_T("                        Find effective address <address>.\n")
	_T("  fi                    Step forward until PC points to RTS, RTD or RTE.\n")
	_T("  fi <opcode> [<w2>] [<w3>] Step forward until PC points to <opcode>.\n")
	_T("  fp \"<name>\"/<addr>    Step forward until process <name> or <addr> is active.\n")
	_T("  fl                    List breakpoints.\n")
	_T("  fd                    Remove all breakpoints.\n")
	_T("  fs <lines to wait> | <vpos> <hpos> Wait n scanlines/position.\n")
	_T("  fc <CCKs to wait>     Wait n color clocks.\n")
	_T("  fo <num> <reg> <oper> <val> [<mask> <val2>] Conditional register breakpoint.\n")
	_T("   reg=Dx,Ax,PC,USP,ISP,VBR,SR. oper:!=,==,<,>,>=,<=,-,!- (-=val to val2 range).\n")
	_T("  f <addr1> <addr2>     Step forward until <addr1> <= PC <= <addr2>.\n")
	_T("  e[x]                  Dump contents of all custom registers, ea = AGA colors.\n")
	_T("  i [<addr>]            Dump contents of interrupt and trap vectors.\n")
	_T("  il [<mask>]           Exception breakpoint.\n")
	_T("  o <0-2|addr> [<lines>]View memory as Copper instructions.\n")
	_T("  od                    Enable/disable Copper vpos/hpos tracing.\n")
	_T("  ot                    Copper single step trace.\n")
	_T("  ob <addr>             Copper breakpoint.\n")
	_T("  H[H] <cnt>            Show PC history (HH=full CPU info) <cnt> instructions.\n")
	_T("  C <value>             Search for values like energy or lifes in games.\n")
	_T("  Cl                    List currently found trainer addresses.\n")
	_T("  D[idxzs <[max diff]>] Deep trainer. i=new value must be larger, d=smaller,\n")
	_T("                        x = must be same, z = must be different, s = restart.\n")
	_T("  W <addr> <values[.x] separated by space> Write into Amiga memory.\n")
	_T("  W <addr> 'string'     Write into Amiga memory.\n")
	_T("  Wf <addr> <endaddr> <bytes or string like above>, fill memory.\n")
	_T("  Wc <addr> <endaddr> <destaddr>, copy memory.\n")
	_T("  w <num> <address> <length> <R/W/I> <F/C/L/N> [<value>[.x]] (read/write/opcode) (freeze/mustchange/logonly/nobreak).\n")
	_T("                        Add/remove memory watchpoints.\n")
	_T("  wd [<0-1>]            Enable illegal access logger. 1 = enable break.\n")
	_T("  L <file> <addr> [<n>] Load a block of Amiga memory.\n")
	_T("  S <file> <addr> <n>   Save a block of Amiga memory.\n")
	_T("  s \"<string>\"/<values> [<addr>] [<length>]\n")
	_T("                        Search for string/bytes.\n")
	_T("  T or Tt               Show exec tasks and their PCs.\n")
	_T("  Td,Tl,Tr,Tp,Ts,TS,Ti,TO,TM,Tf Show devs, libs, resources, ports, semaphores,\n")
	_T("                        residents, interrupts, doslist, memorylist, fsres.\n")
	_T("  b                     Step to previous state capture position.\n")
	_T("  M<a/b/s> <val>        Enable or disable audio channels, bitplanes or sprites.\n")
	_T("  sp <addr> [<addr2][<size>] Dump sprite information.\n")
	_T("  di <mode> [<track>]   Break on disk access. R=DMA read,W=write,RW=both,P=PIO.\n")
	_T("                        Also enables level 1 disk logging.\n")
	_T("  did <log level>       Enable disk logging.\n")
	_T("  dj [<level bitmask>]  Enable joystick/mouse input debugging.\n")
	_T("  smc [<0-1>]           Enable self-modifying code detector. 1 = enable break.\n")
	_T("  dm                    Dump current address space map.\n")
	_T("  v <vpos> [<hpos>]     Show DMA data (accurate only in cycle-exact mode).\n")
	_T("                        v [-1 to -4] = enable visual DMA debugger.\n")
	_T("  vh [<ratio> <lines>]  \"Heat map\"\n")
	_T("  I <custom event>      Send custom event string\n")
	_T("  ?<value>              Hex ($ and 0x)/Bin (%)/Dec (!) converter and calculator.\n")
#ifdef _WIN32
	_T("  x                     Close debugger.\n")
	_T("  xx                    Switch between console and GUI debugger.\n")
	_T("  mg <address>          Memory dump starting at <address> in GUI.\n")
	_T("  dg <address>          Disassembly starting at <address> in GUI.\n")
#endif
	_T("  q                     Quit the emulator. You don't want to use this command.\n\n")
};

void debug_help (void)
{
	console_out (help);
}


struct mw_acc {
	uae_u32 mask;
	const TCHAR *name;
};

static const struct mw_acc memwatch_access_masks[] =
{
	{ MW_MASK_ALL, _T("ALL") },
	{ MW_MASK_NONE, _T("NONE") },
	{ MW_MASK_ALL & ~(MW_MASK_CPU_I | MW_MASK_CPU_D_R | MW_MASK_CPU_D_W), _T("DMA") },

	{ MW_MASK_BLITTER_A | MW_MASK_BLITTER_B | MW_MASK_BLITTER_C | MW_MASK_BLITTER_D_N | MW_MASK_BLITTER_D_L | MW_MASK_BLITTER_D_F, _T("BLT") },
	{ MW_MASK_BLITTER_D_N | MW_MASK_BLITTER_D_L | MW_MASK_BLITTER_D_F, _T("BLTD") },

	{ MW_MASK_AUDIO_0 | MW_MASK_AUDIO_1 | MW_MASK_AUDIO_2 | MW_MASK_AUDIO_3, _T("AUD") },

	{ MW_MASK_BPL_0 | MW_MASK_BPL_1 | MW_MASK_BPL_2 | MW_MASK_BPL_3 |
		MW_MASK_BPL_4 | MW_MASK_BPL_5 | MW_MASK_BPL_6 | MW_MASK_BPL_7, _T("BPL") },

	{ MW_MASK_SPR_0 | MW_MASK_SPR_1 | MW_MASK_SPR_2 | MW_MASK_SPR_3 |
		MW_MASK_SPR_4 | MW_MASK_SPR_5 | MW_MASK_SPR_6 | MW_MASK_SPR_7, _T("SPR") },

	{ MW_MASK_CPU_I | MW_MASK_CPU_D_R | MW_MASK_CPU_D_W, _T("CPU") },
	{ MW_MASK_CPU_D_R | MW_MASK_CPU_D_W, _T("CPUD") },
	{ MW_MASK_CPU_I, _T("CPUI") },
	{ MW_MASK_CPU_D_R, _T("CPUDR") },
	{ MW_MASK_CPU_D_W, _T("CPUDW") },

	{ MW_MASK_COPPER, _T("COP") },

	{ MW_MASK_BLITTER_A, _T("BLTA") },
	{ MW_MASK_BLITTER_B, _T("BLTB") },
	{ MW_MASK_BLITTER_C, _T("BLTC") },
	{ MW_MASK_BLITTER_D_N, _T("BLTDN") },
	{ MW_MASK_BLITTER_D_L, _T("BLTDL") },
	{ MW_MASK_BLITTER_D_F, _T("BLTDF") },

	{ MW_MASK_DISK, _T("DSK") },

	{ MW_MASK_AUDIO_0, _T("AUD0") },
	{ MW_MASK_AUDIO_1, _T("AUD1") },
	{ MW_MASK_AUDIO_2, _T("AUD2") },
	{ MW_MASK_AUDIO_3, _T("AUD3") },

	{ MW_MASK_BPL_0, _T("BPL0") },
	{ MW_MASK_BPL_1, _T("BPL1") },
	{ MW_MASK_BPL_2, _T("BPL2") },
	{ MW_MASK_BPL_3, _T("BPL3") },
	{ MW_MASK_BPL_4, _T("BPL4") },
	{ MW_MASK_BPL_5, _T("BPL5") },
	{ MW_MASK_BPL_6, _T("BPL6") },
	{ MW_MASK_BPL_7, _T("BPL7") },

	{ MW_MASK_SPR_0, _T("SPR0") },
	{ MW_MASK_SPR_1, _T("SPR1") },
	{ MW_MASK_SPR_2, _T("SPR2") },
	{ MW_MASK_SPR_3, _T("SPR3") },
	{ MW_MASK_SPR_4, _T("SPR4") },
	{ MW_MASK_SPR_5, _T("SPR5") },
	{ MW_MASK_SPR_6, _T("SPR6") },
	{ MW_MASK_SPR_7, _T("SPR7") },

	{ 0, NULL },
};

static void mw_help(void)
{
	for (int i = 0; memwatch_access_masks[i].mask; i++) {
		console_out_f(_T("%s "), memwatch_access_masks[i].name);
	}
	console_out_f(_T("\n"));
}

static int debug_linecounter;
#define MAX_LINECOUNTER 1000

static int debug_out (const TCHAR *format, ...)
{
	va_list parms;
	TCHAR buffer[4000];

	va_start (parms, format);
	_vsntprintf (buffer, 4000 - 1, format, parms);
	va_end (parms);

	console_out (buffer);
	if (debug_linecounter < MAX_LINECOUNTER)
		debug_linecounter++;
	if (debug_linecounter >= MAX_LINECOUNTER)
		return 0;
	return 1;
}

#endif	/* ! WINUAE_FOR_HATARI */

uae_u32 get_byte_debug (uaecptr addr)
{
	uae_u32 v = 0xff;
	if (debug_mmu_mode) {
		flagtype olds = regs.s;
		regs.s = (debug_mmu_mode & 4) != 0;
		TRY(p) {
			if (currprefs.mmu_model == 68030) {
				v = mmu030_get_generic (addr, debug_mmu_mode, sz_byte, MMU030_SSW_SIZE_B);
			} else {
				if (debug_mmu_mode & 1) {
					bool odd = (addr & 1) != 0;
					addr &= ~1;
					v = mmu_get_iword(addr, sz_byte);
					if (!odd)
						v >>= 8;
				} else {
					v = mmu_get_user_byte (addr, regs.s != 0, false, sz_byte, false);
				}
			}
		} CATCH(p) {
		} ENDTRY
		regs.s = olds;
	} else {
#ifndef WINUAE_FOR_HATARI
		v = get_byte (addr);
#else
		v = STMemory_ReadByte ( addr );
#endif
	}
	return v;
}
uae_u32 get_word_debug (uaecptr addr)
{
	uae_u32 v = 0xffff;
	if (debug_mmu_mode) {
		flagtype olds = regs.s;
		regs.s = (debug_mmu_mode & 4) != 0;
		TRY(p) {
			if (currprefs.mmu_model == 68030) {
				v = mmu030_get_generic (addr, debug_mmu_mode, sz_word, MMU030_SSW_SIZE_W);
			} else {
				if (debug_mmu_mode & 1) {
					v = mmu_get_iword(addr, sz_word);
				} else {
					v = mmu_get_user_word (addr, regs.s != 0, false, sz_word, false);
				}
			}
		} CATCH(p) {
		} ENDTRY
		regs.s = olds;
	} else {
#ifndef WINUAE_FOR_HATARI
		v = get_word (addr);
#else
		v = STMemory_ReadWord ( addr );
#endif
	}
	return v;
}
uae_u32 get_long_debug (uaecptr addr)
{
	uae_u32 v = 0xffffffff;
	if (debug_mmu_mode) {
		flagtype olds = regs.s;
		regs.s = (debug_mmu_mode & 4) != 0;
		TRY(p) {
			if (currprefs.mmu_model == 68030) {
				v = mmu030_get_generic (addr, debug_mmu_mode, sz_long, MMU030_SSW_SIZE_L);
			} else {
				if (debug_mmu_mode & 1) {
					v = mmu_get_ilong(addr, sz_long);
				} else {
					v = mmu_get_user_long (addr, regs.s != 0, false, sz_long, false);
				}
			}
		} CATCH(p) {
		} ENDTRY
		regs.s = olds;
	} else {
#ifndef WINUAE_FOR_HATARI
		v = get_long (addr);
#else
		v = STMemory_ReadLong ( addr );
#endif
	}
	return v;
}
uae_u32 get_iword_debug (uaecptr addr)
{
	if (debug_mmu_mode) {
		return get_word_debug (addr);
	} else {
#ifndef WINUAE_FOR_HATARI
		if (valid_address (addr, 2))
			return get_word (addr);
		return 0xffff;
#else
		return get_word_debug (addr);
#endif
	}
}
uae_u32 get_ilong_debug (uaecptr addr)
{
	if (debug_mmu_mode) {
		return get_long_debug (addr);
	} else {
#ifndef WINUAE_FOR_HATARI
		if (valid_address (addr, 4))
			return get_long (addr);
		return 0xffffffff;
#else
		return get_long_debug (addr);
#endif
	}
}
uae_u8 *get_real_address_debug(uaecptr addr)
{
	if (debug_mmu_mode) {
		flagtype olds = regs.s;
		TRY(p) {
			if (currprefs.mmu_model >= 68040)
				addr = mmu_translate(addr, 0, regs.s != 0, (debug_mmu_mode & 1), false, 0);
			else
				addr = mmu030_translate(addr, regs.s != 0, (debug_mmu_mode & 1), false);
		} CATCH(p) {
		} ENDTRY
	}
	return get_real_address(addr);
}

int debug_safe_addr (uaecptr addr, int size)
{
	if (debug_mmu_mode) {
		flagtype olds = regs.s;
		regs.s = (debug_mmu_mode & 4) != 0;
		TRY(p) {
			if (currprefs.mmu_model >= 68040)
				addr = mmu_translate (addr, 0, regs.s != 0, (debug_mmu_mode & 1), false, size);
			else
				addr = mmu030_translate (addr, regs.s != 0, (debug_mmu_mode & 1), false);
		} CATCH(p) {
			STOPTRY;
			return 0;
		} ENDTRY
		regs.s = olds;
	}
	addrbank *ab = &get_mem_bank (addr);
	if (!ab)
		return 0;
	if (ab->flags & ABFLAG_SAFE)
		return 1;
	if (!ab->check (addr, size))
		return 0;
	if (ab->flags & (ABFLAG_RAM | ABFLAG_ROM | ABFLAG_ROMIN | ABFLAG_SAFE))
		return 1;
	return 0;
}

#ifndef WINUAE_FOR_HATARI
static bool iscancel (int counter)
{
	static int cnt;

	cnt++;
	if (cnt < counter)
		return false;
	cnt = 0;
	if (!console_isch ())
		return false;
	console_getch ();
	return true;
}

static bool isoperator(TCHAR **cp)
{
	TCHAR c = **cp;
	return c == '+' || c == '-' || c == '/' || c == '*' || c == '(' || c == ')' || c == '|' || c == '&' || c == '^';
}

static void ignore_ws (TCHAR **c)
{
	while (**c && _istspace(**c))
		(*c)++;
}
static TCHAR peekchar (TCHAR **c)
{
	return **c;
}
static TCHAR readchar (TCHAR **c)
{
	TCHAR cc = **c;
	(*c)++;
	return cc;
}
static TCHAR next_char(TCHAR **c)
{
	ignore_ws (c);
	return *(*c)++;
}
static TCHAR next_char2(TCHAR **c)
{
	return *(*c)++;
}
static TCHAR peek_next_char (TCHAR **c)
{
	TCHAR *pc = *c;
	return pc[1];
}
static int more_params(TCHAR **c)
{
	ignore_ws(c);
	return (**c) != 0;
}
static int more_params2(TCHAR **c)
{
	return (**c) != 0;
}

static uae_u32 readint (TCHAR **c);
static uae_u32 readbin (TCHAR **c);
static uae_u32 readhex (TCHAR **c);

static const TCHAR *debugoper[] = {
	_T("=="),
	_T("!="),
	_T("<="),
	_T(">="),
	_T("<"),
	_T(">"),
	_T("-"),
	_T("!-"),
	NULL
};

static int getoperidx(TCHAR **c)
{
	int i;
	TCHAR *p = *c;
	TCHAR tmp[10];
	int extra = 0;

	i = 0;
	while (p[i]) {
		tmp[i] = _totupper(p[i]);
		if (i >= sizeof(tmp) / sizeof(TCHAR) - 1)
			break;
		i++;
	}
	tmp[i] = 0;
	if (!_tcsncmp(tmp, _T("!="), 2)) {
		(*c) += 2;
		return BREAKPOINT_CMP_NEQUAL;
	} else if (!_tcsncmp(tmp, _T("=="), 2)) {
		(*c) += 2;
		return BREAKPOINT_CMP_EQUAL;
	} else if (!_tcsncmp(tmp, _T(">="), 2)) {
		(*c) += 2;
		return BREAKPOINT_CMP_LARGER_EQUAL;
	} else if (!_tcsncmp(tmp, _T("<="), 2)) {
		(*c) += 2;
		return BREAKPOINT_CMP_SMALLER_EQUAL;
	} else if (!_tcsncmp(tmp, _T(">"), 1)) {
		(*c) += 1;
		return BREAKPOINT_CMP_LARGER;
	} else if (!_tcsncmp(tmp, _T("<"), 1)) {
		(*c) += 1;
		return BREAKPOINT_CMP_SMALLER;
	} else if (!_tcsncmp(tmp, _T("-"), 1)) {
		(*c) += 1;
		return BREAKPOINT_CMP_RANGE;
	} else if (!_tcsncmp(tmp, _T("!-"), 2)) {
		(*c) += 2;
		return BREAKPOINT_CMP_NRANGE;
	}
	return -1;
}

static const TCHAR *debugregs[] = {
	_T("D0"),
	_T("D1"),
	_T("D2"),
	_T("D3"),
	_T("D4"),
	_T("D5"),
	_T("D6"),
	_T("D7"),
	_T("A0"),
	_T("A1"),
	_T("A2"),
	_T("A3"),
	_T("A4"),
	_T("A5"),
	_T("A6"),
	_T("A7"),
	_T("PC"),
	_T("USP"),
	_T("MSP"),
	_T("ISP"),
	_T("VBR"),
	_T("SR"),
	_T("CCR"),
	_T("CACR"),
	_T("CAAR"),
	_T("SFC"),
	_T("DFC"),
	_T("TC"),
	_T("ITT0"),
	_T("ITT1"),
	_T("DTT0"),
	_T("DTT1"),
	_T("BUSC"),
	_T("PCR"),
	NULL
};

static int getregidx(TCHAR **c)
{
	int i;
	TCHAR *p = *c;
	TCHAR tmp[10];
	int extra = 0;

	i = 0;
	while (p[i]) {
		tmp[i] = _totupper(p[i]);
		if (i >= sizeof(tmp) / sizeof(TCHAR) - 1)
			break;
		i++;
	}
	tmp[i] = 0;
	for (int i = 0; debugregs[i]; i++) {
		if (!_tcsncmp(tmp, debugregs[i], _tcslen(debugregs[i]))) {
			(*c) += _tcslen(debugregs[i]);
			return i;
		}
	}
	return -1;
}

static uae_u32 returnregx(int regid)
{
	if (regid < BREAKPOINT_REG_PC)
		return regs.regs[regid];
	switch(regid)
	{
		case BREAKPOINT_REG_PC:
		return M68K_GETPC;
		case BREAKPOINT_REG_USP:
		return regs.usp;
		case BREAKPOINT_REG_MSP:
		return regs.msp;
		case BREAKPOINT_REG_ISP:
		return regs.isp;
		case BREAKPOINT_REG_VBR:
		return regs.vbr;
		case BREAKPOINT_REG_SR:
		MakeSR();
		return regs.sr;
		case BREAKPOINT_REG_CCR:
		MakeSR();
		return regs.sr & 31;
		case BREAKPOINT_REG_CACR:
		return regs.cacr;
		case BREAKPOINT_REG_CAAR:
		return regs.caar;
		case BREAKPOINT_REG_SFC:
		return regs.sfc;
		case BREAKPOINT_REG_DFC:
		return regs.dfc;
		case BREAKPOINT_REG_TC:
		if (currprefs.cpu_model == 68030)
			return tc_030;
		return regs.tcr;
		case BREAKPOINT_REG_ITT0:
		if (currprefs.cpu_model == 68030)
			return tt0_030;
		return regs.itt0;
		case BREAKPOINT_REG_ITT1:
		if (currprefs.cpu_model == 68030)
			return tt1_030;
		return regs.itt1;
		case BREAKPOINT_REG_DTT0:
		return regs.dtt0;
		case BREAKPOINT_REG_DTT1:
		return regs.dtt1;
		case BREAKPOINT_REG_BUSC:
		return regs.buscr;
		case BREAKPOINT_REG_PCR:
		return regs.fpcr;
	}
	return 0;
}

static int readregx (TCHAR **c, uae_u32 *valp)
{
	int i;
	uae_u32 addr;
	TCHAR *p = *c;
	TCHAR tmp[10], *tp;
	int extra = 0;

	addr = 0;
	i = 0;
	while (p[i]) {
		tmp[i] = _totupper (p[i]);
		if (i >= sizeof (tmp) / sizeof (TCHAR) - 1)
			break;
		i++;
	}
	tmp[i] = 0;
	tp = tmp;
	if (_totupper (tmp[0]) == 'R') {
		tp = tmp + 1;
		extra = 1;
	}
	if (!_tcsncmp (tp, _T("USP"), 3)) {
		addr = regs.usp;
		(*c) += 3;
	} else if (!_tcsncmp (tp, _T("VBR"), 3)) {
		addr = regs.vbr;
		(*c) += 3;
	} else if (!_tcsncmp (tp, _T("MSP"), 3)) {
		addr = regs.msp;
		(*c) += 3;
	} else if (!_tcsncmp (tp, _T("ISP"), 3)) {
		addr = regs.isp;
		(*c) += 3;
	} else if (!_tcsncmp (tp, _T("PC"), 2)) {
		addr = regs.pc;
		(*c) += 2;
	} else if (tp[0] == 'A' || tp[0] == 'D') {
		int reg = 0;
		if (tp[0] == 'A')
			reg += 8;
		reg += tp[1] - '0';
		if (reg < 0 || reg > 15)
			return 0;
		addr = regs.regs[reg];
		(*c) += 2;
	} else {
		return 0;
	}
	*valp = addr;
	(*c) += extra;
	return 1;
}

static bool readbinx (TCHAR **c, uae_u32 *valp)
{
	uae_u32 val = 0;
	bool first = true;

	ignore_ws (c);
	for (;;) {
		TCHAR nc = **c;
		if (nc != '1' && nc != '0') {
			if (first)
				return false;
			break;
		}
		first = false;
		(*c)++;
		val <<= 1;
		if (nc == '1')
			val |= 1;
	}
	*valp = val;
	return true;
}

static bool readhexx (TCHAR **c, uae_u32 *valp)
{
	uae_u32 val = 0;
	TCHAR nc;

	ignore_ws (c);
	if (!isxdigit (peekchar (c)))
		return false;
	while (isxdigit (nc = **c)) {
		(*c)++;
		val *= 16;
		nc = _totupper (nc);
		if (isdigit (nc)) {
			val += nc - '0';
		} else {
			val += nc - 'A' + 10;
		}
	}
	*valp = val;
	return true;
}

static bool readintx (TCHAR **c, uae_u32 *valp)
{
	uae_u32 val = 0;
	TCHAR nc;
	int negative = 0;

	ignore_ws (c);
	if (**c == '-')
		negative = 1, (*c)++;
	if (!isdigit (peekchar (c)))
		return false;
	while (isdigit (nc = **c)) {
		(*c)++;
		val *= 10;
		val += nc - '0';
	}
	*valp = val * (negative ? -1 : 1);
	return true;
}

static int checkvaltype2 (TCHAR **c, uae_u32 *val, TCHAR def)
{
	TCHAR nc;

	ignore_ws (c);
	nc = _totupper (**c);
	if (nc == '!') {
		(*c)++;
		return readintx (c, val) ? 1 : 0;
	}
	if (nc == '$') {
		(*c)++;
		return  readhexx (c, val) ? 1 : 0;
	}
	if (nc == '0' && _totupper ((*c)[1]) == 'X') {
		(*c)+= 2;
		return  readhexx (c, val) ? 1 : 0;
	}
	if (nc == '%') {
		(*c)++;
		return readbinx (c, val) ? 1: 0;
	}
	if (nc >= 'A' && nc <= 'Z' && nc != 'A' && nc != 'D') {
		if (readregx (c, val))
			return 1;
	}
	TCHAR name[256];
	name[0] = 0;
	for (int i = 0; i < sizeof name / sizeof(TCHAR) - 1; i++) {
		nc = (*c)[i];
		if (nc == 0 || nc == ' ')
			break;
		name[i] = nc;
		name[i + 1] = 0;
	}
	if (name[0]) {
		TCHAR *np = name;
		if (*np == '#')
			np++;
		if (debugmem_get_symbol_value(np, val)) {
			(*c) += _tcslen(name);
			return 1;
		}
	}
	if (def == '!') {
		return readintx (c, val) ? -1 : 0;
	} else if (def == '$') {
		return readhexx (c, val) ? -1 : 0;
	} else if (def == '%') {
		return readbinx (c, val) ? -1 : 0;
	}
	return 0;
}

static int readsize (int val, TCHAR **c)
{
	TCHAR cc = _totupper (readchar(c));
	if (cc == 'B')
		return 1;
	if (cc == 'W')
		return 2;
	if (cc == '3')
		return 3;
	if (cc == 'L')
		return 4;
	return 0;
}

static int checkvaltype (TCHAR **cp, uae_u32 *val, int *size, TCHAR def)
{
	TCHAR form[256], *p;
	bool gotop = false;
	double out;

	form[0] = 0;
	if (size)
		*size = 0;
	p = form;
	for (;;) {
		uae_u32 v;
		if (!checkvaltype2 (cp, &v, def))
			return 0;
		*val = v;
		// stupid but works!
		_stprintf(p, _T("%u"), v);
		p += _tcslen (p);
		if (peekchar (cp) == '.') {
			readchar (cp);
			if (size)
				*size = readsize (v, cp);
		}
		if (!isoperator (cp))
			break;
		gotop = true;
		*p++= readchar (cp);
		*p = 0;
	}
	if (!gotop) {
		if (size && *size == 0) {
			uae_s32 v = (uae_s32)(*val);
			if (v > 65535 || v < -32767) {
				*size = 4;
			} else if (v > 255 || v < -127) {
				*size = 2;
			} else {
				*size = 1;
			}
		}
		return 1;
	}
	if (calc (form, &out)) {
		*val = (uae_u32)out;
		if (size && *size == 0) {
			uae_s32 v = (uae_s32)(*val);
			if (v > 255 || v < -127) {
				*size = 2;
			} else if (v > 65535 || v < -32767) {
				*size = 4;
			} else {
				*size = 1;
			}
		}
		return 1;
	}
	return 0;
}


static uae_u32 readnum (TCHAR **c, int *size, TCHAR def)
{
	uae_u32 val;
	if (checkvaltype (c, &val, size, def))
		return val;
	return 0;
}

static uae_u32 readint (TCHAR **c)
{
	int size;
	return readnum (c, &size, '!');
}
static uae_u32 readhex (TCHAR **c)
{
	int size;
	return readnum (c, &size, '$');
}
static uae_u32 readbin (TCHAR **c)
{
	int size;
	return readnum (c, &size, '%');
}
static uae_u32 readint (TCHAR **c, int *size)
{
	return readnum (c, size, '!');
}
static uae_u32 readhex (TCHAR **c, int *size)
{
	return readnum (c, size, '$');
}

static int next_string (TCHAR **c, TCHAR *out, int max, int forceupper)
{
	TCHAR *p = out;
	int startmarker = 0;

	if (**c == '\"') {
		startmarker = 1;
		(*c)++;
	}
	*p = 0;
	while (**c != 0) {
		if (**c == '\"' && startmarker)
			break;
		if (**c == 32 && !startmarker) {
			ignore_ws (c);
			break;
		}
		*p = next_char (c);
		if (forceupper)
			*p = _totupper(*p);
		*++p = 0;
		max--;
		if (max <= 1)
			break;
	}
	return _tcslen (out);
}

static void converter (TCHAR **c)
{
	uae_u32 v = readint (c);
	TCHAR s[100];
	int i;

	for (i = 0; i < 32; i++)
		s[i] = (v & (1 << (31 - i))) ? '1' : '0';
	s[i] = 0;
	console_out_f (_T("0x%08X = %%%s = %u = %d\n"), v, s, v, (uae_s32)v);
}

int notinrom (void)
{
	uaecptr pc = munge24 (m68k_getpc ());
	if (pc < 0x00e00000 || pc > 0x00ffffff)
		return 1;
	return 0;
}

static uae_u32 lastaddr (void)
{
	int lastbank = currprefs.address_space_24 ? 255 : 65535;

	for (int i = lastbank; i >= 0; i--) {
		addrbank *ab = get_mem_bank_real(i << 16);
		if (ab->baseaddr && (ab->flags & ABFLAG_RAM)) {
			return (i + 1) << 16;
		}
	}
	return 0;
}

static uaecptr nextaddr (uaecptr addr, uaecptr last, uaecptr *endp, bool verbose)
{
	addrbank *ab;
	int lastbank = currprefs.address_space_24 ? 255 : 65535;

	if (addr != 0xffffffff) {
		addrbank *ab2 = get_mem_bank_real(addr);
		addr++;
		ab = get_mem_bank_real(addr);
		if (ab->baseaddr && (ab->flags & ABFLAG_RAM) && ab == ab2)
			return addr;
	} else {
		addr = 0;
	}

	while (addr < (lastbank << 16)) {
		ab = get_mem_bank_real(addr);
		if (ab->baseaddr && (ab->flags & ABFLAG_RAM))
			break;
		addr += 65536;
	}
	if (addr >= (lastbank << 16)) {
		if (endp)
			*endp = 0xffffffff;
		return 0xffffffff;
	}

	uaecptr start = addr;

	while (addr <= (lastbank << 16)) {
		addrbank *ab2 = get_mem_bank_real(addr);
		if ((last && last != 0xffffffff && addr >= last) || !ab2->baseaddr || !(ab2->flags & ABFLAG_RAM) || ab != ab2) {
			if (endp)
				*endp = addr;
			break;
		}
		addr += 65536;
	}

	if (verbose) {
		console_out_f(_T("Scanning.. %08x - %08x (%s)\n"), start, addr, get_mem_bank(start).name);
	}

	return start;
}

uaecptr dumpmem2 (uaecptr addr, TCHAR *out, int osize)
{
	int i, cols = 8;
	int nonsafe = 0;

	if (osize <= (9 + cols * 5 + 1 + 2 * cols))
		return addr;
	_stprintf (out, _T("%08X "), addr);
	for (i = 0; i < cols; i++) {
		uae_u8 b1, b2;
		b1 = b2 = 0;
		if (debug_safe_addr (addr, 1)) {
			b1 = get_byte_debug (addr + 0);
			b2 = get_byte_debug (addr + 1);
			_stprintf (out + 9 + i * 5, _T("%02X%02X "), b1, b2);
			out[9 + cols * 5 + 1 + i * 2 + 0] = b1 >= 32 && b1 < 127 ? b1 : '.';
			out[9 + cols * 5 + 1 + i * 2 + 1] = b2 >= 32 && b2 < 127 ? b2 : '.';
		} else {
			nonsafe++;
			_tcscpy (out + 9 + i * 5, _T("**** "));
			out[9 + cols * 5 + 1 + i * 2 + 0] = '*';
			out[9 + cols * 5 + 1 + i * 2 + 1] = '*';
		}
		addr += 2;
	}
	out[9 + cols * 5] = ' ';
	out[9 + cols * 5 + 1 + 2 * cols] = 0;
	if (nonsafe == cols) {
		addrbank *ab = &get_mem_bank (addr);
		if (ab->name)
			memcpy (out + (9 + 4 + 1) * sizeof (TCHAR), ab->name, _tcslen (ab->name) * sizeof (TCHAR));
	}
	return addr;
}

static void dumpmem (uaecptr addr, uaecptr *nxmem, int lines)
{
	TCHAR line[MAX_LINEWIDTH + 1];
	for (;lines--;) {
		addr = dumpmem2 (addr, line, sizeof(line));
		debug_out (_T("%s"), line);
		if (!debug_out (_T("\n")))
			break;
	}
	*nxmem = addr;
}

static void dump_custom_regs(bool aga, bool ext)
{
	int len;
	uae_u8 *p1, *p2, *p3, *p4;
	TCHAR extra1[256], extra2[256];

	extra1[0] = 0;
	extra2[0] = 0;
	if (aga) {
		dump_aga_custom();
		return;
	}

	p1 = p2 = save_custom (&len, 0, 1);
	p1 += 4; // skip chipset type
	for (int i = 0; i < 4; i++) {
		p4 = p1 + 0xa0 + i * 16;
		p3 = save_audio (i, &len, 0);
		p4[0] = p3[12];
		p4[1] = p3[13];
		p4[2] = p3[14];
		p4[3] = p3[15];
		p4[4] = p3[4];
		p4[5] = p3[5];
		p4[6] = p3[8];
		p4[7] = p3[9];
		p4[8] = 0;
		p4[9] = p3[1];
		p4[10] = p3[10];
		p4[11] = p3[11];
		free (p3);
	}
	int total = 0;
	int i = 0;
	while (custd[i].name) {
		if (!(custd[i].special & CD_NONE))
			total++;
		i++;
	}
	int cnt1 = 0;
	int cnt2 = 0;
	i = 0;
	while (i < total / 2 + 1) {
		for (;;) {
			cnt2++;
			if (!(custd[cnt2].special & CD_NONE))
				break;
		}
		i++;
	}
	for (int i = 0; i < total / 2 + 1; i++) {
		uae_u16 v1, v2;
		int addr1, addr2;
		addr1 = custd[cnt1].adr & 0x1ff;
		addr2 = custd[cnt2].adr & 0x1ff;
		v1 = (p1[addr1 + 0] << 8) | p1[addr1 + 1];
		v2 = (p1[addr2 + 0] << 8) | p1[addr2 + 1];
		if (ext) {
			struct custom_store *cs;
			cs = &custom_storage[addr1 >> 1];
			_stprintf(extra1, _T("\t%04X %08X %s"), cs->value, cs->pc & ~1, (cs->pc & 1) ? _T("COP") : _T("CPU"));
			cs = &custom_storage[addr2 >> 1];
			_stprintf(extra2, _T("\t%04X %08X %s"), cs->value, cs->pc & ~1, (cs->pc & 1) ? _T("COP") : _T("CPU"));
		}
		console_out_f (_T("%03X %s\t%04X%s\t%03X %s\t%04X%s\n"),
			addr1, custd[cnt1].name, v1, extra1,
			addr2, custd[cnt2].name, v2, extra2);
		for (;;) {
			cnt1++;
			if (!(custd[cnt1].special & CD_NONE))
				break;
		}
		for (;;) {
			cnt2++;
			if (!(custd[cnt2].special & CD_NONE))
				break;
		}
	}
	xfree(p2);
}

static void dump_vectors (uaecptr addr)
{
	int i = 0, j = 0;

	if (addr == 0xffffffff)
		addr = regs.vbr;

	while (int_labels[i].name || trap_labels[j].name) {
		if (int_labels[i].name) {
			console_out_f (_T("$%08X %02d: %12s $%08X  "), int_labels[i].adr + addr, int_labels[i].adr / 4,
				int_labels[i].name, get_long_debug (int_labels[i].adr + addr));
			i++;
		}
		if (trap_labels[j].name) {
			console_out_f (_T("$%08X %02d: %12s $%08X"), trap_labels[j].adr + addr, trap_labels[j].adr / 4,
				trap_labels[j].name, get_long_debug (trap_labels[j].adr + addr));
			j++;
		}
		console_out (_T("\n"));
	}
}

static void disassemble_wait (FILE *file, unsigned long insn)
{
	int vp, hp, ve, he, bfd, v_mask, h_mask;
	int doout = 0;

	vp = (insn & 0xff000000) >> 24;
	hp = (insn & 0x00fe0000) >> 16;
	ve = (insn & 0x00007f00) >> 8;
	he = (insn & 0x000000fe);
	bfd = (insn & 0x00008000) >> 15;

	/* bit15 can never be masked out*/
	v_mask = vp & (ve | 0x80);
	h_mask = hp & he;
	if (v_mask > 0) {
		doout = 1;
		console_out (_T("vpos "));
		if (ve != 0x7f) {
			console_out_f (_T("& 0x%02x "), ve);
		}
		console_out_f (_T(">= 0x%02x"), v_mask);
	}
	if (he > 0) {
		if (v_mask > 0) {
			console_out (_T(" and"));
		}
		console_out (_T(" hpos "));
		if (he != 0xfe) {
			console_out_f (_T("& 0x%02x "), he);
		}
		console_out_f (_T(">= 0x%02x"), h_mask);
	} else {
		if (doout)
			console_out (_T(", "));
		console_out (_T(", ignore horizontal"));
	}

	console_out_f (_T("\n                        \t;  VP %02x, VE %02x; HP %02x, HE %02x; BFD %d\n"),
		vp, ve, hp, he, bfd);
}

#define NR_COPPER_RECORDS 100000
/* Record copper activity for the debugger.  */
struct cop_rec
{
	uae_u16 w1, w2;
	int hpos, vpos;
	int bhpos, bvpos;
	uaecptr addr, nextaddr;
};
static struct cop_rec *cop_record[2];
static int nr_cop_records[2], curr_cop_set, selected_cop_set;

#define NR_DMA_REC_HPOS 256
#define NR_DMA_REC_VPOS 1000
static struct dma_rec *dma_record[2];
static int dma_record_toggle, dma_record_frame[2];

void record_dma_reset (void)
{
	int v, h;
	struct dma_rec *dr, *dr2;

	if (!dma_record[0])
		return;
	dma_record_toggle ^= 1;
	dr = dma_record[dma_record_toggle];
	for (v = 0; v < NR_DMA_REC_VPOS; v++) {
		for (h = 0; h < NR_DMA_REC_HPOS; h++) {
			dr2 = &dr[v * NR_DMA_REC_HPOS + h];
			memset (dr2, 0, sizeof (struct dma_rec));
			dr2->reg = 0xffff;
			dr2->cf_reg = 0xffff;
			dr2->addr = 0xffffffff;
		}
	}
}

void record_copper_reset (void)
{
	/* Start a new set of copper records.  */
	curr_cop_set ^= 1;
	nr_cop_records[curr_cop_set] = 0;
}

STATIC_INLINE uae_u32 ledcolor (uae_u32 c, uae_u32 *rc, uae_u32 *gc, uae_u32 *bc, uae_u32 *a)
{
	uae_u32 v = rc[(c >> 16) & 0xff] | gc[(c >> 8) & 0xff] | bc[(c >> 0) & 0xff];
	if (a)
		v |= a[255 - ((c >> 24) & 0xff)];
	return v;
}

STATIC_INLINE void putpixel (uae_u8 *buf, int bpp, int x, xcolnr c8)
{
	if (x <= 0)
		return;

	switch (bpp) {
	case 1:
		buf[x] = (uae_u8)c8;
		break;
	case 2:
		{
			uae_u16 *p = (uae_u16*)buf + x;
			*p = (uae_u16)c8;
			break;
		}
	case 3:
		/* no 24 bit yet */
		break;
	case 4:
		{
			uae_u32 *p = (uae_u32*)buf + x;
			*p = c8;
			break;
		}
	}
}

#define lc(x) ledcolor (x, xredcolors, xgreencolors, xbluecolors, NULL)
#define DMARECORD_SUBITEMS 8
struct dmadebug
{
	uae_u32 l[DMARECORD_SUBITEMS];
	uae_u8 r, g, b;
	bool enabled;
	int max;
	const TCHAR *name;
};

static uae_u32 intlevc[] = { 0x000000, 0x444444, 0x008800, 0xffff00, 0x000088, 0x880000, 0xff0000, 0xffffff };
static struct dmadebug debug_colors[DMARECORD_MAX];
static bool debug_colors_set;

static void set_dbg_color(int index, int extra, uae_u8 r, uae_u8 g, uae_u8 b, int max, const TCHAR *name)
{
	if (extra == 0) {
		debug_colors[index].r = r;
		debug_colors[index].g = g;
		debug_colors[index].b = b;
		debug_colors[index].enabled = true;
	}
	if (name != NULL)
		debug_colors[index].name = name;
	if (max > 0)
		debug_colors[index].max = max;
	if (extra >= 0) {
		debug_colors[index].l[extra] = lc((r << 16) | (g << 8) | (b << 0));
	} else {
		for (int i = 0; i < DMARECORD_MAX; i++) {
			debug_colors[index].l[i] = lc((r << 16) | (g << 8) | (b << 0));
		}
	}
}

static void set_debug_colors(void)
{
	if (debug_colors_set)
		return;
	debug_colors_set = true;
	set_dbg_color(0,						0, 0x22, 0x22, 0x22, 1, _T("-"));
	set_dbg_color(DMARECORD_REFRESH,		0, 0x44, 0x44, 0x44, 4, _T("Refresh"));
	set_dbg_color(DMARECORD_CPU,			0, 0xa2, 0x53, 0x42, 2, _T("CPU")); // code
	set_dbg_color(DMARECORD_COPPER,			0, 0xee, 0xee, 0x00, 3, _T("Copper"));
	set_dbg_color(DMARECORD_AUDIO,			0, 0xff, 0x00, 0x00, 4, _T("Audio"));
	set_dbg_color(DMARECORD_BLITTER,		0, 0x00, 0x88, 0x88, 2, _T("Blitter"));
	set_dbg_color(DMARECORD_BITPLANE,		0, 0x00, 0x00, 0xff, 8, _T("Bitplane"));
	set_dbg_color(DMARECORD_SPRITE,			0, 0xff, 0x00, 0xff, 8, _T("Sprite"));
	set_dbg_color(DMARECORD_DISK,			0, 0xff, 0xff, 0xff, 3, _T("Disk"));
 	set_dbg_color(DMARECORD_CONFLICT,		0, 0xff, 0xb8, 0x40, 0, _T("Conflict"));

	for (int i = 0; i < DMARECORD_MAX; i++) {
		for (int j = 1; j < DMARECORD_SUBITEMS; j++) {
			debug_colors[i].l[j] = debug_colors[i].l[0];
		}
	}

	set_dbg_color(DMARECORD_CPU,			1, 0xad, 0x98, 0xd6, 0, NULL); // data
	set_dbg_color(DMARECORD_COPPER,			1, 0xaa, 0xaa, 0x22, 0, NULL); // wait
	set_dbg_color(DMARECORD_COPPER,			2, 0x66, 0x66, 0x44, 0, NULL); // special
	set_dbg_color(DMARECORD_BLITTER,		1, 0x00, 0x88, 0xff, 0, NULL); // fill
	set_dbg_color(DMARECORD_BLITTER,		2, 0x00, 0xff, 0x00, 0, NULL); // line
}

static void debug_draw_cycles (uae_u8 *buf, int bpp, int line, int width, int height, uae_u32 *xredcolors, uae_u32 *xgreencolors, uae_u32 *xbluescolors)
{
	int y, x, xx, dx, xplus, yplus;
	struct dma_rec *dr;
	int t;

	if (debug_dma >= 4)
		yplus = 2;
	else
		yplus = 1;
	if (debug_dma >= 5)
		xplus = 3;
	else if (debug_dma >= 3)
		xplus = 2;
	else
		xplus = 1;

	t = dma_record_toggle ^ 1;
	y = line / yplus;
	if (yplus < 2)
		y -= 8;

	if (y < 0)
		return;
	if (y > maxvpos)
		return;
	if (y >= height)
		return;

	dx = width - xplus * ((maxhpos + 1) & ~1) - 16;

	uae_s8 intlev = 0;
	for (x = 0; x < maxhpos; x++) {
		uae_u32 c = debug_colors[0].l[0];
		xx = x * xplus + dx;
		dr = &dma_record[t][y * NR_DMA_REC_HPOS + x];
		if (dr->reg != 0xffff && debug_colors[dr->type].enabled) {
			c = debug_colors[dr->type].l[dr->extra];
		}
		if (dr->intlev > intlev)
			intlev = dr->intlev;
		putpixel (buf, bpp, xx + 4, c);
		if (xplus)
			putpixel (buf, bpp, xx + 4 + 1, c);
		if (debug_dma >= 6)
			putpixel (buf, bpp, xx + 4 + 2, c);
	}
	putpixel (buf, bpp, dx + 0, 0);
	putpixel (buf, bpp, dx + 1, lc(intlevc[intlev]));
	putpixel (buf, bpp, dx + 2, lc(intlevc[intlev]));
	putpixel (buf, bpp, dx + 3, 0);
}

#define HEATMAP_WIDTH 256
#define HEATMAP_HEIGHT 256
#define HEATMAP_COUNT 32
#define HEATMAP_DIV 8
static const int max_heatmap = 16 * 1048576; // 16M
static uae_u32 *heatmap_debug_colors;

static struct memory_heatmap *heatmap;
struct memory_heatmap
{
	uae_u32 mask;
	uae_u32 cpucnt;
	uae_u16 cnt;
	uae_u16 type, extra;
};

static void debug_draw_heatmap(uae_u8 *buf, int bpp, int line, int width, int height, uae_u32 *xredcolors, uae_u32 *xgreencolors, uae_u32 *xbluescolors)
{
	struct memory_heatmap *mht = heatmap;
	int dx = 16;
	int y = line;

	if (y < 0 || y >= HEATMAP_HEIGHT)
		return;

	mht += y * HEATMAP_WIDTH;

	for (int x = 0; x < HEATMAP_WIDTH; x++) {
		uae_u32 c = heatmap_debug_colors[mht->cnt * DMARECORD_MAX + mht->type];
		//c = heatmap_debug_colors[(HEATMAP_COUNT - 1) * DMARECORD_MAX + DMARECORD_CPU_I];
		int xx = x + dx;
		putpixel(buf, bpp, xx, c);
		if (mht->cnt > 0)
			mht->cnt--;
		mht++;
	}
}

void debug_draw(uae_u8 *buf, int bpp, int line, int width, int height, uae_u32 *xredcolors, uae_u32 *xgreencolors, uae_u32 *xbluescolors)
{
	if (!heatmap_debug_colors) {
		heatmap_debug_colors = xcalloc(uae_u32, DMARECORD_MAX * HEATMAP_COUNT);
		set_debug_colors();
		for (int i = 0; i < HEATMAP_COUNT; i++) {
			uae_u32 *cp = heatmap_debug_colors + i * DMARECORD_MAX;
			for (int j = 0; j < DMARECORD_MAX; j++) {
				uae_u8 r = debug_colors[j].r;
				uae_u8 g = debug_colors[j].g;
				uae_u8 b = debug_colors[j].b;
				r = r * i / HEATMAP_COUNT;
				g = g * i / HEATMAP_COUNT;
				b = b * i / HEATMAP_COUNT;
				cp[j] = lc((r << 16) | (g << 8) | (b << 0));
			}
		}
	}

	if (heatmap) {
		debug_draw_heatmap(buf, bpp, line, width, height, xredcolors, xgreencolors, xbluecolors);
	} else if (dma_record[0]) {
		debug_draw_cycles(buf, bpp, line, width, height, xredcolors, xgreencolors, xbluecolors);
	}
}

struct heatmapstore
{
	TCHAR *s;
	double v;
};

static void heatmap_stats(TCHAR **c)
{
	int range = 95;
	int maxlines = 30;
	double max;
	int maxcnt;
	uae_u32 mask = MW_MASK_CPU_I;
	const TCHAR *maskname = NULL;

	if (more_params(c)) {
		if (**c == 'c' && peek_next_char(c) == 0) {
			for (int i = 0; i < max_heatmap / HEATMAP_DIV; i++) {
				struct memory_heatmap *hm = &heatmap[i];
				memset(hm, 0, sizeof(struct memory_heatmap));
			}
			console_out(_T("heatmap data cleared\n"));
			return;
		}
		if (!isdigit(peek_next_char(c))) {
			TCHAR str[100];
			if (next_string(c, str, sizeof str / sizeof (TCHAR), true)) {
				for (int j = 0; memwatch_access_masks[j].mask; j++) {
					if (!_tcsicmp(str, memwatch_access_masks[j].name)) {
						mask = memwatch_access_masks[j].mask;
						maskname = memwatch_access_masks[j].name;
						console_out_f(_T("Mask %08x Name %s\n"), mask, maskname);
						break;
					}
				}
			}
			if (more_params(c)) {
				maxlines = readint(c);
			}
		} else {
			range = readint(c);
			if (more_params(c)) {
				maxlines = readint(c);
			}
		}
	}
	if (maxlines <= 0)
		maxlines = 10000;

	if (mask != MW_MASK_CPU_I) {

		int found = -1;
		int firstaddress = 0;
		for (int lines = 0; lines < maxlines; lines++) {

			for (; firstaddress < max_heatmap / HEATMAP_DIV; firstaddress++) {
				struct memory_heatmap *hm = &heatmap[firstaddress];
				if (hm->mask & mask)
					break;
			}

			if (firstaddress == max_heatmap / HEATMAP_DIV)
				return;

			int lastaddress;
			for (lastaddress = firstaddress; lastaddress < max_heatmap / HEATMAP_DIV; lastaddress++) {
				struct memory_heatmap *hm = &heatmap[lastaddress];
				if (!(hm->mask & mask))
					break;
			}
			lastaddress--;

			console_out_f(_T("%03d: %08x - %08x %08x (%d) %s\n"), 
				lines,
				firstaddress * HEATMAP_DIV, lastaddress * HEATMAP_DIV + HEATMAP_DIV - 1,
				lastaddress * HEATMAP_DIV - firstaddress * HEATMAP_DIV + HEATMAP_DIV - 1,
				lastaddress * HEATMAP_DIV - firstaddress * HEATMAP_DIV + HEATMAP_DIV - 1, 
				maskname);

			firstaddress = lastaddress + 1;
		}

	} else {
#define MAX_HEATMAP_LINES 1000
		struct heatmapstore linestore[MAX_HEATMAP_LINES] = { 0 };
		int storecnt = 0;
		uae_u32 maxlimit = 0xffffffff;

		max = 0;
		maxcnt = 0;
		for (int i = 0; i < max_heatmap / HEATMAP_DIV; i++) {
			struct memory_heatmap *hm = &heatmap[i];
			if (hm->cpucnt > 0) {
				max += hm->cpucnt;
				maxcnt++;
			}
		}

		if (!maxcnt) {
			console_out(_T("No CPU accesses found\n"));
			return;
		}

		for (int lines = 0; lines < maxlines; lines++) {

			int found = -1;
			int foundcnt = 0;

			for (int i = 0; i < max_heatmap / HEATMAP_DIV; i++) {
				struct memory_heatmap *hm = &heatmap[i];
				if (hm->cpucnt > 0 && hm->cpucnt > foundcnt && hm->cpucnt < maxlimit) {
					foundcnt = hm->cpucnt;
					found = i;
				}
			}
			if (found < 0)
				break;

			int totalcnt = 0;
			int cntrange = foundcnt * range / 100;
			if (cntrange <= 0)
				cntrange = 1;

			int lastaddress;
			for (lastaddress = found; lastaddress < max_heatmap / HEATMAP_DIV; lastaddress++) {
				struct memory_heatmap *hm = &heatmap[lastaddress];
				if (hm->cpucnt == 0 || hm->cpucnt < cntrange || hm->cpucnt >= maxlimit)
					break;
				totalcnt += hm->cpucnt;
			}
			lastaddress--;

			int firstaddress;
			for (firstaddress = found - 1; firstaddress >= 0; firstaddress--) {
				struct memory_heatmap *hm = &heatmap[firstaddress];
				if (hm->cpucnt == 0 || hm->cpucnt < cntrange || hm->cpucnt >= maxlimit)
					break;
				totalcnt += hm->cpucnt;
			}
			firstaddress--;

			firstaddress *= HEATMAP_DIV;
			lastaddress *= HEATMAP_DIV;

			TCHAR tmp[100];
			double pct = totalcnt / max * 100.0;
			_stprintf(tmp, _T("%03d: %08x - %08x %08x (%d) %.5f%%\n"), lines + 1,
				firstaddress, lastaddress + HEATMAP_DIV - 1,
				lastaddress - firstaddress + HEATMAP_DIV - 1,
				lastaddress - firstaddress + HEATMAP_DIV - 1,
				pct);
			linestore[storecnt].s = my_strdup(tmp);
			linestore[storecnt].v = pct;

			storecnt++;
			if (storecnt >= MAX_HEATMAP_LINES)
				break;

			maxlimit = foundcnt;
		}

		for (int lines1 = 0; lines1 < storecnt; lines1++) {
			for (int lines2 = lines1 + 1; lines2 < storecnt; lines2++) {
				if (linestore[lines1].v < linestore[lines2].v) {
					struct heatmapstore hms;
					memcpy(&hms, &linestore[lines1], sizeof(struct heatmapstore));
					memcpy(&linestore[lines1], &linestore[lines2], sizeof(struct heatmapstore));
					memcpy(&linestore[lines2], &hms, sizeof(struct heatmapstore));
				}
			}
		}
		for (int lines1 = 0; lines1 < storecnt; lines1++) {
			console_out(linestore[lines1].s);
			xfree(linestore[lines1].s);
		}

	}

}

static void free_heatmap(void)
{
	xfree(heatmap);
	heatmap = NULL;
	debug_heatmap = 0;
}

static void init_heatmap(void)
{
	if (!heatmap)
		heatmap = xcalloc(struct memory_heatmap, max_heatmap / HEATMAP_DIV);
}

static void memwatch_heatmap (uaecptr addr, int rwi, int size, uae_u32 accessmask)
{
	if (addr >= max_heatmap || !heatmap)
		return;
	struct memory_heatmap *hm = &heatmap[addr / HEATMAP_DIV];
	if (accessmask & MW_MASK_CPU_I) {
		hm->cpucnt++;
	}
	hm->cnt = HEATMAP_COUNT - 1;
	int type = 0;
	int extra = 0;
	for (int i = 0; i < 32; i++) {
		if (accessmask & (1 << i)) {
			switch (1 << i)
			{
				case MW_MASK_BPL_0:
				case MW_MASK_BPL_1:
				case MW_MASK_BPL_2:
				case MW_MASK_BPL_3:
				case MW_MASK_BPL_4:
				case MW_MASK_BPL_5:
				case MW_MASK_BPL_6:
				case MW_MASK_BPL_7:
				type = DMARECORD_BITPLANE;
				break;
				case MW_MASK_AUDIO_0:
				case MW_MASK_AUDIO_1:
				case MW_MASK_AUDIO_2:
				case MW_MASK_AUDIO_3:
				type = DMARECORD_AUDIO;
				break;
				case MW_MASK_BLITTER_A:
				case MW_MASK_BLITTER_B:
				case MW_MASK_BLITTER_C:
				case MW_MASK_BLITTER_D_N:
				case MW_MASK_BLITTER_D_F:
				case MW_MASK_BLITTER_D_L:
				type = DMARECORD_BLITTER;
				break;
				case MW_MASK_COPPER:
				type = DMARECORD_COPPER;
				break;
				case MW_MASK_DISK:
				type = DMARECORD_DISK;
				break;
				case MW_MASK_CPU_I:
				type = DMARECORD_CPU;
				break;
				case MW_MASK_CPU_D_R:
				case MW_MASK_CPU_D_W:
				type = DMARECORD_CPU;
				extra = 1;
				break;
			}
		}
	}
	hm->type = type;
	hm->extra = extra;
	hm->mask |= accessmask;
}

void record_dma_event (uae_u32 evt, int hpos, int vpos)
{
	struct dma_rec *dr;

	if (!dma_record[0])
		return;
	if (hpos >= NR_DMA_REC_HPOS || vpos >= NR_DMA_REC_VPOS)
		return;
	dr = &dma_record[dma_record_toggle][vpos * NR_DMA_REC_HPOS + hpos];
	dr->evt |= evt;
}

void record_dma_replace(int hpos, int vpos, int type, int extra)
{
	struct dma_rec *dr;
	if (!dma_record[0])
		return;
	if (hpos >= NR_DMA_REC_HPOS || vpos >= NR_DMA_REC_VPOS)
		return;
	dr = &dma_record[dma_record_toggle][vpos * NR_DMA_REC_HPOS + hpos];
	if (dr->reg == 0xffff) {
		write_log(_T("DMA record replace without old data!\n"));
		return;
	}
	if (dr->type != type) {
		write_log(_T("DMA record replace type change %d -> %d!\n"), dr->type, type);
		return;
	}
	dr->extra = extra;
}

static void dma_conflict(int vpos, int hpos, struct dma_rec *dr, int reg, bool write)
{
	write_log(_T("DMA conflict %c: v=%d h=%d OREG=%04X NREG=%04X\n"), write ? 'W' : 'R', vpos, hpos, dr->reg, reg);
}

void record_dma_write(uae_u16 reg, uae_u32 dat, uae_u32 addr, int hpos, int vpos, int type, int extra)
{
	struct dma_rec *dr;

	if (!dma_record[0]) {
		dma_record[0] = xmalloc(struct dma_rec, NR_DMA_REC_HPOS * NR_DMA_REC_VPOS);
		dma_record[1] = xmalloc(struct dma_rec, NR_DMA_REC_HPOS * NR_DMA_REC_VPOS);
		dma_record_toggle = 0;
		record_dma_reset();
		dma_record_frame[0] = -1;
		dma_record_frame[1] = -1;
	}

	if (hpos >= NR_DMA_REC_HPOS || vpos >= NR_DMA_REC_VPOS)
		return;

	dr = &dma_record[dma_record_toggle][vpos * NR_DMA_REC_HPOS + hpos];
	dma_record_frame[dma_record_toggle] = timeframes;
	if (dr->reg != 0xffff) {
		dr->cf_reg = reg;
		dr->cf_dat = dat;
		dr->cf_addr = addr;
		dma_conflict(vpos, hpos, dr, reg, false);
		return;
	}
	dr->reg = reg;
	dr->dat = dat;
	dr->addr = addr;
	dr->type = type;
	dr->extra = extra;
	dr->intlev = regs.intmask;
	dr->size = 2;
	last_dma_rec = dr;
}
struct dma_rec *last_dma_rec;
void record_dma_read_value(uae_u32 v)
{
	if (last_dma_rec) {
		if (last_dma_rec->cf_reg != 0xffff) {
			last_dma_rec->cf_dat = v;
		} else {
			last_dma_rec->dat = v;
		}
		last_dma_rec->size = 2;
	}
}
void record_dma_read_value_wide(uae_u64 v, bool quad)
{
	if (last_dma_rec) {
		if (last_dma_rec->cf_reg != 0xffff) {
			last_dma_rec->cf_dat = v;
		} else {
			last_dma_rec->dat = v;
		}
		last_dma_rec->size = quad ? 8 : 4;
	}
}
void record_dma_read(uae_u16 reg, uae_u32 addr, int hpos, int vpos, int type, int extra)
{
	struct dma_rec *dr;

	if (!dma_record[0]) {
		dma_record[0] = xmalloc (struct dma_rec, NR_DMA_REC_HPOS * NR_DMA_REC_VPOS);
		dma_record[1] = xmalloc (struct dma_rec, NR_DMA_REC_HPOS * NR_DMA_REC_VPOS);
		dma_record_toggle = 0;
		record_dma_reset ();
		dma_record_frame[0] = -1;
		dma_record_frame[1] = -1;
	}

	if (hpos >= NR_DMA_REC_HPOS || vpos >= NR_DMA_REC_VPOS)
		return;

	dr = &dma_record[dma_record_toggle][vpos * NR_DMA_REC_HPOS + hpos];
	dma_record_frame[dma_record_toggle] = timeframes;
	if (dr->reg != 0xffff) {
		if (dr->reg != reg) {
			dma_conflict(vpos, hpos, dr, reg, false);
			dr->cf_reg = reg;
			dr->cf_addr = addr;
		}
		return;
	}
	dr->reg = reg;
	dr->dat = 0;
	dr->addr = addr;
	dr->type = type;
	dr->extra = extra;
	dr->intlev = regs.intmask;
	last_dma_rec = dr;
}


static bool get_record_dma_info(struct dma_rec *dr, int hpos, int vpos, uae_u32 cycles, TCHAR *l1, TCHAR *l2, TCHAR *l3, TCHAR *l4, TCHAR *l5)
{
	int longsize = dr->size;
	bool got = false;
	int r = dr->reg;
	int regsize = 3;
	const TCHAR *sr;
	int br = dr->extra & 7;
	int chcnt = -1;
	TCHAR srtext[10];
	bool extra64 = false;
	uae_u32 extraval;
	bool noval = false;

	if (l1)
		l1[0] = 0;
	if (l2)
		l2[0] = 0;
	if (l3)
		l3[0] = 0;
	if (l4)
		l4[0] = 0;
	if (l5)
		l5[0] = 0;

	if (dr->type != 0 || dr->reg != 0xffff || dr->evt)
		got = true;

	sr = _T("    ");
	if (dr->type == DMARECORD_COPPER) {
		if (dr->extra == 3)
			sr = _T("COPW");
		else
			sr = _T("COP ");
	} else if (dr->type == DMARECORD_BLITTER) {
		if (dr->extra & 0x20) {
			if (br == 0)
				sr = _T("BLL-A");
			if (br == 1)
				sr = _T("BLL-B");
			if (br == 2)
				sr = _T("BLL-C");
			if (br == 3)
				sr = _T("BLL-D");
		} else if (dr->extra & 0x10) {
			if (br == 0)
				sr = _T("BLF-A");
			if (br == 1)
				sr = _T("BLF-B");
			if (br == 2)
				sr = _T("BLF-C");
			if (br == 3)
				sr = _T("BLF-D");
		} else {
			if (br == 0)
				sr = _T("BLT-A");
			if (br == 1)
				sr = _T("BLT-B");
			if (br == 2)
				sr = _T("BLT-C");
			if (br == 3)
				sr = _T("BLT-D");
		}
		regsize = 2;
	} else if (dr->type == DMARECORD_REFRESH) {
		sr = _T("RFS");
		chcnt = br;
		noval = true;
	} else if (dr->type == DMARECORD_AUDIO) {
		sr = _T("AUD");
		chcnt = br;
	} else if (dr->type == DMARECORD_DISK) {
		sr = _T("DSK");
		chcnt = br;
	} else if (dr->type == DMARECORD_SPRITE) {
		sr = _T("SPR");
		chcnt = br;
	} else if (dr->type == DMARECORD_BITPLANE) {
		sr = _T("BPL");
		chcnt = br + 1;
	}
	if (dr->cf_reg != 0xffff) {
		_stprintf(srtext, _T("!%03x "), dr->cf_reg);
		chcnt = -1;
	} else {
		_tcscpy(srtext, sr);
	}
	_stprintf (l1, _T("[%02X %3d]"), hpos, hpos);
	if (l4) {
		_tcscpy (l4, _T("        "));
	}
	if (r != 0xffff) {
		if (r & 0x1000) {
			if ((r & 0x0100) == 0x0000)
				_tcscpy (l2, _T("CPU-R "));
			else if ((r & 0x0100) == 0x0100)
				_tcscpy (l2, _T("CPU-W "));
			if ((r & 0xff) == 4) {
				l2[5] = 'L';
				longsize = 4;
			}
			if ((r & 0xff) == 2)
				l2[5] = 'W';
			if ((r & 0xff) == 1)
				l2[5] = 'B';
		} else {
			if (chcnt >= 0) {
				if (regsize == 3)
					_stprintf(l2, _T("%3s%d %03X"), srtext, chcnt, r);
				else
					_stprintf(l2, _T("%4s%d %02X"), srtext, chcnt, r);
			} else {
				if (regsize == 3)
					_stprintf(l2, _T("%4s %03X"), srtext, r);
				else
					_stprintf(l2, _T("%5s %02X"), srtext, r);
			}
		}
		if (l3 && !noval) {
			uae_u64 v = dr->dat;
			if (longsize == 4) {
				_stprintf(l3, _T("%08X"), (uae_u32)v);
			} else if (longsize == 8) {
				_stprintf(l3, _T("%08X"), (uae_u32)(v >> 32));
				extra64 = true;
				extraval = (uae_u32)v;
			} else {
				_stprintf(l3, _T("    %04X"), (uae_u32)(v & 0xffff));
			}
		}
		if (l4 && dr->addr != 0xffffffff)
			_stprintf (l4, _T("%08X"), dr->addr & 0x00ffffff);
	} else {
		_tcscpy (l2, _T("        "));
		if (l3) {
			_tcscpy (l3, _T("        "));
		}
	}
	if (l3) {
		int cl2 = 0;
		if (dr->evt & DMA_EVENT_BLITFINALD)
			l3[cl2++] = 'D';
		if (dr->evt & DMA_EVENT_BLITSTARTFINISH)
			l3[cl2++] = 'B';
		if (dr->evt & DMA_EVENT_CPUBLITTERSTEAL)
			l3[cl2++] = 's';
		if (dr->evt & DMA_EVENT_CPUBLITTERSTOLEN)
			l3[cl2++] = 'S';
		if (dr->evt & DMA_EVENT_BLITIRQ)
			l3[cl2++] = 'b';
		if (dr->evt & DMA_EVENT_BPLFETCHUPDATE)
			l3[cl2++] = 'p';
		if (dr->evt & (DMA_EVENT_COPPERWAKE | DMA_EVENT_COPPERSKIP))
			l3[cl2++] = 'W';
		if (dr->evt & DMA_EVENT_COPPERSKIP)
			l3[cl2++] = 'S';
		if (dr->evt & DMA_EVENT_NOONEGETS) {
			l3[cl2++] = '#';
		} else if (dr->evt & DMA_EVENT_COPPERWANTED) {
			l3[cl2++] = 'c';
		}
		if (dr->evt & DMA_EVENT_CPUIRQ)
			l3[cl2++] = 'I';
		if (dr->evt & DMA_EVENT_INTREQ)
			l3[cl2++] = 'i';
		if (dr->evt & DMA_EVENT_SPECIAL)
			l3[cl2++] = 'X';
		if (dr->evt & DMA_EVENT_DDFSTRT)
			l3[cl2++] = '0';
		if (dr->evt & DMA_EVENT_DDFSTOP)
			l3[cl2++] = '1';
		if (dr->evt & DMA_EVENT_DDFSTOP2)
			l3[cl2++] = '2';

		if (dr->evt & DMA_EVENT_HDIWS) {
			l3[cl2++] = '(';
		}
		if (dr->evt & DMA_EVENT_HDIWE) {
			l3[cl2++] = ')';
		}

		if (dr->evt & DMA_EVENT_HBS) {
			l3[cl2++] = '[';
		}
		if (dr->evt & DMA_EVENT_HBE) {
			l3[cl2++] = ']';
		}
		if (dr->evt & DMA_EVENT_HSS) {
			l3[cl2++] = '{';
		}
		if (dr->evt & DMA_EVENT_HSE) {
			l3[cl2++] = '}';
		}

		if (dr->evt & (DMA_EVENT_VB | DMA_EVENT_VS | DMA_EVENT_LOL | DMA_EVENT_LOF | DMA_EVENT_VDIW)) {
			l3[cl2++] = '*';
		}
		if (dr->evt & DMA_EVENT_VS) {
			l3[cl2++] = 'S';
		}
		if (dr->evt & DMA_EVENT_VB) {
			l3[cl2++] = 'B';
		}
		if (dr->evt & DMA_EVENT_LOL) {
			l3[cl2++] = 'L';
		}
		if (dr->evt & DMA_EVENT_LOF) {
			l3[cl2++] = 'F';
		}
		if (dr->evt & DMA_EVENT_VDIW) {
			l3[cl2++] = '=';
		}
		if (dr->evt & (DMA_EVENT_VB | DMA_EVENT_VS | DMA_EVENT_LOL | DMA_EVENT_LOF | DMA_EVENT_VDIW)) {
			l3[cl2++] = 0;
		}
	}
	if (l5) {
		_stprintf (l5, _T("%08X"), cycles + (vpos * maxhpos + hpos) * CYCLE_UNIT);
	}
	if (extra64) {
		_tcscpy(l5, l4);
		_stprintf(l4, _T("%08X"), extraval);
	}
	return got;
}



static void decode_dma_record (int hpos, int vpos, int toggle, bool logfile)
{
	struct dma_rec *dr, *dr_start;
	int h, i, maxh;
	uae_u32 cycles;

	if (!dma_record[0] || hpos < 0 || vpos < 0)
		return;
	dr_start = dr = &dma_record[dma_record_toggle ^ toggle][vpos * NR_DMA_REC_HPOS];
	if (logfile)
		write_dlog (_T("Line: %02X %3d HPOS %02X %3d:\n"), vpos, vpos, hpos, hpos);
	else
		console_out_f (_T("Line: %02X %3d HPOS %02X %3d:\n"), vpos, vpos, hpos, hpos);
	h = hpos;
	dr += hpos;
	maxh = hpos + (logfile ? maxhpos_short : 80);
	if (maxh > maxhpos_short)
		maxh = maxhpos_short;
	cycles = vsync_cycles;
	if (toggle)
		cycles -= maxvpos * maxhpos_short * CYCLE_UNIT;
	while (h < maxh) {
		int cols = (logfile ? 16 : 8);
		TCHAR l1[200];
		TCHAR l2[200];
		TCHAR l3[200];
		TCHAR l4[200];
		TCHAR l5[200];
		l1[0] = 0;
		l2[0] = 0;
		l3[0] = 0;
		l4[0] = 0;
		l5[0] = 0;
		for (i = 0; i < cols && h < maxh; i++, h++, dr++) {
			TCHAR l1l[16], l2l[16], l3l[16], l4l[16], l5l[16];

			get_record_dma_info(dr, h, vpos, cycles, l1l, l2l, l3l, l4l, l5l);
			if (dr_start[3 + 2].evt & DMA_EVENT_LOL) {
				if (maxh == maxhpos_short) {
					maxh++;
				}
			}

			TCHAR *p = l1 + _tcslen(l1);
			_stprintf(p, _T("%9s "), l1l);
			p = l2 + _tcslen(l2);
			_stprintf(p, _T("%9s "), l2l);
			p = l3 + _tcslen(l3);
			_stprintf(p, _T("%9s "), l3l);
			p = l4 + _tcslen(l4);
			_stprintf(p, _T("%9s "), l4l);
			p = l5 + _tcslen(l5);
			_stprintf(p, _T("%9s "), l5l);
		}
		if (logfile) {
			write_dlog (_T("%s\n"), l1);
			write_dlog (_T("%s\n"), l2);
			write_dlog (_T("%s\n"), l3);
			write_dlog (_T("%s\n"), l4);
			write_dlog (_T("%s\n"), l5);
			write_dlog (_T("\n"));
		} else {
			console_out_f (_T("%s\n"), l1);
			console_out_f (_T("%s\n"), l2);
			console_out_f (_T("%s\n"), l3);
			console_out_f (_T("%s\n"), l4);
			console_out_f (_T("%s\n"), l5);
			console_out_f (_T("\n"));
		}
	}
	if (logfile)
		flush_log();
}

void log_dma_record (void)
{
	if (!input_record && !input_play)
		return;
	if (!debug_dma)
		debug_dma = 1;
	decode_dma_record (0, 0, 0, true);
}

static void init_record_copper(void)
{
	if (!cop_record[0]) {
		cop_record[0] = xmalloc(struct cop_rec, NR_COPPER_RECORDS);
		cop_record[1] = xmalloc(struct cop_rec, NR_COPPER_RECORDS);
	}
}

void record_copper_blitwait (uaecptr addr, int hpos, int vpos)
{
	int t = nr_cop_records[curr_cop_set];
	init_record_copper();
	cop_record[curr_cop_set][t].bhpos = hpos;
	cop_record[curr_cop_set][t].bvpos = vpos;
}

void record_copper (uaecptr addr, uaecptr nextaddr, uae_u16 word1, uae_u16 word2, int hpos, int vpos)
{
	int t = nr_cop_records[curr_cop_set];
	init_record_copper();
	if (t < NR_COPPER_RECORDS) {
		cop_record[curr_cop_set][t].addr = addr;
		cop_record[curr_cop_set][t].nextaddr = nextaddr;
		cop_record[curr_cop_set][t].w1 = word1;
		cop_record[curr_cop_set][t].w2 = word2;
		cop_record[curr_cop_set][t].hpos = hpos;
		cop_record[curr_cop_set][t].vpos = vpos;
		cop_record[curr_cop_set][t].bvpos = -1;
		nr_cop_records[curr_cop_set] = t + 1;
	}
	if (debug_copper & 2) { /* trace */
		debug_copper &= ~2;
		activate_debugger_new();
	}
	if ((debug_copper & 4) && addr >= debug_copper_pc && addr <= debug_copper_pc + 3) {
		debug_copper &= ~4;
		activate_debugger_new();
	}
}

static struct cop_rec *find_copper_records(uaecptr addr)
{
	int s = selected_cop_set;
	int t = nr_cop_records[s];
	int i;
	for (i = 0; i < t; i++) {
		if (cop_record[s][i].addr == addr)
			return &cop_record[s][i];
	}
	return 0;
}

/* simple decode copper by Mark Cox */
static uaecptr decode_copper_insn(FILE *file, uae_u16 mword1, uae_u16 mword2, uaecptr addr)
{
	struct cop_rec *cr = NULL;
	uae_u32 insn_type, insn;
	TCHAR here = ' ';
	TCHAR record[] = _T("          ");

	if ((cr = find_copper_records(addr))) {
		_stprintf(record, _T(" [%03x %03x]"), cr->vpos, cr->hpos);
		insn = (cr->w1 << 16) | cr->w2;
	} else {
		insn = (mword1 << 16) | mword2;
	}

	insn_type = insn & 0x00010001;

	if (get_copper_address(-1) >= addr && get_copper_address(-1) <= addr + 3)
		here = '*';

	console_out_f (_T("%c%08x: %04x %04x%s\t;%c "), here, addr, insn >> 16, insn & 0xFFFF, record, insn != ((mword1 << 16) | mword2) ? '!' : ' ');

	switch (insn_type) {
	case 0x00010000: /* WAIT insn */
		console_out(_T("Wait for "));
		disassemble_wait(file, insn);

		if (insn == 0xfffffffe)
			console_out(_T("                           \t;  End of Copperlist\n"));

		break;

	case 0x00010001: /* SKIP insn */
		console_out(_T("Skip if "));
		disassemble_wait(file, insn);
		break;

	case 0x00000000:
	case 0x00000001: /* MOVE insn */
		{
			int addr = (insn >> 16) & 0x1fe;
			int i = 0;
			while (custd[i].name) {
				if (custd[i].adr == addr + 0xdff000)
					break;
				i++;
			}
			if (custd[i].name)
				console_out_f(_T("%s := 0x%04x\n"), custd[i].name, insn & 0xffff);
			else
				console_out_f(_T("%04x := 0x%04x\n"), addr, insn & 0xffff);
		}
		break;

	default:
		abort ();
	}

	if (cr && cr->bvpos >= 0) {
		console_out_f(_T("                 BLT [%03x %03x]\n"), cr->bvpos, cr->bhpos);
	}
	if (cr && cr->nextaddr != 0xffffffff && cr->nextaddr != addr + 4) {
		console_out_f(_T(" %08x: Copper jump\n"), cr->nextaddr);
		return cr->nextaddr;
	}
	return addr + 4;
}

static uaecptr decode_copperlist(FILE *file, uaecptr address, int nolines)
{
	uaecptr next;
	while (nolines-- > 0) {
		next = decode_copper_insn(file, chipmem_wget_indirect(address), chipmem_wget_indirect(address + 2), address);
		address = next;
	}
	return address;
	/* You may wonder why I don't stop this at the end of the copperlist?
	* Well, often nice things are hidden at the end and it is debatable the actual
	* values that mean the end of the copperlist */
}

static int copper_debugger (TCHAR **c)
{
	static uaecptr nxcopper;
	uae_u32 maddr;
	int lines;

	if (**c == 'd') {
		next_char (c);
		if (debug_copper)
			debug_copper = 0;
		else
			debug_copper = 1;
		console_out_f (_T("Copper debugger %s.\n"), debug_copper ? _T("enabled") : _T("disabled"));
	} else if (**c == 't') {
		debug_copper = 1|2;
		return 1;
	} else if (**c == 'b') {
		(*c)++;
		debug_copper = 1|4;
		if (more_params (c)) {
			debug_copper_pc = readhex (c);
			console_out_f (_T("Copper breakpoint @0x%08x\n"), debug_copper_pc);
		} else {
			debug_copper &= ~4;
		}
	} else {
		if (more_params(c)) {
			maddr = readhex(c);
			if (maddr == 1 || maddr == 2 || maddr == 3)
				maddr = get_copper_address(maddr);
			else if (maddr == 0)
				maddr = get_copper_address(-1);
		} else {
			maddr = nxcopper;
		}
		selected_cop_set = curr_cop_set;
		if (!find_copper_records(maddr)) {
			selected_cop_set = curr_cop_set ^ 1;
		}

		if (more_params (c))
			lines = readhex (c);
		else
			lines = 20;

		nxcopper = decode_copperlist (stdout, maddr, lines);
	}
	return 0;
}

#define MAX_CHEAT_VIEW 100
struct trainerstruct {
	uaecptr addr;
	int size;
};

static struct trainerstruct *trainerdata;
static int totaltrainers;

static void clearcheater(void)
{
	if (!trainerdata)
		trainerdata =  xmalloc(struct trainerstruct, MAX_CHEAT_VIEW);
	memset(trainerdata, 0, sizeof (struct trainerstruct) * MAX_CHEAT_VIEW);
	totaltrainers = 0;
}
static int addcheater(uaecptr addr, int size)
{
	if (totaltrainers >= MAX_CHEAT_VIEW)
		return 0;
	trainerdata[totaltrainers].addr = addr;
	trainerdata[totaltrainers].size = size;
	totaltrainers++;
	return 1;
}
static void listcheater(int mode, int size)
{
	int i, skip;

	if (!trainerdata)
		return;
	if (mode)
		skip = 6;
	else
		skip = 8;
	for(i = 0; i < totaltrainers; i++) {
		struct trainerstruct *ts = &trainerdata[i];
		uae_u16 b;

		if (size) {
			b = get_byte_debug (ts->addr);
		} else {
			b = get_word_debug (ts->addr);
		}
		if (mode)
			console_out_f (_T("%08X=%04X "), ts->addr, b);
		else
			console_out_f (_T("%08X "), ts->addr);
		if ((i % skip) == skip)
			console_out (_T("\n"));
	}
}

static void deepcheatsearch (TCHAR **c)
{
	static int first = 1;
	static uae_u8 *memtmp;
	static int memsize, memsize2;
	uae_u8 *p1, *p2;
	uaecptr addr, end;
	int i, wasmodified, nonmodified;
	static int size;
	static int inconly, deconly, maxdiff;
	int addrcnt, cnt;
	TCHAR v;

	v = _totupper (**c);

	if(!memtmp || v == 'S') {
		maxdiff = 0x10000;
		inconly = 0;
		deconly = 0;
		size = 1;
	}

	if (**c)
		(*c)++;
	ignore_ws (c);
	if ((**c) == '1' || (**c) == '2') {
		size = **c - '0';
		(*c)++;
	}
	if (more_params (c))
		maxdiff = readint (c);

	if (!memtmp || v == 'S') {
		first = 1;
		xfree (memtmp);
		memsize = 0;
		addr = 0xffffffff;
		while ((addr = nextaddr (addr, 0, &end, false)) != 0xffffffff)  {
			memsize += end - addr;
			addr = end - 1;
		}
		memsize2 = (memsize + 7) / 8;
		memtmp = xmalloc (uae_u8, memsize + memsize2);
		if (!memtmp)
			return;
		memset (memtmp + memsize, 0xff, memsize2);
		p1 = memtmp;
		addr = 0xffffffff;
		while ((addr = nextaddr (addr, 0, &end, true)) != 0xffffffff) {
			for (i = addr; i < end; i++)
				*p1++ = get_byte_debug (i);
			addr = end - 1;
		}
		console_out (_T("Deep trainer first pass complete.\n"));
		return;
	}
	inconly = deconly = 0;
	wasmodified = v == 'X' ? 0 : 1;
	nonmodified = v == 'Z' ? 1 : 0;
	if (v == 'I')
		inconly = 1;
	if (v == 'D')
		deconly = 1;
	p1 = memtmp;
	p2 = memtmp + memsize;
	addrcnt = 0;
	cnt = 0;
	addr = 0xffffffff;
	while ((addr = nextaddr (addr, 0, NULL, true)) != 0xffffffff) {
		uae_s32 b, b2;
		int doremove = 0;
		int addroff = addrcnt >> 3;
		int addrmask ;

		if (size == 1) {
			b = (uae_s8)get_byte_debug (addr);
			b2 = (uae_s8)p1[addrcnt];
			addrmask = 1 << (addrcnt & 7);
		} else {
			b = (uae_s16)get_word_debug (addr);
			b2 = (uae_s16)((p1[addrcnt] << 8) | p1[addrcnt + 1]);
			addrmask = 3 << (addrcnt & 7);
		}

		if (p2[addroff] & addrmask) {
			if (wasmodified && !nonmodified) {
				int diff = b - b2;
				if (b == b2)
					doremove = 1;
				if (abs(diff) > maxdiff)
					doremove = 1;
				if (inconly && diff < 0)
					doremove = 1;
				if (deconly && diff > 0)
					doremove = 1;
			} else if (nonmodified && b == b2) {
				doremove = 1;
			} else if (!wasmodified && b != b2) {
				doremove = 1;
			}
			if (doremove)
				p2[addroff] &= ~addrmask;
			else
				cnt++;
		}
		if (size == 1) {
			p1[addrcnt] = b;
			addrcnt++;
		} else {
			p1[addrcnt] = b >> 8;
			p1[addrcnt + 1] = b >> 0;
			addr = nextaddr (addr, 0, NULL, true);
			if (addr == 0xffffffff)
				break;
			addrcnt += 2;
		}
		if  (iscancel (65536)) {
			console_out_f (_T("Aborted at %08X\n"), addr);
			break;
		}
	}

	console_out_f (_T("%d addresses found\n"), cnt);
	if (cnt <= MAX_CHEAT_VIEW) {
		clearcheater ();
		cnt = 0;
		addrcnt = 0;
		addr = 0xffffffff;
		while ((addr = nextaddr(addr, 0, NULL, true)) != 0xffffffff) {
			int addroff = addrcnt >> 3;
			int addrmask = (size == 1 ? 1 : 3) << (addrcnt & 7);
			if (p2[addroff] & addrmask)
				addcheater (addr, size);
			addrcnt += size;
			cnt++;
		}
		if (cnt > 0)
			console_out (_T("\n"));
		listcheater (1, size);
	} else {
		console_out (_T("Now continue with 'g' and use 'D' again after you have lost another life\n"));
	}
}

/* cheat-search by Toni Wilen (originally by Holger Jakob) */
static void cheatsearch (TCHAR **c)
{
	static uae_u8 *vlist;
	static int listsize;
	static int first = 1;
	static int size = 1;
	uae_u32 val, memcnt, prevmemcnt;
	int i, count, vcnt, memsize;
	uaecptr addr, end;

	memsize = 0;
	addr = 0xffffffff;
	while ((addr = nextaddr (addr, 0, &end, false)) != 0xffffffff)  {
		memsize += end - addr;
		addr = end - 1;
	}

	if (_totupper (**c) == 'L') {
		listcheater (1, size);
		return;
	}
	ignore_ws (c);
	if (!more_params (c)) {
		first = 1;
		console_out (_T("Search reset\n"));
		xfree (vlist);
		listsize = memsize;
		vlist = xcalloc (uae_u8, listsize >> 3);
		return;
	}
	if (first)
		val = readint (c, &size);
	else
		val = readint (c);

	if (vlist == NULL) {
		listsize = memsize;
		vlist = xcalloc (uae_u8, listsize >> 3);
	}

	count = 0;
	vcnt = 0;

	clearcheater ();
	addr = 0xffffffff;
	prevmemcnt = memcnt = 0;
	while ((addr = nextaddr (addr, 0, &end, true)) != 0xffffffff) {
		if (addr + size < end) {
			for (i = 0; i < size; i++) {
				int shift = (size - i - 1) * 8;
				if (get_byte_debug (addr + i) != ((val >> shift) & 0xff))
					break;
			}
			if (i == size) {
				int voffset = memcnt >> 3;
				int vmask = 1 << (memcnt & 7);
				if (!first) {
					while (prevmemcnt < memcnt) {
						vlist[prevmemcnt >> 3] &= ~(1 << (prevmemcnt & 7));
						prevmemcnt++;
					}
					if (vlist[voffset] & vmask) {
						count++;
						addcheater(addr, size);
					} else {
						vlist[voffset] &= ~vmask;
					}
					prevmemcnt = memcnt + 1;
				} else {
					vlist[voffset] |= vmask;
					count++;
				}
			}
		}
		memcnt++;
		if  (iscancel (65536)) {
			console_out_f (_T("Aborted at %08X\n"), addr);
			break;
		}
	}
	if (!first) {
		while (prevmemcnt < memcnt) {
			vlist[prevmemcnt >> 3] &= ~(1 << (prevmemcnt & 7));
			prevmemcnt++;
		}
		listcheater (0, size);
	}
	console_out_f (_T("Found %d possible addresses with 0x%X (%u) (%d bytes)\n"), count, val, val, size);
	if (count > 0)
		console_out (_T("Now continue with 'g' and use 'C' with a different value\n"));
	first = 0;
}

struct breakpoint_node bpnodes[BREAKPOINT_TOTAL];
static addrbank **debug_mem_banks;
static addrbank *debug_mem_area;
struct memwatch_node mwnodes[MEMWATCH_TOTAL];
static int mwnodes_start, mwnodes_end;
static struct memwatch_node mwhit;

#define MUNGWALL_SLOTS 16
struct mungwall_data
{
	int slots;
	uae_u32 start[MUNGWALL_SLOTS], end[MUNGWALL_SLOTS];
};
static struct mungwall_data **mungwall;

static uae_u8 *illgdebug, *illghdebug;
static int illgdebug_break;

static void illg_free (void)
{
	xfree (illgdebug);
	illgdebug = NULL;
	xfree (illghdebug);
	illghdebug = NULL;
}

static void illg_init (void)
{
	int i;
	uae_u8 c = 3;
	uaecptr addr, end;

	illgdebug = xcalloc (uae_u8, 0x01000000);
	illghdebug = xcalloc (uae_u8, 65536);
	if (!illgdebug || !illghdebug) {
		illg_free();
		return;
	}
	addr = 0xffffffff;
	while ((addr = nextaddr (addr, 0, &end, false)) != 0xffffffff)  {
		if (end < 0x01000000) {
			memset (illgdebug + addr, c, end - addr);
		} else {
			uae_u32 s = addr >> 16;
			uae_u32 e = end >> 16;
			memset (illghdebug + s, c, e - s);
		}
		addr = end - 1;
	}
	for (int i = 0; i < MAX_RTG_BOARDS; i++) {
		if (currprefs.rtgboards[i].rtgmem_size)
			memset (illghdebug + (gfxmem_banks[i]->start >> 16), 3, currprefs.rtgboards[i].rtgmem_size >> 16);
	}

	i = 0;
	while (custd[i].name) {
		int rw = (custd[i].special & CD_WO) ? 2 : 1;
		illgdebug[custd[i].adr] = rw;
		illgdebug[custd[i].adr + 1] = rw;
		i++;
	}
	for (i = 0; i < 16; i++) { /* CIAs */
		if (i == 11)
			continue;
		illgdebug[0xbfe001 + i * 0x100] = c;
		illgdebug[0xbfd000 + i * 0x100] = c;
	}
	memset (illgdebug + 0xf80000, 1, 512 * 1024); /* KS ROM */
	memset (illgdebug + 0xdc0000, c, 0x3f); /* clock */
#ifdef CDTV
	if (currprefs.cs_cdtvram) {
		memset (illgdebug + 0xdc8000, c, 4096); /* CDTV batt RAM */
		memset (illgdebug + 0xf00000, 1, 256 * 1024); /* CDTV ext ROM */
	}
#endif
#ifdef CD32
	if (currprefs.cs_cd32cd) {
		memset (illgdebug + AKIKO_BASE, c, AKIKO_BASE_END - AKIKO_BASE);
		memset (illgdebug + 0xe00000, 1, 512 * 1024); /* CD32 ext ROM */
	}
#endif
	if (currprefs.cs_ksmirror_e0)
		memset (illgdebug + 0xe00000, 1, 512 * 1024);
	if (currprefs.cs_ksmirror_a8)
		memset (illgdebug + 0xa80000, 1, 2 * 512 * 1024);
#ifdef FILESYS
	if (uae_boot_rom_type) /* filesys "rom" */
		memset (illgdebug + rtarea_base, 1, 0x10000);
#endif
	if (currprefs.cs_ide > 0)
		memset (illgdebug + 0xdd0000, 3, 65536);
}

/* add special custom register check here */
static void illg_debug_check (uaecptr addr, int rwi, int size, uae_u32 val)
{
	return;
}

static void illg_debug_do (uaecptr addr, int rwi, int size, uae_u32 val)
{
	uae_u8 mask;
	uae_u32 pc = m68k_getpc ();
	int i;

	for (i = size - 1; i >= 0; i--) {
		uae_u8 v = val >> (i * 8);
		uae_u32 ad = addr + i;
		if (ad >= 0x01000000)
			mask = illghdebug[ad >> 16];
		else
			mask = illgdebug[ad];
		if ((mask & 3) == 3)
			return;
		if (mask & 0x80) {
			illg_debug_check (ad, rwi, size, val);
		} else if ((mask & 3) == 0) {
			if (rwi & 2)
				console_out_f (_T("W: %08X=%02X PC=%08X\n"), ad, v, pc);
			else if (rwi & 1)
				console_out_f (_T("R: %08X    PC=%08X\n"), ad, pc);
			if (illgdebug_break)
				activate_debugger_new();
		} else if (!(mask & 1) && (rwi & 1)) {
			console_out_f (_T("RO: %08X=%02X PC=%08X\n"), ad, v, pc);
			if (illgdebug_break)
				activate_debugger_new();
		} else if (!(mask & 2) && (rwi & 2)) {
			console_out_f (_T("WO: %08X    PC=%08X\n"), ad, pc);
			if (illgdebug_break)
				activate_debugger_new();
		}
	}
}

static int debug_mem_off (uaecptr *addrp)
{
	uaecptr addr = *addrp;
	addrbank *ba;
	int offset = munge24 (addr) >> 16;
	if (!debug_mem_banks)
		return offset;
	ba = debug_mem_banks[offset];
	if (!ba)
		return offset;
	if (ba->mask || ba->startmask) {
		uae_u32 start = ba->startmask ? ba->startmask : ba->start;
		addr -= start;
		addr &= ba->mask;
		addr += start;
	}
	*addrp = addr;
	return offset;
}

struct smc_item {
	uae_u32 addr;
	uae_u8 cnt;
};

static int smc_size, smc_mode;
static struct smc_item *smc_table;

static void smc_free (void)
{
	if (smc_table)
		console_out (_T("SMCD disabled\n"));
	xfree(smc_table);
	smc_mode = 0;
	smc_table = NULL;
}

static void initialize_memwatch (int mode);
static void smc_detect_init (TCHAR **c)
{
	int v, i;

	ignore_ws (c);
	v = readint (c);
	smc_free ();
	smc_size = 1 << 24;
	if (currprefs.z3fastmem[0].size)
		smc_size = currprefs.z3autoconfig_start + currprefs.z3fastmem[0].size;
	smc_size += 4;
	smc_table = xmalloc (struct smc_item, smc_size);
	if (!smc_table)
		return;
	for (i = 0; i < smc_size; i++) {
		smc_table[i].addr = 0xffffffff;
		smc_table[i].cnt = 0;
	}
	if (!memwatch_enabled)
		initialize_memwatch (0);
	if (v)
		smc_mode = 1;
	console_out_f (_T("SMCD enabled. Break=%d\n"), smc_mode);
}

#define SMC_MAXHITS 8
static void smc_detector (uaecptr addr, int rwi, int size, uae_u32 *valp)
{
	int i, hitcnt;
	uaecptr hitaddr, hitpc;

	if (!smc_table)
		return;
	if (addr >= smc_size)
		return;
	if (rwi == 2) {
		for (i = 0; i < size; i++) {
			if (smc_table[addr + i].cnt < SMC_MAXHITS) {
				smc_table[addr + i].addr = m68k_getpc ();
			}
		}
		return;
	}
	hitpc = smc_table[addr].addr;
	if (hitpc == 0xffffffff)
		return;
	hitaddr = addr;
	hitcnt = 0;
	while (addr < smc_size && smc_table[addr].addr != 0xffffffff) {
		smc_table[addr++].addr = 0xffffffff;
		hitcnt++;
	}
	if ((hitpc & 0xFFF80000) == 0xF80000)
		return;
	if (currprefs.cpu_model == 68000 && currprefs.cpu_compatible) {
		/* ignore single-word unconditional jump instructions
		* (instruction prefetch from PC+2 can cause false positives) */
		if (regs.irc == 0x4e75 || regs.irc == 4e74 || regs.irc == 0x4e72 || regs.irc == 0x4e77)
			return; /* RTS, RTD, RTE, RTR */
		if ((regs.irc & 0xff00) == 0x6000 && (regs.irc & 0x00ff) != 0 && (regs.irc & 0x00ff) != 0xff)
			return; /* BRA.B */
	}
	if (hitcnt < 100) {
		smc_table[hitaddr].cnt++;
		console_out_f (_T("SMC at %08X - %08X (%d) from %08X\n"),
			hitaddr, hitaddr + hitcnt, hitcnt, hitpc);
		if (smc_mode)
			activate_debugger_new();
		if (smc_table[hitaddr].cnt >= SMC_MAXHITS)
			console_out_f (_T("* hit count >= %d, future hits ignored\n"), SMC_MAXHITS);
	}
}

uae_u8 *save_debug_memwatch (int *len, uae_u8 *dstptr)
{
	uae_u8 *dstbak, *dst;
	int total;

	total = 0;
	for (int i = 0; i < MEMWATCH_TOTAL; i++) {
		if (mwnodes[i].size > 0)
			total++;
	}
	if (!total)
		return NULL;

	if (dstptr)
		dstbak = dst = dstptr;
	else
		dstbak = dst = xmalloc (uae_u8, 1000);
	save_u32 (1);
	save_u8 (total);
	for (int i = 0; i < MEMWATCH_TOTAL; i++) {
		struct memwatch_node *m = &mwnodes[i];
		if (m->size <= 0)
			continue;
		save_store_pos ();
		save_u8 (i);
		save_u8 (m->modval_written);
		save_u8 (m->mustchange);
		save_u8 (m->frozen);
		save_u8 (m->val_enabled);
		save_u8 (m->rwi);
		save_u32 (m->addr);
		save_u32 (m->size);
		save_u32 (m->modval);
		save_u32 (m->val_mask);
		save_u32 (m->val_size);
		save_u32 (m->val);
		save_u32 (m->pc);
		save_u32 (m->access_mask);
		save_u32 (m->reg);
		save_u8(m->nobreak);
		save_u8(m->reportonly);
		save_store_size ();
	}
	*len = dst - dstbak;
	return dstbak;
}

uae_u8 *restore_debug_memwatch (uae_u8 *src)
{
	if (restore_u32 () != 1)
		return src;
	int total = restore_u8 ();
	for (int i = 0; i < total; i++) {
		restore_store_pos ();
		int idx = restore_u8 ();
		struct memwatch_node *m = &mwnodes[idx];
		m->modval_written = restore_u8 ();
		m->mustchange = restore_u8 ();
		m->frozen = restore_u8 ();
		m->val_enabled = restore_u8 ();
		m->rwi = restore_u8 ();
		m->addr = restore_u32 ();
		m->size = restore_u32 ();
		m->modval = restore_u32 ();
		m->val_mask = restore_u32 ();
		m->val_size = restore_u32 ();
		m->val = restore_u32 ();
		m->pc = restore_u32 ();
		m->access_mask = restore_u32();
		m->reg = restore_u32();
		m->nobreak = restore_u8();
		m->reportonly = restore_u8();
		restore_store_size ();
	}
	return src;
}

void restore_debug_memwatch_finish (void)
{
	for (int i = 0; i < MEMWATCH_TOTAL; i++) {
		struct memwatch_node *m = &mwnodes[i];
		if (m->size) {
			if (!memwatch_enabled)
				initialize_memwatch (0);
			return;
		}
	}
}

void debug_check_reg(uae_u32 addr, int write, uae_u16 v)
{
	if (!memwatch_access_validator)
		return;
	int reg = addr & 0x1ff;
	const struct customData *cd = &custd[reg >> 1];

	if (((addr & 0xfe00) != 0xf000 && (addr & 0xffff0000) != 0) || ((addr & 0xffff0000) != 0 && (addr & 0xffff0000) != 0x00df0000) || (addr & 0x0600)) {
		write_log(_T("Mirror custom register %08x (%s) %s access. PC=%08x\n"), addr, cd->name, write ? _T("write") : _T("read"), M68K_GETPC);
	}

	int spc = cd->special;
	if ((spc & CD_AGA) && !(currprefs.chipset_mask & CSMASK_AGA))
		spc |= CD_NONE;
	if ((spc & CD_ECS_DENISE) && !(currprefs.chipset_mask & CSMASK_ECS_DENISE))
		spc |= CD_NONE;
	if ((spc & CD_ECS_AGNUS) && !(currprefs.chipset_mask & CSMASK_ECS_AGNUS))
		spc |= CD_NONE;
	if (spc & CD_NONE) {
		write_log(_T("Non-existing custom register %04x (%s) %s access. PC=%08x\n"), reg, cd->name, write ? _T("write") : _T("read"), M68K_GETPC);
		return;
	}

	if (spc & CD_COLOR) {
		if (currprefs.chipset_mask & CSMASK_AGA)
			return;
	}

	if (write & !(spc & CD_WO)) {
		write_log(_T("Write access to read-only custom register %04x (%s). PC=%08x\n"), reg, cd->name, M68K_GETPC);
		return;
	} else if (!write && (spc & CD_WO)) {
		write_log(_T("Read access from write-only custom register %04x (%s). PC=%08x\n"), reg, cd->name, M68K_GETPC);
		return;
	}

	if (write && cd->mask[2]) {
		int idx = (currprefs.chipset_mask & CSMASK_AGA) ? 2 : (currprefs.chipset_mask & CSMASK_ECS_AGNUS) ? 1 : 0;
		uae_u16 mask = cd->mask[idx];
		if (v & ~mask) {
			write_log(_T("Unuset bits set %04x when writing custom register %04x (%s) PC=%08x\n"), v & ~mask, reg, cd->name, M68K_GETPC);
		}
	}

	if (spc & CD_DMA_PTR) {
		uae_u32 addr = (custom_storage[((reg & ~2) >> 1)].value << 16) | custom_storage[((reg | 2) >> 1)].value;
		if (currprefs.z3chipmem.size) {
			if (addr >= currprefs.z3chipmem.start_address && addr < currprefs.z3chipmem.start_address + currprefs.z3chipmem.size)
				return;
		}
		if(addr >= currprefs.chipmem.size)
			write_log(_T("DMA pointer %04x (%s) set to invalid value %08x %s=%08x\n"), reg, cd->name, addr,
				custom_storage[reg >> 1].pc & 1 ? _T("COP") : _T("PC"), custom_storage[reg >> 1].pc);
	}
}

void debug_invalid_reg(int reg, int size, uae_u16 v)
{
	if (!memwatch_access_validator)
		return;
	reg &= 0x1ff;
	if (size == 1) {
		if (reg == 2) // DMACONR low byte
			return;
		if (reg == 6) // VPOS
			return;
	}
	const struct customData *cd = &custd[reg >> 1];
	if (size == -2 && (reg & 1)) {
		write_log(_T("Unaligned word write to register %04x (%s) val %04x PC=%08x\n"), reg, cd->name, v, M68K_GETPC);
	} else if (size == -1) {
		write_log(_T("Byte write to register %04x (%s) val %02x PC=%08x\n"), reg, cd->name, v & 0xff, M68K_GETPC);
	} else if (size == 2 && (reg & 1)) {
		write_log(_T("Unaligned word read from register %04x (%s) PC=%08x\n"), reg, cd->name, M68K_GETPC);
	} else if (size == 1) {
		write_log(_T("Byte read from register %04x (%s) PC=%08x\n"), reg, cd->name, M68K_GETPC);
	}
}

static void is_valid_dma(int reg, int ptrreg, uaecptr addr)
{
	if (!memwatch_access_validator)
		return;
	if (reg == 0x1fe) // refresh
		return;
	if (currprefs.z3chipmem.size) {
		if (addr >= currprefs.z3chipmem.start_address && addr < currprefs.z3chipmem.start_address + currprefs.z3chipmem.size)
			return;
	}
	if (!(addr & ~(currprefs.chipmem.size - 1)))
		return;
	const struct customData *cdreg = &custd[reg >> 1];
	const struct customData *cdptr = &custd[ptrreg >> 1];
	write_log(_T("DMA DAT %04x (%s), PT %04x (%s) accessed invalid memory %08x. Init: %08x, PC/COP=%08x\n"),
		reg, cdreg->name, ptrreg, cdptr->name, addr,
		(custom_storage[ptrreg >> 1].value << 16) | (custom_storage[(ptrreg >> 1) + 1].value),
		custom_storage[ptrreg >> 1].pc);
}

static void mungwall_memwatch(uaecptr addr, int rwi, int size, uae_u32 valp)
{
	struct mungwall_data *mwd = mungwall[addr >> 16];
	if (!mwd)
		return;
	for (int i = 0; i < mwd->slots; i++) {
		if (!mwd->end[i])
			continue;
		if (addr + size > mwd->start[i] && addr < mwd->end[i]) {

		}
	}
}

static void memwatch_hit_msg(int mw)
{
	console_out_f(_T("Memwatch %d: break at %08X.%c %c%c%c %08X PC=%08X "), mw, mwhit.addr,
		mwhit.size == 1 ? 'B' : (mwhit.size == 2 ? 'W' : 'L'),
		(mwhit.rwi & 1) ? 'R' : ' ', (mwhit.rwi & 2) ? 'W' : ' ', (mwhit.rwi & 4) ? 'I' : ' ',
		mwhit.val, mwhit.pc);
	for (int i = 0; memwatch_access_masks[i].mask; i++) {
		if (mwhit.access_mask == memwatch_access_masks[i].mask)
			console_out_f(_T("%s (%03x)\n"), memwatch_access_masks[i].name, mwhit.reg);
	}
	if (mwhit.access_mask & (MW_MASK_BLITTER_A | MW_MASK_BLITTER_B | MW_MASK_BLITTER_C | MW_MASK_BLITTER_D_N | MW_MASK_BLITTER_D_L | MW_MASK_BLITTER_D_F)) {
		blitter_debugdump();
	}
}

static int memwatch_func (uaecptr addr, int rwi, int size, uae_u32 *valp, uae_u32 accessmask, uae_u32 reg)
{
	uae_u32 val = *valp;

	if (inside_debugger)
		return 1;

	if (mungwall)
		mungwall_memwatch(addr, rwi, size, val);

	if (illgdebug)
		illg_debug_do (addr, rwi, size, val);

	if (heatmap)
		memwatch_heatmap (addr, rwi, size, accessmask);

	addr = munge24 (addr);

	if (smc_table && (rwi >= 2))
		smc_detector (addr, rwi, size, valp);

	for (int i = mwnodes_start; i <= mwnodes_end; i++) {
		struct memwatch_node *m = &mwnodes[i];
		uaecptr addr2 = m->addr;
		uaecptr addr3 = addr2 + m->size;
		int rwi2 = m->rwi;
		uae_u32 oldval = 0;
		int isoldval = 0;
		int brk = 0;

		if (m->size == 0)
			continue;
		if (!(rwi & rwi2))
			continue;
		if (!(m->access_mask & accessmask))
			continue;

		if (addr >= addr2 && addr < addr3)
			brk = 1;
		if (!brk && size == 2 && (addr + 1 >= addr2 && addr + 1 < addr3))
			brk = 1;
		if (!brk && size == 4 && ((addr + 2 >= addr2 && addr + 2 < addr3) || (addr + 3 >= addr2 && addr + 3 < addr3)))
			brk = 1;

		if (!brk)
			continue;

		if (m->bus_error) {
			if (((m->bus_error & 1) && (rwi & 1)) || ((m->bus_error & 4) && (rwi & 4)) || ((m->bus_error & 2) && (rwi & 2))) {
				hardware_exception2(addr, val, (rwi & 2) != 0, (rwi & 4) != 0, size == 4 ? sz_long : (size == 2 ? sz_word : sz_byte));
			}
			continue;
		}

		if (mem_banks[addr >> 16]->check (addr, size)) {
			uae_u8 *p = mem_banks[addr >> 16]->xlateaddr (addr);
			if (size == 1)
				oldval = p[0];
			else if (size == 2)
				oldval = (p[0] << 8) | p[1];
			else
				oldval = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3] << 0);
			isoldval = 1;
		}

		if (m->pc != 0xffffffff) {
			if (m->pc != regs.instruction_pc)
				continue;
		}

		if (!m->frozen && m->val_enabled) {
			int trigger = 0;
			uae_u32 mask = m->size == 4 ? 0xffffffff : (1 << (m->size * 8)) - 1;
			uae_u32 mval = m->val;
			int scnt = size;
			for (;;) {
				if (((mval & mask) & m->val_mask) == ((val & mask) & m->val_mask))
					trigger = 1;
				if (mask & 0x80000000)
					break;
				if (m->size == 1) {
					mask <<= 8;
					mval <<= 8;
					scnt--;
				} else if (m->size == 2) {
					mask <<= 16;
					scnt -= 2;
					mval <<= 16;
				} else {
					scnt -= 4;
				}
				if (scnt <= 0)
					break;
			}
			if (!trigger)
				continue;
		}

		if (m->mustchange && rwi == 2 && isoldval) {
			if (oldval == *valp)
				continue;
		}

		if (m->modval_written) {
			if (!rwi) {
				brk = 0;
			} else if (m->modval_written == 1) {
				m->modval_written = 2;
				m->modval = val;
				brk = 0;
			} else if (m->modval == val) {
				brk = 0;
			}
		}
		if (m->frozen) {
			if (m->val_enabled) {
				int shift = (addr + size - 1) - (m->addr + m->val_size - 1);
				uae_u32 sval;
				uae_u32 mask;

				if (m->val_size == 4)
					mask = 0xffffffff;
				else if (m->val_size == 2)
					mask = 0x0000ffff;
				else
					mask = 0x000000ff;

				sval = m->val;
				if (shift < 0) {
					shift = -8 * shift;
					sval >>= shift;
					mask >>= shift;
				} else {
					shift = 8 * shift;
					sval <<= shift;
					mask <<= shift;
				}
				*valp = (sval & mask) | ((*valp) & ~mask);
				//write_log (_T("%08x %08x %08x %08x %d\n"), addr, m->addr, *valp, mask, shift);
				return 1;
			}
			return 0;
		}
		//	if (!notinrom ())
		//	    return 1;
		mwhit.addr = addr;
		mwhit.rwi = rwi;
		mwhit.size = size;
		mwhit.val = 0;
		mwhit.access_mask = accessmask;
		mwhit.reg = reg;
		if (mwhit.rwi & 2)
			mwhit.val = val;
		mwhit.pc = M68K_GETPC;
		memwatch_triggered = i + 1;
		if (m->reportonly) {
			memwatch_hit_msg(memwatch_triggered - 1);
		}
		if (!m->nobreak && !m->reportonly) {
			debugging = 1;
			debug_pc = M68K_GETPC;
			debug_cycles();
			set_special(SPCFLAG_BRK);
		}
		return 1;
	}
	return 1;
}

#endif	/* WINUAE_FOR_HATARI */

#ifndef WINUAE_FOR_HATARI
static int mmu_hit (uaecptr addr, int size, int rwi, uae_u32 *v);

static uae_u32 REGPARAM2 mmu_lget (uaecptr addr)
{
	int off = debug_mem_off (&addr);
	uae_u32 v = 0;
	if (!mmu_hit (addr, 4, 0, &v))
		v = debug_mem_banks[off]->lget (addr);
	return v;
}
static uae_u32 REGPARAM2 mmu_wget (uaecptr addr)
{
	int off = debug_mem_off (&addr);
	uae_u32 v = 0;
	if (!mmu_hit (addr, 2, 0, &v))
		v = debug_mem_banks[off]->wget (addr);
	return v;
}
static uae_u32 REGPARAM2 mmu_bget (uaecptr addr)
{
	int off = debug_mem_off (&addr);
	uae_u32 v = 0;
	if (!mmu_hit(addr, 1, 0, &v))
		v = debug_mem_banks[off]->bget (addr);
	return v;
}
static void REGPARAM2 mmu_lput (uaecptr addr, uae_u32 v)
{
	int off = debug_mem_off (&addr);
	if (!mmu_hit (addr, 4, 1, &v))
		debug_mem_banks[off]->lput (addr, v);
}
static void REGPARAM2 mmu_wput (uaecptr addr, uae_u32 v)
{
	int off = debug_mem_off (&addr);
	if (!mmu_hit (addr, 2, 1, &v))
		debug_mem_banks[off]->wput (addr, v);
}
static void REGPARAM2 mmu_bput (uaecptr addr, uae_u32 v)
{
	int off = debug_mem_off (&addr);
	if (!mmu_hit (addr, 1, 1, &v))
		debug_mem_banks[off]->bput (addr, v);
}
static uae_u32 REGPARAM2 mmu_lgeti (uaecptr addr)
{
	int off = debug_mem_off (&addr);
	uae_u32 v = 0;
	if (!mmu_hit (addr, 4, 4, &v))
		v = debug_mem_banks[off]->lgeti (addr);
	return v;
}
static uae_u32 REGPARAM2 mmu_wgeti (uaecptr addr)
{
	int off = debug_mem_off (&addr);
	uae_u32 v = 0;
	if (!mmu_hit (addr, 2, 4, &v))
		v = debug_mem_banks[off]->wgeti (addr);
	return v;
}

static uae_u32 REGPARAM2 debug_lget(uaecptr addr)
{
	uae_u32 off = debug_mem_off(&addr);
	uae_u32 v;
	v = debug_mem_banks[off]->lget(addr);
	memwatch_func(addr, 1, 4, &v, MW_MASK_CPU_D_R, 0);
	return v;
}
static uae_u32 REGPARAM2 debug_wget (uaecptr addr)
{
	int off = debug_mem_off (&addr);
	uae_u32 v;
	v = debug_mem_banks[off]->wget (addr);
	memwatch_func (addr, 1, 2, &v, MW_MASK_CPU_D_R, 0);
	return v;
}
static uae_u32 REGPARAM2 debug_bget (uaecptr addr)
{
	int off = debug_mem_off (&addr);
	uae_u32 v;
	v = debug_mem_banks[off]->bget (addr);
	memwatch_func (addr, 1, 1, &v, MW_MASK_CPU_D_R, 0);
	return v;
}
static uae_u32 REGPARAM2 debug_lgeti (uaecptr addr)
{
	int off = debug_mem_off (&addr);
	uae_u32 v;
	v = debug_mem_banks[off]->lgeti (addr);
	memwatch_func (addr, 4, 4, &v, MW_MASK_CPU_I, 0);
	return v;
}
static uae_u32 REGPARAM2 debug_wgeti (uaecptr addr)
{
	int off = debug_mem_off (&addr);
	uae_u32 v;
	v = debug_mem_banks[off]->wgeti (addr);
	memwatch_func (addr, 4, 2, &v, MW_MASK_CPU_I, 0);
	return v;
}
static void REGPARAM2 debug_lput (uaecptr addr, uae_u32 v)
{
	int off = debug_mem_off (&addr);
	if (memwatch_func (addr, 2, 4, &v, MW_MASK_CPU_D_W, 0))
		debug_mem_banks[off]->lput (addr, v);
}
static void REGPARAM2 debug_wput (uaecptr addr, uae_u32 v)
{
	int off = debug_mem_off (&addr);
	if (memwatch_func (addr, 2, 2, &v, MW_MASK_CPU_D_W, 0))
		debug_mem_banks[off]->wput (addr, v);
}
static void REGPARAM2 debug_bput (uaecptr addr, uae_u32 v)
{
	int off = debug_mem_off (&addr);
	if (memwatch_func (addr, 2, 1, &v, MW_MASK_CPU_D_W, 0))
		debug_mem_banks[off]->bput (addr, v);
}
static int REGPARAM2 debug_check (uaecptr addr, uae_u32 size)
{
	return debug_mem_banks[munge24 (addr) >> 16]->check (addr, size);
}
static uae_u8 *REGPARAM2 debug_xlate (uaecptr addr)
{
	return debug_mem_banks[munge24 (addr) >> 16]->xlateaddr (addr);
}

struct peekdma peekdma_data;

static void peekdma_save(int type, uaecptr addr, uae_u32 mask, int reg, int ptrreg)
{
	peekdma_data.type = type;
	peekdma_data.addr = addr;
	peekdma_data.mask = mask;
	peekdma_data.reg = reg;
	peekdma_data.ptrreg = ptrreg;
}

void debug_getpeekdma_value(uae_u32 v)
{
	uae_u32 vv = v;
	if (!memwatch_enabled) {
		return;
	}
	is_valid_dma(peekdma_data.reg, peekdma_data.ptrreg, peekdma_data.addr);
	if (debug_mem_banks[peekdma_data.addr >> 16] == NULL) {
		return;
	}
	if (!currprefs.z3chipmem.size) {
		peekdma_data.addr &= chipmem_bank.mask;
	}
	memwatch_func(peekdma_data.addr, 1, 2, &vv, peekdma_data.mask, peekdma_data.reg);
}
void debug_getpeekdma_value_long(uae_u32 v, int offset)
{
	uae_u32 vv = v;
	uae_u32 mask = 0xffffffff;
	if (!memwatch_enabled) {
		return;
	}
	is_valid_dma(peekdma_data.reg, peekdma_data.ptrreg, peekdma_data.addr + offset);
	if (debug_mem_banks[(peekdma_data.addr + offset) >> 16] == NULL) {
		return;
	}
	if (!currprefs.z3chipmem.size) {
		mask = chipmem_bank.mask;
	}
	memwatch_func((peekdma_data.addr + offset) & mask, 1, 4, &vv, peekdma_data.mask, peekdma_data.reg);
}


uae_u32 debug_putpeekdma_chipset(uaecptr addr, uae_u32 v, uae_u32 mask, int reg)
{
	peekdma_save(0, addr, mask, reg, 0);
	if (!memwatch_enabled)
		return v;
	peekdma_data.addr &= 0x1fe;
	peekdma_data.addr += 0xdff000;
	memwatch_func(peekdma_data.addr, 2, 2, &v, peekdma_data.mask, peekdma_data.reg);
	return v;
}

uae_u32 debug_putpeekdma_chipram(uaecptr addr, uae_u32 v, uae_u32 mask, int reg, int ptrreg)
{
	peekdma_save(1, addr, mask, reg, ptrreg);
	if (!memwatch_enabled)
		return v;
	is_valid_dma(peekdma_data.reg, peekdma_data.ptrreg, peekdma_data.addr);
	if (debug_mem_banks[peekdma_data.addr >> 16] == NULL)
		return v;
	if (!currprefs.z3chipmem.size)
		peekdma_data.addr &= chipmem_bank.mask;
	memwatch_func(peekdma_data.addr & chipmem_bank.mask, 2, 2, &v, peekdma_data.mask, peekdma_data.reg);
	return v;
}

void debug_getpeekdma_chipram(uaecptr addr, uae_u32 mask, int reg, int ptrreg)
{
	peekdma_save(2, addr, mask, reg, ptrreg);
}

static void debug_putlpeek (uaecptr addr, uae_u32 v)
{
	if (!memwatch_enabled)
		return;
	memwatch_func (addr, 2, 4, &v, MW_MASK_CPU_D_W, 0);
}
void debug_wputpeek (uaecptr addr, uae_u32 v)
{
	if (!memwatch_enabled)
		return;
	memwatch_func (addr, 2, 2, &v, MW_MASK_CPU_D_W, 0);
}
void debug_bputpeek (uaecptr addr, uae_u32 v)
{
	if (!memwatch_enabled)
		return;
	memwatch_func (addr, 2, 1, &v, MW_MASK_CPU_D_W, 0);
}
void debug_bgetpeek (uaecptr addr, uae_u32 v)
{
	uae_u32 vv = v;
	if (!memwatch_enabled)
		return;
	memwatch_func (addr, 1, 1, &vv, MW_MASK_CPU_D_R, 0);
}
void debug_wgetpeek (uaecptr addr, uae_u32 v)
{
	uae_u32 vv = v;
	if (!memwatch_enabled)
		return;
	memwatch_func (addr, 1, 2, &vv, MW_MASK_CPU_D_R, 0);
}
void debug_lgetpeek (uaecptr addr, uae_u32 v)
{
	uae_u32 vv = v;
	if (!memwatch_enabled)
		return;
	memwatch_func (addr, 1, 4, &vv, MW_MASK_CPU_D_R, 0);
}

#ifndef WINUAE_FOR_HATARI

struct membank_store
{
	addrbank *addr;
	addrbank newbank;
	int banknr;
};

static struct membank_store *membank_stores;
static int membank_total;
#define MEMWATCH_STORE_SLOTS 32

static void memwatch_reset (void)
{
	for (int i = 0; i < membank_total; i++) {
		addrbank *ab = debug_mem_banks[i];
		if (!ab)
			continue;
		map_banks_quick (ab, i, 1, 1);
	}
	for (int i = 0; membank_stores[i].addr; i++) {
		struct membank_store *ms = &membank_stores[i];
		/* name was allocated in memwatch_remap */
		xfree ((char*)ms->newbank.name);
		memset (ms, 0, sizeof (struct membank_store));
		ms->addr = NULL;
	}
	memset (debug_mem_banks, 0, membank_total * sizeof (addrbank*));
}

static void memwatch_remap (uaecptr addr)
{
	int mode = 0;
	int i;
	int banknr;
	struct membank_store *ms;
	addrbank *bank;
	addrbank *newbank = NULL;

	addr &= ~65535;
	banknr = addr >> 16;
	if (debug_mem_banks[banknr])
		return;
	bank = mem_banks[banknr];
	for (i = 0 ; i < MEMWATCH_STORE_SLOTS; i++) {
		ms = &membank_stores[i];
		if (ms->addr == NULL)
			break;
		if (ms->addr == bank) {
			newbank = &ms->newbank;
			break;
		}
	}
	if (i >= MEMWATCH_STORE_SLOTS)
		return;
	if (!newbank) {
		TCHAR tmp[200];
		_stprintf (tmp, _T("%s [D]"), bank->name);
		ms->addr = bank;
		ms->banknr = banknr;
		newbank = &ms->newbank;
		memcpy (newbank, bank, sizeof(addrbank));
		newbank->bget = mode ? mmu_bget : debug_bget;
		newbank->wget = mode ? mmu_wget : debug_wget;
		newbank->lget = mode ? mmu_lget : debug_lget;
		newbank->bput = mode ? mmu_bput : debug_bput;
		newbank->wput = mode ? mmu_wput : debug_wput;
		newbank->lput = mode ? mmu_lput : debug_lput;
		newbank->check = debug_check;
		newbank->xlateaddr = debug_xlate;
		newbank->wgeti = mode ? mmu_wgeti : debug_wgeti;
		newbank->lgeti = mode ? mmu_lgeti : debug_lgeti;
		/* name will be freed by memwatch_reset */
		newbank->name = my_strdup (tmp);
		if (!newbank->mask)
			newbank->mask = -1;
		newbank->baseaddr_direct_r = 0;
		newbank->baseaddr_direct_w = 0;
	}
	debug_mem_banks[banknr] = bank;
	map_banks_quick (newbank, banknr, 1, 1);
	// map aliases
	for (i = 0; i < membank_total; i++) {
		uaecptr addr2 = i << 16;
		addrbank *ab = &get_mem_bank(addr2);
		if (ab != ms->addr)
			continue;
		if ((addr2 & ab->mask) == (addr & bank->mask)) {
			debug_mem_banks[i] = ms->addr;
			map_banks_quick (newbank, i, 1, 1);
		}
	}
}

static void memwatch_setup (void)
{
	memwatch_reset ();
	mwnodes_start = MEMWATCH_TOTAL - 1;
	mwnodes_end = 0;
	for (int i = 0; i < MEMWATCH_TOTAL; i++) {
		struct memwatch_node *m = &mwnodes[i];
		uae_u32 size = 0;
		if (!m->size)
			continue;
		if (mwnodes_start > i)
			mwnodes_start = i;
		if (mwnodes_end < i)
			mwnodes_end = i;
		while (size < m->size) {
			memwatch_remap (m->addr + size);
			size += 65536;
		}
	}
}

static int deinitialize_memwatch (void)
{
	int oldmode;

	if (!memwatch_enabled && !mmu_enabled)
		return -1;
	memwatch_reset ();
	oldmode = mmu_enabled ? 1 : 0;
	xfree (debug_mem_banks);
	debug_mem_banks = NULL;
	xfree (debug_mem_area);
	debug_mem_area = NULL;
	xfree (membank_stores);
	membank_stores = NULL;
	memwatch_enabled = 0;
	mmu_enabled = 0;
	xfree (illgdebug);
	illgdebug = 0;
	return oldmode;
}

static void initialize_memwatch (int mode)
{
	membank_total = currprefs.address_space_24 ? 256 : 65536;
	deinitialize_memwatch ();
	debug_mem_banks = xcalloc (addrbank*, 65536);
	if (!debug_mem_banks)
		return;
	if (membank_total < 65536) {
		for (int i = 256; i < 65536; i++) {
			debug_mem_banks[i] = &dummy_bank;
		}
	}
	debug_mem_area = xcalloc (addrbank, membank_total);
	membank_stores = xcalloc (struct membank_store, MEMWATCH_STORE_SLOTS);
	for (int i = 0; i < MEMWATCH_TOTAL; i++) {
		struct memwatch_node *m = &mwnodes[i];
		m->pc = 0xffffffff;
	}
#if 0
	int i, j, as;
	addrbank *a1, *a2, *oa;
	oa = NULL;
	for (i = 0; i < as; i++) {
		a1 = debug_mem_banks[i] = debug_mem_area + i;
		a2 = mem_banks[i];
		if (a2 != oa) {
			for (j = 0; membank_stores[j].addr; j++) {
				if (membank_stores[j].addr == a2)
					break;
			}
			if (membank_stores[j].addr == NULL) {
				membank_stores[j].addr = a2;
				memcpy (&membank_stores[j].store, a2, sizeof (addrbank));
			}
		}
		memcpy (a1, a2, sizeof (addrbank));
	}
	for (i = 0; i < as; i++) {
		a2 = mem_banks[i];
		a2->bget = mode ? mmu_bget : debug_bget;
		a2->wget = mode ? mmu_wget : debug_wget;
		a2->lget = mode ? mmu_lget : debug_lget;
		a2->bput = mode ? mmu_bput : debug_bput;
		a2->wput = mode ? mmu_wput : debug_wput;
		a2->lput = mode ? mmu_lput : debug_lput;
		a2->check = debug_check;
		a2->xlateaddr = debug_xlate;
		a2->wgeti = mode ? mmu_wgeti : debug_wgeti;
		a2->lgeti = mode ? mmu_lgeti : debug_lgeti;
	}
#endif
	if (mode)
		mmu_enabled = 1;
	else
		memwatch_enabled = 1;
}

int debug_bankchange (int mode)
{
	if (mode == -1) {
		int v = deinitialize_memwatch ();
		if (v < 0)
			return -2;
		return v;
	}
	if (mode >= 0) {
		initialize_memwatch (mode);
		memwatch_setup ();
	}
	return -1;
}

addrbank *get_mem_bank_real(uaecptr addr)
{
	addrbank *ab = &get_mem_bank(addr);
	if (!memwatch_enabled)
		return ab;
	addrbank *ab2 = debug_mem_banks[addr >> 16];
	if (ab2)
		return ab2;
	return ab;
}

static const TCHAR *getsizechar (int size)
{
	if (size == 4)
		return _T(".l");
	if (size == 3)
		return _T(".3");
	if (size == 2)
		return _T(".w");
	if (size == 1)
		return _T(".b");
	return _T("");
}

void memwatch_dump2 (TCHAR *buf, int bufsize, int num)
{
	int i;
	struct memwatch_node *mwn;

	if (buf)
		memset (buf, 0, bufsize * sizeof (TCHAR));
	for (i = 0; i < MEMWATCH_TOTAL; i++) {
		if ((num >= 0 && num == i) || (num < 0)) {
			uae_u32 usedmask = 0;
			mwn = &mwnodes[i];
			if (mwn->size == 0)
				continue;
			buf = buf_out (buf, &bufsize, _T("%2d: %08X - %08X (%d) %c%c%c"),
				i, mwn->addr, mwn->addr + (mwn->size - 1), mwn->size,
				(mwn->rwi & 1) ? 'R' : ' ', (mwn->rwi & 2) ? 'W' : ' ', (mwn->rwi & 4) ? 'I' : ' ');
			if (mwn->frozen)
				buf = buf_out (buf, &bufsize, _T(" F"));
			if (mwn->val_enabled)
				buf = buf_out (buf, &bufsize, _T(" =%X%s"), mwn->val, getsizechar (mwn->val_size));
			if (mwn->modval_written)
				buf = buf_out (buf, &bufsize, _T(" =M"));
			if (mwn->mustchange)
				buf = buf_out(buf, &bufsize, _T(" C"));
			if (mwn->pc != 0xffffffff)
				buf = buf_out(buf, &bufsize, _T(" PC=%08x"), mwn->pc);
			if (mwn->reportonly)
				buf = buf_out(buf, &bufsize, _T(" L"));
			if (mwn->nobreak)
				buf = buf_out(buf, &bufsize, _T(" N"));
			if (mwn->bus_error) {
				buf = buf_out(buf, &bufsize, _T(" BER%s%s%s"),
					(mwn->bus_error & 1) ? _T("R") : _T(""),
					(mwn->bus_error & 2) ? _T("W") : _T(""),
					(mwn->bus_error & 4) ? _T("P") : _T(""));
			}
			for (int j = 0; memwatch_access_masks[j].mask; j++) {
				uae_u32 mask = memwatch_access_masks[j].mask;
				if ((mwn->access_mask & mask) == mask && (usedmask & mask) == 0) {
					buf = buf_out(buf, &bufsize, _T(" "));
					buf = buf_out(buf, &bufsize, memwatch_access_masks[j].name);
					usedmask |= mask;
				}
			}
			buf = buf_out (buf, &bufsize, _T("\n"));
		}
	}
}

static void memwatch_dump (int num)
{
	TCHAR *buf;
	int multiplier = num < 0 ? MEMWATCH_TOTAL : 1;

	buf = xmalloc (TCHAR, 50 * multiplier);
	if (!buf)
		return;
	memwatch_dump2 (buf, 50 * multiplier, num);
	f_out (stdout, _T("%s"), buf);
	xfree (buf);
}

static void memwatch (TCHAR **c)
{
	int num;
	struct memwatch_node *mwn;
	TCHAR nc, *cp;

	if (!memwatch_enabled) {
		initialize_memwatch (0);
		console_out (_T("Memwatch breakpoints enabled\n"));
		memwatch_access_validator = 0;
	}

	cp = *c;
	ignore_ws (c);
	if (!more_params (c)) {
		memwatch_dump (-1);
		return;
	}
	nc = next_char (c);
	if (nc == '-') {
		deinitialize_memwatch ();
		console_out (_T("Memwatch breakpoints disabled\n"));
		return;
	}
	if (nc == 'l') {
		memwatch_access_validator = !memwatch_access_validator;
		console_out_f(_T("Memwatch DMA validator %s\n"), memwatch_access_validator ? _T("enabled") : _T("disabled"));
		return;
	}

	if (nc == 'd') {
		if (illgdebug) {
			ignore_ws (c);
			if (more_params (c)) {
				uae_u32 addr = readhex (c);
				uae_u32 len = 1;
				if (more_params (c))
					len = readhex (c);
				console_out_f (_T("Cleared logging addresses %08X - %08X\n"), addr, addr + len);
				while (len > 0) {
					addr &= 0xffffff;
					illgdebug[addr] = 7;
					addr++;
					len--;
				}
			} else {
				illg_free();
				console_out (_T("Illegal memory access logging disabled\n"));
			}
		} else {
			illg_init ();
			ignore_ws (c);
			illgdebug_break = 0;
			if (more_params (c))
				illgdebug_break = 1;
			console_out_f (_T("Illegal memory access logging enabled. Break=%d\n"), illgdebug_break);
		}
		return;
	}
	*c = cp;
	num = readint (c);
	if (num < 0 || num >= MEMWATCH_TOTAL)
		return;
	mwn = &mwnodes[num];
	mwn->size = 0;
	ignore_ws (c);
	if (!more_params (c)) {
		console_out_f (_T("Memwatch %d removed\n"), num);
		memwatch_setup ();
		return;
	}
	mwn->addr = readhex (c);
	mwn->size = 1;
	mwn->rwi = 7;
	mwn->val_enabled = 0;
	mwn->val_mask = 0xffffffff;
	mwn->val = 0;
	mwn->access_mask = 0;
	mwn->reg = 0xffffffff;
	mwn->frozen = 0;
	mwn->modval_written = 0;
	mwn->mustchange = 0;
	mwn->bus_error = 0;
	mwn->reportonly = false;
	ignore_ws (c);
	if (more_params (c)) {
		mwn->size = readhex (c);
		ignore_ws (c);
		if (more_params (c)) {
			TCHAR *cs = *c;
			while (*cs) {
				for (int i = 0; memwatch_access_masks[i].mask; i++) {
					const TCHAR *n = memwatch_access_masks[i].name;
					int len = _tcslen(n);
					if (!_tcsnicmp(cs, n, len)) {
						if (cs[len] == 0 || cs[len] == 10 || cs[len] == 13 || cs[len] == ' ') {
							mwn->access_mask |= memwatch_access_masks[i].mask;
							while (len > 0) {
								len--;
								cs[len] = ' ';
							}
						}
					}
				}
				cs++;
			}
			ignore_ws (c);
			if (more_params(c)) {
				for (;;) {
					TCHAR ncc = _totupper(peek_next_char(c));
					TCHAR nc = _totupper(next_char(c));
					if (mwn->rwi == 7 && (nc == 'W' || nc == 'R' || nc == 'I'))
						mwn->rwi = 0;
					if (nc == 'F')
						mwn->frozen = 1;
					if (nc == 'W')
						mwn->rwi |= 2;
					if (nc == 'I')
						mwn->rwi |= 4;
					if (nc == 'R')
						mwn->rwi |= 1;
					if (nc == 'B') {
						mwn->bus_error = 0;
						for (;;) {
							ncc = next_char2(c);
							if (ncc == ' ' || ncc == 0)
								break;
							if (ncc == 'R') {
								mwn->bus_error |= 1;
								mwn->rwi |= 1;
							} else if (ncc == 'W') {
								mwn->bus_error |= 2;
								mwn->rwi |= 2;
							} else if (ncc == 'P') {
								mwn->bus_error |= 4;
								mwn->rwi |= 4;
							} else {
								break;
							}
						}
						if (!mwn->rwi)
							mwn->rwi = 7;
						if (!mwn->bus_error)
							mwn->bus_error = 7;
					}
					if (nc == 'L')
						mwn->reportonly = true;
					if (nc == 'N')
						mwn->nobreak = true;
					if (ncc == ' ' || ncc == 0)
						break;
					if (nc == 'P' && ncc == 'C') {
						next_char(c);
						mwn->pc = readhex(c, NULL);
					}
					if (!more_params(c))
						break;
				}
				ignore_ws (c);
			}
			if (more_params (c)) {
				if (_totupper (**c) == 'M') {
					mwn->modval_written = 1;
				} else if (_totupper (**c) == 'C') {
					mwn->mustchange = 1;
				} else {
					mwn->val = readhex (c, &mwn->val_size);
					mwn->val_enabled = 1;
				}
			}
		}
	}
	if (!mwn->access_mask)
		mwn->access_mask = MW_MASK_CPU_D_R | MW_MASK_CPU_D_W | MW_MASK_CPU_I;
	if (mwn->frozen && mwn->rwi == 0)
		mwn->rwi = 3;
	memwatch_setup ();
	memwatch_dump (num);
}

static void copymem(TCHAR **c)
{
	uae_u32 addr = 0, eaddr = 0, dst = 0;

	ignore_ws(c);
	if (!more_params (c))
		return;
	addr = readhex (c);
	ignore_ws (c);
	if (!more_params (c))
		return;
	eaddr = readhex (c);
	ignore_ws (c);
	if (!more_params (c))
		return;
	dst = readhex (c);

	if (addr >= eaddr)
		return;
	uae_u32 addrb = addr;
	uae_u32 dstb = dst;
	uae_u32 len = eaddr - addr;
	if (dst <= addr) {
		while (addr < eaddr) {
			put_byte(dst, get_byte(addr));
			addr++;
			dst++;
		}
	} else {
		dst += eaddr - addr;
		while (addr < eaddr) {
			dst--;
			eaddr--;
			put_byte(dst, get_byte(eaddr));
		}
	}
	console_out_f(_T("Copied from %08x - %08x to %08x - %08x\n"), addrb, addrb + len - 1, dstb, dstb + len - 1);
}

static void writeintomem (TCHAR **c)
{
	uae_u32 addr = 0;
	uae_u32 eaddr = 0xffffffff;
	uae_u32 val = 0;
	TCHAR cc;
	int len = 1;
	bool fillmode = false;

	if (**c == 'f') {
		fillmode = true;
		(*c)++;
	} else if (**c == 'c') {
		(*c)++;
		copymem(c);
		return;
	}

	ignore_ws(c);
	addr = readhex (c);
	ignore_ws (c);

	if (fillmode) {
		if (!more_params (c))
			return;
		eaddr = readhex(c);
		ignore_ws (c);
	}

	if (!more_params (c))
		return;
	TCHAR *cb = *c;
	cc = peekchar (c);
	uae_u32 addrc = addr;
	for(;;) {
		uae_u32 addrb = addr;
		*c = cb;
		if (cc == '\'' || cc == '\"') {
			next_char2(c);
			while (more_params2(c)) {
				TCHAR str[2];
				char *astr;
				cc = next_char2(c);
				if (cc == '\'' || cc == '\"')
					break;
				str[0] = cc;
				str[1] = 0;
				astr = ua (str);
				put_byte (addr, astr[0]);
				xfree (astr);
				addr++;
				if (addr >= eaddr)
					break;
			}
		} else {
			for (;;) {
				ignore_ws (c);
				if (!more_params (c))
					break;
				val = readhex (c, &len);

				if (len == 4) {
					put_long (addr, val);
					cc = 'L';
				} else if (len == 2) {
					put_word (addr, val);
					cc = 'W';
				} else if (len == 1) {
					put_byte (addr, val);
					cc = 'B';
				} else {
					break;
				}
				if (!fillmode)
					console_out_f (_T("Wrote %X (%u) at %08X.%c\n"), val, val, addr, cc);
				addr += len;
				if (addr >= eaddr)
					break;
			}
		}
		if (eaddr == 0xffffffff || addr <= addrb || addr >= eaddr)
			break;
	}
	if (eaddr != 0xffffffff)
		console_out_f(_T("Wrote data to %08x - %08x\n"), addrc, addr);
}

static uae_u8 *dump_xlate (uae_u32 addr)
{
	if (!mem_banks[addr >> 16]->check (addr, 1))
		return NULL;
	return mem_banks[addr >> 16]->xlateaddr (addr);
}

#if 0
#define UAE_MEMORY_REGIONS_MAX 64
#define UAE_MEMORY_REGION_NAME_LENGTH 64

#define UAE_MEMORY_REGION_RAM (1 << 0)
#define UAE_MEMORY_REGION_ALIAS (1 << 1)
#define UAE_MEMORY_REGION_MIRROR (1 << 2)

typedef struct UaeMemoryRegion {
	uaecptr start;
	int size;
	TCHAR name[UAE_MEMORY_REGION_NAME_LENGTH];
	TCHAR rom_name[UAE_MEMORY_REGION_NAME_LENGTH];
	uaecptr alias;
	int flags;
} UaeMemoryRegion;

typedef struct UaeMemoryMap {
	UaeMemoryRegion regions[UAE_MEMORY_REGIONS_MAX];
	int num_regions;
} UaeMemoryMap;
#endif

static const TCHAR *bankmodes[] = { _T("F32"), _T("C16"), _T("C32"), _T("CIA"), _T("F16"), _T("F16X") };

static void memory_map_dump_3(UaeMemoryMap *map, int log)
{
	bool imold;
	int i, j, max;
	addrbank *a1 = mem_banks[0];
	TCHAR txt[256];

	map->num_regions = 0;
	imold = currprefs.illegal_mem;
	currprefs.illegal_mem = false;
	max = currprefs.address_space_24 ? 256 : 65536;
	j = 0;
	for (i = 0; i < max + 1; i++) {
		addrbank *a2 = NULL;
		if (i < max)
			a2 = mem_banks[i];
		if (a1 != a2) {
			int k, mirrored, mirrored2, size, size_out;
			TCHAR size_ext;
			uae_u8 *caddr;
			TCHAR tmp[MAX_DPATH];
			const TCHAR *name = a1->name;
			struct addrbank_sub *sb = a1->sub_banks;
			int bankoffset = 0;
			int region_size;

			k = j;
			caddr = dump_xlate (k << 16);
			mirrored = caddr ? 1 : 0;
			k++;
			while (k < i && caddr) {
				if (dump_xlate (k << 16) == caddr) {
					mirrored++;
				}
				k++;
			}
			mirrored2 = mirrored;
			if (mirrored2 == 0)
				mirrored2 = 1;

			while (bankoffset < 65536) {
				int bankoffset2 = bankoffset;
				if (sb) {
					uaecptr daddr;
					if (!sb->bank)
						break;
					daddr = (j << 16) | bankoffset;
					a1 = get_sub_bank(&daddr);
					name = a1->name;
					for (;;) {
						bankoffset2 += MEMORY_MIN_SUBBANK;
						if (bankoffset2 >= 65536)
							break;
						daddr = (j << 16) | bankoffset2;
						addrbank *dab = get_sub_bank(&daddr);
						if (dab != a1)
							break;
					}
					sb++;
					size = (bankoffset2 - bankoffset) / 1024;
					region_size = size * 1024;
				} else {
					size = (i - j) << (16 - 10);
					region_size = ((i - j) << 16) / mirrored2;
				}

				if (name == NULL)
					name = _T("<none>");

				size_out = size;
				size_ext = 'K';
				if (j >= 256 && (size_out / mirrored2 >= 1024) && !((size_out / mirrored2) & 1023)) {
					size_out /= 1024;
					size_ext = 'M';
				}
				_stprintf (txt, _T("%08X %7d%c/%d = %7d%c %s%s %s %s"), (j << 16) | bankoffset, size_out, size_ext,
					mirrored, mirrored ? size_out / mirrored : size_out, size_ext,
					(a1->flags & ABFLAG_CACHE_ENABLE_INS) ? _T("I") : _T("-"),
					(a1->flags & ABFLAG_CACHE_ENABLE_DATA) ? _T("D") : _T("-"),
					bankmodes[ce_banktype[j]],
					name);
				tmp[0] = 0;
				if ((a1->flags & ABFLAG_ROM) && mirrored) {
					TCHAR *p = txt + _tcslen (txt);
					uae_u32 crc = 0xffffffff;
					if (a1->check(((j << 16) | bankoffset), (size * 1024) / mirrored))
						crc = get_crc32 (a1->xlateaddr((j << 16) | bankoffset), (size * 1024) / mirrored);
					struct romdata *rd = getromdatabycrc (crc);
					_stprintf (p, _T(" (%08X)"), crc);
					if (rd) {
						tmp[0] = '=';
						getromname (rd, tmp + 1);
						_tcscat (tmp, _T("\n"));
					}
				}

				if (a1 != &dummy_bank) {
					for (int m = 0; m < mirrored2; m++) {
						if (map->num_regions >= UAE_MEMORY_REGIONS_MAX)
							break;
						UaeMemoryRegion *r = &map->regions[map->num_regions];
						r->start = (j << 16) + bankoffset + region_size * m;
						r->size = region_size;
						r->flags = 0;
						r->memory = NULL;
						if (!(a1->flags & ABFLAG_PPCIOSPACE)) {
							r->memory = dump_xlate((j << 16) | bankoffset);
							if (r->memory)
								r->flags |= UAE_MEMORY_REGION_RAM;
						}
						/* just to make it easier to spot in debugger */
						r->alias = 0xffffffff;
						if (m >= 0) {
							r->alias = j << 16;
							r->flags |= UAE_MEMORY_REGION_ALIAS | UAE_MEMORY_REGION_MIRROR;
						}
						_stprintf(r->name, _T("%s"), name);
						_stprintf(r->rom_name, _T("%s"), tmp);
						map->num_regions += 1;
					}
				}
				_tcscat (txt, _T("\n"));
				if (log > 0)
					write_log (_T("%s"), txt);
				else if (log == 0)
					console_out (txt);
				if (tmp[0]) {
					if (log > 0)
						write_log (_T("%s"), tmp);
					else if (log == 0)
						console_out (tmp);
				}
				if (!sb)
					break;
				bankoffset = bankoffset2;
			}
			j = i;
			a1 = a2;
		}
	}
	pci_dump(log);
	currprefs.illegal_mem = imold;
}

void uae_memory_map(UaeMemoryMap *map)
{
	memory_map_dump_3(map, -1);
}

static void memory_map_dump_2 (int log)
{
	UaeMemoryMap map;
	memory_map_dump_3(&map, log);
#if 0
	for (int i = 0; i < map.num_regions; i++) {
		TCHAR txt[256];
		UaeMemoryRegion *r = &map.regions[i];
		int size = r->size / 1024;
		TCHAR size_ext = 'K';
		int mirrored = 1;
		int size_out = 0;
		_stprintf (txt, _T("%08X %7u%c/%d = %7u%c %s\n"), r->start, size, size_ext,
			r->flags & UAE_MEMORY_REGION_RAM, size, size_ext, r->name);
		if (log)
			write_log (_T("%s"), txt);
		else
			console_out (txt);
		if (r->rom_name[0]) {
			if (log)
				write_log (_T("%s"), r->rom_name);
			else
				console_out (r->rom_name);
		}
	}
#endif
}

void memory_map_dump (void)
{
	memory_map_dump_2(1);
}

STATIC_INLINE uaecptr BPTR2APTR (uaecptr addr)
{
	return addr << 2;
}
static TCHAR *BSTR2CSTR (uae_u8 *bstr)
{
	TCHAR *s;
	char *cstr = xmalloc (char, bstr[0] + 1);
	if (cstr) {
		memcpy (cstr, bstr + 1, bstr[0]);
		cstr[bstr[0]] = 0;
	}
	s = au (cstr);
	xfree (cstr);
	return s;
}

static void print_task_info (uaecptr node, bool nonactive)
{
	TCHAR *s;
	int process = get_byte_debug (node + 8) == 13 ? 1 : 0;

	console_out_f (_T("%08X: "), node);
	s = au ((char*)get_real_address_debug(get_long_debug (node + 10)));
	console_out_f (process ? _T("PROCESS '%s'\n") : _T("TASK    '%s'\n"), s);
	xfree (s);
	if (process) {
		uaecptr cli = BPTR2APTR (get_long_debug (node + 172));
		int tasknum = get_long_debug (node + 140);
		if (cli && tasknum) {
			uae_u8 *command_bstr = get_real_address_debug(BPTR2APTR (get_long_debug (cli + 16)));
			TCHAR *command = BSTR2CSTR (command_bstr);
			console_out_f (_T(" [%d, '%s']\n"), tasknum, command);
			xfree (command);
		} else {
			console_out (_T("\n"));
		}
	}
	if (nonactive) {
		uae_u32 sigwait = get_long_debug(node + 22);
		if (sigwait)
			console_out_f(_T("          Waiting signals: %08x\n"), sigwait);
		int offset = kickstart_version >= 37 ? 74 : 70;
		uae_u32 sp = get_long_debug(node + 54) + offset;
		uae_u32 pc = get_long_debug(sp);
		console_out_f(_T("          SP: %08x PC: %08x\n"), sp, pc);
	}
}

static void show_exec_tasks (void)
{
	uaecptr execbase = get_long_debug (4);
	uaecptr taskready = execbase + 406;
	uaecptr taskwait = execbase + 420;
	uaecptr node;
	console_out_f (_T("Execbase at 0x%08X\n"), execbase);
	console_out (_T("Current:\n"));
	node = get_long_debug (execbase + 276);
	print_task_info (node, false);
	console_out_f (_T("Ready:\n"));
	node = get_long_debug (taskready);
	while (node && get_long_debug(node)) {
		print_task_info (node, true);
		node = get_long_debug (node);
	}
	console_out (_T("Waiting:\n"));
	node = get_long_debug (taskwait);
	while (node && get_long_debug(node)) {
		print_task_info (node, true);
		node = get_long_debug (node);
	}
}

static uaecptr get_base (const uae_char *name, int offset)
{
	uaecptr v = get_long_debug (4);
	addrbank *b = &get_mem_bank(v);

	if (!b || !b->check (v, 400) || !(b->flags & ABFLAG_RAM))
		return 0;
	v += offset;
	while ((v = get_long_debug (v))) {
		uae_u32 v2;
		uae_u8 *p;
		b = &get_mem_bank (v);
		if (!b || !b->check (v, 32) || (!(b->flags & ABFLAG_RAM) && !(b->flags & ABFLAG_ROMIN)))
			goto fail;
		v2 = get_long_debug (v + 10); // name
		b = &get_mem_bank (v2);
		if (!b || !b->check (v2, 20))
			goto fail;
		if ((b->flags & ABFLAG_ROM) || (b->flags & ABFLAG_RAM) || (b->flags & ABFLAG_ROMIN)) {
			p = get_real_address_debug(v2);
			if (!memcmp (p, name, strlen (name) + 1))
				return v;
		}
	}
	return 0;
fail:
	return 0xffffffff;
}

static TCHAR *getfrombstr(uaecptr pp)
{
	uae_u8 len = get_byte(pp << 2);
	TCHAR *s = xcalloc (TCHAR, len + 1);
	char data[256];
	for (int i = 0; i < len; i++) {
		data[i] = get_byte((pp << 2) + 1 + i);
		data[i + 1] = 0;
	}
	return au_copy (s, len + 1, data);
}

// read one byte from expansion autoconfig ROM
static void copyromdata(uae_u8 bustype, uaecptr rom, int offset, uae_u8 *out, int size)
{
	switch (bustype & 0xc0)
	{
	case 0x00: // nibble
		while (size-- > 0) {
			*out++ = (get_byte_debug(rom + offset * 4 + 0) & 0xf0) | ((get_byte_debug(rom + offset * 4 + 2) & 0xf0) >> 4);
			offset++;
		}
		break;
	case 0x40: // byte
		while (size-- > 0) {
			*out++ = get_byte_debug(rom + offset * 2);
			offset++;
		}
		break;
	case 0x80: // word
	default:
		while (size-- > 0) {
			*out++ = get_byte_debug(rom + offset);
			offset++;
		}
		break;
	}
}

static void show_exec_lists (TCHAR *t)
{
	uaecptr execbase = get_long_debug (4);
	uaecptr list = 0, node;
	TCHAR c = t[0];

	if (c == 'o' || c == 'O') { // doslist
		uaecptr dosbase = get_base ("dos.library", 378);
		if (dosbase) {
			uaecptr rootnode = get_long_debug (dosbase + 34);
			uaecptr dosinfo = get_long_debug (rootnode + 24) << 2;
			console_out_f (_T("ROOTNODE: %08x DOSINFO: %08x\n"), rootnode, dosinfo);
			uaecptr doslist = get_long_debug (dosinfo + 4) << 2;
			while (doslist) {
				int type = get_long_debug (doslist + 4);
				uaecptr msgport = get_long_debug (doslist + 8);
				uaecptr lock = get_long_debug(doslist + 12);
				TCHAR *name = getfrombstr(get_long_debug(doslist + 40));
				console_out_f(_T("%08x: Type=%d Port=%08x Lock=%08x '%s'\n"), doslist, type, msgport, lock, name);
				if (type == 0) {
					uaecptr fssm = get_long_debug(doslist + 28) << 2;
					console_out_f (_T(" - H=%08x Stack=%5d Pri=%2d Start=%08x Seg=%08x GV=%08x\n"),
						get_long_debug (doslist + 16) << 2, get_long_debug (doslist + 20),
						get_long_debug (doslist + 24), fssm,
						get_long_debug (doslist + 32) << 2, get_long_debug (doslist + 36));
					if (fssm >= 0x100 && (fssm & 3) == 0) {
						TCHAR *unitname = getfrombstr(get_long_debug(fssm + 4));
						console_out_f (_T("   %s:%d %08x\n"), unitname, get_long_debug(fssm), get_long_debug(fssm + 8));
						uaecptr de = get_long_debug(fssm + 8) << 2;
						if (de) {
							console_out_f (_T("    TableSize       %u\n"), get_long_debug(de + 0));
							console_out_f (_T("    SizeBlock       %u\n"), get_long_debug(de + 4));
							console_out_f (_T("    SecOrg          %u\n"), get_long_debug(de + 8));
							console_out_f (_T("    Surfaces        %u\n"), get_long_debug(de + 12));
							console_out_f (_T("    SectorPerBlock  %u\n"), get_long_debug(de + 16));
							console_out_f (_T("    BlocksPerTrack  %u\n"), get_long_debug(de + 20));
							console_out_f (_T("    Reserved        %u\n"), get_long_debug(de + 24));
							console_out_f (_T("    PreAlloc        %u\n"), get_long_debug(de + 28));
							console_out_f (_T("    Interleave      %u\n"), get_long_debug(de + 32));
							console_out_f (_T("    LowCyl          %u\n"), get_long_debug(de + 36));
							console_out_f (_T("    HighCyl         %u (Total %u)\n"), get_long_debug(de + 40), get_long_debug(de + 40) - get_long_debug(de + 36) + 1);
							console_out_f (_T("    NumBuffers      %u\n"), get_long_debug(de + 44));
							console_out_f (_T("    BufMemType      0x%08x\n"), get_long_debug(de + 48));
							console_out_f (_T("    MaxTransfer     0x%08x\n"), get_long_debug(de + 52));
							console_out_f (_T("    Mask            0x%08x\n"), get_long_debug(de + 56));
							console_out_f (_T("    BootPri         %d\n"), get_long_debug(de + 60));
							console_out_f (_T("    DosType         0x%08x\n"), get_long_debug(de + 64));
						}
						xfree(unitname);
					}
				} else if (type == 2) {
					console_out_f(_T(" - VolumeDate=%08x %08x %08x LockList=%08x DiskType=%08x\n"),
						get_long_debug(doslist + 16), get_long_debug(doslist + 20), get_long_debug(doslist + 24),
						get_long_debug(doslist + 28),
						get_long_debug(doslist + 32));
				}
				xfree (name);
				doslist = get_long_debug (doslist) << 2;
			}
		} else {
			console_out_f (_T("can't find dos.library\n"));
		}
		return;
	} else if (c == 'i' || c == 'I') { // interrupts
		static const int it[] = {  1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0 };
		static const int it2[] = { 1, 1, 1, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 6, 6, 7 };
		list = execbase + 84;
		for (int i = 0; i < 16; i++) {
			console_out_f (_T("%2d %d: %08X\n"), i + 1, it2[i], list);
			if (it[i]) {
				console_out_f (_T("  [H] %08X\n"), get_long_debug (list));
				node = get_long_debug (list + 8);
				if (node) {
					uae_u8 *addr = get_real_address_debug(get_long_debug (node + 10));
					TCHAR *name = addr ? au ((char*)addr) : au("<null>");
					console_out_f (_T("      %08X (C=%08X D=%08X) '%s'\n"), node, get_long_debug (list + 4), get_long_debug (list), name);
					xfree (name);
				}
			} else {
				int cnt = 0;
				node = get_long_debug (list);
				node = get_long_debug (node);
				while (get_long_debug (node)) {
					uae_u8 *addr = get_real_address_debug(get_long_debug (node + 10));
					TCHAR *name = addr ? au ((char*)addr) : au("<null>");
					uae_s8 pri = get_byte_debug(node + 9);
					console_out_f (_T("  [S] %08x %+03d (C=%08x D=%08X) '%s'\n"), node, pri, get_long_debug (node + 18), get_long_debug (node + 14), name);
					if (i == 4 - 1 || i == 14 - 1) {
						if (!_tcsicmp (name, _T("cia-a")) || !_tcsicmp (name, _T("cia-b"))) {
							static const TCHAR *ciai[] = { _T("A"), _T("B"), _T("ALRM"), _T("SP"), _T("FLG") };
							uaecptr cia = node + 22;
							for (int j = 0; j < 5; j++) {
								uaecptr ciap = get_long_debug (cia);
								console_out_f (_T("        %5s: %08x"), ciai[j], ciap);
								if (ciap) {
									uae_u8 *addr2 = get_real_address_debug(get_long_debug (ciap + 10));
									TCHAR *name2 = addr ? au ((char*)addr2) : au("<null>");
									console_out_f (_T(" (C=%08x D=%08X) '%s'"), get_long_debug (ciap + 18), get_long_debug (ciap + 14), name2);
									xfree (name2);
								}
								console_out_f (_T("\n"));
								cia += 4;
							}
						}
					}
					xfree (name);
					node = get_long_debug (node);
					cnt++;
				}
				if (!cnt)
					console_out_f (_T("  [S] <none>\n"));
			}
			list += 12;
		}
		return;
	} else if (c == 'e') { // expansion
		uaecptr expbase = get_base("expansion.library", 378);
		if (expbase) {
			if (t[1] == 'm') {
				uaecptr list = get_long_debug(expbase + 74);
				while (list && get_long_debug(list)) {
					uaecptr name = get_long(list + 10);
					uae_s8 pri = get_byte(list + 9);
					uae_u16 flags = get_word_debug(list + 14);
					uae_u32 dn = get_long_debug(list + 16);
					uae_u8 *addr = get_real_address_debug(name);
					TCHAR *name1 = addr ? au((char*)addr) : au("<null>");
					my_trim(name1);
					console_out_f(_T("%08x %04x %08x %d %s\n"), list, flags, dn, pri, name1);
					xfree(name1);
					list = get_long_debug(list);
				}
			} else {
				list = get_long_debug(expbase + 60);
				while (list && get_long_debug(list)) {
					uae_u32 addr = get_long_debug(list + 32);
					uae_u16 rom_vector = get_word_debug(list + 16 + 10);
					uae_u8 type = get_byte_debug(list + 16 + 0);
					console_out_f(_T("%02x %02x %08x %08x %04x %02x %08x %04x (%u/%u)\n"),
						type, get_byte_debug(list + 16 + 2),
						addr, get_long_debug(list + 36),
						get_word_debug(list + 16 + 4), get_byte_debug(list + 16 + 1),
						get_long_debug(list + 16 + 6), rom_vector,
						get_word_debug(list + 16 + 4), get_byte_debug(list + 16 + 1));
					for (int i = 0; i < 16; i++) {
						console_out_f(_T("%02x"), get_byte_debug(list + 16 + i));
						if (i < 15)
							console_out_f(_T("."));
					}
					console_out_f(_T("\n"));
					if ((type & 0x10)) {
						uae_u8 diagarea[256];
						uae_u16 nameoffset;
						uaecptr rom = addr + rom_vector;
						uae_u8 config = get_byte_debug(rom);
						copyromdata(config, rom, 0, diagarea, 16);
						nameoffset = (diagarea[8] << 8) | diagarea[9];
						console_out_f(_T(" %02x %02x Size %04x Diag %04x Boot %04x Name %04x %04x %04x\n"),
							diagarea[0], diagarea[1],
							(diagarea[2] << 8) | diagarea[3],
							(diagarea[4] << 8) | diagarea[5],
							(diagarea[6] << 8) | diagarea[7],
							nameoffset,
							(diagarea[10] << 8) | diagarea[11],
							(diagarea[12] << 8) | diagarea[13]);
						if (nameoffset != 0 && nameoffset != 0xffff) {
							copyromdata(config, rom, nameoffset, diagarea, 256);
							diagarea[sizeof diagarea - 1] = 0;
							TCHAR *str = au((char*)diagarea);
							console_out_f(_T(" '%s'\n"), str);
							xfree(str);
						}
					}
					list = get_long_debug(list);
				}
			}
		}
		return;
	} else if (c == 'R') { // residents
		list = get_long_debug(execbase + 300);
		while (list) {
			uaecptr resident = get_long_debug (list);
			if (!resident)
				break;
			if (resident & 0x80000000) {
				console_out_f (_T("-> %08X\n"), resident & 0x7fffffff);
				list = resident & 0x7fffffff;
				continue;
			}
			uae_u8 *addr;
			addr = get_real_address_debug(get_long_debug (resident + 14));
			TCHAR *name1 = addr ? au ((char*)addr) : au("<null>");
			my_trim (name1);
			addr = get_real_address_debug(get_long_debug (resident + 18));
			TCHAR *name2 = addr ? au ((char*)addr) : au("<null>");
			my_trim (name2);
			console_out_f (_T("%08X %08X: %02X %3d %02X %+3.3d '%s' ('%s')\n"),
				list, resident,
				get_byte_debug (resident + 10), get_byte_debug (resident + 11),
				get_byte_debug (resident + 12), (uae_s8)get_byte_debug (resident + 13),
				name1, name2);
			xfree (name2);
			xfree (name1);
			list += 4;
		}
		return;
	} else if (c == 'f' || c== 'F') { // filesystem.resource
		uaecptr fs = get_base ("FileSystem.resource", 336);
		if (fs) {
			static const TCHAR *fsnames[] = {
				_T("DosType"),
				_T("Version"),
				_T("PatchFlags"),
				_T("Type"),
				_T("Task"),
				_T("Lock"),
				_T("Handler"),
				_T("StackSize"),
				_T("Priority"),
				_T("Startup"),
				_T("SegList"),
				_T("GlobalVec"),
				NULL
			};
			uae_u8 *addr = get_real_address_debug(get_long_debug (fs + 14));
			TCHAR *name = addr ? au ((char*)addr) : au ("<null>");
			my_trim (name);
			console_out_f (_T("%08x: '%s'\n"), fs, name);
			xfree (name);
			node = get_long_debug (fs + 18);
			while (get_long_debug (node)) {
				TCHAR *name = au ((char*)get_real_address_debug(get_long_debug (node + 10)));
				my_trim (name);
				console_out_f (_T("%08x: '%s'\n"), node, name);
				xfree (name);
				for (int i = 0; fsnames[i]; i++) {
					uae_u32 v = get_long_debug (node + 14 + i * 4);
					console_out_f (_T("%16s = %08x %d\n"), fsnames[i], v, v);
				}
				console_out_f (_T("\n"));
				node = get_long_debug (node);
			}

		} else {
			console_out_f (_T("FileSystem.resource not found.\n"));
		}
		return;
	} else if (c == 'm' || c == 'M') { // memory
		list = execbase + 322;
		node = get_long_debug (list);
		while (get_long_debug (node)) {
			TCHAR *name = au ((char*)get_real_address_debug(get_long_debug (node + 10)));
			uae_u16 v = get_word_debug (node + 8);
			console_out_f (_T("%08x %d %d %s\n"), node, (int)((v >> 8) & 0xff), (uae_s8)(v & 0xff), name);
			xfree (name);
			console_out_f (_T("Attributes %04x First %08x Lower %08x Upper %08x Free %d\n"),
				get_word_debug (node + 14), get_long_debug (node + 16), get_long_debug (node + 20), 
				get_long_debug (node + 24), get_long_debug (node + 28));
			uaecptr mc = get_long_debug (node + 16);
			while (mc) {
				uae_u32 mc1 = get_long_debug (mc);
				uae_u32 mc2 = get_long_debug (mc + 4);
				console_out_f (_T(" %08x: %08x-%08x,%08x,%08x (%d)\n"), mc, mc, mc + mc2, mc1, mc2, mc2);
				mc = mc1;
			}
			console_out_f (_T("\n"));
			node = get_long_debug (node);
		}
		return;
	}

	bool full = false;
	switch (c)
	{
	case 'r': // resources
		list = execbase + 336;
		break;
	case 'd': // devices
		list = execbase + 350;
		full = true;
		break;
	case 'l': // libraries
		list = execbase + 378;
		full = true;
		break;
	case 'p': // ports
		list = execbase + 392;
		break;
	case 's': // semaphores
		list = execbase + 532;
		break;
	}
	if (list == 0)
		return;
	node = get_long_debug (list);
	while (get_long_debug (node)) {
		TCHAR *name = au ((char*)get_real_address_debug(get_long_debug (node + 10)));
		uae_u16 v = get_word_debug (node + 8);
		console_out_f (_T("%08x %d %d"), node, (int)((v >> 8) & 0xff), (uae_s8)(v & 0xff));
		if (full) {
			uae_u16 ver = get_word_debug(node + 20);
			uae_u16 rev = get_word_debug(node + 22);
			uae_u32 op = get_word_debug(node + 32);
			console_out_f(_T(" %d.%d %d"), ver, rev, op);
		}
		console_out_f(_T(" %s"), name);
		xfree (name);
		if (full) {
			uaecptr idstring = get_long_debug(node + 24);
			if (idstring) {
				name = au((char*)get_real_address_debug(idstring));
				console_out_f(_T(" (%s)"), name);
				xfree(name);
			}
		}
		console_out_f(_T("\n"));
		node = get_long_debug (node);
	}
}

static int debug_vpos = -1;
static int debug_hpos = -1;

static void breakfunc(uae_u32 v)
{
	write_log(_T("Cycle breakpoint hit\n"));
	debugging = 1;
	debug_vpos = -1;
	debug_hpos = -1;
	debug_cycles(2);
	set_special(SPCFLAG_BRK);
}

void debug_hsync(void)
{
	if (debug_vpos < 0) {
		return;
	}
	if (debug_vpos != vpos) {
		return;
	}
	if (debug_hpos <= 0) {
		breakfunc(0);
	} else {
		if (current_hpos() < debug_hpos) {
			event2_newevent_x(-1, debug_hpos - current_hpos(), 0, breakfunc);
		} else {
			breakfunc(0);
		}
	}
}

static int cycle_breakpoint(TCHAR **c)
{
	TCHAR nc = (*c)[0];
	next_char(c);
	if (more_params(c)) {
		int count = readint(c);
		if (nc == 's') {
			if (more_params(c)) {
				debug_vpos = count;
				debug_hpos = readint(c);
				if (debug_vpos == vpos && debug_hpos > current_hpos()) {
					debug_vpos = -1;
					count = debug_hpos - current_hpos();
					debug_hpos = -1;
				} else {
					return 1;
				}
			} else {
				count *= maxhpos;
			}
		}
		if (count > 0) {
			event2_newevent_x(-1, count, 0, breakfunc);
		}
		return 1;
	}
	return 0;
}

#if 0
static int trace_same_insn_count;
static uae_u8 trace_insn_copy[10];
static struct regstruct trace_prev_regs;
#endif
static uaecptr nextpc;

int instruction_breakpoint (TCHAR **c)
{
	struct breakpoint_node *bpn;
	int i;

	if (more_params (c)) {
		TCHAR nc = _totupper ((*c)[0]);
		if (nc == 'O') {
			// bpnum register operation value1 [mask value2]
			next_char(c);
			if (more_params(c)) {
				int bpidx = readint(c);
				if (more_params(c) && bpidx >= 0 && bpidx < BREAKPOINT_TOTAL) {
					bpn = &bpnodes[bpidx];
					int regid = getregidx(c);
					if (regid >= 0) {
						bpn->type = regid;
						bpn->mask = 0xffffffff;
						if (more_params(c)) {
							int operid = getoperidx(c);
							if (more_params(c) && operid >= 0) {
								bpn->oper = operid;
								bpn->value1 = readhex(c);
								bpn->enabled = 1;
								if (more_params(c)) {
									bpn->mask = readhex(c);
									if (more_params(c))  {
										bpn->value2 = readhex(c);
									}
								}
								console_out(_T("Breakpoint added.\n"));
							}
						}
					}
				}
			}
			return 0;
		} else if (nc == 'I') {
			uae_u16 opcodes[32];
			next_char(c);
			ignore_ws(c);
			trace_param[1] = 0x10000;
			trace_param[2] = 0x10000;

			int w = m68k_asm(*c, opcodes, 0);
			if (w > 0) {
				trace_param[0] = opcodes[0];
				if (w > 1) {
					trace_param[1] = opcodes[1];
					if (w > 2) {
						trace_param[2] = opcodes[2];
					}
				}
			} else {
				if (more_params(c)) {
					trace_param[0] = readhex(c);
					if (more_params(c)) {
						trace_param[1] = readhex(c);
					}
					if (more_params(c)) {
						trace_param[2] = readhex(c);
					}
				} else {
					trace_param[0] = 0x10000;
				}
			}
			trace_mode = TRACE_MATCH_INS;
			return 1;
		} else if (nc == 'D' && (*c)[1] == 0) {
			for (i = 0; i < BREAKPOINT_TOTAL; i++)
				bpnodes[i].enabled = 0;
			console_out(_T("All breakpoints removed.\n"));
			return 0;
		} else if (nc == 'R' && (*c)[1] == 0) {
			if (more_params(c)) {
				int bpnum = readint(c);
				if (bpnum >= 0 && bpnum < BREAKPOINT_TOTAL) {
					bpnodes[bpnum].enabled = 0;
					console_out_f(_T("Breakpoint %d removed.\n"), bpnum);
				}
			}
			return 0;
		} else if (nc == 'L') {
			int got = 0;
			for (i = 0; i < BREAKPOINT_TOTAL; i++) {
				bpn = &bpnodes[i];
				if (!bpn->enabled)
					continue;
				console_out_f (_T("%d: %s %s %08x [%08x %08x]\n"), i, debugregs[bpn->type], debugoper[bpn->oper], bpn->value1, bpn->mask, bpn->value2);
				got = 1;
			}
			if (!got)
				console_out (_T("No breakpoints.\n"));
			else
				console_out (_T("\n"));
			return 0;
		}
		trace_mode = TRACE_RANGE_PC;
		trace_param[0] = readhex (c);
		if (more_params (c)) {
			trace_param[1] = readhex (c);
			return 1;
		} else {
			for (i = 0; i < BREAKPOINT_TOTAL; i++) {
				bpn = &bpnodes[i];
				if (bpn->enabled && bpn->value1 == trace_param[0]) {
					bpn->enabled = 0;
					console_out (_T("Breakpoint removed.\n"));
					trace_mode = 0;
					return 0;
				}
			}
			for (i = 0; i < BREAKPOINT_TOTAL; i++) {
				bpn = &bpnodes[i];
				if (bpn->enabled)
					continue;
				bpn->value1 = trace_param[0];
				bpn->type = BREAKPOINT_REG_PC;
				bpn->oper = BREAKPOINT_CMP_EQUAL;
				bpn->enabled = 1;
				console_out (_T("Breakpoint added.\n"));
				trace_mode = 0;
				break;
			}
			return 0;
		}
	}
	trace_mode = TRACE_RAM_PC;
	return 1;
}

static int process_breakpoint (TCHAR **c)
{
	processptr = 0;
	xfree (processname);
	processname = NULL;
	if (!more_params (c))
		return 0;
	if (**c == '\"') {
		TCHAR pn[200];
		next_string (c, pn, 200, 0);
		processname = ua (pn);
	} else {
		processptr = readhex (c);
	}
	trace_mode = TRACE_CHECKONLY;
	return 1;
}

static void saveloadmem (TCHAR **cc, bool save)
{
	uae_u8 b;
	uae_u32 src, src2;
	int len, len2;
	TCHAR *name;
	FILE *fp;

	if (!more_params (cc))
		goto S_argh;

	name = *cc;
	while (**cc != '\0' && !isspace (**cc))
		(*cc)++;
	if (!isspace (**cc))
		goto S_argh;

	**cc = '\0';
	(*cc)++;
	if (!more_params (cc))
		goto S_argh;
	src2 = src = readhex (cc);
	if (save) {
		if (!more_params(cc))
			goto S_argh;
	}
	len2 = len = -1;
	if (more_params(cc)) {
		len2 = len = readhex (cc);
	}
	fp = uae_tfopen (name, save ? _T("wb") : _T("rb"));
	if (fp == NULL) {
		console_out_f (_T("Couldn't open file '%s'.\n"), name);
		return;
	}
	if (save) {
		while (len > 0) {
			b = get_byte_debug (src);
			src++;
			len--;
			if (fwrite (&b, 1, 1, fp) != 1) {
				console_out (_T("Error writing file.\n"));
				break;
			}
		}
		if (len == 0)
			console_out_f (_T("Wrote %08X - %08X (%d bytes) to '%s'.\n"),
				src2, src2 + len2, len2, name);
	} else {
		len2 = 0;
		while (len != 0) {
			if (fread(&b, 1, 1, fp) != 1) {
				if (len > 0)
					console_out (_T("Unexpected end of file.\n"));
				len = 0;
				break;
			}
			put_byte (src, b);
			src++;
			if (len > 0)
				len--;
			len2++;
		}
		if (len == 0)
			console_out_f (_T("Read %08X - %08X (%d bytes) to '%s'.\n"),
				src2, src2 + len2, len2, name);
	}
	fclose (fp);
	return;
S_argh:
	console_out (_T("Command needs more arguments!\n"));
}

static void searchmem (TCHAR **cc)
{
	int i, sslen, got, val, stringmode;
	uae_u8 ss[256];
	uae_u32 addr, endaddr;
	TCHAR nc;

	got = 0;
	sslen = 0;
	stringmode = 0;
	ignore_ws (cc);
	if (**cc == '"') {
		stringmode = 1;
		(*cc)++;
		while (**cc != '"' && **cc != 0) {
			ss[sslen++] = tolower (**cc);
			(*cc)++;
		}
		if (**cc != 0)
			(*cc)++;
	} else {
		for (;;) {
			if (**cc == 32 || **cc == 0)
				break;
			nc = _totupper (next_char (cc));
			if (isspace (nc))
				break;
			if (isdigit(nc))
				val = nc - '0';
			else
				val = nc - 'A' + 10;
			if (val < 0 || val > 15)
				return;
			val *= 16;
			if (**cc == 32 || **cc == 0)
				break;
			nc = _totupper (next_char (cc));
			if (isspace (nc))
				break;
			if (isdigit(nc))
				val += nc - '0';
			else
				val += nc - 'A' + 10;
			if (val < 0 || val > 255)
				return;
			ss[sslen++] = (uae_u8)val;
		}
	}
	if (sslen == 0)
		return;
	ignore_ws (cc);
	addr = 0xffffffff;
	endaddr = lastaddr ();
	if (more_params (cc)) {
		addr = readhex (cc);
		addr--;
		if (more_params(cc)) {
			endaddr = readhex(cc);
		}
	}
	console_out_f (_T("Searching from %08X to %08X..\n"), addr + 1, endaddr);
	while ((addr = nextaddr (addr, endaddr, NULL, true)) != 0xffffffff) {
		if (addr == endaddr)
			break;
		for (i = 0; i < sslen; i++) {
			uae_u8 b = get_byte_debug (addr + i);
			if (stringmode) {
				if (tolower (b) != ss[i])
					break;
			} else {
				if (b != ss[i])
					break;
			}
		}
		if (i == sslen) {
			got++;
			console_out_f (_T(" %08X"), addr);
			if (got > 100) {
				console_out (_T("\nMore than 100 results, aborting.."));
				break;
			}
		}
		if  (iscancel (65536)) {
			console_out_f (_T("Aborted at %08X\n"), addr);
			break;
		}
	}
	if (!got)
		console_out (_T("nothing found"));
	console_out (_T("\n"));
}

static int staterecorder (TCHAR **cc)
{
#if 0
	TCHAR nc;

	if (!more_params (cc)) {
		if (savestate_dorewind (1)) {
			debug_rewind = 1;
			return 1;
		}
		return 0;
	}
	nc = next_char (cc);
	if (nc == 'l') {
		savestate_listrewind ();
		return 0;
	}
#endif
	return 0;
}

static int debugtest_modes[DEBUGTEST_MAX];
static const TCHAR *debugtest_names[] = {
	_T("Blitter"), _T("Keyboard"), _T("Floppy")
};

void debugtest (enum debugtest_item di, const TCHAR *format, ...)
{
	va_list parms;
	TCHAR buffer[1000];

	if (!debugtest_modes[di])
		return;
	va_start (parms, format);
	_vsntprintf (buffer, 1000 - 1, format, parms);
	va_end (parms);
	write_log (_T("%s PC=%08X: %s\n"), debugtest_names[di], M68K_GETPC, buffer);
	if (debugtest_modes[di] == 2)
		activate_debugger_new();
}

static void debugtest_set (TCHAR **inptr)
{
	int i, val, val2;
	ignore_ws (inptr);

	val2 = 1;
	if (!more_params (inptr)) {
		for (i = 0; i < DEBUGTEST_MAX; i++)
			debugtest_modes[i] = 0;
		console_out (_T("All debugtests disabled\n"));
		return;
	}
	val = readint (inptr);
	if (more_params (inptr)) {
		val2 = readint (inptr);
		if (val2 > 0)
			val2 = 2;
	}
	if (val < 0) {
		for (i = 0; i < DEBUGTEST_MAX; i++)
			debugtest_modes[i] = val2;
		console_out (_T("All debugtests enabled\n"));
		return;
	}
	if (val >= 0 && val < DEBUGTEST_MAX) {
		if (debugtest_modes[val])
			debugtest_modes[val] = 0;
		else
			debugtest_modes[val] = val2;
		console_out_f (_T("Debugtest '%s': %s. break = %s\n"),
			debugtest_names[val], debugtest_modes[val] ? _T("on") :_T("off"), val2 == 2 ? _T("on") : _T("off"));
	}
}

static void debug_sprite (TCHAR **inptr)
{
	uaecptr saddr, addr, addr2;
	int xpos, xpos_ecs;
	int ypos, ypos_ecs;
	int ypose, ypose_ecs;
	int attach;
	uae_u64 w1, w2, ww1, ww2;
	int size = 1, width;
	int ecs, sh10;
	int y, i;
	TCHAR tmp[80];
	int max = 2;

	addr2 = 0;
	ignore_ws (inptr);
	addr = readhex (inptr);
	ignore_ws (inptr);
	if (more_params (inptr))
		size = readhex (inptr);
	if (size != 1 && size != 2 && size != 4) {
		addr2 = size;
		ignore_ws (inptr);
		if (more_params (inptr))
			size = readint (inptr);
		if (size != 1 && size != 2 && size != 4)
			size = 1;
	}
	for (;;) {
		ecs = 0;
		sh10 = 0;
		saddr = addr;
		width = size * 16;
		w1 = get_word_debug (addr);
		w2 = get_word_debug (addr + size * 2);
		console_out_f (_T("    %06X "), addr);
		for (i = 0; i < size * 2; i++)
			console_out_f (_T("%04X "), get_word_debug (addr + i * 2));
		console_out_f (_T("\n"));

		ypos = w1 >> 8;
		xpos = w1 & 255;
		ypose = w2 >> 8;
		attach = (w2 & 0x80) ? 1 : 0;
		if (w2 & 4)
			ypos |= 256;
		if (w2 & 2)
			ypose |= 256;
		ypos_ecs = ypos;
		ypose_ecs = ypose;
		if (w2 & 0x40)
			ypos_ecs |= 512;
		if (w2 & 0x20)
			ypose_ecs |= 512;
		xpos <<= 1;
		if (w2 & 0x01)
			xpos |= 1;
		xpos_ecs = xpos << 2;
		if (w2 & 0x10)
			xpos_ecs |= 2;
		if (w2 & 0x08)
			xpos_ecs |= 1;
		if (w2 & (0x40 | 0x20 | 0x10 | 0x08))
			ecs = 1;
		if (w1 & 0x80)
			sh10 = 1;
		if (ypose < ypos)
			ypose += 256;

		for (y = ypos; y < ypose; y++) {
			int x;
			addr += size * 4;
			if (addr2)
				addr2 += size * 4;
			if (size == 1) {
				w1 = get_word_debug (addr);
				w2 = get_word_debug (addr + 2);
				if (addr2) {
					ww1 = get_word_debug (addr2);
					ww2 = get_word_debug (addr2 + 2);
				}
			} else if (size == 2) {
				w1 = get_long_debug (addr);
				w2 = get_long_debug (addr + 4);
				if (addr2) {
					ww1 = get_long_debug (addr2);
					ww2 = get_long_debug (addr2 + 4);
				}
			} else if (size == 4) {
				w1 = get_long_debug (addr + 0);
				w2 = get_long_debug (addr + 8);
				w1 <<= 32;
				w2 <<= 32;
				w1 |= get_long_debug (addr + 4);
				w2 |= get_long_debug (addr + 12);
				if (addr2) {
					ww1 = get_long_debug (addr2 + 0);
					ww2 = get_long_debug (addr2 + 8);
					ww1 <<= 32;
					ww2 <<= 32;
					ww1 |= get_long_debug (addr2 + 4);
					ww2 |= get_long_debug (addr2 + 12);
				}
			}
			width = size * 16;
			for (x = 0; x < width; x++) {
				int v1 = w1 & 1;
				int v2 = w2 & 1;
				int v = v1 * 2 + v2;
				w1 >>= 1;
				w2 >>= 1;
				if (addr2) {
					int vv1 = ww1 & 1;
					int vv2 = ww2 & 1;
					int vv = vv1 * 2 + vv2;
					vv1 >>= 1;
					vv2 >>= 1;
					v *= 4;
					v += vv;
					tmp[width - (x + 1)] = v >= 10 ? 'A' + v - 10 : v + '0';
				} else {
					tmp[width - (x + 1)] = v + '0';
				}
			}
			tmp[width] = 0;
			console_out_f (_T("%3d %06X %s\n"), y, addr, tmp);
		}

		console_out_f (_T("Sprite address %08X, width = %d\n"), saddr, size * 16);
		console_out_f (_T("OCS: StartX=%d StartY=%d EndY=%d\n"), xpos, ypos, ypose);
		console_out_f (_T("ECS: StartX=%d (%d.%d) StartY=%d EndY=%d%s\n"), xpos_ecs, xpos_ecs / 4, xpos_ecs & 3, ypos_ecs, ypose_ecs, ecs ? _T(" (*)") : _T(""));
		console_out_f (_T("Attach: %d. AGA SSCAN/SH10 bit: %d\n"), attach, sh10);

		addr += size * 4;
		if (get_word_debug (addr) == 0 && get_word_debug (addr + size * 4) == 0)
			break;
		max--;
		if (max <= 0)
			break;
	}

}

int debug_write_memory_16 (uaecptr addr, uae_u16 v)
{
	addrbank *ad;
	
	ad = &get_mem_bank (addr);
	if (ad) {
		ad->wput (addr, v);
		return 1;
	}
	return -1;
}
int debug_write_memory_8 (uaecptr addr, uae_u8 v)
{
	addrbank *ad;
	
	ad = &get_mem_bank (addr);
	if (ad) {
		ad->bput (addr, v);
		return 1;
	}
	return -1;
}
int debug_peek_memory_16 (uaecptr addr)
{
	addrbank *ad;
	
	ad = &get_mem_bank (addr);
	if (ad->flags & (ABFLAG_RAM | ABFLAG_ROM | ABFLAG_ROMIN | ABFLAG_SAFE))
		return ad->wget (addr);
	if (ad == &custom_bank) {
		addr &= 0x1fe;
		return (ar_custom[addr + 0] << 8) | ar_custom[addr + 1];
	}
	return -1;
}
int debug_read_memory_16 (uaecptr addr)
{
	addrbank *ad;
	
	ad = &get_mem_bank (addr);
	if (ad)
		return ad->wget (addr);
	return -1;
}
int debug_read_memory_8 (uaecptr addr)
{
	addrbank *ad;
	
	ad = &get_mem_bank (addr);
	if (ad)
		return ad->bget (addr);
	return -1;
}

static void disk_debug (TCHAR **inptr)
{
	TCHAR parm[10];
	int i;

	if (**inptr == 'd') {
		(*inptr)++;
		ignore_ws (inptr);
		disk_debug_logging = readint (inptr);
		console_out_f (_T("Disk logging level %d\n"), disk_debug_logging);
		return;
	}
	disk_debug_mode = 0;
	disk_debug_track = -1;
	ignore_ws (inptr);
	if (!next_string (inptr, parm, sizeof (parm) / sizeof (TCHAR), 1))
		goto end;
	for (i = 0; i < _tcslen(parm); i++) {
		if (parm[i] == 'R')
			disk_debug_mode |= DISK_DEBUG_DMA_READ;
		if (parm[i] == 'W')
			disk_debug_mode |= DISK_DEBUG_DMA_WRITE;
		if (parm[i] == 'P')
			disk_debug_mode |= DISK_DEBUG_PIO;
	}
	if (more_params(inptr))
		disk_debug_track = readint (inptr);
	if (disk_debug_track < 0 || disk_debug_track > 2 * 83)
		disk_debug_track = -1;
	if (disk_debug_logging == 0)
		disk_debug_logging = 1;
end:
	console_out_f (_T("Disk breakpoint mode %c%c%c track %d\n"),
		disk_debug_mode & DISK_DEBUG_DMA_READ ? 'R' : '-',
		disk_debug_mode & DISK_DEBUG_DMA_WRITE ? 'W' : '-',
		disk_debug_mode & DISK_DEBUG_PIO ? 'P' : '-',
		disk_debug_track);
}

static void find_ea (TCHAR **inptr)
{
	uae_u32 ea, sea, dea;
	uaecptr addr, end, end2;
	int hits = 0;

	addr = 0xffffffff;
	end = lastaddr ();
	ea = readhex (inptr);
	if (more_params(inptr)) {
		addr = readhex (inptr);
		addr--;
		if (more_params(inptr)) {
			end = readhex(inptr);
		}
	}
	console_out_f (_T("Searching from %08X to %08X\n"), addr + 1, end);
	end2 = 0;
	while((addr = nextaddr (addr, end, &end2, true)) != 0xffffffff) {
		if ((addr & 1) == 0 && addr + 6 <= end2) {
			sea = 0xffffffff;
			dea = 0xffffffff;
			m68k_disasm_ea (addr, NULL, 1, &sea, &dea, 0xffffffff);
			if (ea == sea || ea == dea) {
				m68k_disasm (addr, NULL, 0xffffffff, 1);
				hits++;
				if (hits > 100) {
					console_out_f (_T("Too many hits. End addr = %08X\n"), addr);
					break;
				}
			}
			if  (iscancel (65536)) {
				console_out_f (_T("Aborted at %08X\n"), addr);
				break;
			}
		}
	}
}

static void m68k_modify (TCHAR **inptr)
{
	uae_u32 v;
	TCHAR parm[10];
	TCHAR c1, c2;
	int i;

	if (!next_string (inptr, parm, sizeof (parm) / sizeof (TCHAR), 1))
		return;
	c1 = _totupper (parm[0]);
	c2 = 99;
	if (c1 == 'A' || c1 == 'D' || c1 == 'P') {
		c2 = _totupper (parm[1]);
		if (isdigit (c2))
			c2 -= '0';
		else
			c2 = 99;
	}
	v = readhex (inptr);
	if (c1 == 'A' && c2 < 8)
		regs.regs[8 + c2] = v;
	else if (c1 == 'D' && c2 < 8)
		regs.regs[c2] = v;
	else if (c1 == 'P' && c2 == 0)
		regs.irc = v;
	else if (c1 == 'P' && c2 == 1)
		regs.ir = v;
	else if (!_tcscmp (parm, _T("SR"))) {
		regs.sr = v;
		MakeFromSR ();
	} else if (!_tcscmp (parm, _T("CCR"))) {
		regs.sr = (regs.sr & ~31) | (v & 31);
		MakeFromSR ();
	} else if (!_tcscmp (parm, _T("USP"))) {
		regs.usp = v;
	} else if (!_tcscmp (parm, _T("ISP"))) {
		regs.isp = v;
	} else if (!_tcscmp (parm, _T("PC"))) {
		m68k_setpc (v);
		fill_prefetch ();
	} else {
		for (i = 0; m2cregs[i].regname; i++) {
			if (!_tcscmp (parm, m2cregs[i].regname))
				val_move2c2 (m2cregs[i].regno, v);
		}
	}
}

static void ppc_disasm(uaecptr addr, uaecptr *nextpc, int cnt)
{
	PPCD_CB disa;

	while(cnt-- > 0) {
		uae_u32 instr = get_long_debug(addr);
		disa.pc = addr;
		disa.instr = instr;
		PPCDisasm(&disa);
		TCHAR *mnemo = au(disa.mnemonic);
		TCHAR *ops = au(disa.operands);
		console_out_f(_T("%08X  %08X  %-12s%-30s\n"), addr, instr, mnemo, ops);
		xfree(ops);
		xfree(mnemo);
		addr += 4;
	}
	if (nextpc)
		*nextpc = addr;
}

static void dma_disasm(int frames, int vp, int hp, int frames_end, int vp_end, int hp_end)
{
	if (!dma_record[0] || frames < 0 || vp < 0 || hp < 0)
		return;
	for (;;) {
		struct dma_rec *dr = NULL;
		if (dma_record_frame[0] == frames)
			dr = &dma_record[0][vp * NR_DMA_REC_HPOS + hp];
		else if (dma_record_frame[1] == frames)
			dr = &dma_record[1][vp * NR_DMA_REC_HPOS + hp];
		if (!dr)
			return;
		TCHAR l1[16], l2[16], l3[16], l4[16];
		if (get_record_dma_info(dr, hp, vp, 0, l1, l2, l3, l4, NULL)) {
			console_out_f(_T(" - %02X %s %s %s\n"), hp, l2, l3, l4);
		}
		hp++;
		if (hp >= maxhpos) {
			hp = 0;
			vp++;
			if (vp >= maxvpos + 1) {
				vp = 0;
				frames++;
				break;
			}
		}
		if ((frames == frames_end && vp == vp_end && hp == hp_end) || frames > frames_end)
			break;
		if (vp_end < 0 || hp_end < 0 || frames_end < 0)
			break;
	}
}

static uaecptr nxdis, nxmem, asmaddr;
static bool ppcmode, asmmode;

static bool debug_line (TCHAR *input)
{
	TCHAR cmd, *inptr;
	uaecptr addr;

	inptr = input;

	if (asmmode) {
		if (more_params(&inptr)) {
			if (!_tcsicmp(inptr, _T("x"))) {
				asmmode = false;
				return false;
			}
			uae_u16 asmout[16];
			int inss = m68k_asm(inptr, asmout, asmaddr);
			if (inss > 0) {
				for (int i = 0; i < inss; i++) {
					put_word(asmaddr + i * 2, asmout[i]);
				}
				m68k_disasm(asmaddr, &nxdis, 0xffffffff, 1);
				asmaddr = nxdis;
			}
			console_out_f(_T("%08X "), asmaddr);
			return false;
		} else {
			asmmode = false;
			return false;
		}
	}

	cmd = next_char (&inptr);

	switch (cmd)
	{
		case 'I':
		if (more_params (&inptr)) {
			static int recursive;
			if (!recursive) {
				recursive++;
				handle_custom_event(inptr, 0);
				device_check_config();
				recursive--;
			}
		}
		break;
		case 'c': dumpcia (); dumpdisk (_T("DEBUG")); dumpcustom (); break;
		case 'i':
		{
			if (*inptr == 'l') {
				next_char (&inptr);
				if (more_params (&inptr)) {
					debug_illegal_mask = readhex (&inptr);
					if (more_params(&inptr))
						debug_illegal_mask |= ((uae_u64)readhex(&inptr)) << 32;
				} else {
					debug_illegal_mask = debug_illegal ? 0 : -1;
					debug_illegal_mask &= ~((uae_u64)255 << 24); // mask interrupts
				}
				console_out_f (_T("Exception breakpoint mask: %08X %08X\n"), (uae_u32)(debug_illegal_mask >> 32), (uae_u32)debug_illegal_mask);
				debug_illegal = debug_illegal_mask ? 1 : 0;
			} else {
				addr = 0xffffffff;
				if (more_params (&inptr))
					addr = readhex (&inptr);
				dump_vectors (addr);
			}
			break;
		}
		case 'e':
		{
			bool aga = tolower(*inptr) == 'a';
			if (aga)
				next_char(&inptr);
			bool ext = tolower(*inptr) == 'x';
			dump_custom_regs(aga, ext);
		}
		break;
		case 'r':
		{
			if (*inptr == 'c') {
				next_char(&inptr);
				m68k_dumpcache(*inptr == 'd');
			} else if (*inptr == 's') {
				if (*(inptr + 1) == 's')
					debugmem_list_stackframe(true);
				else
					debugmem_list_stackframe(false);
			} else if (more_params(&inptr)) {
				m68k_modify(&inptr);
			} else {
				m68k_dumpstate(&nextpc, 0xffffffff);
			}
		}
		break;
		case 'D': deepcheatsearch (&inptr); break;
		case 'C': cheatsearch (&inptr); break;
		case 'W': writeintomem (&inptr); break;
		case 'w': memwatch (&inptr); break;
		case 'S': saveloadmem (&inptr, true); break;
		case 'L': saveloadmem (&inptr, false); break;
		case 's':
			if (*inptr == 'e' && *(inptr + 1) == 'g') {
				next_char(&inptr);
				next_char(&inptr);
				addr = 0xffffffff;
				if (*inptr == 's') {
					debugmem_list_segment(1, addr);
				} else {
					if (more_params(&inptr)) {
						addr = readhex(&inptr);
					}
					debugmem_list_segment(0, addr);
				}
			} else if (*inptr == 'c') {
				screenshot(-1, 1, 1);
			} else if (*inptr == 'p') {
				inptr++;
				debug_sprite (&inptr);
			} else if (*inptr == 'm') {
				if (*(inptr + 1) == 'c') {
					next_char (&inptr);
					next_char (&inptr);
					if (!smc_table)
						smc_detect_init (&inptr);
					else
						smc_free ();
				}
			} else {
				searchmem (&inptr);
			}
			break;
		case 'a':
			asmaddr = nxdis;
			if (more_params(&inptr)) {
				asmaddr = readhex(&inptr);
				if (more_params(&inptr)) {
					uae_u16 asmout[16];
					int inss = m68k_asm(inptr, asmout, asmaddr);
					if (inss > 0) {
						for (int i = 0; i < inss; i++) {
							put_word(asmaddr + i * 2, asmout[i]);
						}
						m68k_disasm(asmaddr, &nxdis, 1, 0xffffffff);
						asmaddr = nxdis;
						return false;
					}
				}
			}
			asmmode = true;
			console_out_f(_T("%08X "), asmaddr);
		break;
		case 'd':
			{
				if (*inptr == 'i') {
					next_char (&inptr);
					disk_debug (&inptr);
				} else if (*inptr == 'j') {
					inptr++;
					inputdevice_logging = 1 | 2;
					if (more_params (&inptr))
						inputdevice_logging = readint (&inptr);
					console_out_f (_T("Input logging level %d\n"), inputdevice_logging);
				} else if (*inptr == 'm') {
					memory_map_dump_2 (0);
				} else if (*inptr == 't') {
					next_char (&inptr);
					debugtest_set (&inptr);
#ifdef _WIN32
				} else if (*inptr == 'g') {
					extern void update_disassembly (uae_u32);
					next_char (&inptr);
					if (more_params (&inptr))
						update_disassembly (readhex (&inptr));
#endif
				} else {
					uae_u32 daddr;
					int count;
					if (*inptr == 'p' && inptr[1] == 'p' && inptr[2] == 'c') {
						ppcmode = true;
						next_char(&inptr);
					} else if(*inptr == 'o') {
						ppcmode = false;
						next_char(&inptr);
					}
					if (more_params (&inptr))
						daddr = readhex (&inptr);
					else
						daddr = nxdis;
					if (more_params (&inptr))
						count = readhex (&inptr);
					else
						count = 10;
					if (ppcmode) {
						ppc_disasm(daddr, &nxdis, count);
					} else {
						m68k_disasm (daddr, &nxdis, 0xffffffff, count);
					}
				}
			}
			break;
		case 'T':
			if (inptr[0] == 'L')
				debugger_scan_libraries();
			else if (inptr[0] == 't' || inptr[0] == 0)
				show_exec_tasks ();
			else
				show_exec_lists (&inptr[0]);
			break;
		case 't':
			no_trace_exceptions = 0;
			debug_cycles(2);
			trace_param[0] = trace_param[1] = 0;
			if (*inptr == 't') {
				no_trace_exceptions = 1;
				inptr++;
			}
			if (*inptr == 'r') {
				// break when PC in debugmem
				if (debugmem_get_range(&trace_param[0], &trace_param[1])) {
					trace_mode = TRACE_RANGE_PC;
					return true;
				}
			} else if (*inptr == 's') {
				if (*(inptr + 1) == 'e') {
					debugmem_enable_stackframe(true);
				} else if (*(inptr + 1) == 'd') {
					debugmem_enable_stackframe(false);
				} else if (*(inptr + 1) == 'p') {
					if (debugmem_break_stack_pop()) {
						debugger_active = 0;
						return true;
					}
				} else {
					if (debugmem_break_stack_pop()) {
						debugger_active = 0;
						return true;
					}
				}
			} else if (*inptr == 'l') {
				// skip next source line
				if (debugmem_isactive()) {
					trace_mode = TRACE_SKIP_LINE;
					trace_param[0] = 1;
					trace_param[1] = debugmem_get_sourceline(M68K_GETPC, NULL, 0);
					return true;
				}
			} else {
				if (more_params(&inptr))
					trace_param[0] = readint(&inptr);
				if (trace_param[0] <= 0 || trace_param[0] > 10000)
					trace_param[0] = 1;
				trace_mode = TRACE_SKIP_INS;
				exception_debugging = 1;
				return true;
			}
			break;
		case 'z':
			trace_mode = TRACE_MATCH_PC;
			trace_param[0] = nextpc;
			exception_debugging = 1;
			debug_cycles(2);
			return true;

		case 'f':
			if (inptr[0] == 'a') {
				next_char (&inptr);
				find_ea (&inptr);
			} else if (inptr[0] == 'p') {
				inptr++;
				if (process_breakpoint (&inptr))
					return true;
			} else if (inptr[0] == 'c' || inptr[0] == 's') {
				if (cycle_breakpoint(&inptr)) {
					return true;
				}
			} else if (inptr[0] == 'e' && inptr[1] == 'n') {
				break_if_enforcer = break_if_enforcer ? false : true;
				console_out_f(_T("Break when enforcer hit: %s\n"), break_if_enforcer ? _T("enabled") : _T("disabled"));
			} else {
				if (instruction_breakpoint(&inptr)) {
					debug_cycles(1);
					return true;
				}
			}
			break;

		case 'q':
			uae_quit();
			deactivate_debugger();
			return true;

		case 'g':
			if (more_params (&inptr)) {
				m68k_setpc (readhex (&inptr));
				fill_prefetch ();
			}
			deactivate_debugger();
			return true;

		case 'x':
			if (_totupper(inptr[0]) == 'X') {
				debugger_change(-1);
			} else {
				deactivate_debugger();
				close_console();
				return true;
			}
			break;

		case 'H':
			{
				int count, temp, badly, skip;
				uae_u32 addr = 0;
				uae_u32 oldpc = m68k_getpc ();
				int lastframes, lastvpos, lasthpos;
				struct regstruct save_regs = regs;

				badly = 0;
				if (inptr[0] == 'H') {
					badly = 1;
					inptr++;
				}

				if (more_params(&inptr))
					count = readint (&inptr);
				else
					count = 10;
				if (count > 1000) {
					addr = count;
					count = MAX_HIST;
				}
				if (count < 0)
					break;
				skip = count;
				if (more_params (&inptr))
					skip = count - readint (&inptr);

				temp = lasthist;
				while (count-- > 0 && temp != firsthist) {
					if (temp == 0)
						temp = MAX_HIST - 1;
					else
						temp--;
				}
				lastframes = lastvpos = lasthpos = -1;
				while (temp != lasthist) {
					regs = history[temp].regs;
					if (regs.pc == addr || addr == 0) {
						m68k_setpc (regs.pc);
						if (badly) {
							m68k_dumpstate(NULL, 0xffffffff);
						} else {
							if (lastvpos >= 0) {
								dma_disasm(lastframes, lastvpos, lasthpos, history[temp].fp, history[temp].vpos, history[temp].hpos);
							}
							lastframes = history[temp].fp;
							lastvpos = history[temp].vpos;
							lasthpos = history[temp].hpos;
							console_out_f(_T("%2d "), regs.intmask ? regs.intmask : (regs.s ? -1 : 0));
							m68k_disasm (regs.pc, NULL, 0xffffffff, 1);
						}
						if (addr && regs.pc == addr)
							break;
					}
					if (skip-- < 0)
						break;
					if (++temp == MAX_HIST)
						temp = 0;
				}
				regs = save_regs;
				m68k_setpc (oldpc);
			}
			break;
		case 'M':
			if (more_params (&inptr)) {
				switch (next_char (&inptr))
				{
				case 'a':
					if (more_params (&inptr))
						audio_channel_mask = readhex (&inptr);
					console_out_f (_T("Audio mask = %02X\n"), audio_channel_mask);
					break;
				case 's':
					if (more_params (&inptr))
						debug_sprite_mask = readhex (&inptr);
					console_out_f (_T("Sprite mask: %02X\n"), debug_sprite_mask);
					break;
				case 'b':
					if (more_params (&inptr)) {
						debug_bpl_mask = readhex (&inptr) & 0xff;
						if (more_params (&inptr))
							debug_bpl_mask_one = readhex (&inptr) & 0xff;
						notice_screen_contents_lost(0);
					}
					console_out_f (_T("Bitplane mask: %02X (%02X)\n"), debug_bpl_mask, debug_bpl_mask_one);
					break;
				}
			}
			break;
		case 'm':
			{
				uae_u32 maddr;
				int lines;
#ifdef _WIN32
				if (*inptr == 'g') {
					extern void update_memdump (uae_u32);
					next_char (&inptr);
					if (more_params (&inptr))
						update_memdump (readhex (&inptr));
					break;
				}
#endif
				if (*inptr == 'm' && inptr[1] == 'u') {
					inptr += 2;
					if (inptr[0] == 'd') {
						if (currprefs.mmu_model >= 68040)
							mmu_dump_tables();
					} else {
						if (currprefs.mmu_model) {
							if (more_params (&inptr))
								debug_mmu_mode = readint (&inptr);
							else
								debug_mmu_mode = 0;
							console_out_f (_T("MMU translation function code = %d\n"), debug_mmu_mode);
						}
					}
					break;
				}
				if (more_params (&inptr)) {
					maddr = readhex (&inptr);
				} else {
					maddr = nxmem;
				}
				if (more_params (&inptr))
					lines = readhex (&inptr);
				else
					lines = 20;
				dumpmem (maddr, &nxmem, lines);
			}
			break;
		case 'v':
		case 'V':
			{
				int v1 = vpos, v2 = 0;
				if (*inptr == 'h') {
					inptr++;
					if (more_params(&inptr) && *inptr == '?') {
						mw_help();
					} else if (!heatmap) {
						debug_heatmap = 1;
						init_heatmap();
						if (more_params(&inptr)) {
							v1 = readint(&inptr);
							if (v1 < 0) {
								debug_heatmap = 2;
							}
						}
						TCHAR buf[200];
						TCHAR *pbuf;
						_stprintf(buf, _T("0 dff000 200 NONE"));
						pbuf = buf;
						memwatch(&pbuf);
						_stprintf(buf, _T("1 0 %08x NONE"), currprefs.chipmem.size);
						pbuf = buf;
						memwatch(&pbuf);
						if (currprefs.bogomem.size) {
							_stprintf(buf, _T("2 c00000 %08x NONE"), currprefs.bogomem.size);
							pbuf = buf;
							memwatch(&pbuf);
						}
						console_out_f(_T("Heatmap enabled\n"));
					} else {
						if (*inptr == 'd') {
							console_out_f(_T("Heatmap disabled\n"));
							free_heatmap();
						} else {
							heatmap_stats(&inptr);
						}
					}
				} else if (*inptr == 'o') {
					if (debug_dma) {
						console_out_f (_T("DMA debugger disabled\n"), debug_dma);
						record_dma_reset();
						reset_drawing();
						debug_dma = 0;
					}
				} else if (*inptr == 'm') {
					set_debug_colors();
					inptr++;
					if (more_params(&inptr)) {
						v1 = readint(&inptr);
						if (v1 >= 0 && v1 < DMARECORD_MAX) {
							v2 = readint(&inptr);
							if (v2 >= 0 && v2 <= DMARECORD_SUBITEMS) {
								if (more_params(&inptr)) {
									uae_u32 rgb = readhex(&inptr);
									if (v2 == 0) {
										for (int i = 0; i < DMARECORD_SUBITEMS; i++) {
											debug_colors[v1].l[i] = rgb;
										}
									} else {
										v2--;
										debug_colors[v1].l[v2] = rgb;
									}
									debug_colors[v1].enabled = true;
								} else {
									debug_colors[v1].enabled = !debug_colors[v1].enabled;
								}
								console_out_f(_T("%d,%d: %08x %s %s\n"), v1, v2, debug_colors[v1].l[v2], debug_colors[v1].enabled ? _T("*") : _T(" "), debug_colors[v1].name);
							}
						}
					} else {
						for (int i = 0; i < DMARECORD_MAX; i++) {
							for (int j = 0; j < DMARECORD_SUBITEMS; j++) {
								if (j < debug_colors[i].max) {
									console_out_f(_T("%d,%d: %08x %s %s\n"), i, j, debug_colors[i].l[j], debug_colors[i].enabled ? _T("*") : _T(" "), debug_colors[i].name);
								}
							}
						}
					}
				} else {
					if (more_params(&inptr) && *inptr == '?') {
						mw_help();
					} else {
						free_heatmap();
						int nextcmd = peekchar(&inptr);
						if (nextcmd == 'l') {
							next_char(&inptr);
						}
						if (more_params (&inptr))
							v1 = readint (&inptr);
						if (more_params (&inptr))
							v2 = readint (&inptr);
						if (debug_dma && v1 >= 0 && v2 >= 0) {
							decode_dma_record (v2, v1, cmd == 'v', nextcmd == 'l');
						} else {
							if (debug_dma) {
								record_dma_reset();
								reset_drawing();
							}
							debug_dma = v1 < 0 ? -v1 : 1;
							console_out_f (_T("DMA debugger enabled, mode=%d.\n"), debug_dma);
						}
					}
				}
			}
			break;
		case 'o':
			{
				if (copper_debugger (&inptr)) {
					debugger_active = 0;
					debugging = 0;
					return true;
				}
				break;
			}
		case 'O':
			break;
		case 'b':
			if (staterecorder (&inptr))
				return true;
			break;
		case 'u':
			{
				if (more_params(&inptr)) {
					if (*inptr == 'a') {
						debugmem_inhibit_break(1);
						console_out(_T("All break to debugger methods inhibited.\n"));
					} else if (*inptr == 'c') {
						debugmem_inhibit_break(-1);
						console_out(_T("All break to debugger methods allowed.\n"));
					}
				} else {
					if (debugmem_inhibit_break(0)) {
						console_out(_T("Current break to debugger method inhibited.\n"));
					} else {
						console_out(_T("Current break to debugger method allowed.\n"));
					}
				}
			}
			break;
		case 'U':
			if (currprefs.mmu_model && more_params (&inptr)) {
				int i;
				uaecptr addrl = readhex (&inptr);
				uaecptr addrp;
				console_out_f (_T("%08X translates to:\n"), addrl);
				for (i = 0; i < 4; i++) {
					bool super = (i & 2) != 0;
					bool data = (i & 1) != 0;
					console_out_f (_T("S%dD%d="), super, data);
					TRY(prb) {
						if (currprefs.mmu_model >= 68040)
							addrp = mmu_translate (addrl, 0, super, data, false, sz_long);
						else
							addrp = mmu030_translate (addrl, super, data, false);
						console_out_f (_T("%08X"), addrp);
						TRY(prb2) {
							if (currprefs.mmu_model >= 68040)
								addrp = mmu_translate (addrl, 0, super, data, true, sz_long);
							else
								addrp = mmu030_translate (addrl, super, data, true);
							console_out_f (_T(" RW"));
						} CATCH(prb2) {
							console_out_f (_T(" RO"));
						} ENDTRY
					} CATCH(prb) {
						console_out_f (_T("***********"));
					} ENDTRY
					console_out_f (_T(" "));
				}
				console_out_f (_T("\n"));
			}
			break;
		case 'h':
		case '?':
			if (more_params (&inptr))
				converter (&inptr);
			else
				debug_help ();
			break;
	}
	return false;
}

static void debug_1 (void)
{
	TCHAR input[MAX_LINEWIDTH];

	custom_dumpstate(0);
	m68k_dumpstate(&nextpc, debug_pc);
	debug_pc = 0xffffffff;
	nxdis = nextpc; nxmem = 0;
	debugger_active = 1;

	for (;;) {
		int v;

		if (!debugger_active)
			return;
		update_debug_info ();
		console_out (_T(">"));
		console_flush ();
		debug_linecounter = 0;
		v = console_get (input, MAX_LINEWIDTH);
		if (v < 0)
			return;
		if (v == 0)
			continue;
		if (debug_line (input))
			return;
	}
}

static void addhistory (void)
{
	uae_u32 pc = currprefs.cpu_model >= 68020 && currprefs.cpu_compatible ? regs.instruction_pc : m68k_getpc();
	history[lasthist].regs = regs;
	history[lasthist].regs.pc = pc;
	history[lasthist].vpos = vpos;
	history[lasthist].hpos = current_hpos();
	history[lasthist].fp = timeframes;

	if (++lasthist == MAX_HIST)
		lasthist = 0;
	if (lasthist == firsthist) {
		if (++firsthist == MAX_HIST) firsthist = 0;
	}
}

static void debug_continue(void)
{
	set_special (SPCFLAG_BRK);
}

void debug (void)
{
	int i;
	int wasactive;

	if (savestate_state)
		return;

	bogusframe = 1;
	disasm_init();
	addhistory ();

#if 0
	if (do_skip && skipaddr_start == 0xC0DEDBAD) {
		if (trace_same_insn_count > 0) {
			if (memcmp (trace_insn_copy, regs.pc_p, 10) == 0
				&& memcmp (trace_prev_regs.regs, regs.regs, sizeof regs.regs) == 0)
			{
				trace_same_insn_count++;
				return;
			}
		}
		if (trace_same_insn_count > 1)
			fprintf (logfile, "[ repeated %d times ]\n", trace_same_insn_count);
		m68k_dumpstate (logfile, &nextpc);
		trace_same_insn_count = 1;
		memcpy (trace_insn_copy, regs.pc_p, 10);
		memcpy (&trace_prev_regs, &regs, sizeof regs);
	}
#endif

	if (!memwatch_triggered) {
		if (trace_mode) {
			uae_u32 pc;
			uae_u16 opcode;
			int bp = 0;

			pc = munge24 (m68k_getpc ());
			opcode = currprefs.cpu_model < 68020 && (currprefs.cpu_compatible || currprefs.cpu_cycle_exact) ? regs.ir : get_word_debug (pc);

			for (i = 0; i < BREAKPOINT_TOTAL; i++) {
				struct breakpoint_node *bpn = &bpnodes[i];
				if (!bpn->enabled)
					continue;
				if (bpn->type == BREAKPOINT_REG_PC) {
					if (bpn->value1 == pc) {
						bp = i + 1;
						break;
					}
				} else if (bpn->type >= 0 && bpn->type < BREAKPOINT_REG_END) {
					uae_u32 value1 = bpn->value1 & bpn->mask;
					uae_u32 value2 = bpn->value2 & bpn->mask;
					uae_u32 cval = returnregx(bpn->type) & bpn->mask;
					switch (bpn->oper)
					{
						case BREAKPOINT_CMP_EQUAL:
						if (cval == value1)
							bp = i + 1;
						break;
						case BREAKPOINT_CMP_NEQUAL:
						if (cval != value1)
							bp = i + 1;
						break;
						case BREAKPOINT_CMP_SMALLER:
						if (cval <= value1)
							bp = i + 1;
						break;
						case BREAKPOINT_CMP_LARGER:
						if (cval >= value1)
							bp = i + 1;
						break;
						case BREAKPOINT_CMP_RANGE:
						if (cval >= value1 && cval <= value2)
							bp = i + 1;
						break;
						case BREAKPOINT_CMP_NRANGE:
						if (cval <= value1 || cval >= value2)
							bp = i + 1;
						break;
					}
				}
			}

			if (trace_mode) {
				if (trace_mode == TRACE_MATCH_PC && trace_param[0] == pc)
					bp = -1;
				if (trace_mode == TRACE_RAM_PC) {
					addrbank *ab = &get_mem_bank(pc);
					if (ab->flags & ABFLAG_RAM) {
						uae_u16 ins = get_word_debug(pc);
						// skip JMP xxxxxx (LVOs)
						if (ins != 0x4ef9) {
							bp = -1;
						}
					}
				}
				if ((processptr || processname) && notinrom()) {
					uaecptr execbase = get_long_debug (4);
					uaecptr activetask = get_long_debug (execbase + 276);
					int process = get_byte_debug (activetask + 8) == 13 ? 1 : 0;
					char *name = (char*)get_real_address_debug(get_long_debug (activetask + 10));
					if (process) {
						uaecptr cli = BPTR2APTR(get_long_debug (activetask + 172));
						uaecptr seglist = 0;

						uae_char *command = NULL;
						if (cli) {
							if (processname)
								command = (char*)get_real_address_debug(BPTR2APTR(get_long_debug (cli + 16)));
							seglist = BPTR2APTR(get_long_debug (cli + 60));
						} else {
							seglist = BPTR2APTR(get_long_debug (activetask + 128));
							seglist = BPTR2APTR(get_long_debug (seglist + 12));
						}
						if (activetask == processptr || (processname && (!stricmp (name, processname) || (command && command[0] && !strnicmp (command + 1, processname, command[0]) && processname[command[0]] == 0)))) {
							while (seglist) {
								uae_u32 size = get_long_debug (seglist - 4) - 4;
								if (pc >= (seglist + 4) && pc < (seglist + size)) {
									bp = i + 1;
									break;
								}
								seglist = BPTR2APTR(get_long_debug (seglist));
							}
						}
					}
				} else if (trace_mode == TRACE_MATCH_INS) {
					if (trace_param[0] == 0x10000) {
						if (opcode == 0x4e75 || opcode == 0x4e73 || opcode == 0x4e77)
							bp = -1;
					} else if (opcode == trace_param[0]) {
						bp = -1;
						for (int op = 1; op < 3; op++) {
							if (trace_param[op] != 0x10000) {
								uae_u16 w = 0xffff;
								debug_get_prefetch(op, &w);
								if (w != trace_param[op])
									bp = 0;
							}
						}
					}
				} else if (trace_mode == TRACE_SKIP_INS) {
					if (trace_param[0] != 0)
						trace_param[0]--;
					if (trace_param[0] == 0) {
						bp = -1;
					}
#if 0
				} else if (skipaddr_start == 0xffffffff && skipaddr_doskip > 0) {
					bp = -1;
#endif
				} else if (trace_mode == TRACE_RANGE_PC) {
					if (pc >= trace_param[0] && pc < trace_param[1])
						bp = -1;
				} else if (trace_mode == TRACE_SKIP_LINE) {
					if (trace_param[0] != 0)
						trace_param[0]--;
					if (trace_param[0] == 0) {
						int line = debugmem_get_sourceline(pc, NULL, 0);
						if (line > 0 && line != trace_param[1])
							bp = -1;
					}
				}
			}
			if (!bp) {
				debug_continue();
				return;
			}
			if (bp > 0)
				console_out_f(_T("Breakpoint %d triggered.\n"), bp - 1);
			debug_cycles(1);
		}
	} else {
		memwatch_hit_msg(memwatch_triggered - 1);
		memwatch_triggered = 0;
	}

	wasactive = ismouseactive ();
#ifdef WITH_PPC
	uae_ppc_pause(1);
#endif
	inputdevice_unacquire ();
	pause_sound ();
	setmouseactive(0, 0);
	activate_console ();
	trace_mode = 0;
	exception_debugging = 0;
	debug_rewind = 0;
	processptr = 0;
#if 0
	if (!currprefs.statecapture) {
		changed_prefs.statecapture = currprefs.statecapture = 1;
		savestate_init ();
	}
#endif
	debugmem_disable();

	if (trace_cycles && last_frame >= 0) {
		if (last_frame + 2 >= timeframes || trace_cycles > 1) {
			console_out_f(_T("Cycles: %d Chip, %d CPU. (V=%d H=%d -> V=%d H=%d)\n"),
				(last_cycles2 - last_cycles1) / CYCLE_UNIT,
				(last_cycles2 - last_cycles1) / cpucycleunit,
				last_vpos1, last_hpos1,
				last_vpos2, last_hpos2);
		}
	}
	trace_cycles = 0;

	debug_1 ();
	debugmem_enable();
	if (!debug_rewind && !currprefs.cachesize
#ifdef FILESYS
		&& nr_units () == 0
#endif
		) {
			savestate_capture (1);
	}
	if (!trace_mode) {
		for (i = 0; i < BREAKPOINT_TOTAL; i++) {
			if (bpnodes[i].enabled)
				trace_mode = TRACE_CHECKONLY;
		}
	}
	if (trace_mode) {
		set_special (SPCFLAG_BRK);
		debugging = -1;
	}
	resume_sound ();
	inputdevice_acquire (TRUE);
#ifdef WITH_PPC
	uae_ppc_pause(0);
#endif
	setmouseactive(0, wasactive ? 2 : 0);

	last_cycles1 = get_cycles();
	last_vpos1 = vpos;
	last_hpos1 = current_hpos();
	last_frame = timeframes;
}

const TCHAR *debuginfo (int mode)
{
	static TCHAR txt[100];
	uae_u32 pc = M68K_GETPC;
	_stprintf (txt, _T("PC=%08X INS=%04X %04X %04X"),
		pc, get_word_debug (pc), get_word_debug (pc + 2), get_word_debug (pc + 4));
	return txt;
}

#endif	/* WINUAE_FOR_HATARI */

void mmu_disasm (uaecptr pc, int lines)
{
	debug_mmu_mode = regs.s ? 6 : 2;
	m68k_dumpstate(NULL, 0xffffffff);
	m68k_disasm (pc, NULL, 0xffffffff, lines);
}

static int mmu_logging;

#define MMU_PAGE_SHIFT 16

struct mmudata {
	uae_u32 flags;
	uae_u32 addr;
	uae_u32 len;
	uae_u32 remap;
	uae_u32 p_addr;
};

static struct mmudata *mmubanks;
static uae_u32 mmu_struct, mmu_callback, mmu_regs;
static uae_u32 mmu_fault_bank_addr, mmu_fault_addr;
static int mmu_fault_size, mmu_fault_rw;
static int mmu_slots;
static struct regstruct mmur;

struct mmunode {
	struct mmudata *mmubank;
	struct mmunode *next;
};
static struct mmunode **mmunl;
extern regstruct mmu_backup_regs;

#define MMU_READ_U (1 << 0)
#define MMU_WRITE_U (1 << 1)
#define MMU_READ_S (1 << 2)
#define MMU_WRITE_S (1 << 3)
#define MMU_READI_U (1 << 4)
#define MMU_READI_S (1 << 5)

#define MMU_MAP_READ_U (1 << 8)
#define MMU_MAP_WRITE_U (1 << 9)
#define MMU_MAP_READ_S (1 << 10)
#define MMU_MAP_WRITE_S (1 << 11)
#define MMU_MAP_READI_U (1 << 12)
#define MMU_MAP_READI_S (1 << 13)

void mmu_do_hit (void)
{
	int i;
	uaecptr p;
	uae_u32 pc;

	mmu_triggered = 0;
	pc = m68k_getpc ();
	p = mmu_regs + 18 * 4;
	put_long (p, pc);
	regs = mmu_backup_regs;
	regs.intmask = 7;
	regs.t0 = regs.t1 = 0;
	if (!regs.s) {
		regs.usp = m68k_areg (regs, 7);
		if (currprefs.cpu_model >= 68020)
			m68k_areg (regs, 7) = regs.m ? regs.msp : regs.isp;
		else
			m68k_areg (regs, 7) = regs.isp;
		regs.s = 1;
	}
	MakeSR ();
	m68k_setpc (mmu_callback);
	fill_prefetch ();

	if (currprefs.cpu_model > 68000) {
		for (i = 0 ; i < 9; i++) {
			m68k_areg (regs, 7) -= 4;
			put_long (m68k_areg (regs, 7), 0);
		}
		m68k_areg (regs, 7) -= 4;
		put_long (m68k_areg (regs, 7), mmu_fault_addr);
		m68k_areg (regs, 7) -= 2;
		put_word (m68k_areg (regs, 7), 0); /* WB1S */
		m68k_areg (regs, 7) -= 2;
		put_word (m68k_areg (regs, 7), 0); /* WB2S */
		m68k_areg (regs, 7) -= 2;
		put_word (m68k_areg (regs, 7), 0); /* WB3S */
		m68k_areg (regs, 7) -= 2;
		put_word (m68k_areg (regs, 7),
			(mmu_fault_rw ? 0 : 0x100) | (mmu_fault_size << 5)); /* SSW */
		m68k_areg (regs, 7) -= 4;
		put_long (m68k_areg (regs, 7), mmu_fault_bank_addr);
		m68k_areg (regs, 7) -= 2;
		put_word (m68k_areg (regs, 7), 0x7002);
	}
	m68k_areg (regs, 7) -= 4;
	put_long (m68k_areg (regs, 7), get_long_debug (p - 4));
	m68k_areg (regs, 7) -= 2;
	put_word (m68k_areg (regs, 7), mmur.sr);
#ifdef JIT
	set_special(SPCFLAG_END_COMPILE);
#endif
}

static void mmu_do_hit_pre (struct mmudata *md, uaecptr addr, int size, int rwi, uae_u32 v)
{
	uae_u32 p, pc;
	int i;

	mmur = regs;
	pc = m68k_getpc ();
	if (mmu_logging)
		console_out_f (_T("MMU: hit %08X SZ=%d RW=%d V=%08X PC=%08X\n"), addr, size, rwi, v, pc);

	p = mmu_regs;
	put_long (p, 0); p += 4;
	for (i = 0; i < 16; i++) {
		put_long (p, regs.regs[i]);
		p += 4;
	}
	put_long (p, pc); p += 4;
	put_long (p, 0); p += 4;
	put_long (p, regs.usp); p += 4;
	put_long (p, regs.isp); p += 4;
	put_long (p, regs.msp); p += 4;
	put_word (p, regs.sr); p += 2;
	put_word (p, (size << 1) | ((rwi & 2) ? 1 : 0)); /* size and rw */ p += 2;
	put_long (p, addr); /* fault address */ p += 4;
	put_long (p, md->p_addr); /* bank address */ p += 4;
	put_long (p, v); p += 4;
	mmu_fault_addr = addr;
	mmu_fault_bank_addr = md->p_addr;
	mmu_fault_size = size;
	mmu_fault_rw = rwi;
	mmu_triggered = 1;
}

static int mmu_hit (uaecptr addr, int size, int rwi, uae_u32 *v)
{
	int s, trig;
	uae_u32 flags;
	struct mmudata *md;
	struct mmunode *mn;

	if (mmu_triggered)
		return 1;

	mn = mmunl[addr >> MMU_PAGE_SHIFT];
	if (mn == NULL)
		return 0;

	s = regs.s;
	while (mn) {
		md = mn->mmubank;
		if (addr >= md->addr && addr < md->addr + md->len) {
			flags = md->flags;
			if (flags & (MMU_MAP_READ_U | MMU_MAP_WRITE_U | MMU_MAP_READ_S | MMU_MAP_WRITE_S | MMU_MAP_READI_U | MMU_MAP_READI_S)) {
				trig = 0;
				if (!s && (flags & MMU_MAP_READ_U) && (rwi & 1))
					trig = 1;
				if (!s && (flags & MMU_MAP_WRITE_U) && (rwi & 2))
					trig = 1;
				if (s && (flags & MMU_MAP_READ_S) && (rwi & 1))
					trig = 1;
				if (s && (flags & MMU_MAP_WRITE_S) && (rwi & 2))
					trig = 1;
				if (!s && (flags & MMU_MAP_READI_U) && (rwi & 4))
					trig = 1;
				if (s && (flags & MMU_MAP_READI_S) && (rwi & 4))
					trig = 1;
				if (trig) {
					uaecptr maddr = md->remap + (addr - md->addr);
					if (maddr == addr) /* infinite mmu hit loop? no thanks.. */
						return 1;
					if (mmu_logging)
						console_out_f (_T("MMU: remap %08X -> %08X SZ=%d RW=%d\n"), addr, maddr, size, rwi);
					if ((rwi & 2)) {
						switch (size)
						{
						case 4:
							put_long (maddr, *v);
							break;
						case 2:
							put_word (maddr, *v);
							break;
						case 1:
							put_byte (maddr, *v);
							break;
						}
					} else {
						switch (size)
						{
						case 4:
							*v = get_long_debug (maddr);
							break;
						case 2:
							*v = get_word_debug (maddr);
							break;
						case 1:
							*v = get_byte_debug (maddr);
							break;
						}
					}
					return 1;
				}
			}
			if (flags & (MMU_READ_U | MMU_WRITE_U | MMU_READ_S | MMU_WRITE_S | MMU_READI_U | MMU_READI_S)) {
				trig = 0;
				if (!s && (flags & MMU_READ_U) && (rwi & 1))
					trig = 1;
				if (!s && (flags & MMU_WRITE_U) && (rwi & 2))
					trig = 1;
				if (s && (flags & MMU_READ_S) && (rwi & 1))
					trig = 1;
				if (s && (flags & MMU_WRITE_S) && (rwi & 2))
					trig = 1;
				if (!s && (flags & MMU_READI_U) && (rwi & 4))
					trig = 1;
				if (s && (flags & MMU_READI_S) && (rwi & 4))
					trig = 1;
				if (trig) {
					mmu_do_hit_pre (md, addr, size, rwi, *v);
					return 1;
				}
			}
		}
		mn = mn->next;
	}
	return 0;
}

#ifdef JIT

static void mmu_free_node(struct mmunode *mn)
{
	if (!mn)
		return;
	mmu_free_node (mn->next);
	xfree (mn);
}

static void mmu_free(void)
{
	struct mmunode *mn;
	int i;

	for (i = 0; i < mmu_slots; i++) {
		mn = mmunl[i];
		mmu_free_node (mn);
	}
	xfree (mmunl);
	mmunl = NULL;
	xfree (mmubanks);
	mmubanks = NULL;
}

#endif

static int getmmubank(struct mmudata *snptr, uaecptr p)
{
	snptr->flags = get_long_debug (p);
	if (snptr->flags == 0xffffffff)
		return 1;
	snptr->addr = get_long_debug (p + 4);
	snptr->len = get_long_debug (p + 8);
	snptr->remap = get_long_debug (p + 12);
	snptr->p_addr = p;
	return 0;
}

int mmu_init(int mode, uaecptr parm, uaecptr parm2)
{
	uaecptr p, p2, banks;
	int size;
	struct mmudata *snptr;
	struct mmunode *mn;
#ifdef JIT
	static int wasjit;
	if (currprefs.cachesize) {
		wasjit = currprefs.cachesize;
		changed_prefs.cachesize = 0;
		console_out (_T("MMU: JIT disabled\n"));
		check_prefs_changed_comp(false);
	}
	if (mode == 0) {
		if (mmu_enabled) {
			mmu_free ();
			deinitialize_memwatch ();
			console_out (_T("MMU: disabled\n"));
			changed_prefs.cachesize = wasjit;
		}
		mmu_logging = 0;
		return 1;
	}
#endif

	if (mode == 1) {
		if (!mmu_enabled)
			return 0xffffffff;
		return mmu_struct;
	}

	p = parm;
	mmu_struct = p;
	if (get_long_debug (p) != 1) {
		console_out_f (_T("MMU: version mismatch %d <> %d\n"), get_long_debug (p), 1);
		return 0;
	}
	p += 4;
	mmu_logging = get_long_debug (p) & 1;
	p += 4;
	mmu_callback = get_long_debug (p);
	p += 4;
	mmu_regs = get_long_debug (p);
	p += 4;

	if (mode == 3) {
		int off;
		uaecptr addr = get_long_debug (parm2 + 4);
		if (!mmu_enabled)
			return 0;
		off = addr >> MMU_PAGE_SHIFT;
		mn = mmunl[off];
		while (mn) {
			if (mn->mmubank->p_addr == parm2) {
				getmmubank(mn->mmubank, parm2);
				if (mmu_logging)
					console_out_f (_T("MMU: bank update %08X: %08X - %08X %08X\n"),
					mn->mmubank->flags, mn->mmubank->addr, mn->mmubank->len + mn->mmubank->addr,
					mn->mmubank->remap);
			}
			mn = mn->next;
		}
		return 1;
	}

	mmu_slots = 1 << ((currprefs.address_space_24 ? 24 : 32) - MMU_PAGE_SHIFT);
	mmunl = xcalloc (struct mmunode*, mmu_slots);
	size = 1;
	p2 = get_long_debug (p);
	while (get_long_debug (p2) != 0xffffffff) {
		p2 += 16;
		size++;
	}
	p = banks = get_long_debug (p);
	snptr = mmubanks = xmalloc (struct mmudata, size);
	for (;;) {
		int off;
		if (getmmubank(snptr, p))
			break;
		p += 16;
		off = snptr->addr >> MMU_PAGE_SHIFT;
		if (mmunl[off] == NULL) {
			mn = mmunl[off] = xcalloc (struct mmunode, 1);
		} else {
			mn = mmunl[off];
			while (mn->next)
				mn = mn->next;
			mn = mn->next = xcalloc (struct mmunode, 1);
		}
		mn->mmubank = snptr;
		snptr++;
	}

	initialize_memwatch (1);
	console_out_f (_T("MMU: enabled, %d banks, CB=%08X S=%08X BNK=%08X SF=%08X, %d*%d\n"),
		size - 1, mmu_callback, parm, banks, mmu_regs, mmu_slots, 1 << MMU_PAGE_SHIFT);
	set_special (SPCFLAG_BRK);
	return 1;
}

void debug_parser (const TCHAR *cmd, TCHAR *out, uae_u32 outsize)
{
	TCHAR empty[2] = { 0 };
	TCHAR *input = my_strdup (cmd);
	if (out == NULL && outsize == 0) {
		setconsolemode (empty, 1);
	} else if (out != NULL && outsize > 0) {
		out[0] = 0;
		setconsolemode (out, outsize);
	}
	debug_line (input);
	setconsolemode (NULL, 0);
	xfree (input);
}

/*

trainer file is .ini file with following one or more [patch] sections.
Each [patch] section describes single trainer option.

After [patch] section must come at least one patch descriptor.

[patch]
name=name
enable=true/false
event=KEY_F1

; patch descriptor
data=200e46802d400026200cxx02 ; this is comment
offset=2
access=write
setvalue=<value>
type=nop/freeze/set/setonce

; patch descriptor
data=11223344556677889900
offset=10
replacedata=4e71
replaceoffset=4

; next patch section
[patch]


name: name of the option (appears in GUI in the future)
enable: true = automatically enabled at startup. (false=manually activated using key shortcut etc.., will be implemented later)
event: inputevents.def event name

data: match data, when emulated CPU executes first opcode of this data and following words also match: match is detected. x = anything.
offset: word offset from beginning of "data" that points to memory read/write instruction that you want to "patch". Default=0.
access: read=read access, write=write access. Default: write if instruction does both memory read and write, read if read-only.

setvalue: value to write if type is set or setonce.
type=nop: found instruction's write does nothing. This instruction only. Other instruction(s) modifying same memory location are not skipped.
type=freeze: found instruction's memory read always returns value in memory. Write does nothing.
type=set: found instruction's memory read always returns "setvalue" contents. Write works normally.
type=setonce: "setvalue" contents are written to memory when patch is detected.

replacedata: data to be copied over data + replaceoffset. x masking is also supported. Memory is modified.
replaceoffset: word offset from data.

---

Internally it uses debugger memory watch points to modify/freeze memory contents. No memory or code is modified.
Only type=setonce and replacedata modifies memory.

When CPU emulator current to be executed instruction's matches contents of data[offset], other words of data are also checked.
If all words match: instruction's effective address(es) are calculated and matching (read/write) EA is stored. Matching part
of patch is now done.

Reason for this complexity is to enable single patch to work even if game is relocatable or it uses different memory
locations depending on hardware config.

If type=nop/freeze/set: debugger memwatch point is set that handles faking of read/write access.
If type=setonce: "setvalue" contents gets written to detected effective address.
If replacedata is set: copy code.

Detection phase may cause increased CPU load, this may get optimized more but it shouldn't be (too) noticeable in basic
A500 or A1200 modes.

*/

#define TRAINER_NOP 0
#define TRAINER_FREEZE 1
#define TRAINER_SET 2
#define TRAINER_SETONCE 3

struct trainerpatch
{
	TCHAR *name;
	uae_u16 *data;
	uae_u16 *maskdata;
	uae_u16 *replacedata;
	uae_u16 *replacemaskdata;
	uae_u16 *replacedata_original;
	uae_u16 first;
	int length;
	int offset;
	int access;
	int replacelength;
	int replaceoffset;
	uaecptr addr;
	uaecptr varaddr;
	int varsize;
	uae_u32 oldval;
	int patchtype;
	int setvalue;
	int *events;
	int eventcount;
	int memwatchindex;
	bool enabledatstart;
	bool enabled;
};

static struct trainerpatch **tpptr;
static int tpptrcnt;
bool debug_opcode_watch;

static int debug_trainer_get_ea(struct trainerpatch *tp, uaecptr pc, uae_u16 opcode, uaecptr *addr)
{
	struct instr *dp = table68k + opcode;
	uae_u32 sea = 0, dea = 0;
	uaecptr spc = 0, dpc = 0;
	uaecptr pc2 = pc + 2;
	if (dp->suse) {
		spc = pc2;
		pc2 = ShowEA(NULL, pc2, opcode, dp->sreg, dp->smode, dp->size, NULL, &sea, NULL, 1);
		if (sea == spc)
			spc = 0xffffffff;
	}
	if (dp->duse) {
		dpc = pc2;
		pc2 = ShowEA(NULL, pc2, opcode, dp->dreg, dp->dmode, dp->size, NULL, &dea, NULL, 1);
		if (dea == dpc)
			dpc = 0xffffffff;
	}
	if (dea && dpc != 0xffffffff && tp->access == 1) {
		*addr = dea;
		return 1 << dp->size;
	}
	if (sea && spc != 0xffffffff && tp->access == 0) {
		*addr = sea;
		return 1 << dp->size;
	}
	if (dea && tp->access > 1) {
		*addr = dea;
		return 1 << dp->size;
	}
	if (sea && tp->access > 1) {
		*addr = sea;
		return 1 << dp->size;
	}
	return 0;
}

static void debug_trainer_enable(struct trainerpatch *tp, bool enable)
{
	if (tp->enabled == enable)
		return;

	if (tp->replacedata) {
		if (enable) {
			bool first = false;
			if (!tp->replacedata_original) {
				tp->replacedata_original = xcalloc(uae_u16, tp->replacelength);
				first = true;
			}
			for (int j = 0; j < tp->replacelength; j++) {
				uae_u16 v = tp->replacedata[j];
				uae_u16 m = tp->replacemaskdata[j];
				uaecptr addr = (tp->addr - tp->offset * 2) + j * 2 + tp->replaceoffset * 2;
				if (m == 0xffff) {
					x_put_word(addr, v);
				} else {
					uae_u16 vo = x_get_word(addr);
					x_put_word(addr, (vo & ~m) | (v & m));
					if (first)
						tp->replacedata_original[j] = vo;
				}
			}
		} else if (tp->replacedata_original) {
			for (int j = 0; j < tp->replacelength; j++) {
				uae_u16 m = tp->replacemaskdata[j];
				uaecptr addr = (tp->addr - tp->offset * 2) + j * 2 + tp->replaceoffset * 2;
				if (m != 0xffff) {
					x_put_word(addr, tp->replacedata_original[j]);
				}
			}
		}
	}

	if (tp->patchtype == TRAINER_SETONCE && tp->varaddr != 0xffffffff) {
		uae_u32 v = enable ? tp->setvalue : tp->oldval;
		switch (tp->varsize)
		{
		case 1:
			x_put_byte(tp->varaddr, tp->setvalue);
			break;
		case 2:
			x_put_word(tp->varaddr, tp->setvalue);
			break;
		case 4:
			x_put_long(tp->varaddr, tp->setvalue);
			break;
		}
	}

	if ((tp->patchtype == TRAINER_NOP || tp->patchtype == TRAINER_FREEZE || tp->patchtype == TRAINER_SET) && tp->varaddr != 0xffffffff) {
		struct memwatch_node *mwn;
		if (!memwatch_enabled)
			initialize_memwatch(0);
		if (enable) {
			int i;
			for (i = MEMWATCH_TOTAL - 1; i >= 0; i--) {
				mwn = &mwnodes[i];
				if (!mwn->size)
					break;
			}
			if (i < 0) {
				write_log(_T("Trainer out of free memwatchpoints ('%s' %08x\n).\n"), tp->name, tp->addr);
			} else {
				mwn->addr = tp->varaddr;
				mwn->size = tp->varsize;
				mwn->rwi = 1 | 2;
				mwn->access_mask = MW_MASK_CPU_D_R | MW_MASK_CPU_D_W;
				mwn->reg = 0xffffffff;
				mwn->pc = tp->patchtype == TRAINER_NOP ? tp->addr : 0xffffffff;
				mwn->frozen = tp->patchtype == TRAINER_FREEZE || tp->patchtype == TRAINER_NOP;
				mwn->modval_written = 0;
				mwn->val_enabled = 0;
				mwn->val_mask = 0xffffffff;
				mwn->val = 0;
				mwn->reportonly = false;
				if (tp->patchtype == TRAINER_SET) {
					mwn->val_enabled = 1;
					mwn->val = tp->setvalue;
				}
				mwn->nobreak = true;
				memwatch_setup();
				TCHAR buf[256];
				memwatch_dump2(buf, sizeof(buf) / sizeof(TCHAR), i);
				write_log(_T("%s"), buf);
			}
		} else {
			mwn = &mwnodes[tp->memwatchindex];
			mwn->size = 0;
			memwatch_setup();
		}
	}

	write_log(_T("Trainer '%s' %s (addr=%08x)\n"), tp->name, enable ? _T("enabled") : _T("disabled"), tp->addr);
	tp->enabled = enable;
}

void debug_trainer_match(void)
{
	uaecptr pc = m68k_getpc();
	uae_u16 opcode = x_get_word(pc);
	for (int i = 0; i < tpptrcnt; i++) {
		struct trainerpatch *tp = tpptr[i];
		if (tp->first != opcode)
			continue;
		if (tp->addr)
			continue;
		int j;
		for (j = 0; j < tp->length; j++) {
			uae_u16 d = x_get_word(pc + (j - tp->offset) * 2);
			if ((d & tp->maskdata[j]) != tp->data[j])
				break;
		}
		if (j < tp->length)
			continue;
		tp->first = 0xffff;
		tp->addr = pc;
		tp->varsize = -1;
		tp->varaddr = 0xffffffff;
		tp->oldval = 0xffffffff;
		if (tp->access >= 0) {
			tp->varsize = debug_trainer_get_ea(tp, pc, opcode, &tp->varaddr);
			switch (tp->varsize)
			{
			case 1:
				tp->oldval = x_get_byte(tp->varaddr);
				break;
			case 2:
				tp->oldval = x_get_word(tp->varaddr);
				break;
			case 4:
				tp->oldval = x_get_long(tp->varaddr);
				break;
			}
		}
		write_log(_T("Patch %d match at %08x. Addr %08x, size %d, val %08x\n"), i, pc, tp->varaddr, tp->varsize, tp->oldval);

		if (tp->enabledatstart)
			debug_trainer_enable(tp, true);

		// all detected?
		for (j = 0; j < tpptrcnt; j++) {
			struct trainerpatch *tp = tpptr[j];
			if (!tp->addr)
				break;
		}
		if (j == tpptrcnt)
			debug_opcode_watch = false;
	}
}

static int parsetrainerdata(const TCHAR *data, uae_u16 *outdata, uae_u16 *outmask)
{
	int len = _tcslen(data);
	uae_u16 v = 0, vm = 0;
	int j = 0;
	for (int i = 0; i < len; ) {
		TCHAR c1 = _totupper(data[i + 0]);
		TCHAR c2 = _totupper(data[i + 1]);
		if (c1 > 0 && c1 <= ' ') {
			i++;
			continue;
		}
		if (i + 1 >= len)
			return 0;

		vm <<= 8;
		vm |= 0xff;
		if (c1 == 'X' || c1 == '?')
			vm &= 0x0f;
		if (c2 == 'X' || c2 == '?')
			vm &= 0xf0;

		if (c1 >= 'A')
			c1 -= 'A' - 10;
		else if (c1 >= '0')
			c1 -= '0';
		if (c2 >= 'A')
			c2 -= 'A' - 10;
		else if (c2 >= '0')
			c2 -= '0';

		v <<= 8;
		if (c1 >= 0 && c1 < 16)
			v |= c1 << 4;
		if (c2 >= 0 && c2 < 16)
			v |= c2;

		if (i & 2) {
			outdata[j] = v;
			outmask[j] = vm;
			j++;
		}

		i += 2;
	}
	return j;
}

void debug_init_trainer(const TCHAR *file)
{
	TCHAR section[256];
	int cnt = 1;

	struct ini_data *ini = ini_load(file, false);
	if (!ini)
		return;

	write_log(_T("Loaded '%s'\n"), file);

	_tcscpy(section, _T("patch"));

	for (;;) {
		struct ini_context ictx;
		ini_initcontext(ini, &ictx);

		for (;;) {
			TCHAR *name = NULL;
			TCHAR *data;

			ini_getstring_multi(ini, section, _T("name"), &name, &ictx);

			if (!ini_getstring_multi(ini, section, _T("data"), &data, &ictx))
				break;
			ini_setcurrentasstart(ini, &ictx);
			ini_setlast(ini, section, _T("data"), &ictx);

			TCHAR *p = _tcschr(data, ';');
			if (p)
				*p = 0;
			my_trim(data);

			struct trainerpatch *tp = xcalloc(struct trainerpatch, 1);

			int datalen = (_tcslen(data) + 3) / 4;
			tp->data = xcalloc(uae_u16, datalen);
			tp->maskdata = xcalloc(uae_u16, datalen);
			tp->length = parsetrainerdata(data, tp->data, tp->maskdata);
			xfree(data);

			ini_getval_multi(ini, section, _T("offset"), &tp->offset, &ictx);
			if (tp->offset < 0 || tp->offset >= tp->length)
				tp->offset = 0;

			if (ini_getstring_multi(ini, section, _T("replacedata"), &data, &ictx)) {
				int replacedatalen = (_tcslen(data) + 3) / 4;
				tp->replacedata = xcalloc(uae_u16, replacedatalen);
				tp->replacemaskdata = xcalloc(uae_u16, replacedatalen);
				tp->replacelength = parsetrainerdata(data, tp->replacedata, tp->replacemaskdata);
				ini_getval_multi(ini, section, _T("replaceoffset"), &tp->offset, &ictx);
				if (tp->replaceoffset < 0 || tp->replaceoffset >= tp->length)
					tp->replaceoffset = 0;
				tp->access = -1;
				xfree(data);
			}

			tp->access = 2;
			if (ini_getstring_multi(ini, section, _T("access"), &data, &ictx)) {
				if (!_tcsicmp(data, _T("read")))
					tp->access = 0;
				else if (!_tcsicmp(data, _T("write")))
					tp->access = 1;
				xfree(data);
			}

			if (ini_getstring_multi(ini, section, _T("type"), &data, &ictx)) {
				if (!_tcsicmp(data, _T("freeze")))
					tp->patchtype = TRAINER_FREEZE;
				else if (!_tcsicmp(data, _T("nop")))
					tp->patchtype = TRAINER_NOP;
				else if (!_tcsicmp(data, _T("set")))
					tp->patchtype = TRAINER_SET;
				else if (!_tcsicmp(data, _T("setonce")))
					tp->patchtype = TRAINER_SETONCE;
				xfree(data);
			}

			if (ini_getstring_multi(ini, section, _T("setvalue"), &data, &ictx)) {
				TCHAR *endptr;
				if (data[0] == '$') {
					tp->setvalue = _tcstol(data + 1, &endptr, 16);
				} else if (_tcslen(data) > 2 && data[0] == '0' && _totupper(data[1]) == 'x') {
					tp->setvalue = _tcstol(data + 2, &endptr, 16);
				} else {
					tp->setvalue = _tcstol(data, &endptr, 10);
				}
				xfree(data);
			}

			if (ini_getstring(ini, section, _T("enable"), &data)) {
				if (!_tcsicmp(data, _T("true")))
					tp->enabledatstart = true;
				xfree(data);
			}

			if (ini_getstring(ini, section, _T("event"), &data)) {
				TCHAR *s = data;
				_tcscat(s, _T(","));
				while (*s) {
					bool end = false;
					while (*s == ' ')
						s++;
					TCHAR *se = _tcschr(s, ',');
					if (se) {
						*se = 0;
					} else {
						end = true;
					}
					TCHAR *se2 = se - 1;
					while (se2 > s) {
						if (*se2 != ' ')
							break;
						*se2 = 0;
						se2--;
					}
					int evt = inputdevice_geteventid(s);
					if (evt > 0) {
						if (tp->events) {
							tp->events = xrealloc(int, tp->events, tp->eventcount + 1);
						} else {
							tp->events = xmalloc(int, 1);
						}
						tp->events[tp->eventcount++] = evt;
					} else {
						write_log(_T("Unknown event '%s'\n"), s);
					}
					if (end)
						break;
					s = se + 1;
				}
				xfree(data);
			}

			tp->first = tp->data[tp->offset];
			tp->name = name;

			if (tpptrcnt)
				tpptr = xrealloc(struct trainerpatch*, tpptr, tpptrcnt + 1);
			else
				tpptr = xcalloc(struct trainerpatch*, tpptrcnt + 1);
			tpptr[tpptrcnt++] = tp;

			write_log(_T("%d: '%s' parsed and enabled\n"), cnt, tp->name ? tp->name : _T("<no name>"));
			cnt++;

			ini_setlastasstart(ini, &ictx);
		}

		if (!ini_nextsection(ini, section))
			break;

	}

	if (tpptrcnt > 0)
		debug_opcode_watch = true;

	ini_free(ini);
}

bool debug_trainer_event(int evt, int state)
{
	for (int i = 0; i < tpptrcnt; i++) {
		struct trainerpatch *tp = tpptr[i];
		for (int j = 0; j < tp->eventcount; j++) {
			if (tp->events[j] <= 0)
				continue;
			if (tp->events[j] == evt) {
				if (!state)
					return true;
				write_log(_T("Trainer %d ('%s') -> %s\n"), i, tp->name, tp->enabled ? _T("off") : _T("on"));
				debug_trainer_enable(tp, !tp->enabled);
				return true;
			}
		}
	}
	return false;
}
#endif	/* WINUAE_FOR_HATARI */


bool debug_get_prefetch(int idx, uae_u16 *opword)
{
	if (currprefs.cpu_compatible) {
		if (currprefs.cpu_model < 68020) {
			if (idx == 0) {
				*opword = regs.ir;
				return true;
			}
			if (idx == 1) {
				*opword = regs.irc;
				return true;
			}
			*opword = get_word_debug(m68k_getpc() + idx * 2);
			return false;
		} else {
			if (regs.prefetch020_valid[idx]) {
				*opword = regs.prefetch020[idx];
				return true;
			}
			*opword = get_word_debug(m68k_getpc() + idx * 2);
			return false;
		}

	} else {
		*opword = get_word_debug(m68k_getpc() + idx * 2);
		return false;
	}
}

#ifndef WINUAE_FOR_HATARI
#define DEBUGSPRINTF_SIZE 32
static int debugsprintf_cnt;
struct dsprintfstack
{
	uae_u32 val;
	int size;
};
static dsprintfstack debugsprintf_stack[DEBUGSPRINTF_SIZE];
static uae_u16 debugsprintf_latch, debugsprintf_latched;
static uae_u32 debugsprintf_cycles, debugsprintf_cycles_set;
static uaecptr debugsprintf_va;
static int debugsprintf_mode;

static void read_bstring(char *out, int max, uae_u32 addr)
{
	out[0] = 0;
	if (!valid_address(addr, 1))
		return;
	uae_u8 l = get_byte(addr);
	if (l > max)
		l = max;
	addr++;
	for (int i = 0; i < l && i < max; i++) {
		uae_u8 c = 0;
		if (valid_address(addr, 1)) {
			c = get_byte(addr);
		}
		if (c == 0) {
			c = '.';
		}
		addr++;
		out[i] = c;
		out[i + 1] = 0;
	}
}

static void read_string(char *out, int max, uae_u32 addr)
{
	out[0] = 0;
	for (int i = 0; i < max; i++) {
		uae_u8 c = 0;
		if (valid_address(addr, 1)) {
			c = get_byte(addr);
		}
		addr++;
		out[i] = c;
		out[i + 1] = 0;
		if (!c)
			break;
	}
}

static void parse_custom(char *out, int buffersize, char *format, char *p, char c)
{
	bool gotv = false;
	bool gots = false;
	out[0] = 0;
	uae_u32 v = 0;
	char s[256];
	if (!strcmp(p, "CYCLES")) {
		if (debugsprintf_cycles_set) {
			v = (get_cycles() - debugsprintf_cycles) / CYCLE_UNIT;
		} else {
			v = 0xffffffff;
		}
		gotv = true;
	}
	if (gotv) {
		if (c == 'x' || c == 'X' || c == 'd' || c == 'i' || c == 'u' || c == 'o') {
			char *fs = format + strlen(format);
			*fs++ = c;
			*fs = 0;
			snprintf(out, buffersize, format, v);
		} else {
			strcpy(s, "****");
			gots = true;
		}
	}
	if (gots) {
		char *fs = format + strlen(format);
		*fs++ = 's';
		*fs = 0;
		snprintf(out, buffersize, format, s);
	}
}

static uae_u32 get_value(struct dsprintfstack **stackp, uae_u32 *sizep, uaecptr *ptrp, uae_u32 size)
{
	if (debugsprintf_mode) {
		uae_u32 v;
		uaecptr ptr = *ptrp;
		if (size == sz_long) {
			v = get_long_debug(ptr);
			ptr += 4;
		} else if (size == sz_word) {
			v = get_word_debug(ptr);
			ptr += 2;
		} else {
			v = get_byte_debug(ptr);
			ptr++;
		}
		*ptrp = ptr;
		*sizep = size;
		return v;
	} else {
		struct dsprintfstack *stack = *stackp;
		uae_u32 v = stack->val;
		if (stack->size == 0)
			v &= 0xff;
		else if (stack->size == 1)
			v &= 0xffff;
		if (size == 1)
			v &= 0xffff;
		*sizep = size;
		stack++;
		*stackp = stack;
		return v;
	}
}

static void debug_sprintf_do(uae_u32 s)
{
	int cnt = 0;
	char format[MAX_DPATH];
	char out[MAX_DPATH];
	read_string(format, MAX_DPATH - 1, s);
	char *p = format;
	char *d = out;
	bool gotm = false;
	bool l = false;
	uaecptr ptr = debugsprintf_va;
	struct dsprintfstack *stack = debugsprintf_stack;
	char fstr[100], *fstrp;
	int buffersize = MAX_DPATH - 1;
	fstrp = fstr;
	*d = 0;
	for (;;) {
		char c = *p++;
		if (c == 0)
			break;
		if (gotm) {
			bool got = false;
			buffersize = MAX_DPATH - strlen(out);
			if (buffersize <= 1)
				break;
			if (c == '%') {
				*d++ = '%';
				gotm = false;
			} else if (c == 'l') {
				l = true;
			} else if (c == 'c') {
				uae_u32 size;
				uae_u32 val = get_value(&stack, &size, &ptr, l ? sz_long : sz_word);
				*fstrp++ = c;
				*fstrp = 0;
				snprintf(d, buffersize, fstr, val);
				got = true;
			} else if (c == 'b') {
				uae_u32 size;
				uae_u32 val = get_value(&stack, &size, &ptr, sz_long);
				char tmp[MAX_DPATH];
				read_bstring(tmp, MAX_DPATH - 1, val);
				*fstrp++ = 's';
				*fstrp = 0;
				snprintf(d, buffersize, fstr, tmp);
				got = true;
			} else if (c == 's') {
				uae_u32 size;
				uae_u32 val = get_value(&stack, &size, &ptr, sz_long);
				char tmp[MAX_DPATH];
				read_string(tmp, MAX_DPATH - 1, val);
				*fstrp++ = c;
				*fstrp = 0;
				snprintf(d, buffersize, fstr, tmp);
				got = true;
			} else if (c == 'p') {
				uae_u32 size;
				uae_u32 val = get_value(&stack, &size, &ptr, sz_long);
				snprintf(d, buffersize, "$%08x", val);
				got = true;
			} else if (c == 'x' || c == 'X' || c == 'd' || c == 'i' || c == 'u' || c == 'o') {
				uae_u32 size;
				uae_u32 val = get_value(&stack, &size, &ptr, l ? sz_long : sz_word);
				if (c == 'd' || c == 'i') {
					if (size == sz_word && (val & 0x8000)) {
						val = (uae_s32)(uae_s16)val;
					}
				}
				*fstrp++ = c;
				*fstrp = 0;
				snprintf(d, buffersize, fstr, val);
				got = true;
			} else if (c == '[') {
				char *next = strchr(p, ']');
				if (next && next[1]) {
					char customout[MAX_DPATH];
					customout[0] = 0;
					*next = 0;
					parse_custom(d, buffersize, fstr, p, next[1]);
					p = next + 2;
					got = true;
				} else {
					gotm = false;
				}
			} else {
				if (fstrp - fstr < sizeof(fstr) - 1) {
					*fstrp++ = c;
					*fstrp = 0;
				}
			}
			if (got) {
				d += strlen(d);
				gotm = false;
			}
		} else if (c == '%') {
			l = false;
			fstrp = fstr;
			*fstrp++ = c;
			*fstrp = 0;
			gotm = true;
		} else {
			*d++ = c;
		}
		*d = 0;
	}
	write_log("%s", out);
}

bool debug_sprintf(uaecptr addr, uae_u32 val, int size)
{
	if (!currprefs.debug_mem)
		return false;

	uae_u32 v = val;
	if (size == sz_word && currprefs.cpu_model < 68020) {
		v &= 0xffff;
		if (!(addr & 2)) {
			debugsprintf_latch = v;
			debugsprintf_latched = 1;
		} else if (debugsprintf_latched) {
			v |= debugsprintf_latch << 16;
			size = sz_long;
			if (!(addr & 4) && debugsprintf_cnt > 0) {
				debugsprintf_cnt--;
			}
		}
	}
	if (size != sz_word) {
		debugsprintf_latched = 0;
	}
	if ((addr & (8 | 4)) == 4) {
		if (size != sz_long)
			return true;
		debug_sprintf_do(v);
		debugsprintf_cnt = 0;
		debugsprintf_latched = 0;
		debugsprintf_cycles = get_cycles();
		debugsprintf_cycles_set = 1;
	} else if ((addr & (8 | 4)) == 8) {
		if (size != sz_long)
			return true;
		debugsprintf_va = val;
		debugsprintf_mode = 1;
	} else {
		if (debugsprintf_cnt < DEBUGSPRINTF_SIZE) {
			debugsprintf_stack[debugsprintf_cnt].val = v;
			debugsprintf_stack[debugsprintf_cnt].size = size;
			debugsprintf_cnt++;
		}
		debugsprintf_mode = 0;
	}
	return true;
}
#endif
