/**
 ** Supermodel
 ** A Sega Model 3 Arcade Emulator.
 ** Copyright 2011 Bart Trzynadlowski, Nik Henson
 **
 ** This file is part of Supermodel.
 **
 ** Supermodel is free software: you can redistribute it and/or modify it under
 ** the terms of the GNU General Public License as published by the Free 
 ** Software Foundation, either version 3 of the License, or (at your option)
 ** any later version.
 **
 ** Supermodel is distributed in the hope that it will be useful, but WITHOUT
 ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 ** FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 ** more details.
 **
 ** You should have received a copy of the GNU General Public License along
 ** with Supermodel.  If not, see <http://www.gnu.org/licenses/>.
 **/

/*
 * ConsoleDebugger.cpp
 */

#ifdef SUPERMODEL_DEBUGGER

#include "ConsoleDebugger.h"
#include "CPUDebug.h"
#include "CodeAnalyser.h"
#include "Label.h"

#include <cctype>
#include <string>

namespace Debugger
{
	CConsoleDebugger::CConsoleDebugger() : CDebugger(), 
		m_nextFrame(false), m_listDism(0), m_listMem(0), m_analyseCode(true), 
		m_addrFmt(HexDollar), m_portFmt(Decimal), m_dataFmt(HexDollar),
		m_showLabels(true), m_labelsOverAddr(true), m_showOpCodes(false), m_memBytesPerRow(12), m_file(NULL)
	{
		//
	}

	// TODO - tidy up this function, ie do some proper parsing of commands - it is a mess!
	void CConsoleDebugger::WaitCommand(CCPUDebug *cpu)
	{
		m_cpu = cpu;

		UINT32 pc = m_cpu->pc;
		m_listDism = (m_cpu->instrCount > 0 && pc > 10 * m_cpu->minInstrLen ? pc - 10 * m_cpu->minInstrLen : 0);

		char bpChr;
		char addrStr[20];
		char labelStr[13];
		char opCodes[50];
		int codesLen;
		char mnemonic[255];
		char operands[255];
		char cmd[255];
		
		for (;;)
		{
			// Get code analyser and if available analyse code now if required
			if (m_analyseCode)
			{
				CCodeAnalyser *analyser = m_cpu->GetCodeAnalyser();
				if (analyser->NeedsAnalysis())
				{
					Print("Analysing %s...\n", m_cpu->name);
					analyser->AnalyseCode();
				}
			}
		
			// Close redirected output file, if exists
			if (m_file != NULL)
			{
				fclose(m_file);
				m_file = NULL;
			}

			// Get details for current PC address
			bool hasLabel;
			if (m_cpu->instrCount > 0)
			{
				m_cpu->FormatAddress(addrStr, pc);
				hasLabel = GetLabelText(labelStr, 12, pc);
				codesLen = m_cpu->Disassemble(pc, mnemonic, operands);
				FormatOpCodes(opCodes, pc, abs(codesLen));
			}
			else
			{
				addrStr[0] = '-';
				addrStr[1] = '\0';
				hasLabel = false;
				labelStr[0] = '\0';
				opCodes[0] = '-';
				opCodes[1] = '\0';
				codesLen = 0;
			}
			CBreakpoint *bp = m_cpu->GetBreakpoint(pc);
			bpChr = (bp != NULL ? bp->symbol : ' ');
		
			// Output command prompt
			Print("%s%c", m_cpu->name, bpChr);
			if (m_showLabels)
			{
				if (m_labelsOverAddr)
					Print("%-12s ", (hasLabel ? labelStr : addrStr));
				else
					Print("%s %-12s ", addrStr, labelStr);
			}
			else
				Print("%s ", addrStr);
			if (m_showOpCodes) 
				Print("[%s] ", opCodes);
			if (codesLen > 0)
				Print("%-*s %s > ", (int)m_cpu->maxMnemLen, mnemonic, operands);
			else
				Print("??? > ");
			Flush();

			// Wait for command
			Read(cmd, 255);

			if (cmd[0] == '\0')
			{
				m_cpu->SetStepMode(StepInto);
				break;
			}

			// Check for redirection
			char *pos = strchr(cmd, '>');
			if (pos != NULL)
			{
				*pos = '\0';
				pos++;
				const char *mode;
				if (*pos == '>')
				{
					mode = "ab";
					pos++;
				}
				else
					mode = "w";
				while (*pos == ' ')
					pos++;
				if (*pos != '\0')
				{
					m_file = fopen(pos, mode);
					if (m_file == NULL)
					{
						Error("Unable to direct output to file %s\n", pos);	
						continue;
					}
				}
				else
				{
					Error("Missing file to direct output to\n");
					continue;
				}
			}

			char *token = strtok(cmd, " ");
			if (ProcessToken(token, cmd))
				break;

			pc = m_cpu->pc;
		}
	}

	bool CConsoleDebugger::ProcessToken(const char *token, const char *cmd)
	{
		UINT32 pc = m_cpu->pc;

		int number;
		unsigned size;
		const char *sizeStr;
		UINT32 addr;
		UINT16 portNum;
		UINT64 data;
		char addrStr[50];
		char portNumStr[50];
		char dataStr[50];
		char mod[10];
		EFormat fmt;

		//
		// Execution
		//
		if (CheckToken(token, "n", "next"))							// next [<count>=1]
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token != NULL)
			{
				if (!ParseInt(token, &number) || number <= 0)
				{
					Error("Enter a valid instruction count.\n");
					return false;
				}
				
				if (number > 1)
					Print("Running %d instructions.\n", number);
				m_cpu->SetCount(number);
			}
			else
				m_cpu->SetStepMode(StepInto);
			return true;
		}
		else if (CheckToken(token, "nf", "nextframe"))				// nextframe [<count>=1]
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token != NULL)
			{
				if (!ParseInt(token, &number) || number <= 0)
				{
					Error("Enter a valid frame count.\n");
					return false;
				}
				
				if (number > 1)
					Print("Running %d frames.\n", number);
				m_nextFrameCount = number;
			}
			else
				m_nextFrameCount = 1;
			m_nextFrame = true;
			return true;
		}
		else if (CheckToken(token, "s", "stepover"))				// stepover
		{
			m_cpu->SetStepMode(StepOver);
			return true;
		}
		else if (CheckToken(token, "si", "stepinto"))				// stepinto
		{
			m_cpu->SetStepMode(StepInto);
			return true;
		}
		else if (CheckToken(token, "so", "stepout"))				// stepout
		{
			m_cpu->SetStepMode(StepOut);
			return true;
		}
		else if (CheckToken(token, "c", "continue"))				// continue [<addr>]
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token != NULL)
			{
				if (!ParseAddress(token, &addr))
				{
					Error("Enter a valid address.\n");
					return false;
				}

				m_cpu->FormatAddress(addrStr, addr);
				Print("Continuing until %s.\n", addrStr);
				m_cpu->SetUntil(addr);
			}
			else
				m_cpu->SetContinue();
			return true;
		}
		else if (CheckToken(token, "spc", "setpc"))					// setpc <addr>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing address.\n");
				return false;
			}
			if (!ParseAddress(token, &addr))
			{
				Error("Enter a valid address.\n");
				return false;
			}

			if (!m_cpu->SetPC(addr))
			{
				Error("Unable to set PC.\n");
				return false;
			}

			m_cpu->FormatAddress(addrStr, addr);
			Print("PC set to %s.\n", addrStr);
			return true;
		}
		//
		// CPUs
		//
		else if (CheckToken(token, "lc", "listcpus"))				// listcpus
		{
			ListCPUs();
		}
		else if (CheckToken(token, "sc", "switchcpu"))				// switchcpu (<name>|<num>)
		{	
			// Parse arguments
			token = strtok(NULL, " ");
			CCPUDebug *cpu;
			if (!ParseCPU(token, cpu))
				return false;
			
			if (!cpu->IsEnabled())
			{
				Error("CPU %s is currently disabled for debugging.\n", cpu->name);
				return false;
			}

			m_cpu = cpu;
			pc = cpu->pc;
			m_listDism = (cpu->instrCount > 0 && pc > 10 * cpu->minInstrLen ? pc - 10 * cpu->minInstrLen : 0);
			return false;
		}
		else if (CheckToken(token, "dc", "disablecpu"))				// disablecpu (<name>|<num>)
		{
			// Parse arguments
			token = strtok(NULL, " ");
			CCPUDebug *cpu;
			if (!ParseCPU(token, cpu))
				return false;
			
			if (cpu == m_cpu)
			{
				Error("Cannot enable/disable debugging on current CPU.\n");
				return false;
			}
			cpu->SetEnabled(false);
			Print("Disabled debugging on CPU %s.\n", cpu->name);
		}
		else if (CheckToken(token, "ec", "enablecpu"))				// enablecpu (<name>|<num>)
		{
			// Parse arguments
			token = strtok(NULL, " ");
			CCPUDebug *cpu;
			if (!ParseCPU(token, cpu))
				return false;

			if (cpu == m_cpu)
			{
				Error("Cannot enable/disable debugging on current CPU.\n");
				return false;
			}
			cpu->SetEnabled(true);
			Print("Enabled debugging on CPU %s.\n", cpu->name);
		}
		//
		// Registers
		//
		else if (CheckToken(token, "lr", "listregisters"))			// listregisters
		{
			ListRegisters();
		}
		else if (CheckToken(token, "pr", "printregister"))			// printregister <reg>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			CRegister *reg;
			if (!ParseRegister(token, reg))
				return false;
			
			reg->GetValue(dataStr);
			Print("Register %s = %s\n", reg->name, dataStr);
		}
		else if (CheckToken(token, "sr", "setregister"))			// setregister <reg> <expr>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			CRegister *reg;
			if (!ParseRegister(token, reg))
				return false;
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing value to set.\n");
				return false;
			}

			if (!reg->SetValue(token))
			{
				Error("Unable to set value of register %s.\n", reg->name);
				return false;
			}
			reg->GetValue(dataStr);
			Print("Set register %s to %s.\n", reg->name, dataStr);
		}
		else if (CheckToken(token, "lm", "listmonitors"))			// listmonitors
		{
			ListMonitors();
		}	
		else if (CheckToken(token, "m", "monitor") ||				// addmonitor <reg>
			CheckToken(token, "am", "addmonitor"))
		{
			// Parse arguments
			token = strtok(NULL, " ");
			CRegister *reg;
			if (!ParseRegister(token, reg))
				return false;

			m_cpu->AddRegMonitor(reg->name);
			Print("Monitor added to register %s.\n", reg->name);
		}
		else if (CheckToken(token, "rm", "removemonitor"))			// removemonitor <reg>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			CRegister *reg;
			if (!ParseRegister(token, reg))
				return false;

			m_cpu->RemoveRegMonitor(reg->name);
			Print("Monitor for register %s removed.\n", reg->name);
		}
		else if (CheckToken(token, "ram", "removeallmonitors"))     // removeallmonitors
		{
			m_cpu->RemoveAllRegMonitors();
			Print("All register monitors removed.\n");
		}
		//
		// Exceptions & interrupts
		//
		else if (CheckToken(token, "le", "listexceptions"))			// listexceptions
		{
			ListExceptions();
		}
		else if (CheckToken(token, "li", "listinterrupts"))			// listinterrupts
		{
			ListInterrupts();
		}
		else if (CheckToken(token, "t", "trap") ||					// addtrap ((e)xception|(i)nterrupt) <id>
			CheckToken(token, "at", "addtrap"))
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing type (e)xception or (i)interrupt\n");
				return false;
			}
			
			if (CheckToken(token, "e", "exception"))
			{
				token = strtok(NULL, " ");
				if (token == NULL)
				{
					Error("Missing exception id.\n");
					return false;
				}
				CException *ex = m_cpu->GetException(token);
				if (ex == NULL)
				{
					Error("Enter a valid exception id.\n");
					return false;
				}

				ex->trap = true;
				Print("Trap added for exceptions of type %s.\n", ex->id);
			}
			else if (CheckToken(token, "i", "interrupt"))
			{
				token = strtok(NULL, " ");
				if (token == NULL)
				{
					Error("Missing interrupt id.\n");
					return false;
				}
				CInterrupt *in = m_cpu->GetInterrupt(token);
				if (in == NULL)
				{
					Error("Enter a valid interrupt id.\n");
					return false;
				}

				in->trap = true;
				Print("Trap added for interrupts of type %s.\n", in->id);
			}
			else
			{
				Error("Enter valid type (e)xception or (i)interrupt.\n");
				return false;
			}
		}
		else if (CheckToken(token, "rt", "removetrap"))				// removetrap ((e)xception|(i)nterrupt) <id>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing type (e)xception or (i)interrupt.\n");
				return false;
			}
			
			if (CheckToken(token, "e", "exception"))
			{
				token = strtok(NULL, " ");
				if (token == NULL)
				{
					Error("Missing exception id.\n");
					return false;
				}
				CException *ex = m_cpu->GetException(token);
				if (ex == NULL)
				{
					Error("Enter a valid exception id.\n");
					return false;
				}

				ex->trap = false;
				Print("Trap for exceptions of type %s removed.\n", ex->id);
			}
			else if (CheckToken(token, "i", "interrupt"))
			{
				token = strtok(NULL, " ");
				if (token == NULL)
				{
					Error("Missing interrupt id.\n");
					return false;
				}
				CInterrupt *in = m_cpu->GetInterrupt(token);
				if (in == NULL)
				{
					Error("Enter a valid interrupt id.\n");
					return false;
				}

				in->trap = false;
				Print("Trap for interrupts ot type %s removed.\n", in->id);
			}
			else
			{
				Error("Enter a valid type (e)xception or (i)interrupt.\n");
				return false;
			}
		}
		else if (CheckToken(token, "rat", "removealltraps"))		// removealltraps [(a)ll|(e)xceptions|(i)nterrupts]
		{
			bool removeExs;
			bool removeInts;
			const char *coverage;
			if (token == NULL || CheckToken(token, "a", "all"))
			{
				removeExs  = true;
				removeInts = true;
				coverage = "exceptions and interrupts";
			}
			else if (CheckToken(token, "e", "exceptions"))
			{
				removeExs = true;
				removeInts = false;
				coverage = "exceptions";
			}
			else if (CheckToken(token, "i", "interrupts"))
			{
				removeExs = false;
				removeInts = true;
				coverage = "interrupts";
			}
			else
			{
				Error("Enter a valid mode (a)ll, (e)xceptions or (i)nterrupts\n");
				return false;
			}

			if (removeExs)
			{	
				for (vector<CException*>::iterator it = m_cpu->exceps.begin(); it != m_cpu->exceps.end(); it++)
					(*it)->trap = false;
			}
			if (removeInts)
			{
				for (vector<CInterrupt*>::iterator it = m_cpu->inters.begin(); it != m_cpu->inters.end(); it++)
					(*it)->trap = false;
			}
			Print("All traps for %s removed.\n", coverage);
		}
		//
		// Disassembly, labels & comments
		//
		else if (CheckToken(token, "l", "list") ||					// listdisassembly [<start>=last [#<instrs>=20|<end>]]
			CheckToken(token, "ld", "listdisassembly"))
		{
			// Get start address
			UINT32 start;
			token = strtok(NULL, " ");
			if (token == NULL)
				token = "last";
			if (stricmp(token, "last") == 0)
			{
				// Use end of last listing
				start = m_listDism;
			}
			else if (!ParseAddress(token, &start))
			{
				Error("Enter a valid start address.\n");
				return false;
			}

			// Get end address
			UINT32 end;
			unsigned numInstrs;
			token = strtok(NULL, " ");
			if (token != NULL)
			{
				if (token[0] == '#')
				{
					if (!ParseInt(token + 1, &number) || number <= 0)
					{
						Error("Enter a valid number of instructions.\n");
						return false;
					}
					numInstrs = (unsigned)number;
					end = 0xFFFFFFFF;
				}
				else 
				{
					if (!ParseAddress(token, &end))
					{
						Error("Enter a valid end address.\n");
						return false;
					}
					numInstrs = 0xFFFFFFFF;
				}
			}
			else
			{
				// Default is 20 instructions after start
				end = 0xFFFFFFFF;
				numInstrs = 20;
			}

			// List the disassembled code
			m_listDism = ListDisassembly(start, end, numInstrs);
		}
		else if (CheckToken(token, "ll", "listlabels"))				// listlabels [(d)efault|(c)ustom|(a)utos|(e)ntrypoints|e(x)cephandlers|(i)interhandlers|(j)umptargets|(l)ooppoints]
		{
			// Parse arguments
			token = strtok(NULL, " ");
			bool customLabels;
			ELabelFlags labelFlags;
			if (token == NULL || CheckToken(token, "d", "default"))
			{
				customLabels = true;
				labelFlags = (ELabelFlags)(LFEntryPoint | LFExcepHandler | LFInterHandler | LFSubroutine);
			}
			else if (CheckToken(token, "c", "custom"))
			{
				customLabels = true;
				labelFlags = LFNone;
			}
			else if (CheckToken(token, "a", "autos"))
			{
				customLabels = true;
				labelFlags = (ELabelFlags)(LFEntryPoint | LFExcepHandler | LFInterHandler | LFSubroutine | LFJumpTarget | LFLoopPoint);
			}
			else if (CheckToken(token, "e", "entrypoints"))
			{
				customLabels = false;
				labelFlags = LFEntryPoint;
			}
			else if (CheckToken(token, "x", "excephandlers"))
			{
				customLabels = false;
				labelFlags = LFExcepHandler;
			}
			else if (CheckToken(token, "i", "interhandlers"))
			{
				customLabels = false;
				labelFlags = LFInterHandler;
			}
			else if (CheckToken(token, "s", "subroutines"))
			{
				customLabels = false;
				labelFlags = LFSubroutine;
			}
			else if (CheckToken(token, "j", "jumptargets"))
			{
				customLabels = false;
				labelFlags = LFJumpTarget;
			}
			else if (CheckToken(token, "l", "looppoints"))
			{
				customLabels = false;
				labelFlags = LFLoopPoint;
			}
			else
			{
				Error("Enter a valid filter (a)ll, (c)ustom, (e)ntrypoints, e(x)cephandlers, (i)interhandlers, (j)umptargets or (l)ooppoints.\n");
				return false;
			}

			ListLabels(customLabels, labelFlags);
		}
		else if (CheckToken(token, "al", "addlabel"))				// addlabel <addr> <name>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing label address.\n");
				return false;
			}
			else if (!ParseAddress(token, &addr))
			{
				Error("Enter a valid label address.\n");
				return false;
			}

			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing label name.\n");
				return false;
			}
			const char *name = token;
			
			// Add label
			CLabel *label = m_cpu->AddLabel(addr, name);
			m_cpu->FormatAddress(addrStr, label->addr);
			Print("Label %s added at %s.\n", label->name, addrStr);
		}
		else if (CheckToken(token, "rl", "removelabel"))			// removelabel [<name>|<addr>]
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
				token = "-";
			
			if (!ParseAddress(token, &addr))
			{
				Error("Enter a valid label name or address.\n");
				return false;
			}

			CLabel *label = m_cpu->GetLabel(addr);
			if (label == NULL)
			{
				m_cpu->FormatAddress(addrStr, addr);
				Error("No label at %s.\n", addrStr);
				return false;
			}
			
			const char *name = label->name;
			m_cpu->FormatAddress(addrStr, label->addr);
			Print("Custom label '%s' removed at address %s.\n", name, addrStr);
		}
		else if (CheckToken(token, "ral", "removealllabels"))		// removealllabels
		{
			m_cpu->RemoveAllLabels();
			Print("All custom labels removed.\n");
		}
		else if (CheckToken(token, "ac", "addcomment"))				// addcomment <addr> <text...>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing comment address.\n");
				return false;
			}
			else if (!ParseAddress(token, &addr))
			{
				Error("Enter a valid comment address.\n");
				return false;
			}

			char text[255];
			text[0] = '\0';
			token = strtok(NULL, " ");
			while (token != NULL)
			{
				size_t len = strlen(text);
				if (len + strlen(token) > 253)
					break;
				if (len > 0)
					strcat(text, " ");
				strcat(text, token);
				token = strtok(NULL, " ");
			}
			if (text[0] == '\0')
			{
				Error("Missing comment text.\n");
				return false;
			}

			// Add comment
			CComment *comment = m_cpu->AddComment(addr, text);
			m_cpu->FormatAddress(addrStr, comment->addr);
			Print("Comment added at %s.\n", addrStr);
		}
		else if (CheckToken(token, "rc", "removecomment"))			// removecomment [<addr>]
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
				token = "-";
			if (!ParseAddress(token, &addr))
			{
				Error("Enter a valid comment address.\n");
				return false;
			}

			m_cpu->FormatAddress(addrStr, addr);
			if (m_cpu->RemoveComment(addr))
				Print("Comment at address %s removed.\n", addrStr);
			else
				Error("No comment at address %s.\n", addrStr);
		}
		else if (CheckToken(token, "rac", "removeallcomments"))     // removeallcomments
		{
			m_cpu->RemoveAllComments();
			Print("All comments removed.\n");
		}
		//
		// Breakpoints
		//
		else if (CheckToken(token, "lb", "listbreakpoints"))		// listbreakpoints
		{
			ListBreakpoints();
		}
		else if (CheckToken(token, "b", "breakpoint") ||			// addbreakpoint [<addr> [[s)imple|(c)ount <count>)]]
			CheckToken(token, "ab", "addbreakpoint"))
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
				token = "-";
			if (!ParseAddress(token, &addr))
			{
				Error("Enter a valid address.\n");
				return false;
			}
			token = strtok(NULL, " ");
			CBreakpoint *bp;
			if (token == NULL || CheckToken(token, "s", "simple"))
				bp = m_cpu->AddSimpleBreakpoint(addr);
			else if (CheckToken(token, "c", "count"))
			{
				token = strtok(NULL, " ");
				if (token == NULL)
				{
					Error("Missing count.\n");
					return false;
				}
				int count;
				if (!ParseInt(token, &count) || count <= 0)
				{
					Error("Enter a valid count.\n");
					return false;
				}

				bp = m_cpu->AddCountBreakpoint(addr, count);
			}
			else if (CheckToken(token, "p", "print"))
				bp = m_cpu->AddPrintBreakpoint(addr);
			else
			{
				Error("Enter a valid breakpoint type (s)imple or (c)ount.\n");
				return false;
			}

			m_cpu->FormatAddress(addrStr, bp->addr);
			Print("Breakpoint #%d added at address %s.\n", bp->num, addrStr);
		}
		else if (CheckToken(token, "rb", "removebreakpoint"))		// removebreakpoint [#<num>|<addr>]
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
				token = "-";
			if (token[0] == '#')
			{
				// Remove breakpoint by number
				if (!ParseInt(token + 1, &number) || number < 0 || number >= m_cpu->bps.size())
				{
					Error("Enter a valid breakpoint number.\n");
					return false;
				}

				// Remove breakpoint
				m_cpu->RemoveBreakpoint(m_cpu->bps[number]);
				Print("Breakpoint #%d removed.\n", number);
			}
			else
			{
				// Remove breakpoint by address
				if (!ParseAddress(token, &addr))
				{
					Error("Enter a valid address.\n");
					return false;
				}

				// Remove breakpoint
				m_cpu->FormatAddress(addrStr, addr);
				if (m_cpu->RemoveBreakpoint(addr))
					Print("Breakpoint at address %s removed.\n", addrStr);
				else
					Error("No breakpoint at address %s.\n", addrStr);
			}
		}
		else if (CheckToken(token, "rab", "removeallbreakpoints"))  // removeallbreakpoints
		{
			m_cpu->RemoveAllBreakpoints();
			Print("All breakpoints removed.\n");
		}	
		//
		// Memory, I/O & watches
		//
		else if (CheckToken(token, "ln", "listregions"))			// listregions
		{
			ListRegions();
		}
		else if (CheckToken(token, "ly", "listmemory"))				// listmemory [<start>=last [#<rows>=8|<end>]]
		{
			// Get start address
			UINT32 start;
			token = strtok(NULL, " ");
			if (token == NULL)
				token = "last";
			if (stricmp(token, "last") == 0)
			{
				// Use end of last listing
				start = m_listMem;
			}
			else if (!ParseAddress(token, &start))
			{
				Error("Enter a valid start address.\n");
				return false;
			}
	
			// Get end address
			UINT32 end;
			token = strtok(NULL, " ");
			if (token != NULL)
			{
				if (token[0] == '#')
				{
					if (!ParseInt(token + 1, &number) || number <= 0)
					{
						Error("Enter a valid number of rows.\n");
						return false;
					}
					end = start + number * m_memBytesPerRow;
				}
				else
				{
					if (!ParseAddress(token, &end))
					{
						Error("Enter a valid end address.\n");
						return false;
					}
				}
			}
			else
				// Default is 8 rows after start
				end = start + 8 * m_memBytesPerRow;

			// List the memory
			m_listMem = ListMemory(start, end, m_memBytesPerRow);
		}
		else if (CheckToken(token, "py", "printmemory", mod, 9, "b")) // printmemory[.<size>=b] <addr> [(h)ex|hexdo(l)lar|hex(p)osth|(d)ecimal|(b)inary]
		{
			// Parse arguments
			if (!ParseDataSize(mod, size))
				return false;
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing address.\n");
				return false;
			}
			if (!ParseAddress(token, &addr))
			{
				Error("Enter a valid address.\n");
				return false;
			}
			token = strtok(NULL, " ");
			if (token != NULL)
			{
				if (!ParseFormat(token, fmt))
					return false;
			}
			else
				fmt = m_dataFmt;

			// Read and print memory
			char uSizeStr[12];
			sizeStr = GetDataSizeStr(size, false);
			UpperFirst(uSizeStr, sizeStr);
			data = m_cpu->ReadMem(addr, size);
			FormatData(dataStr, fmt, size, data);
			m_cpu->FormatAddress(addrStr, addr);
			Print("%s data at %s = %s.\n", uSizeStr, addrStr, dataStr);
		}
		else if (CheckToken(token, "sy", "setmemory", mod, 9, "b"))	// setmemory[.<size>=b] <addr> <value>
		{
			// Parse arguments
			if (!ParseDataSize(mod, size))
				return false;
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing address.\n");
				return false;
			}
			if (!ParseAddress(token, &addr))
			{
				Error("Enter a valid address.\n");
				return false;
			}
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing value to set.\n");
				return false;
			}
			sizeStr = GetDataSizeStr(size, false);
			if (!m_cpu->ParseData(token, size, &data))
			{
				Error("Enter a valid %s value.\n", sizeStr);
				return false;
			}	
			
			// Set memory
			char uSizeStr[12];
			UpperFirst(uSizeStr, sizeStr);
			m_cpu->WriteMem(addr, size, data);
			m_cpu->FormatData(dataStr, size, data);
			m_cpu->FormatAddress(addrStr, addr);
			Print("Set %s data at %s to %s.\n", uSizeStr, addrStr, dataStr);
		}
		else if (CheckToken(token, "lo", "listios"))				// listios
		{
			ListIOs();
		}		
		else if (CheckToken(token, "lw", "listmemwatches"))			// listmemwatches
		{
			ListMemWatches();
		}
		else if (CheckToken(token, "w", "memwatch", mod, 9, "b") ||	// addmemwatch[.<size>=b] <addr> [((n)one|(r)ead|(w)rite|(rw)eadwrite) [((s)imple|(c)ount <count>|(m)atch <sequence>|captu(r)e <maxlen>|(p)rint)]]
			CheckToken(token, "aw", "addmemwatch", mod, 9, "b"))
		{
			// Parse arguments
			if (!ParseDataSize(mod, size))
				return false;
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing address.\n");
				return false;
			}
			if (!ParseAddress(token, &addr))
			{
				Error("Enter a valid address.\n");
				return false;
			}
			token = strtok(NULL, " ");
			bool read;
			bool write;
			if (token == NULL || CheckToken(token, "n", "none"))
			{
				read = false;
				write = false;
			}
			else if (CheckToken(token, "r", "read"))
			{
				read = true;
				write = false;
			}
			else if (CheckToken(token, "w", "write"))
			{
				read = false;
				write = true;
			}
			else if (CheckToken(token, "rw", "read/write") || CheckToken(token, "wr", "write/read"))
			{
				read = true;
				write = true;
			}
			else
			{
				Error("Enter valid read/write flags (n)one, (r)ead, (w)rite or (rw)ead/write.\n");
				return false;
			}

			// Add mem watch
			CWatch *watch;
			token = strtok(NULL, " ");
			if (token == NULL || CheckToken(token, "s", "simple"))
				watch = m_cpu->AddSimpleMemWatch(addr, size, read, write);
			else if (CheckToken(token, "c", "count"))
			{
				token = strtok(NULL, " ");
				if (token == NULL)
				{
					Error("Missing count.\n");
					return false;
				}
				int count;
				if (!ParseInt(token, &count) || count <= 0)
				{
					Error("Enter a valid count.\n");
					return false;
				}
				watch = m_cpu->AddCountMemWatch(addr, size, read, write, count);
			}
			else if (CheckToken(token, "m", "match"))
			{
				vector<UINT64> dataSeq;
				while ((token = strtok(NULL, " ")) != NULL)
				{
					if (m_cpu->ParseData(token, size, &data))
						dataSeq.push_back(data);
				}
				if (dataSeq.size() == 0)
				{
					sizeStr = GetDataSizeStr(size, false);
					Error("Enter a sequence of %s data to match.", sizeStr);
					return false;
				}
				watch = m_cpu->AddMatchMemWatch(addr, size, read, write, dataSeq);
			}
			else if (CheckToken(token, "r", "capture"))
			{
				token = strtok(NULL, " ");
				if (token == NULL)
				{
					Error("Missing maximum capture length.\n");
					return false;
				}
				int maxLen;
				if (!ParseInt(token, &maxLen) || maxLen <= 0)
				{
					Error("Enter a valid maximum capture length.\n");
					return false;
				}
				watch = m_cpu->AddCaptureMemWatch(addr, size, read, write, maxLen);
			}
			else if (CheckToken(token, "p", "print"))
				watch = m_cpu->AddPrintMemWatch(addr, size, read, write);
			else
			{
				Error("Enter a valid watch type (s)imple, (c)ount, (m)atch, captu(r)e or (p)rint.\n");
				return false;
			}

			m_cpu->FormatAddress(addrStr, watch->addr);
			number = GetIndexOfMemWatch(watch);
			Print("Memory watch #%d added at address %s.\n", number, addrStr);
		}
		else if (CheckToken(token, "rw", "removememwatch"))			// removememwatch (#<num>|<addr>)
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing watch number or address.\n");
				return false;
			}
			if (token[0] == '#')
			{
				// Remove watch by number
				vector<CWatch*> watches;
				GetAllMemWatches(watches);
				if (!ParseInt(token + 1, &number) || number < 0 || number >= watches.size())
				{
					Error("Enter a valid watch number.\n");
					return false;
				}

				// Remove watch
				m_cpu->RemoveWatch(watches[number]);
				Print("Memory watch #%d removed.\n", number);
			}
			else
			{
				// Remove watch by address
				if (!ParseAddress(token, &addr))
				{
					Error("Enter a valid address.\n");
					return false;
				}

				// Remove watch
				m_cpu->FormatAddress(addrStr, addr);
				if (m_cpu->RemoveMemWatch(addr, 1))
					Print("Memory watch at address %s removed.\n", addrStr);
				else
					Error("No memory watch at address %s.\n", addrStr);
			}
		}
		else if (CheckToken(token, "raw", "removeallmemwatches"))	// removeallmemwatches
		{
			// Remove all memory watches
			vector<CWatch*> watches;
			GetAllMemWatches(watches);
			for (vector<CWatch*>::iterator it = watches.begin(); it != watches.end(); it++)
				m_cpu->RemoveWatch(*it);
			Print("All memory watches removed.\n");
		}
		else if (CheckToken(token, "lpw", "listportwatches"))		// listportwatches
		{
			ListPortWatches();
		}
		else if (CheckToken(token, "pw", "portwatch") ||			// addportwatch <port> [((n)one|(i)nput|(o)|(io)nputoutput) [((s)imple|(c)ount <count>|(m)atch <sequence>|captu(r)e <maxlen>|(p)rint)]]
			CheckToken(token, "apw", "addportwatch"))
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing port number.\n");
				return false;
			}
			if (!m_cpu->ParsePortNum(token, &portNum))
			{
				Error("Enter a valid port number.\n");
				return false;
			}
			token = strtok(NULL, " ");
			bool input;
			bool output;
			if (token == NULL || CheckToken(token, "n", "none"))
			{
				input = false;
				output = false;
			}
			else if (CheckToken(token, "i", "input"))
			{
				input = true;
				output = false;
			}
			else if (CheckToken(token, "o", "output"))
			{
				input = false;
				output = true;
			}
			else if (CheckToken(token, "io", "input/output") || CheckToken(token, "oi", "output/input"))
			{
				input = true;
				output = true;
			}
			else
			{
				Error("Enter valid input/output flags (n)one, (i)nput, (o)utput or (io)nput/output.\n");
				return false;
			}

			// Add watch
			CPortIO *port = m_cpu->GetPortIO(portNum);
			CWatch *watch;
			token = strtok(NULL, " ");
			if (token == NULL || CheckToken(token, "s", "simple"))
				watch = port->AddSimpleWatch(input, output);
			else if (CheckToken(token, "c", "count"))
			{
				token = strtok(NULL, " ");
				if (token == NULL)
				{
					Error("Missing count.\n");
					return false;
				}
				int count;
				if (!ParseInt(token, &count) || count <= 0)
				{
					Error("Enter a valid count.\n");
					return false;
				}
				watch = port->AddCountWatch(input, output, count);
			}
			else if (CheckToken(token, "m", "match"))
			{
				vector<UINT64> dataSeq;
				while ((token = strtok(NULL, " ")) != NULL)
				{
					if (m_cpu->ParseData(token, port->dataSize, &data))
						dataSeq.push_back(data);
				}
				if (dataSeq.size() == 0)
				{
					Error("Enter a sequence of %s to match.", GetDataSizeStr(port->dataSize, false));
					return false;
				}
				watch = port->AddMatchWatch(input, output, dataSeq);
			}
			else if (CheckToken(token, "r", "capture"))
			{
				token = strtok(NULL, " ");
				if (token == NULL)
				{
					Error("Missing maximum capture length.\n");
					return false;
				}
				int maxLen;
				if (!ParseInt(token, &maxLen) || maxLen <= 0)
				{
					Error("Enter a valid maximum capture length.\n");
					return false;
				}
				watch = port->AddCaptureWatch(input, output, maxLen);
			}
			else if (CheckToken(token, "p", "print"))
				watch = port->AddPrintWatch(input, output);
			else
			{
				Error("Enter a valid watch type (s)imple, (c)ount, (m)atch, captu(r)e or (p)rint.\n");
				return false;
			}
			
			m_cpu->FormatPortNum(portNumStr, portNum);
			number = GetIndexOfPortWatch(watch);
			Print("Port watch %d added for port %u.\n", number, portNumStr);
		}
		else if (CheckToken(token, "rpw", "removeportwatch"))		// removeportwatch (a|n|p) [NUM|PORT]
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing watch number or port number.\n");
				return false;
			}
			if (token[0] == '#')
			{
				// Remove watch by number
				vector<CWatch*> watches;
				GetAllPortWatches(watches);
				if (!ParseInt(token + 1, &number) || number < 0 || number >= watches.size())
				{
					Error("Enter a valid watch number.\n");
					return false;
				}

				// Remove watch
				m_cpu->RemoveWatch(watches[number]);
				Print("Port watch #%d removed.\n", number);
			}
			else
			{
				// Remove watch by port number
				if (!m_cpu->ParsePortNum(token, &portNum))
				{
					Error("Enter a valid port number.\n");
					return false;
				}

				// Remove watch
				CPortIO *port = m_cpu->GetPortIO(portNum);
				m_cpu->FormatPortNum(portNumStr, portNum);
				if (port->RemoveWatch())
					Print("Port watch for port %s removed.\n", portNumStr);
				else
					Error("No port watch for port %s.\n", portNumStr);
			}
		}
		else if (CheckToken(token, "rapw", "removeallportwatches")) // removeallportwatches
		{
			// Remove all port watches
			vector<CWatch*> watches;
			GetAllPortWatches(watches);
			for (vector<CWatch*>::iterator it = watches.begin(); it != watches.end(); it++)
				m_cpu->RemoveWatch(*it);
			Print("All port watches removed.\n");
		}
		//
		// General
		//		
		else if (CheckToken(token, "p", "print", mod, 9, ""))		// print[.<size>=v] <expr> [(h)ex|hexdo(l)lar|hex(p)osth|(d)ecimal|(b)inary]
		{
			// Parse arguments
			bool useDefSize;
			if (mod[0] == '\0')
				useDefSize = true;
			else if (ParseDataSize(mod, size))
				useDefSize = false;
			else
				return false;
			token = strtok(NULL, " ");
			if (token == NULL)
			{	
				Error("Missing expression.\n");
				return false;
			}
			const char *expr = token;
			token = strtok(NULL, " ");
			if (token != NULL)
			{
				if (!ParseFormat(token, fmt))
					return false;
			}
			else
				fmt = m_dataFmt;

			// Check labels and registers
			CLabel *label = m_cpu->GetLabel(expr);
			if (label != NULL)
			{
				data = label->addr;
				if (useDefSize)
					size = m_cpu->memBusWidth / 8;
			}
			else
			{
				CCodeAnalyser *analyser = m_cpu->GetCodeAnalyser();
				CAutoLabel *autoLabel = analyser->analysis->GetAutoLabel(expr);
				if (autoLabel != NULL)
				{
					data = autoLabel->addr;
					if (useDefSize)
						size = m_cpu->memBusWidth / 8;
				}
				else
				{
					CRegister *reg = m_cpu->GetRegister(expr);
					if (reg != NULL)
					{
						data = reg->GetValueAsInt();
						if (useDefSize)
							size = reg->dataWidth / 8;
					}
					else
					{
						if (!ParseData(expr, m_dataFmt, 8, &data))
						{
							Print("Unable to parse expression %s\n", expr);
							return false;
						}
						if (useDefSize)
							size = 8;
					}
				}
			}
			
			char result[255];
			FormatData(result, fmt, size, data);
			if (useDefSize)
				Print("%s = %s\n", expr, result);
			else
			{
				sizeStr = GetDataSizeStr(size, true);
				Print("%s = %s.%s\n", expr, result, sizeStr);
			}
		}
		else if (CheckToken(token, "cfg", "configure"))				// configure <options...>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				// If no arguments, then print out current configuration
				Print("Configuration:\n");
				Print(" %-20s %-12s %s\n", "Code Analysis",     (m_analyseCode ? "On" : "Off"), "(a)nalysis"); 
				Print(" %-20s %-12s %s\n", "Address Format",    GetFmtConfig(m_addrFmt),        "a(d)dressfmt"); 
				Print(" %-20s %-12s %s\n", "Port Format",       GetFmtConfig(m_portFmt),        "(p)ortfmt"); 
				Print(" %-20s %-12s %s\n", "Data Format",       GetFmtConfig(m_dataFmt),        "da(t)afmt");
				Print(" %-20s %-12s %s\n", "Show Labels",       (m_showLabels  ? "On" : "Off"), "show(l)abels");
				Print(" %-20s %-12s %s\n", "Show Opcodes",      (m_showOpCodes ? "On" : "Off"), "show(o)pcodes");
				Print(" %-20s %-12u %s\n", "Mem Bytes Per Row", m_memBytesPerRow,               "mem(b)ytesrow");
				return false;
			}

			if (CheckToken(token, "a", "analysis"))
			{
				token = strtok(NULL, " ");
				SetBoolConfig(token, m_analyseCode);
			}
			else if (CheckToken(token, "d", "addressfmt"))
			{
				token = strtok(NULL, " ");
				SetFmtConfig(token, m_addrFmt);
			}
			else if (CheckToken(token, "p", "portfmt"))
			{
				token = strtok(NULL, " ");
				SetFmtConfig(token, m_portFmt);
			}
			else if (CheckToken(token, "t", "datafmt"))
			{            
				token = strtok(NULL, " ");
				SetFmtConfig(token, m_dataFmt);
			}
			else if (CheckToken(token, "l", "showlabels"))
			{
				token = strtok(NULL, " ");
				SetBoolConfig(token, m_showLabels);
			}
			else if (CheckToken(token, "o", "showopcodes"))
			{
				token = strtok(NULL, " ");
				SetBoolConfig(token, m_showOpCodes);
			}
			else if (CheckToken(token, "b", "membytesrow"))
			{
				token = strtok(NULL, " ");
				SetNumConfig(token, m_memBytesPerRow);
			}
			else
			{
				Error("Enter a valid option (a)nalysis, a(d)dressfmt, (p)ortfmt, da(t)afmt, show(l)abels, show(o)pcodes, mem(b)ytesrow.\n");
				return false;
			}
		}
		else if (CheckToken(token, "ls", "loadstate"))				// loadstate <filename>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing filename.\n");
				return false;
			}

			if (LoadState(token))
				Print("Debugger state successfully loaded from <%s>\n", token);
			else
				Error("Unable to load debugger state from <%s>\n", token);
		}
		else if (CheckToken(token, "ss", "savestate"))				// savestate <filename>
		{
			// Parse arguments
			token = strtok(NULL, " ");
			if (token == NULL)
			{
				Error("Missing filename.\n");
				return false;
			}

			if (SaveState(token))
				Print("Debugger state successfully saved to <%s>\n", token);
			else
				Error("Unable to save debugger state to <%s>\n", token);
		}
		else if (CheckToken(token, "h", "help"))					// help
		{
			// TODO - improve the following
			const char *fmt = "  %-6s %-25s %s\n";

			Print("Debugger Commands:\n");
			
			Print(" Execution:\n");
			Print(fmt, "n",      "next",                   "[<count>=1]");
			Print(fmt, "nf",     "nextframe",              "[<count>=1]");
			Print(fmt, "s",      "stepover",               "");
			Print(fmt, "si",     "stepinto",               "");
			Print(fmt, "so",     "stepout",                "");
			Print(fmt, "c",      "continue",               "[<addr>]");
			Print(fmt, "spc",    "setpc",                  "<addr>");                   

			Print(" CPUs:\n");
			Print(fmt, "lc",     "listcpus",               "");
			Print(fmt, "sc",     "switchcpu",              "(<name>|<num>)");
			Print(fmt, "dc",     "disablecpu",             "(<name>|<num>)");
			Print(fmt, "ec",     "enablecpu",              "(<name>|<num>)");
			
			Print(" Registers:\n");
			Print(fmt, "lr",     "listregisters",          "");
			Print(fmt, "pr",     "printregister",          "<reg>");
			Print(fmt, "sr",     "setregister",            "<reg> <value>");
			Print(fmt, "lm",     "listmonitors",           "");
			Print(fmt, "m/am",   "addmonitor",             "<reg>");
			Print(fmt, "rm",     "removemonitor",          "<reg>");
			Print(fmt, "ram",    "removeallmonitors",      "");
			
			Print(" Exceptions & interrupts:\n");
			Print(fmt, "le",     "listexceptions",         "");
			Print(fmt, "li",     "listinterrupts",         "");
			Print(fmt, "t/at",   "addtrap",		           "((e)xception|(i)nterrupt) <id>");
			Print(fmt, "rt",     "removetrap",             "((e)xception|(i)nterrupt) <id>");
			Print(fmt, "rat",    "removealltraps",         "[(a)ll|(e)xceptions|(i)nterrupts]");

			Print(" Disassembly, labels & comments:\n");
			Print(fmt, "l/ld",   "listdisassembly",        "[<start>=last [#<instrs>=20|<end>]]");
			Print(fmt, "ll",     "listlabels",             "[(d)efault|(c)ustom|(a)utos|(e)ntrypoints|e(x)cephandlers|(i)interhandlers|(j)umptargets|(l)ooppoints]");
			Print(fmt, "al",     "addlabel",               "<addr> <name>");
			Print(fmt, "rl",     "removelabel",            "[<name>|<addr>]");
			Print(fmt, "ral",    "removealllabels",        "");
			Print(fmt, "ac",     "addcomment",             "<addr> <text...>");
			Print(fmt, "rc",     "removecomment",          "[<addr>]");
			Print(fmt, "rac",    "removeallcomments",      "");

			Print(" Breakpoints:\n");
			Print(fmt, "lb",     "listbreakpoints",        "");
			Print(fmt, "b/ab",   "addbreakpoint",          "[<addr> [[s)imple|(c)ount <count>)]]");
			Print(fmt, "rb",     "removebreakpoint",       "[#<num>|<addr>]");
			Print(fmt, "rab",    "removeallbreakpoints",   "");
			
			Print(" Memory, I/O & watches:\n");
			Print(fmt, "ln",     "listregions",            "");
			Print(fmt, "ly",     "listmemory",             "[<start>=last [#<rows>=8|<end>]]");
			Print(fmt, "py",     "printmemory[.<size>=b]", "<addr>");
			Print(fmt, "sy",     "setmemory[.<size>=b]",   "<addr> <value>");
			Print(fmt, "lo",     "listios",                "");
			Print(fmt, "lw",     "listmemwatches",         "");
			Print(fmt, "w/aw",   "addmemwatch[.<size>=b]", "<addr> [((n)one|(r)ead|(w)rite|(rw)eadwrite) [((s)imple|(c)ount <count>|(m)atch <sequence>|captu(r)e <maxlen>|(p)rint)]]");
			Print(fmt, "rw",     "removememwatch",         "(#<num>|<addr>)");
			Print(fmt, "raw",    "removeallmemwatches",    "");
			Print(fmt, "lpw",    "listportwatches",        "");
			Print(fmt, "pw/apw", "addportwatch",           "<port> [((n)one|(i)nput|(o)utput|(io)nputoutput) [(s)imple|(c)ount <count>|(m)atch <sequence>|captu(r)e <maxlen>|(p)rint]]");
			Print(fmt, "rpw",    "removeportwatch",        "(#<num>|<port>)");
			Print(fmt, "rapw",   "removeallportwatches",   "");
			
			Print("General:\n");
			Print(fmt, "p",      "print[.<size>=v]",       "<expr> [(h)ex|hexdo(l)lar|hex(p)osth|(d)ecimal|(b)inary]");
			Print(fmt, "cfg",	 "configure",			   "<options...>");
			Print(fmt, "ls",     "loststate",              "<filename>");
			Print(fmt, "ss",     "savestate",              "<filename>");
			Print(fmt, "h",      "help",                   "");
			Print(fmt, "x",      "exit",                   "");
		}
		else if (CheckToken(token, "x", "exit"))					// exit
		{
			Print("Exiting...\n");
			SetExit();
			return true;
		}
		else
			Print("Unknown command '%s'.\n", token);
		return false;
	}

	void CConsoleDebugger::Read(char *str, size_t maxSize)
	{
		if (fgets(str, maxSize, stdin) != NULL)
		{
			char *pos = strchr(str, '\n');
			if (pos)
				*pos = '\0';
		}
		else
			str[0] = '\0';
	}

	void CConsoleDebugger::Print(const char *fmtStr, ...)
	{
		va_list vl;
		va_start(vl, fmtStr);
		PrintVL(fmtStr, vl);
		va_end(vl);
	}

	void CConsoleDebugger::Error(const char *fmtStr, ...)
	{
		// Don't log errors to file
		va_list vl;
		va_start(vl, fmtStr);
		vprintf(fmtStr, vl);	
		va_end(vl);
	}

	void CConsoleDebugger::PrintVL(const char *fmtStr, va_list vl)
	{
		if (m_file != NULL)
			vfprintf(m_file, fmtStr, vl);
		else
			vprintf(fmtStr, vl);	
	}

	void CConsoleDebugger::Flush()
	{
		fflush(stdout);
	}

	bool CConsoleDebugger::CheckToken(const char *token, const char *simple, const char *full)
	{
		return stricmp(token, simple) == 0 || stricmp(token, full) == 0;
	}

	bool CConsoleDebugger::CheckToken(const char *token, const char *simple, const char *full, char *modifier, size_t modSize, const char *defaultMod)
	{
		const char *pos = strchr(token, '.');
		if (pos == NULL)
		{
			strncpy(modifier, defaultMod, modSize);
			modifier[modSize] = '\0';
			return CheckToken(token, simple, full);
		}
		else
		{
			pos++;
			if (pos == '\0')
				return false;
			strncpy(modifier, pos, modSize);
			modifier[modSize] = '\0';
			
			char actual[255];
			size_t actSize = min<size_t>(pos - token - 1, 254);
			strncpy(actual, token, actSize);
			actual[actSize] = '\0';
			return CheckToken(actual, simple, full);
		}
	}

	void CConsoleDebugger::Truncate(char *dst, size_t maxLen, const char *src)
	{
		strncpy(dst, src, maxLen);
		if (strlen(src) > maxLen)
			strncpy(&dst[maxLen - 3], "...", 3);
		dst[maxLen] = '\0';
	}

	void CConsoleDebugger::UpperFirst(char *dst, const char *src)
	{
		if (*src != '\0')
		{
			*dst++ = toupper(*src++);
			while (*src != '\0')
				*dst++ = *src++;
		}
		*dst = '\0';
	}

	void CConsoleDebugger::FormatOpCodes(char *str, int addr, int codesLen)
	{
		char *p = str;
		int i = 0;
		while (i < codesLen)
		{
			UINT8 opCode = (UINT8)m_cpu->ReadMem(addr++, 1);
			sprintf(p, "%02X", opCode);
			p += 2;
			i++;
		}
		while (i++ < m_cpu->maxInstrLen)
		{
			*p++ = ' ';
			*p++ = ' ';
		}
		*p = '\0';
	}

	bool CConsoleDebugger::GetLabelText(char *str, int maxLen, UINT32 addr)
	{
		char labelStr[255];
				
		CLabel *label = m_cpu->GetLabel(addr);
		if (label != NULL)
		{
			Truncate(str, maxLen, label->name);
			return true;
		}

		if (m_analyseCode)
		{
			CCodeAnalyser *analyser = m_cpu->GetCodeAnalyser();
			CAutoLabel *autoLabel = analyser->analysis->GetAutoLabel(addr);
			if (autoLabel != NULL && autoLabel->GetLabel(labelStr))
			{
				Truncate(str, maxLen, labelStr);		
				return true;
			}
		}
		
		str[0] = '\0';
		return false;
	}

	bool CConsoleDebugger::SetBoolConfig(const char *str, bool &cfg)
	{
		if (str == NULL)
		{
			Print("Current setting: %-12s\n", (cfg ? "On" : "Off"));
			Print("Change setting with (o)n, o(f)f.\n");
			return false;
		}

		if (CheckToken(str, "o", "on"))
		{
			cfg = true;
			Print("Changed setting: On\n");
			ApplyConfig();
			return true;
		}
		else if (CheckToken(str, "f", "off"))
		{
			cfg = false;
			Print("Changed setting: Off\n");
			ApplyConfig();
			return true;
		}
		else
		{
			Error("Enter a valid setting (o)n, o(f)f.\n");
			return false;
		}
	}

	bool CConsoleDebugger::SetNumConfig(const char *str, unsigned &cfg)
	{
		if (str == NULL)
		{
			Print("Current setting: %-12u\n", cfg);
			return false;
		}

		int number;
		if (!ParseInt(str, &number))
		{
			Error("Enter a valid number.\n");
			return false;
		}

		cfg = (unsigned)number;
		Print("Changed setting: %-12u\n", cfg);
		ApplyConfig();
		return true;
	}

	const char *CConsoleDebugger::GetFmtConfig(EFormat fmt)
	{
		switch (fmt)
		{
			case Hex0x:     return "Hex (0x00)";
			case HexDollar: return "Hex ($00)";
			case HexPostH:  return "Hex (00h)";
			case Decimal:   return "Decimal";
			case Binary:    return "Binary";
			default:        return "-";
		}
	}

	bool CConsoleDebugger::SetFmtConfig(const char *str, EFormat &cfg)
	{
		if (str == NULL)
		{
			Print("Current setting: %-12s\n", GetFmtConfig(cfg));
			Print("Change setting with (h)ex, hex(z)ero, hexdo(l)ar, hex(p)osth, (d)ecimal, (b)inary.\n");
			return false;
		}
	
		if (!ParseFormat(str, cfg))
			return false;

		Print("Changed setting: %-12s\n", GetFmtConfig(cfg));
		ApplyConfig();
		return true;
	}

	bool CConsoleDebugger::ParseAddress(const char *str, UINT32 *addr)
	{
		if (m_cpu->instrCount > 0 && CheckToken(str, "-", "current"))
		{
			*addr = m_cpu->pc;
			return true;
		}
		return m_cpu->ParseAddress(str, addr);
	}

	const char *CConsoleDebugger::GetDataSizeStr(unsigned dataSize, bool shortName)
	{
		switch (dataSize)
		{
			case 1:  return (shortName ? "b" : "byte");
			case 2:  return (shortName ? "w" : "word");
			case 4:  return (shortName ? "l" : "long");
			case 8:  return (shortName ? "v" : "vlong");
			default: return GetSizeString(dataSize);
		}
	}

	bool CConsoleDebugger::ParseDataSize(const char *str, unsigned &dataSize)
	{
		int num = -1;
		ParseInt(str, &num);
		if (CheckToken(str, "b", "byte") || num == 1)
		{
			dataSize = 1;
			return true;
		}
		else if (CheckToken(str, "w", "word") || num == 2)
		{
			dataSize = 2;
			return true;
		}
		else if (CheckToken(str, "l", "long") || num == 4)
		{
			dataSize = 4;
			return true;
		}
		else if (CheckToken(str, "v", "verylong") || num == 8)
		{
			dataSize = 8;
			return true;
		}
		else
		{
			Error("Enter a valid size (b)yte, (w)ord, (l)ong or (v)erylong or a number 1, 2, 4 or 8.\n");
			return false;
		}
	}

	bool CConsoleDebugger::ParseFormat(const char *str, EFormat &fmt)
	{
		if      (CheckToken(str, "h", "hex"))       fmt = Hex;
		else if (CheckToken(str, "z", "hexzero"))   fmt = Hex0x;
		else if (CheckToken(str, "l", "hexdollar")) fmt = HexDollar;	
		else if (CheckToken(str, "p", "hexposth"))  fmt = HexPostH;
		else if (CheckToken(str, "d", "decimal"))   fmt = Decimal;
		else if (CheckToken(str, "b", "binary"))    fmt = Binary;
		else
		{
			Error("Enter a valid format (h)ex, hex(z)ero, hexdo(l)ar, hex(p)osth, (d)ecimal, (b)inary.\n");
			return false;
		}
		return true;
	}

	bool CConsoleDebugger::ParseCPU(const char *str, CCPUDebug *&cpu)
	{
		if (str == NULL)
		{
			Error("Missing CPU name or number.\n");
			return false;
		}
		int cpuNum;
		if (ParseInt(str, &cpuNum))
		{
			if (cpuNum >= 0 && cpuNum < (int)cpus.size())
			{
				cpu = cpus[cpuNum];
				return true;
			}
		}
		cpu = GetCPU(str);
		if (cpu == NULL)
		{
			Error("No CPU with that name or number.\n");
			return false;
		}
		return true;
	}

	bool CConsoleDebugger::ParseRegister(const char *str, CRegister *&reg)
	{
		if (str == NULL)
		{
			Error("Missing register name.\n");
			return false;
		}
		reg = m_cpu->GetRegister(str);
		if (reg == NULL)
		{
			Error("Enter a valid register name.\n");
			return false;
		}
		return true;
	}

	void CConsoleDebugger::ListCPUs()
	{
		Print("CPUs:\n");
		if (cpus.size() == 0)
		{
			Print(" None\n");
			return;
		}

		Print(" %-3s %-9s %-6s %-9s %-7s %-12s %-12s %-9s\n", "Num", "Name", "Type", "Debugging", "State", "Instr Count", "Total Cycles", "Frequency"); 
		unsigned num = 0;
		double freq;
		char onCPU;
		const char *debugStr;
		const char *stateStr;
		for (vector<CCPUDebug*>::iterator it = cpus.begin(); it != cpus.end(); it++)
		{
			onCPU = (*it == m_cpu ? '*': ' ');
			debugStr = ((*it)->IsEnabled() ? "Enabled" : "Disabled");
			stateStr = ((*it)->active ? "Running" : "Waiting");
			
			if ((*it)->instrCount > 0)
			{
				if (frameCount > 0)
				{
					freq = ((*it)->cyclesPerPoll * 60.0) / 1000000.0;
					Print("%c%-3u %-9s %-6s %-9s %-7s %12llu %12llu %6.1fMHz\n", onCPU, num++, (*it)->name, (*it)->type, debugStr, stateStr, (*it)->instrCount - 1, (*it)->totalCycles, freq);
				}
				else
					Print("%c%-3u %-9s %-6s %-9s %-7s %12llu %12llu %9s\n", onCPU, num++, (*it)->name, (*it)->type, debugStr, stateStr, (*it)->instrCount - 1, (*it)->totalCycles, "-");
			}
			else
				Print("%c%-3u %-9s %-6s %-9s %-7s %12s %12s %9s\n", onCPU, num++, (*it)->name, (*it)->type, debugStr, stateStr, "-", "-", "-");
		}
	}

	void CConsoleDebugger::ListRegisters()
	{
		Print("%s Registers:\n", m_cpu->name);
		if (m_cpu->regs.size() == 0)
		{
			Print(" None\n");
			return;
		}
		
		// Get groups
		vector<const char*> groups;
		vector<vector<CRegister*> > regsByGroup;
		size_t totalRows = 0;
		for (vector<CRegister*>::iterator it = m_cpu->regs.begin(); it != m_cpu->regs.end(); it++)
		{
			const char *group = (*it)->group;
			// TODO - find with stricmp rather than default find
			if (find(groups.begin(), groups.end(), group) == groups.end())
			{
				groups.push_back(group);
				regsByGroup.resize(regsByGroup.size() + 1);
			}
			int index = find(groups.begin(), groups.end(), group) - groups.begin();
			vector<CRegister*> *pRegsInGroup = &regsByGroup[index];
			pRegsInGroup->push_back(*it);
			totalRows = max<size_t>(totalRows, pRegsInGroup->size());
		}

		// Get max label and value widths in each group and print group headers
		size_t numGroups = groups.size();
		vector<size_t> labelWidths(numGroups);
		vector<size_t> valueWidths(numGroups);
		vector<size_t> groupWidths(numGroups);
		char valStr[50];
		Print(" ");
		for (size_t index = 0; index < numGroups; index++)
		{
			labelWidths[index] = 0;
			valueWidths[index] = 0;

			vector<CRegister*> *pRegsInGroup = &regsByGroup[index];
			for (vector<CRegister*>::iterator it = pRegsInGroup->begin(); it != pRegsInGroup->end(); it++)
			{
				labelWidths[index] = max<size_t>(labelWidths[index], strlen((*it)->name));
				(*it)->GetValue(valStr);
				valueWidths[index] = max<size_t>(valueWidths[index], strlen(valStr));
			}

			const char *group = groups[index];
			groupWidths[index] = max<size_t>(labelWidths[index] + valueWidths[index] + 3, strlen(group) + 1);
			Print("%-*s", (int)groupWidths[index], group);
		}
		Print("\n");

		// Print rows of register values
		char rowStr[50];
		for (size_t row = 0; row < totalRows; row++)
		{
			Print(" ");
			for (size_t index = 0; index < numGroups; index++)
			{
				vector<CRegister*> *pRegsInGroup = &regsByGroup[index];
				if (row < pRegsInGroup->size())
				{
					CRegister *reg = (*pRegsInGroup)[row];
					reg->GetValue(valStr);
					bool hasMon = m_cpu->GetRegMonitor(reg->name) != NULL;

					sprintf(rowStr, "%c%-*s %-*s", (hasMon ? '*' : ' '), (int)labelWidths[index], reg->name, (int)valueWidths[index], valStr);
				}
				else
					rowStr[0] = '\0';

				Print("%-*s", (int)groupWidths[index], rowStr);
			}
			Print("\n");
		}
	}	

	void CConsoleDebugger::ListExceptions()
	{
		Print("%s Exceptions:\n", m_cpu->name);
		if (m_cpu->exceps.size() == 0)
		{
			Print(" None\n");
			return;
		}

		char addrStr[20];
		UINT32 handlerAddr;
		for (vector<CException*>::iterator it = m_cpu->exceps.begin(); it != m_cpu->exceps.end(); it++)
		{
			if (m_cpu->GetHandlerAddr(*it, handlerAddr))
				m_cpu->FormatAddress(addrStr, handlerAddr, true, (m_analyseCode ? LFExcepHandler : LFNone));
			else
				addrStr[0] = '\0';
			Print("%c%-12s %-30s %-12s %u\n", ((*it)->trap ? '*' : ' '), (*it)->id, (*it)->name, addrStr, (*it)->count);
		}
	}

	void CConsoleDebugger::ListInterrupts()
	{
		Print("%s Interrupts:\n", m_cpu->name);
		if (m_cpu->inters.size() == 0)
		{
			Print(" None\n");
			return;
		}

		char addrStr[20];
		UINT32 handlerAddr;
		for (vector<CInterrupt*>::iterator it = m_cpu->inters.begin(); it != m_cpu->inters.end(); it++)
		{
			if (m_cpu->GetHandlerAddr(*it, handlerAddr))
				m_cpu->FormatAddress(addrStr, handlerAddr, true, (m_analyseCode ? LFInterHandler : LFNone));
			else
				addrStr[0] = '\0';
			Print("%c%-12s %-30s %-12s %u\n", ((*it)->trap ? '*' : ' '), (*it)->id, (*it)->name, addrStr, (*it)->count);
		}
	}

	void CConsoleDebugger::ListIOs()
	{
		Print("%s I/O Ports:\n", m_cpu->name);
		if (m_cpu->ios.size() == 0)
		{
			Print(" None\n");
			return;
		}

		const char *group = NULL;
		char locStr[255];
		char dirChar;
		char inStr[50];
		char outStr[50];
		for (vector<CIO*>::iterator it = m_cpu->ios.begin(); it != m_cpu->ios.end(); it++)
		{
			// Print group heading if starting a new group
			if (group == NULL || stricmp((*it)->group, group) != 0)
			{
				group = (*it)->group;
				Print(" %s:\n", group);
			}

			// Get location string (memory address or port number)
			(*it)->GetLocation(locStr);
			
			// See whether port was last read or written
			if ((*it)->inCount + (*it)->outCount > 0)
				dirChar = ((*it)->last == &(*it)->lastIn ? '<' : '>');
			else
				dirChar = ' ';
			// Format last input
			if ((*it)->inCount > 0)
				m_cpu->FormatData(inStr, (*it)->dataSize, (*it)->lastIn);
			else
			{
				inStr[0] = '*';
				inStr[1] = '\0';
			}
			size_t inLen = strlen(inStr);
			int inLPad = 5 - (int)inLen / 2;
			int inRPad = 5 - (int)inLen + (int)inLen / 2;
			// Format last output
			if ((*it)->outCount > 0)
				m_cpu->FormatData(outStr, (*it)->dataSize, (*it)->lastOut);
			else
			{
				outStr[0] = '*';
				outStr[1] = '\0';
			}
			size_t outLen = strlen(outStr);
			int outLPad = 5 - (int)outLen / 2;
			int outRPad = 5 - (int)outLen + (int)outLen / 2;
			
			// Print details
			Print(" %c%-12s %-30s %-8s %*s%s%*s%c%*s%s%*s\n", ((*it)->watch != NULL ? '*' : ' '), locStr, (*it)->name, 
				GetDataSizeStr((*it)->dataSize, true), inLPad, "", inStr, inRPad, "", dirChar, outLPad, "", outStr, outRPad, "");
		}
	}

	void CConsoleDebugger::ListRegions()
	{
		Print("%s Regions:\n", m_cpu->name);
		if (m_cpu->regions.size() == 0)
		{
			Print(" None\n");
			return;
		}

		char startStr[255];
		char endStr[255];
		for (vector<CRegion*>::iterator it = m_cpu->regions.begin(); it != m_cpu->regions.end(); it++)
		{
			// Format start and end address
			m_cpu->FormatAddress(startStr, (*it)->addr);
			m_cpu->FormatAddress(endStr, (*it)->addrEnd);

			// Print details
			Print("%c%s-%s %s\n", ((*it)->isCode ? '*' : ' '), startStr, endStr, (*it)->name);
		}
	}

	void CConsoleDebugger::ListLabels(bool customLabels, ELabelFlags autoLabelFlags)
	{
		Print("%s Labels:\n", m_cpu->name);
		
		unsigned count = 0;

		char addrStr[20];
		if (customLabels && m_cpu->labels.size() > 0)
		{
			Print(" Custom Labels:\n");
			for (vector<CLabel*>::iterator it = m_cpu->labels.begin(); it != m_cpu->labels.end(); it++)
			{
				m_cpu->FormatAddress(addrStr, (*it)->addr);
				Print("  %s %s\n", addrStr, (*it)->name);
				count++;
			}
		}

		if (m_analyseCode)
		{
			char labelStr[255];
			CCodeAnalyser *analyser = m_cpu->GetCodeAnalyser();
			for (unsigned index = 0; index < CAutoLabel::numLabelFlags; index++)
			{
				ELabelFlags flag = CAutoLabel::GetLabelFlag(index);
				if (!(autoLabelFlags & flag))
					continue;
				vector<CAutoLabel*> withFlag = analyser->analysis->GetAutoLabels(flag);
				if (withFlag.size() == 0)
					continue;
				const char *flagStr = CAutoLabel::GetFlagString(flag);
				Print(" %ss:\n", flagStr);
				for (vector<CAutoLabel*>::iterator it = withFlag.begin(); it != withFlag.end(); it++)
				{
					if (!(*it)->GetLabel(labelStr, flag))
						continue;
					CBreakpoint *bp = m_cpu->GetBreakpoint((*it)->addr);
					char bpChr = (bp != NULL ? bp->symbol : ' ');
					m_cpu->FormatAddress(addrStr, (*it)->addr);
					Print(" %c%s %s\n", bpChr, addrStr, labelStr);
					count++;
				}
			}
		}

		if (count == 0)
		{
			Print(" None\n");
			return;
		}
	}

	void CConsoleDebugger::GetAllMemWatches(vector<CWatch*> &watches)
	{
		for (vector<CWatch*>::iterator it = m_cpu->memWatches.begin(); it != m_cpu->memWatches.end(); it++)
			watches.push_back(*it);
		for (vector<CWatch*>::iterator it = m_cpu->ioWatches.begin(); it != m_cpu->ioWatches.end(); it++)
		{
			if (dynamic_cast<CMappedIO*>((*it)->io) != NULL)
				watches.push_back(*it);
		}
	}

	int CConsoleDebugger::GetIndexOfMemWatch(CWatch *watch)
	{
		vector<CWatch*> watches;
		GetAllMemWatches(watches);
		vector<CWatch*>::iterator it = find(watches.begin(), watches.end(), watch);
		if (it == watches.end())
			return -1;
		return it - watches.begin();
	}

	void CConsoleDebugger::ListMemWatches()
	{
		Print("%s Memory Watches:\n", m_cpu->name);

		vector<CWatch*> watches;
		GetAllMemWatches(watches);

		if (watches.size() == 0)
		{
			Print(" None\n");
			return;
		}

		Print(" %-3s %-8s %-12s %-5s %-4s %-20s %s\n", "Num", "Type", "Address", "Size", "Trig", "Value", "Info");

		char typeStr[12];
		char addrStr[20];
		char sizeStr[20];
		const char *dSizeStr;
		const char *trigStr;
		char valStr[20];
		char infoStr[255];
		unsigned wNum = 0;
		for (vector<CWatch*>::iterator it = watches.begin(); it != watches.end(); it++)
		{
			UpperFirst(typeStr, (*it)->type);
			m_cpu->FormatAddress(addrStr, (*it)->addr, true);
			dSizeStr = GetDataSizeStr((*it)->size, false);
			UpperFirst(sizeStr, dSizeStr);
			if      ((*it)->trigRead && (*it)->trigWrite) trigStr = "R/W";
			else if ((*it)->trigRead)                     trigStr = "R";
			else if ((*it)->trigWrite)                    trigStr = "W";
			else                                          trigStr = "-";
			m_cpu->FormatData(valStr, (*it)->size, (*it)->GetValue());
			if (!(*it)->GetInfo(infoStr))
			{
				infoStr[0] = '-';
				infoStr[1] = '\0';
			}
			Print(" %-3u %-8s %-12s %-5s %-4s %-20s %s\n", wNum++, typeStr, addrStr, sizeStr, trigStr, valStr, infoStr);
		}
	}

	void CConsoleDebugger::GetAllPortWatches(vector<CWatch*> &watches)
	{
		for (vector<CWatch*>::iterator it = m_cpu->ioWatches.begin(); it != m_cpu->ioWatches.end(); it++)
		{
			if (dynamic_cast<CPortIO*>((*it)->io) != NULL)
				watches.push_back(*it);
		}
	}

	int CConsoleDebugger::GetIndexOfPortWatch(CWatch *watch)
	{
		vector<CWatch*> watches;
		GetAllPortWatches(watches);
		vector<CWatch*>::iterator it = find(watches.begin(), watches.end(), watch);
		if (it == watches.end())
			return -1;
		return it - watches.begin();
	}

	void CConsoleDebugger::ListPortWatches()
	{
		Print("%s I/O Port Watches:\n", m_cpu->name);

		vector<CWatch*> watches;
		GetAllPortWatches(watches);

		if (watches.size() == 0)
		{
			Print(" None\n");
			return;
		}

		Print(" %-3s %-8s %-12s %-4s %-20s %s\n", "Num", "Type", "Location", "Trig", "Last In/Out", "Info");

		char typeStr[12];
		char locStr[255];
		const char *trigStr;
		char valStr[20];
		char infoStr[255];
		unsigned wNum = 0;
		for (vector<CWatch*>::iterator it = watches.begin(); it != watches.end(); it++)
		{
			UpperFirst(typeStr, (*it)->type);
			(*it)->io->GetLocation(locStr);
			if      ((*it)->trigRead && (*it)->trigWrite) trigStr = "I/O";
			else if ((*it)->trigRead)                     trigStr = "I";
			else if ((*it)->trigWrite)                    trigStr = "O";
			else                                          trigStr = "-";
			if ((*it)->readCount + (*it)->writeCount == 0)
			{
				valStr[0] = '-';
				valStr[1] = '\0';
			}
			else
				m_cpu->FormatData(valStr, (*it)->size, (*it)->GetValue());
			if (!(*it)->GetInfo(infoStr))
			{
				infoStr[0] = '-';
				infoStr[1] = '\0';
			}
			Print(" %-3u %-8s %-12s %-4s %-20s %s\n", wNum++, typeStr, locStr, trigStr, valStr, infoStr);
		}
	}

	void CConsoleDebugger::ListBreakpoints()
	{
		Print("%s Breakpoints:\n", m_cpu->name);
		if (m_cpu->bps.size() == 0)
		{
			Print(" None\n");
			return;
		}

		Print(" %-3s %-8s %-12s %-20s\n", "Num", "Type", "Address", "Info");

		char typeStr[12];
		char addrStr[20];
		char infoStr[255];
		for (vector<CBreakpoint*>::iterator it = m_cpu->bps.begin(); it != m_cpu->bps.end(); it++)
		{
			UpperFirst(typeStr, (*it)->type);
			m_cpu->FormatAddress(addrStr, (*it)->addr, true, (m_analyseCode ? LFAll : LFNone));
			if (!(*it)->GetInfo(infoStr))
			{
				infoStr[0] = '-';
				infoStr[1] = '\0';
			}
			Print(" %-3u %-8s %-12s %-20s\n", (*it)->num, typeStr, addrStr, infoStr);
		}
	}

	void CConsoleDebugger::ListMonitors()
	{
		Print("%s Register Monitors:\n", m_cpu->name);
		if (m_cpu->regMons.size() == 0)
		{
			Print(" None\n");
			return;
		}

		char valStr[255];
		int num = 0;
		for (vector<CRegMonitor*>::iterator it = m_cpu->regMons.begin(); it != m_cpu->regMons.end(); it++)
		{
			(*it)->GetBeforeValue(valStr);
			Print(" %d - %s change from: %s\n", num++, (*it)->reg->name, valStr);
		}
	}

	UINT32 CConsoleDebugger::ListDisassembly(UINT32 start, UINT32 end, unsigned numInstrs)
	{
		UINT32 addr;
		char startStr[20];
		char endStr[20];
		char opCodes[50];
		char mnemonic[100];
		char operands[155];
		char addrStr[20];
		char labelStr[13];
		unsigned instr;

		UINT32 pc = m_cpu->pc;		
		CCodeAnalyser *analyser = (m_analyseCode ? m_cpu->GetCodeAnalyser() : NULL);

		// Align address to instruction boundary
		start -= start%m_cpu->minInstrLen;

		if (analyser != NULL)
		{
			// Use code analyser to find valid start address
			if (!analyser->analysis->GetNextValidAddr(start))
				return start;
		}
		else
		{
			// In the absence of code analyser, try to align code with current PC address
			if (m_cpu->instrCount > 0 && pc >= start && pc <= end)
			{
				unsigned count = m_cpu->instrCount;
				while (start < end && count-- > 0)
				{	
					bool okay = false;
					addr = start;
					instr = 0;
					while (addr < end && instr < numInstrs)
					{
						if (addr == pc)
						{
							okay = true;
							break;
						}
						int codesLen = m_cpu->GetOpLength(addr);
						addr += abs(codesLen);
						instr++;
					}
					if (okay)
						break;
					start += m_cpu->minInstrLen;
				}
			}
		}

		// Get true end address
		addr = start;
		instr = 0;
		while (addr < end && instr < numInstrs)
		{
			// Get instruction length
			int codesLen = m_cpu->Disassemble(addr, mnemonic, operands);
			// Move onto next valid instruction address
			addr += abs(codesLen);
			if (analyser != NULL && !analyser->analysis->GetNextValidAddr(addr))
				break;
			instr++;
		}
		end = addr;
				
		// Format start and end addresses and output title
		m_cpu->FormatAddress(startStr, start);
		m_cpu->FormatAddress(endStr, end);
		Print("%s Code %s - %s:\n", m_cpu->name, startStr, endStr);

		// Output the disassembly
		addr = start;
		instr = 0;
		while (addr < end)
		{
			// Add markers for current PC address and any breakpoints
			char ind[4];
			if (m_cpu->instrCount > 0 && addr == pc)
			{
				ind[0] = '>';
				ind[1] = '>';
			}
			else
			{
				ind[0] = ' ';
				ind[1] = ' ';
			}
			CBreakpoint *bp = m_cpu->GetBreakpoint(addr);
			ind[2] = (bp != NULL ? bp->symbol : ' ');
			ind[3] = '\0';
			
			// Format current address
			m_cpu->FormatAddress(addrStr, addr);

			// Get labels at address (if any)
			bool hasLabel = GetLabelText(labelStr, 12, addr);

			// Get mnemonic, operands and instruction length and print them
			int codesLen = m_cpu->Disassemble(addr, mnemonic, operands);		
			FormatOpCodes(opCodes, addr, abs(codesLen));
			
			// Get comment at address (if any)
			CComment *comment = m_cpu->GetComment(addr);
			
			// Output line
			Print("%s", ind);
			if (m_showLabels)
			{
				if (m_labelsOverAddr)
					Print("%-12s ", (hasLabel ? labelStr : addrStr));
				else
					Print("%s %-12s ", addrStr, labelStr);
			}
			else
				Print("%s ", addrStr);
			if (m_showOpCodes) 
				Print("[%s] ", opCodes);
			if (codesLen > 0)
				Print("%-*s %-20s", (int)m_cpu->maxMnemLen, mnemonic, operands);
			else
				Print("???");
			if (comment != NULL)
				Print(" ; %s", comment->text);
			Print("\n");

			// Move onto next valid instruction address
			addr += abs(codesLen);
			if (analyser != NULL && !analyser->analysis->GetNextValidAddr(addr))
				break;
		}
		return end;
	}

	UINT32 CConsoleDebugger::ListMemory(UINT32 start, UINT32 end, unsigned bytesPerRow)
	{
		char startStr[255];
		char endStr[255];
		char addrStr[20];
		char labelStr[13];
		char wChar, dChar;
		UINT8 data;
		
		// Adjust start/end points to be on row boundary
		start -= (start % bytesPerRow);
		end += bytesPerRow - (end % bytesPerRow);

		// Format start and end addresses and output title
		m_cpu->FormatAddress(startStr, start);
		m_cpu->FormatAddress(endStr, end);
		Print("%s Memory %s - %s:\n", m_cpu->name, startStr, endStr);

		UINT32 addr = start;
		while (addr < end)
		{
			// TODO - check address going out of region or out of range

			// Format current address
			m_cpu->FormatAddress(addrStr, addr);
			
			// Get labels at address (if any)
			bool hasLabel = GetLabelText(labelStr, 12, addr);

			// Output line
			if (m_showLabels)
			{
				if (m_labelsOverAddr)
					Print("   %-12s", (hasLabel ? labelStr : addrStr));
				else
					Print("   %s %-12s", addrStr, labelStr);
			}
			else
				Print("   %s%c", addrStr);
			UINT32 lAddr = addr;
			for (unsigned i = 0; i < bytesPerRow; i++)
			{
				CWatch *watch = m_cpu->GetMemWatch(lAddr, 1);
				// TODO - handling of mapped I/O
				//CMappedIO *io = m_cpu->GetMappedIO(lAddr);
				wChar = (watch != NULL ? watch->symbol : ' ');
				//if (io != NULL)
				//	data = (UINT8)io->last;
				//else
					data = (UINT8)m_cpu->ReadMem(lAddr, 1);
				Print("%c%02X", wChar, data);
				lAddr++;
			}
			Print(" ");
			lAddr = addr;
			for (unsigned i = 0; i < bytesPerRow; i++)
			{
				// TODO - handling of mapped I/O
				//CMappedIO *io = m_cpu->GetMappedIO(lAddr);
				//if (io != NULL)
				//	data = io->last;
				//else
					data = (UINT8)m_cpu->ReadMem(lAddr, 1);
				dChar = (data >= 32 && data <= 126 ? (char)data : '.');
				Print("%c", dChar);
				lAddr++;
			}
			Print("\n");
			addr += bytesPerRow;
		}
		return addr;
	}

	void CConsoleDebugger::AnalysisUpdated(CCodeAnalyser *analyser)
	{
		//
	}

	void CConsoleDebugger::ExceptionTrapped(CException *ex)
	{
		PrintEvent(ex->cpu, "Exception %s (%s) trapped.\n", ex->id, ex->name);
	}

	void CConsoleDebugger::InterruptTrapped(CInterrupt *in)
	{
		PrintEvent(in->cpu, "Interrupt %s (%s) trapped.\n", in->id, in->name);
	}

	void CConsoleDebugger::MemWatchTriggered(CWatch *watch, UINT32 addr, unsigned dataSize, UINT64 data, bool isRead)
	{
		const char *sizeStr = GetDataSizeStr(dataSize, true);
		const char *rwStr = (isRead ? "Read from" : "Write to");
		char dataStr[50];
		char addrStr[255];
		m_cpu->FormatData(dataStr, dataSize, data);
		watch->cpu->FormatAddress(addrStr, addr, true);
		int num = GetIndexOfMemWatch(watch);
		PrintEvent(watch->cpu, "%s %s (%s.%s) triggered memory watch #%d.\n", rwStr, addrStr, dataStr, sizeStr, num);
	}

	void CConsoleDebugger::IOWatchTriggered(CWatch *watch, CIO *io, UINT64 data, bool isInput)
	{
		const char *sizeStr = GetDataSizeStr(io->dataSize, true);
		const char *ioStr = (isInput ? "Input from" : "Output to");
		char dataStr[50];
		char locStr[255];
		m_cpu->FormatData(dataStr, io->dataSize, data);
		watch->io->GetLocation(locStr);
		int num = GetIndexOfPortWatch(watch);
		if (num >= 0)
			PrintEvent(watch->cpu, "%s %s (%s.%s) triggered port watch #%d.\n", ioStr, locStr, dataStr, sizeStr, num);
		else
		{
			num = GetIndexOfMemWatch(watch);
			PrintEvent(watch->cpu, "%s %s (%s.%s) triggered memory watch #%d.\n", ioStr, locStr, dataStr, sizeStr, num);
		}
	}

	void CConsoleDebugger::BreakpointReached(CBreakpoint *bp)
	{
		PrintEvent(bp->cpu, "Breakpoint #%d triggered.\n", bp->num);
	}

	void CConsoleDebugger::MonitorTriggered(CRegMonitor *regMon)
	{
		char valStr[255];
		CRegister *reg = regMon->reg;
		reg->GetValue(valStr);
		PrintEvent(reg->cpu, "Write to register %s (%s) triggered monitor.\n", reg->name, valStr);
	}

	void CConsoleDebugger::ExecutionHalted(CCPUDebug *cpu, EHaltReason reason)
	{
		if (reason&HaltUser)
			PrintEvent(cpu, "Execution halted.\n");
	}
	
	void CConsoleDebugger::Log(CCPUDebug *cpu, const char *typeStr, const char *fmtStr, va_list vl)
	{
		if (cpu != NULL)
		{
			char pcStr[255];
			cpu->FormatAddress(pcStr, cpu->pc, true, LFNone);
			Print("%s @ %s: ", cpu->name, pcStr);
		}
		if (typeStr != NULL)
			Print(" %s - ", typeStr);
		PrintVL(fmtStr, vl);
	}

	void CConsoleDebugger::Attach()
	{
		CDebugger::Attach();

		ApplyConfig();

		Attached();
	}

	void CConsoleDebugger::Detach()
	{
		Detaching();

		CDebugger::Detach();

		// Close redirected output file, if exists
		if (m_file != NULL)
		{
			fclose(m_file);
			m_file = NULL;
		}
	}

	void CConsoleDebugger::Poll()
	{
		CDebugger::Poll();

		if (m_nextFrame)
		{
			if (--m_nextFrameCount == 0)
				ForceBreak(true);
		}	
	}

	void CConsoleDebugger::ApplyConfig()
	{
		for (vector<CCPUDebug*>::iterator it = cpus.begin(); it != cpus.end(); it++)
		{
			(*it)->addrFmt = m_addrFmt;
			(*it)->portFmt = m_portFmt;
			(*it)->dataFmt = m_dataFmt;
		}
	}

	void CConsoleDebugger::Attached()
	{
		//
	}

	void CConsoleDebugger::Detaching()
	{
		//
	}
}

#endif  // SUPERMODEL_DEBUGGER