#include "burner.h"

HWND hDbgDlg = NULL;

#if defined (FBA_DEBUG)

#include "sek.h"
#include "sekdebug.h"
#include "richedit.h"

static bool bLargeWindow;
static bool bOldPause;

static HFONT hCommandFont = NULL;
static unsigned int nMouseWheelScrollLines;

static unsigned int nActiveWindow;
static unsigned int nDisassWindow;
static unsigned int nMemdumpWindow;

static int nDisassCPUType;

static unsigned int nDisassAddress[16];
static int nDisassArea;
static int nDisassAttrib;

static int nMemdumpAddress;
static int nMemdumpArea;
static int nMemdumpAttrib;

static bool bBreakpointHit;

struct DbgM68000State {
	unsigned int a[8]; unsigned int d[8];
	unsigned int pc;
	unsigned int sr;
	unsigned int sp; unsigned int usp; unsigned int isp; unsigned int msp;
	unsigned int vbr;
	unsigned int sfc; unsigned int dfc;
	unsigned int cacr; unsigned int caar;

	unsigned int irq;
};

static DbgM68000State curr_m68000, prev_m68000;

static int nDbgMemoryAreaCount;
static int nDbgCurrentMemoryArea;
static struct BurnArea* DbgMemoryAreaInfo = NULL;

// ----------------------------------------------------------------------------
// Callbacks for m68k's disassembler

static unsigned int DbgFetchByte(unsigned int nAddress) { return SekFetchByte(nAddress); }
static unsigned int DbgFetchWord(unsigned int nAddress) { return SekFetchWord(nAddress); }
static unsigned int DbgFetchLong(unsigned int nAddress) { return SekFetchLong(nAddress); }

static unsigned int DbgFetchByteArea(unsigned int nAddress)
{
	if (nAddress < DbgMemoryAreaInfo[nDisassArea].nLen) {
		return ((unsigned char*)(DbgMemoryAreaInfo[nDisassArea].Data))[nAddress ^ nDisassAttrib];
	}

	return 0;
}
static unsigned int DbgFetchWordArea(unsigned int nAddress)
{
	return (DbgFetchByteArea(nAddress) << 8) | DbgFetchByteArea(nAddress + 1);
}
static unsigned int DbgFetchLongArea(unsigned int nAddress)
{
	return (DbgFetchWordArea(nAddress) << 16) | DbgFetchWordArea(nAddress + 2);
}

// ----------------------------------------------------------------------------
// Message pump used when a breakpoint is hit

static void BreakpointMessagePump()
{
	MSG msg;

	while (GetMessage(&msg, NULL, 0, 0)) {

		if (IsDialogMessage(hDbgDlg, &msg)) {
			continue;
		}
		if (msg.message == (WM_APP + 0)) {
			bBreakpointHit = false;
			return;
		}

		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

// ----------------------------------------------------------------------------
// Feedback functions

static void InitFeedback(HWND hWindow)
{
	TCHAR szBuffer[16384] = _T("");
	TCHAR* pszBuf = szBuffer;

	// RTF header and initial settings
	pszBuf += _stprintf(pszBuf, _T("{\\rtf1\\ansi\\ansicpg1252\\deff0{\\fonttbl(\\f0\\fmodern\\charset0\\fprq0 Courier New;)}{\\colortbl\\red0\\green0\\blue0;\\red96\\green96\\blue96;\\red255\\green0\\blue0;\\red0\\green0\\blue255;}\\deflang1033\\horzdoc\\pard\\plain\\f0\\fs18\\cf2 Welcome to the FB Alpha debugger.\\par}"));
	// Send the new data to the RichEdit control
	SendMessage(hWindow, WM_SETTEXT, (WPARAM)0, (LPARAM)szBuffer);
}

static int AddFeedback(HWND hWindow, TCHAR* pszText)
{
#ifdef UNICODE
	SETTEXTEX settext = { ST_SELECTION, 1200 };
#else
	SETTEXTEX settext = { ST_SELECTION, 1252 };
#endif

	// Add the new info to the top
	SendMessage(hWindow, EM_SETSEL, (WPARAM)0, (LPARAM)0);
	// Doesn't work if you send it Unicode data...
	SendMessageA(hWindow, EM_SETTEXTEX, (WPARAM)&settext, (LPARAM)TCHARToANSI(pszText, NULL, 0));

	// Trim the bottom lines
	SendMessage(hWindow, EM_SETSEL, (WPARAM)SendMessage(hWindow, EM_LINEINDEX, 18, 0) - 1, (LPARAM)-1);
	SendMessage(hWindow, EM_SETTEXTEX, (WPARAM)&settext, (LPARAM)_T(""));

	return 0;
}

// ----------------------------------------------------------------------------
// Trace back instructions starting at a given address

static unsigned int trace_pc, trace_size, trace_max;

static void TraceBackDo(unsigned int start_pc, unsigned int trace_level, int cpu_type)
{
	unsigned int pc;
	unsigned int trace;

	// Go over all possible instruction sizes (the longest 68020 instruction is 30 bytes)
	for (unsigned int size = (cpu_type == M68K_CPU_TYPE_68000) ? 10 : 30; size != 0 ; size -= 2) {
		pc = start_pc;
		trace = trace_level;

		// Make sure we don't go past the start of memory
		if (size > pc) {
			break;
		}

		// Check if there is a valid instruction at the current address
		if (m68k_is_valid_instruction(SekFetchWord(pc - size),  cpu_type) != 0) {
			char buf[100] = "";

			// Check if the instruction at the current address has the right size
			if (m68k_disassemble(buf, pc - size,  cpu_type) == size) {
				pc -= size;
				trace++;

				// Update best result
				if (trace_size < trace) {
					trace_size = trace;
					trace_pc = pc;
				}

				// Recurse until we've traced back enough instructions
				if (trace < trace_max) {
					TraceBackDo(pc, trace,  cpu_type);
				}
			}
		}
	}
}

static unsigned int TraceBack(unsigned int nStartAddress, unsigned int nTotalInstructions, int nCPU)
{
	unsigned int pc = nStartAddress;
	unsigned int trace = nTotalInstructions;

	if (nStartAddress == 0 || nTotalInstructions == 0) {
		return 0;
	}

	// If there's no valid instruction at the start adress, increase it
	while (m68k_is_valid_instruction(SekFetchWord(pc), nCPU) == 0 && (pc - nStartAddress) < ((nCPU == M68K_CPU_TYPE_68000) ? 10 : 30) - 2) {
		pc += 2;
	}

	do {
		trace_size = 0;
		trace_pc = pc;
		// Trace back a few extra instructions to ensure we'll return a valid code path
		trace_max = trace + 4;

		TraceBackDo(pc, 0, nCPU);

		pc -= 2;

	} while (trace_size == 0 && pc >= nStartAddress);

	pc = trace_pc;

	// If we've traced back more instructions than needed, skip the extra ones
	while (trace_size > trace) {
		char buf[100] = "";
		pc += m68k_disassemble(buf, pc, nCPU);
		trace++;
	}

	// If we can't trace back at all, simply decrease the address
	if (trace_size == 0) {
		return nTotalInstructions * 2 < nStartAddress ? nStartAddress - nTotalInstructions * 2 : 0;
	}

	return pc;
}

// ----------------------------------------------------------------------------
// Write a disassembly to a file

static int CreateDisassFile(TCHAR* filename, unsigned int start, unsigned int end, int nCPU)
{
	unsigned int size;
	unsigned int pc = start;
	FILE* fp = _tfopen(filename, _T("wt"));

	if (fp == NULL) {
		return 1;
	}

	// Print the disassembly
	while (pc <= end) {
		char buf[100] = "";
		unsigned int j;

		if (pc > 0x00FFFFFE) {
			break;
		}

		// Get the mnemonic
		size = m68k_disassemble(buf, pc, nCPU);

		// Print the address
		_ftprintf(fp, _T("%06x "), pc);

		// Print the word values
		if (nCPU == M68K_CPU_TYPE_68000) {
			for (j = 10; j > size; j -= 2) {
				_ftprintf(fp, _T("     "));
			}
			for (j =  0; j < size; j += 2) {
				_ftprintf(fp, _T("%04x "), SekFetchWord(pc + j));
			}
		} else {
			if (size > 20) {
				for (j =  0; j < 18; j += 2) {
					_ftprintf(fp, _T("%04x "), SekFetchWord(pc + j));
				}
				_ftprintf(fp, _T("...  "));
			} else {
				for (j = 20; j > size; j -= 2) {
					_ftprintf(fp, _T("     "));
				}
				for (j =  0; j < size; j += 2) {
					_ftprintf(fp, _T("%04x "), SekFetchWord(pc + j));
				}
			}
		}

		// Print the instruction
		_ftprintf(fp, _T("%hs\n"), buf);

		pc += size;
	}

	fclose(fp);

	return 0;
}

// ----------------------------------------------------------------------------
// Create a disassembly in RTF format

// Tabs are specified in twips (a single 16pt cell = 100twips)

static void CreateDisass(HWND hWindow, unsigned int nAddress, int nCPU)
{
	int nPageSize = bLargeWindow ? 16 : 12;
	TCHAR szBuffer[16384] = _T("");
	TCHAR* pszBuf = szBuffer;
	unsigned int pc, nSize;

	pc = nAddress;

	// RTF header and initial settings
	pszBuf += _stprintf(pszBuf, _T("{\\rtf1\\ansi\\ansicpg1252\\deff0{\\fonttbl(\\f0\\fmodern\\charset0\\fprq0 Courier New;)}{\\colortbl\\red0\\green0\\blue0;\\red96\\green96\\blue96;\\red255\\green0\\blue0;\\red255\\green96\\blue96;\\red224\\green224\\blue224;}\\deflang1033\\horzdoc\\pard\\plain\\f0"));
	if (bLargeWindow) {
		if (nCPU == M68K_CPU_TYPE_68000) {
			// Tabstops							5555    4444    3333    2222    1111    Mnemonic
			pszBuf += _stprintf(pszBuf, _T("\\tx1050\\tx1513\\tx1976\\tx2429\\tx2892\\tx3450"));
		} else {
			// Tabstops							6666    5555    4444    3333    2222    1111    Mnemonic
			pszBuf += _stprintf(pszBuf, _T("\\tx1050\\tx1435\\tx1820\\tx2205\\tx2590\\tx2975\\tx3450"));
		}
		// Indentation
		pszBuf += _stprintf(pszBuf, _T("\\fi-4665\\li4665 "));
	} else {
		// Tabstops
		pszBuf += _stprintf(pszBuf, _T("\\tx735"));
	}

	// Print the disassembly
	for (int i = 0; i < nPageSize; i++) {
		char buf[100] = "";
		unsigned int j;
		unsigned int nCol;

		if (pc > 0x00FFFFFE) {

			do {
				nDisassAddress[i++] = 0x00FFFFFE;
			} while (i < nPageSize);

			break;
		}

		nDisassAddress[i] = pc;

		// Get the mnemonic
		nSize = m68k_disassemble(buf, pc, nCPU);
		// Display the instruction at the current PC in red
		nCol = (pc == SekDbgGetRegister(SEK_REG_PC)) ? 2 : 0;

		// Print the address
		pszBuf += _stprintf(pszBuf, _T("\\fs%i\\cf%i %06x\t"), bLargeWindow ? 24 : 16, nCol, pc);

		if (bLargeWindow) {
			// Print the word values
			if (nCPU == M68K_CPU_TYPE_68000) {
				// Print a filler
				pszBuf += _stprintf(pszBuf, _T("\\fs16\\cf4 "));
				for (j = 10; j > nSize; j -= 2) {
					pszBuf += _stprintf(pszBuf, _T(" -- \t"));
				}
				// Print the words that make up the instruction
				pszBuf += _stprintf(pszBuf, _T("\\cf%i "), nCol + 1);
				for (j =  0; j < nSize; j += 2) {
					pszBuf += _stprintf(pszBuf, _T("%04x\t"), SekFetchWord(pc + j));
				}
			} else {
				if (nSize > 12) {
					// There's not enough room for all words, so print the first 5 words that make up the instruction
					pszBuf += _stprintf(pszBuf, _T("\\fs13\\cf%i "), nCol + 1);
					for (j =  0; j < 10; j += 2) {
						pszBuf += _stprintf(pszBuf, _T("%04x\t"), SekFetchWord(pc + j));
					}
					// Print an ellipsis
					pszBuf += _stprintf(pszBuf, _T("... \t"));
				} else {
					// Print a filler
					pszBuf += _stprintf(pszBuf, _T("\\fs13\\cf4 "));
					for (j = 12; j > nSize; j -= 2) {
						pszBuf += _stprintf(pszBuf, _T(" -- \t"));
					}
					// Print the words that make up the instruction
					pszBuf += _stprintf(pszBuf, _T("\\cf%i "), nCol + 1);
					for (j =  0; j < nSize; j += 2) {
						pszBuf += _stprintf(pszBuf, _T("%04x\t"), SekFetchWord(pc + j));
					}
				}
			}
		}

		// Colour Invalid instructions gray
		if (nCol == 0) {
			if (m68k_is_valid_instruction(SekFetchWord(pc), nCPU) == 0) {
				nCol++;
			}
		}
		// Print the instruction
		pszBuf += _stprintf(pszBuf, _T("\\fs%i\\cf%i %hs\\par"), bLargeWindow ? 24 : 16, nCol, buf);

		pc += nSize;
	}
	pszBuf += _stprintf(pszBuf, _T("}\0"));

	// Send the new data to the RichEdit control
	SendMessage(hWindow, WM_SETTEXT, (WPARAM)0, (LPARAM)szBuffer);

#if 0
	FILE* fp = _tfopen(_T("disass.rtf"), _T("wb"));
	if (fp) {
		fwrite(szBuffer, 1, _tcslen(szBuffer), fp);
		fclose(fp);
	}
#endif
}

// ----------------------------------------------------------------------------
// Create a memory dump in RTF format

static unsigned int DbgReadByteArea(unsigned int nAddress)
{
	if (nAddress < DbgMemoryAreaInfo[nDbgCurrentMemoryArea].nLen) {
		return ((unsigned char*)(DbgMemoryAreaInfo[nDbgCurrentMemoryArea].Data))[nAddress ^ nMemdumpAttrib];
	}

	return 0;
}

static void CreateMemdump(HWND hWindow, int nArea, unsigned int nAddress)
{
	int nPageSize = bLargeWindow ? 16 : 12;
	unsigned int (*DbgReadByte)(unsigned int);
	TCHAR szBuffer[16384] = _T("");
	TCHAR* pszBuf = szBuffer;
	unsigned int pc;

	nDbgCurrentMemoryArea = nArea;

	if (nDbgCurrentMemoryArea) {
		DbgReadByte = DbgReadByteArea;
	} else {
		DbgReadByte = SekReadByte;
	}

	pc = nAddress;

	// RTF header and initial settings
	pszBuf += _stprintf(pszBuf, _T("{\\rtf1\\ansi\\ansicpg1252\\deff0{\\fonttbl(\\f0\\fmodern\\fprq0 Courier New\\cpg1252;)}{\\colortbl\\red96\\green96\\blue96;\\red255\\green255\\blue255;\\red0\\green0\\blue0;\\red160\\green160\\blue160;\\red0\\green0\\blue0;\\red224\\green224\\blue224;}\\deflang1033\\horzdoc\\pard\\f0\\plain"));

	if (bLargeWindow) {
		// Tabstops						        00     11       22      33      44      55      66      77      88      99      AA      BB      CC      DD      EE      FF    abcdefghijklnmop
		pszBuf += _stprintf(pszBuf, _T("\\tx1050\\tx1375\\tx1700\\tx2025\\tx2400\\tx2725\\tx3050\\tx3375\\tx3750\\tx4075\\tx4400\\tx4725\\tx5100\\tx5425\\tx5750\\tx6075\\tx6500"));

		// Print the memory dump
		for (int i = 0; i < nPageSize; i++, pc += 16) {
			pszBuf += _stprintf(pszBuf, _T("\\cf4 %06x\t"), pc);
			for (int j = 0; j < 16; j += 2) {
				pszBuf += _stprintf(pszBuf, _T("\\cf0\\cb1 %02x\t\\cf2\\cb3 %02x\t"), DbgReadByte(pc + j), DbgReadByte(pc + j + 1));
			}
			pszBuf += _stprintf(pszBuf, _T("\\cf4 "));
			for (int j = 0; j < 16; j++) {
				unsigned char c = DbgReadByte(pc + j);

				if (c == 0x00 || (c >= 0x09 && c <= 0x0D)) {
					pszBuf += _stprintf(pszBuf, _T("{\\cf5 -}"));
				} else {
					pszBuf += _stprintf(pszBuf, _T("\\\'%02x"), c);
				}
			}
			pszBuf += _stprintf(pszBuf, _T("\\par "));
		}
		pszBuf += _stprintf(pszBuf, _T("}\0"));
	} else {
		pszBuf += _stprintf(pszBuf, _T("\\fs16"));
		for (int i = 0; i < nPageSize; i++, pc += 16) {
			pszBuf += _stprintf(pszBuf, _T("\\cf4 %06x "), pc);
			for (int j = 0; j < 16; j += 2) {
				pszBuf += _stprintf(pszBuf, _T("\\cf0\\cb1 %02x\\cf2\\cb3 %02x"), DbgReadByte(pc + j), DbgReadByte(pc + j + 1));
			}
			pszBuf += _stprintf(pszBuf, _T("\\cf4  "));
			for (int j = 0; j < 16; j++) {
				unsigned char c = DbgReadByte(pc + j);

				if (c == 0x00 || (c >= 0x09 && c <= 0x0D)) {
					pszBuf += _stprintf(pszBuf, _T("{\\cf5 -}"));
				} else {
					pszBuf += _stprintf(pszBuf, _T("\\\'%02x"), c);
				}
			}
			pszBuf += _stprintf(pszBuf, _T("\\par "));
		}
		pszBuf += _stprintf(pszBuf, _T("}\0"));
	}

	// Send the new data to the RichEdit control
	SendMessage(hWindow, WM_SETTEXT, (WPARAM)0, (LPARAM)szBuffer);

#if 0
	FILE* fp = _tfopen(_T("memdump.rtf"), _T("wb"));
	if (fp) {
		fwrite(szBuffer, 1, _tcslen(szBuffer), fp);
		fclose(fp);
	}
#endif
}

// ----------------------------------------------------------------------------
// Memory area setup

static int __cdecl GetMemoryAcb(struct BurnArea* pba)
{
	TCHAR szAreaName[256] = _T("");

	memcpy(DbgMemoryAreaInfo + nDbgMemoryAreaCount, pba, sizeof(BurnArea));

	_stprintf(szAreaName, _T("%hs - %x bytes"), pba->szName, pba->nLen);
	SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN1_DRP1, CB_ADDSTRING, 0, (LPARAM)szAreaName);
	SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN2_DRP1, CB_ADDSTRING, 0, (LPARAM)szAreaName);

	nDbgMemoryAreaCount++;

	return 0;
}

static int GetMemoryAreas()
{
	nDbgMemoryAreaCount = 1;
	DbgMemoryAreaInfo = (BurnArea*)malloc(256 * sizeof(BurnArea));
	if (DbgMemoryAreaInfo == NULL) {
		return 1;
	}

	SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN1_DRP1, CB_ADDSTRING, 0, (LPARAM)_T("Main CPU memory map"));
	SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN2_DRP1, CB_ADDSTRING, 0, (LPARAM)_T("Main CPU memory map"));

	BurnAcb = GetMemoryAcb;
	BurnAreaScan(ACB_MEMORY_ROM | ACB_NVRAM | ACB_MEMCARD | ACB_MEMORY_RAM | ACB_READ, NULL);

	return 0;
}

static int SetupDisass(int nArea)
{
	nDisassAddress[0] = 0;
	nDisassArea = nArea;
	nDisassAttrib = 0;

	nDisassCPUType = SekDbgGetCPUType();

	if (nDisassArea) {
		SekDbgFetchByteDisassembler = DbgFetchByteArea;
		SekDbgFetchWordDisassembler = DbgFetchWordArea;
		SekDbgFetchLongDisassembler = DbgFetchLongArea;
	} else {
		SekDbgFetchByteDisassembler = DbgFetchByte;
		SekDbgFetchWordDisassembler = DbgFetchWord;
		SekDbgFetchLongDisassembler = DbgFetchLong;

		nDisassAddress[0] = TraceBack(SekDbgGetRegister(SEK_REG_PC), 5, nDisassCPUType);
	}

	CreateDisass(GetDlgItem(hDbgDlg, nDisassWindow), nDisassAddress[0], nDisassCPUType);
	SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN1_DRP1, CB_SETCURSEL, (WPARAM)nDisassArea, (LPARAM)0);

	SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN1_DRP2, CB_RESETCONTENT, (WPARAM)0, (LPARAM)0);
	if (nArea) {
		SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN1_DRP2, CB_ADDSTRING, (WPARAM)0, (LPARAM)_T("little-endian 8-bit / big-endian"));
		SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN1_DRP2, CB_ADDSTRING, (WPARAM)0, (LPARAM)_T("little-endian 16-bit"));
		SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN1_DRP2, CB_ADDSTRING, (WPARAM)0, (LPARAM)_T("little-endian 32-bit"));
	} else {
		SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN1_DRP2, CB_ADDSTRING, (WPARAM)0, (LPARAM)_T("Auto"));
	}
	SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN1_DRP2, CB_SETCURSEL, (WPARAM)0, (LPARAM)0);

	return 0;
}

static int SetupMemdump(int nArea)
{
	nMemdumpArea = nArea;
	nMemdumpAddress = 0x000000;
	nMemdumpAttrib = 0;

	CreateMemdump(GetDlgItem(hDbgDlg, nMemdumpWindow), nMemdumpArea, nMemdumpAddress);
	SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN2_DRP1, CB_SETCURSEL, (WPARAM)nMemdumpArea, (LPARAM)0);

	SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN2_DRP2, CB_RESETCONTENT, (WPARAM)0, (LPARAM)0);
	if (nArea) {
		SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN2_DRP2, CB_ADDSTRING, (WPARAM)0, (LPARAM)_T("little-endian 8-bit / big-endian"));
		SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN2_DRP2, CB_ADDSTRING, (WPARAM)0, (LPARAM)_T("little-endian 16-bit"));
		SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN2_DRP2, CB_ADDSTRING, (WPARAM)0, (LPARAM)_T("little-endian 32-bit"));
	} else {
		SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN2_DRP2, CB_ADDSTRING, (WPARAM)0, (LPARAM)_T("Auto"));
	}
	SendDlgItemMessage(hDbgDlg, IDC_DBG_MAINWN2_DRP2, CB_SETCURSEL, (WPARAM)0, (LPARAM)0);

	return 0;
}

// ----------------------------------------------------------------------------
// Create CPU info in RTF format

int UpdateCPUState(DbgM68000State* Curr_M68000) {
	Curr_M68000->a[0] = SekDbgGetRegister(SEK_REG_A0);
	Curr_M68000->a[1] = SekDbgGetRegister(SEK_REG_A1);
	Curr_M68000->a[2] = SekDbgGetRegister(SEK_REG_A2);
	Curr_M68000->a[3] = SekDbgGetRegister(SEK_REG_A3);
	Curr_M68000->a[4] = SekDbgGetRegister(SEK_REG_A4);
	Curr_M68000->a[5] = SekDbgGetRegister(SEK_REG_A5);
	Curr_M68000->a[6] = SekDbgGetRegister(SEK_REG_A6);
	Curr_M68000->a[7] = SekDbgGetRegister(SEK_REG_A7);

	Curr_M68000->d[0] = SekDbgGetRegister(SEK_REG_D0);
	Curr_M68000->d[1] = SekDbgGetRegister(SEK_REG_D1);
	Curr_M68000->d[2] = SekDbgGetRegister(SEK_REG_D2);
	Curr_M68000->d[3] = SekDbgGetRegister(SEK_REG_D3);
	Curr_M68000->d[4] = SekDbgGetRegister(SEK_REG_D4);
	Curr_M68000->d[5] = SekDbgGetRegister(SEK_REG_D5);
	Curr_M68000->d[6] = SekDbgGetRegister(SEK_REG_D6);
	Curr_M68000->d[7] = SekDbgGetRegister(SEK_REG_D7);

	Curr_M68000->pc = SekDbgGetRegister(SEK_REG_PC);
	Curr_M68000->sr = SekDbgGetRegister(SEK_REG_SR);

	Curr_M68000->sp = SekDbgGetRegister(SEK_REG_SP);
	Curr_M68000->usp = SekDbgGetRegister(SEK_REG_USP);
	Curr_M68000->isp = SekDbgGetRegister(SEK_REG_ISP);

	Curr_M68000->irq = SekDbgGetPendingIRQ();

	return 0;
}

static void CreateCPUInfo(HWND hWindow, bool bUpdate, DbgM68000State* Curr_M68000, DbgM68000State* Prev_M68000)
{
	int col;
	TCHAR szBuffer[16384] = _T("");
	TCHAR* pszBuf = szBuffer;

	int curr_sr, diff_sr;

	if (bUpdate) {
		UpdateCPUState(Curr_M68000);
	}

	curr_sr = Curr_M68000->sr;
	diff_sr = Prev_M68000->sr ^ curr_sr;

	// RTF header and initial settings
	pszBuf += _stprintf(pszBuf, _T("{\\rtf1\\ansi\\ansicpg1252\\deff0{\\fonttbl(\\f0\\fmodern\\fprq0 Courier New\\cpg1252;)}{\\colortbl\\red0\\green0\\blue0;\\red128\\green128\\blue128;\\red255\\green0\\blue0;\\red255\\green96\\blue96;}\\deflang1033\\horzdoc\\pard\\f0\\plain"));
	// Tabstops
	if (bLargeWindow) {
		pszBuf += _stprintf(pszBuf, _T("\\tx1950\\fs24 "));
	} else {
		pszBuf += _stprintf(pszBuf, _T("\\tx1365\\fs16 "));
	}

	// Print SR
	pszBuf += _stprintf(pszBuf, _T("SR  "));
	if (nDisassCPUType >= M68K_CPU_TYPE_68EC020) {
		// T
		if (diff_sr & 0xC000) {
			pszBuf += _stprintf(pszBuf, _T("\\cf%i T%i"), (diff_sr & 0xC000) ? 2 : 0, curr_sr >> 14);
		} else {
			pszBuf += _stprintf(pszBuf, _T("\\cf%i \\\'95\\\'95"), (diff_sr & 0xC000) ? 2 : 0);
		}
	} else {
		// T
		pszBuf += _stprintf(pszBuf, _T("\\cf%i %s"), (diff_sr & 0x8000) ? 2 : 0, (curr_sr & 0x8000) ? _T("T") : _T("\\\'95"));
		// Unused
		pszBuf += _stprintf(pszBuf, _T("{\\cf1\\\'B7}"));
	}
	// S
	pszBuf += _stprintf(pszBuf, _T("\\cf%i %s"), (diff_sr & 0x2000) ? 2 : 0, (curr_sr & 0x2000) ? _T("S") : _T("\\\'95"));
	if (nDisassCPUType >= M68K_CPU_TYPE_68EC020) {
		// M
		pszBuf += _stprintf(pszBuf, _T("\\cf%i %s"), (diff_sr & 0x1000) ? 2 : 0, (curr_sr & 0x1000) ? _T("M") : _T("\\\'95"));
	} else {
		// Unused
		pszBuf += _stprintf(pszBuf, _T("{\\cf1\\\'B7}"));
	}
	// Unused
	pszBuf += _stprintf(pszBuf, _T("{\\cf1\\\'B7}"));
	// I
	pszBuf += _stprintf(pszBuf, _T("\\cf%i IM%i"), (diff_sr & 0x0700) ? 2 : 0, (curr_sr & 0x0700) >> 8);
	// Unused
	pszBuf += _stprintf(pszBuf, _T("{\\cf1\\\'B7\\\'B7\\\'B7}"));
	// X
	pszBuf += _stprintf(pszBuf, _T("\\cf%i %s"), (diff_sr & 0x10) ? 2 : 0, (curr_sr & 0x10) ? _T("X") : _T("\\\'95"));
	// N
	pszBuf += _stprintf(pszBuf, _T("\\cf%i %s"), (diff_sr & 0x08) ? 2 : 0, (curr_sr & 0x08) ? _T("N") : _T("\\\'95"));
	// Z
	pszBuf += _stprintf(pszBuf, _T("\\cf%i %s"), (diff_sr & 0x04) ? 2 : 0, (curr_sr & 0x04) ? _T("Z") : _T("\\\'95"));
	// V
	pszBuf += _stprintf(pszBuf, _T("\\cf%i %s"), (diff_sr & 0x02) ? 2 : 0, (curr_sr & 0x02) ? _T("V") : _T("\\\'95"));
	// C
	pszBuf += _stprintf(pszBuf, _T("\\cf%i %s "), (diff_sr & 0x01) ? 2 : 0, (curr_sr & 0x01) ? _T("C") : _T("\\\'95"));
	// Hex value
	pszBuf += _stprintf(pszBuf, _T("\\cf%i %02x\\cf%i %02x\\par "), (diff_sr & 0xFF00) ? 2 : 0, curr_sr >> 8, (diff_sr & 0xFF) ? 2 : 0, curr_sr & 0xFF);

#if 1
	// Print pending IRQ
	{
		int s1, s2;

		if (bLargeWindow) {
			s1 = 16;
			s2 = 22;
		} else {
			s1 = 12;
			s2 = 14;
		}
		if (Curr_M68000->irq & 7) {
			pszBuf += _stprintf(pszBuf, _T("\\cf0 IRQ %i{\\fs%i  \\fs%i\\cf1 pending}\t"), Curr_M68000->irq & 7, s1, s2);
		} else {
			pszBuf += _stprintf(pszBuf, _T("\\cf1 IRQ 0{\\fs%i  \\fs%i pending}\t"), s1, s2);
		}
	}
#else
	if (nDisassCPUType >= M68K_CPU_TYPE_68010) {
		// Print SFC/DFC
		col = (Curr_M68000->sfc ^ Prev_M68000->sfc) ? 2 : 0;
		pszBuf += _stprintf(pszBuf, _T("\\cf0 SFC \\cf%i %i "), col + 1, Curr_M68000->sfc);
		col = (Curr_M68000->dfc ^ Prev_M68000->dfc) ? 2 : 0;
		pszBuf += _stprintf(pszBuf, _T("\\cf0 DFC \\cf%i %i  "), col + 1, Curr_M68000->dfc);
	} else {
		pszBuf += _stprintf(pszBuf, _T("               "));
	}
#endif

	// Print PC
	col = (Curr_M68000->pc ^ Prev_M68000->pc) ? 2 : 0;
	pszBuf += _stprintf(pszBuf, _T("\\cf0 PC  \\cf%i %02x\\cf%i %06x\\par\\par "), col + 1, Curr_M68000->pc >> 24, col, Curr_M68000->pc & 0x00FFFFFF);

	// Print data and address registers
	for (int i = 0; i < 8; i++) {
		col = (Curr_M68000->d[i] ^ Prev_M68000->d[i]) ? 2 : 0;
		pszBuf += _stprintf(pszBuf, _T("\\cf0 D%i  \\cf%i %08x "), i, col, Curr_M68000->d[i]);

		col = (Curr_M68000->a[i] ^ Prev_M68000->a[i]) ? 2 : 0;
		pszBuf += _stprintf(pszBuf, _T("\\cf0 A%i  \\cf%i %02x\\cf%i %06x\\par "), i, col + 1, Curr_M68000->a[i] >> 24, col, Curr_M68000->a[i] & 0x00FFFFFF);
	}
	pszBuf += _stprintf(pszBuf, _T("\\par"));

	if (nDisassCPUType >= M68K_CPU_TYPE_68EC020) {
		// Print vector base register
		col = (Curr_M68000->vbr ^ Prev_M68000->vbr) ? 2 : 0;
		pszBuf += _stprintf(pszBuf, _T("\\cf0 VBR \\cf%i %02x\\cf%i %06x "), col + 1, Curr_M68000->vbr >> 24, col, Curr_M68000->vbr & 0x00FFFFFF);
		// Print master stack pointer
		col = (Curr_M68000->msp ^ Prev_M68000->msp) ? 2 : 0;
		pszBuf += _stprintf(pszBuf, _T("\\cf0 MSP \\cf%i %02x\\cf%i %06x\\par "), col + 1, Curr_M68000->msp >> 24, col, Curr_M68000->msp & 0x00FFFFFF);
	}

	// Print interrupt stack pointer
	col = (Curr_M68000->isp ^ Prev_M68000->isp) ? 2 : 0;
	pszBuf += _stprintf(pszBuf, _T("\\cf0 ISP \\cf%i %02x\\cf%i %06x "), col + 1, Curr_M68000->isp >> 24, col, Curr_M68000->isp & 0x00FFFFFF);
	// Print user stack pointer
	col = (Curr_M68000->usp ^ Prev_M68000->usp) ? 2 : 0;
	pszBuf += _stprintf(pszBuf, _T("\\cf0 USP \\cf%i %02x\\cf%i %06x\\par "), col + 1, Curr_M68000->usp >> 24, col, Curr_M68000->usp & 0x00FFFFFF);

	// Cycles done this frame
	pszBuf += _stprintf(pszBuf, _T("\\par\\cf0 cycle%7i "), SekTotalCycles());
	if (nSekCyclesScanline) {
		pszBuf += _stprintf(pszBuf, _T("scanline %3i\\par"), SekCurrentScanline());
	} else {
		pszBuf += _stprintf(pszBuf, _T("\\cf1 scanline ---\\par"));
	}

	SendMessage(hWindow, WM_SETTEXT, (WPARAM)0, (LPARAM)szBuffer);

	if (bUpdate) {
		memcpy(Prev_M68000, Curr_M68000, sizeof(DbgM68000State));
	}

#if 0
	FILE* fp = _ftopen(_T("cpuinfo.rtf"), _T("wb");
	if (fp) {
		fwrite(szBuffer, 1, strlen(szBuffer), fp);
		fclose(fp);
	}
#endif
}

// ----------------------------------------------------------------------------
// Callbacks for when a breakpoint is hit

void BreakpointProcessing(TCHAR* pszMessage)
{
	bBreakpointHit = true;
	AudBlankSound();

	// Make sure single stepping is disabled
	SekDbgEnableBreakpoints();

	// Update the feedback area
	AddFeedback(GetDlgItem(hDbgDlg, IDC_DBG_INFOWN2), pszMessage);

	// Update info
	CreateCPUInfo(GetDlgItem(hDbgDlg, IDC_DBG_INFOWN1), true, &curr_m68000, &prev_m68000);
	CreateDisass(GetDlgItem(hDbgDlg, nDisassWindow), TraceBack(SekDbgGetRegister(SEK_REG_PC), 5, nDisassCPUType), nDisassCPUType);

	SetWindowText(GetDlgItem(hDbgDlg, IDC_DBG_GO), _T("Go!"));
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_STEPFRAME), TRUE);
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_STEPINTO), TRUE);
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_STEP), TRUE);
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_SKIP), TRUE);

	// Activate message pump
	BreakpointMessagePump();
}

void BreakpointHitRead(unsigned int address, int breakpoint)
{
	TCHAR szBuffer[256] = _T("");
	_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;\\red0\\green0\\blue255;}\\pard\\f0\\fs20\\cf1 Breakpoint R%i! read <- %06x\\par}"), breakpoint, address);

	BreakpointProcessing(szBuffer);
}

void BreakpointHitFetch(unsigned int address, int breakpoint)
{
	TCHAR szBuffer[256] = _T("");

	if (breakpoint == 0) {

		// Remove step into breakpoint
		SekDbgSetBreakpointFetch(0, 0);

		_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;\\red0\\green0\\blue255;}\\pard\\f0\\fs20\\cf1 Step complete! PC == %06x\\par}"), address);
	} else {
		_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;\\red0\\green0\\blue255;}\\pard\\f0\\fs20\\cf1 Breakpoint P%i! PC == %06x\\par}"), breakpoint, address);
	}

	BreakpointProcessing(szBuffer);
}

void BreakpointHitWrite(unsigned int address, int breakpoint)
{
	TCHAR szBuffer[256] = _T("");
	_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;\\red0\\green0\\blue255;}\\pard\\f0\\fs20\\cf1 Breakpoint W%i! write -> %06x\\par}"), breakpoint, address);

	BreakpointProcessing(szBuffer);
}

// ----------------------------------------------------------------------------
// Command parsing

static int cmd_exit(TCHAR* /*arg*/)
{
	SendMessage(hDbgDlg, WM_CLOSE, 0, 0);

	return 0;
}

static int cmd_disass(TCHAR* arg)
{
	TCHAR* pos = arg;
	long address;

	// Check if we need to write to a file
	if (*pos == _T('>')) {
		unsigned int start, end;
		TCHAR filename[MAX_PATH] = _T("");
		TCHAR* fn;

		pos++;

		SKIP_WS(pos);

		fn = pos;

		if (*pos == _T('\"')) {
			fn++;
			pos++;
			FIND_QT(pos);
			_tcsncpy(filename, fn, pos - fn);
			pos++;
		} else {
			FIND_WS(pos);
			_tcsncpy(filename, fn, pos - fn);
		}

		start = _tcstol(pos, &pos, 16);
		end = _tcstol(pos, &pos, 16);

		CreateDisassFile(filename, start, end, nDisassCPUType);

		return 0;
	}

	// Just set the active window when no arguments are specified
	if (*pos == _T('\0')) {
		nActiveWindow = nDisassWindow;

		return 0;
	}

	// Check for keywords
	if (!_tcsicmp(pos, _T("pc"))) {
		CreateDisass(GetDlgItem(hDbgDlg, nDisassWindow), TraceBack(SekDbgGetRegister(SEK_REG_PC), 5, nDisassCPUType), nDisassCPUType);

		nActiveWindow = nDisassWindow;

		return 0;
	}

	// Disassemble to the window
	address = _tcstol(pos, &pos, 16);
	if (address > 0x00FFFFFE || address < 0) {
		return 1;
	}

	CreateDisass(GetDlgItem(hDbgDlg, nDisassWindow), address & 0xFFFFFE, nDisassCPUType);

	nActiveWindow = nDisassWindow;

	return 0;
}

static int cmd_memdump(TCHAR* arg)
{
	TCHAR* pos = arg;
	long address = 0;

	// Just set the active window when no arguments are specified
	if (*pos == _T('\0')) {
		nActiveWindow = nMemdumpWindow;

		return 0;
	}

	// Check for keywords
	if (!_tcsicmp(pos, _T("pc"))) {
		address = SekDbgGetRegister(SEK_REG_PC) - 5 * 16;
	}
	if (!_tcsicmp(pos, _T("sp"))) {
		address = SekDbgGetRegister(SEK_REG_SP);
	}

	if (!address) {
		address = _tcstol(pos, &pos, 16);
	}

	if (address > 0x00FFFFFE || address < 0) {
		return 1;
	}

	nMemdumpAddress = address;
	CreateMemdump(GetDlgItem(hDbgDlg, nMemdumpWindow), nMemdumpArea, nMemdumpAddress);

	nActiveWindow = nMemdumpWindow;

	return 0;
}

static int cmd_go(TCHAR* arg)
{
	TCHAR szBuffer[256];
	TCHAR* pos = arg;
	long address = _tcstol(pos, &pos, 16);

	curr_m68000 = prev_m68000;
	if (address) {
		SekDbgSetRegister(SEK_REG_PC, address);
	}

	SetWindowText(GetDlgItem(hDbgDlg, IDC_DBG_GO), _T("Halt"));
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_STEPFRAME), FALSE);
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_STEPINTO), FALSE);
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_STEP), FALSE);
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_SKIP), FALSE);

	// Update the feedback area
	_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;}\\pard\\f0\\fs20\\cf0 Running, start at PC = %06x\\par}"), SekDbgGetRegister(SEK_REG_PC));
	AddFeedback(GetDlgItem(hDbgDlg, IDC_DBG_INFOWN2), szBuffer);

	if (bRunPause) {
		SetPauseMode(false);
	}
	if (bBreakpointHit) {
		PostMessage(hScrnWnd, WM_APP + 0, 0, 0);
	}

	return 0;
}

static int cmd_halt(TCHAR* /*arg*/)
{
	TCHAR szBuffer[256];

	// remove temporary breakpoint (if any)
	SekDbgSetBreakpointFetch(0, 0);

	SetWindowText(GetDlgItem(hDbgDlg, IDC_DBG_GO), _T("Go!"));
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_STEPFRAME), TRUE);
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_STEPINTO), TRUE);
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_STEP), TRUE);
	EnableWindow(GetDlgItem(hDbgDlg, IDC_DBG_SKIP), TRUE);

	if (!bRunPause) {
		SetPauseMode(true);

		SekOpen(0);

		CreateDisass(GetDlgItem(hDbgDlg, nDisassWindow), TraceBack(SekGetPC(-1), 5, nDisassCPUType), nDisassCPUType);
		CreateCPUInfo(GetDlgItem(hDbgDlg, IDC_DBG_INFOWN1), true, &curr_m68000, &prev_m68000);

		SekClose();
	}

	// Update the feedback area
	_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;}\\pard\\f0\\fs20\\cf0 Stopped, PC = %06x\\par}"), SekDbgGetRegister(SEK_REG_PC));
	AddFeedback(GetDlgItem(hDbgDlg, IDC_DBG_INFOWN2), szBuffer);

	return 0;
}

static int cmd_setbp(TCHAR* arg)
{
	TCHAR* pos = arg;
	TCHAR szBuffer[256] = _T("");

	long number, address;

	SKIP_WS(pos);

	switch (*pos) {
		case _T('r'): {
			number = _tcstol(pos + 1, &pos, 16);
			address = _tcstol(pos, &pos, 16);

			if (number < 1 || number > 8) {
				return 1;
			}

			if (address) {
				_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;}\\pard\\f0\\fs20\\cf0 Breakpoint R%i: read <- %06x\\par}"), number, address);
			} else {
				_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;}\\pard\\f0\\fs20\\cf0 Breakpoint R%i: disabled\\par}"), number);
			}

			SekDbgSetBreakpointDataRead(address, number);

			break;
		}
		case _T('w'): {
			number = _tcstol(pos + 1, &pos, 16);
			address = _tcstol(pos, &pos, 16);

			if (number < 1 || number > 8) {
				return 1;
			}

			if (address) {
				_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;}\\pard\\f0\\fs20\\cf0 Breakpoint W%i: write -> %06x\\par}"), number, address);
			} else {
				_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;}\\pard\\f0\\fs20\\cf0 Breakpoint W%i: disabled\\par}"), number);
			}

			SekDbgSetBreakpointDataWrite(address, number);

			break;
		}
		case _T('p'): {
			number = _tcstol(pos + 1, &pos, 16);
			address = _tcstol(pos, &pos, 16);

			if (number < 1 || number > 8) {
				return 1;
			}

			if (address) {
				_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;}\\pard\\f0\\fs20\\cf0 Breakpoint P%i: PC == %06x\\par}"), number, address);
			} else {
				_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252{\\colortbl;}\\pard\\f0\\fs20\\cf0 Breakpoint P%i: disabled\\par}"), number);
			}

			SekDbgSetBreakpointFetch(address, number);

			break;
		}
	}

	// Update the feedback area
	AddFeedback(GetDlgItem(hDbgDlg, IDC_DBG_INFOWN2), szBuffer);

	return 0;
}

static int cmd_frame(TCHAR* /*arg*/)
{
	TCHAR szBuffer[256] = _T("");

	SetPauseMode(true);

	prev_m68000 = curr_m68000;

	// Run until the next frame starts
	if (bBreakpointHit) {
		PostMessage(hScrnWnd, WM_APP + 0, 0, 0);
		return 0;
	} else {
		VidFrame();
	}

	// Display the image
	VidPaint(0);

	// Update the cpu info & disassembly
	SekOpen(0);
	CreateDisass(GetDlgItem(hDbgDlg, nDisassWindow), TraceBack(SekGetPC(-1), 5, nDisassCPUType), nDisassCPUType);
	CreateCPUInfo(GetDlgItem(hDbgDlg, IDC_DBG_INFOWN1), true, &curr_m68000, &prev_m68000);
	SekClose();

	// Update the feedback area
	_stprintf(szBuffer, _T("{\\rtf1\\ansi\\ansicpg1252\\pard\\f0\\fs20\\cf0 Stepped frame, PC = %06x\\par}"), SekDbgGetRegister(SEK_REG_PC));
	AddFeedback(GetDlgItem(hDbgDlg, IDC_DBG_INFOWN2), szBuffer);

	return 0;
}

static int cmd_single(TCHAR* /*arg*/)
{
	char buf[100] = "";

	int size = m68k_disassemble(buf, SekDbgGetRegister(SEK_REG_PC), nDisassCPUType);

	SekDbgSetBreakpointFetch(SekDbgGetRegister(SEK_REG_PC) + size, 0);

	return cmd_go(_T("0"));
}

static int cmd_into(TCHAR* /*arg*/)
{
	SekDbgEnableSingleStep();

	return cmd_go(_T("0"));
}

static int cmd_skip(TCHAR* /*arg*/)
{
	char buf[100] = "";

	int size = m68k_disassemble(buf, SekDbgGetRegister(SEK_REG_PC), nDisassCPUType);

	prev_m68000 = curr_m68000;

	SekDbgSetRegister(SEK_REG_PC, SekDbgGetRegister(SEK_REG_PC) + size);
	// Update active CPU context
	SekClose();

	CreateCPUInfo(GetDlgItem(hDbgDlg, IDC_DBG_INFOWN1), true, &curr_m68000, &prev_m68000);
	CreateDisass(GetDlgItem(hDbgDlg, nDisassWindow), TraceBack(SekDbgGetRegister(SEK_REG_PC), 5, nDisassCPUType), nDisassCPUType);

	return 0;
}

static struct { TCHAR* cmdstring; int (*cmdfunction)(TCHAR*); } CommandList[] = {
	{ _T(""),		NULL },

	{ _T("x"),		cmd_exit	}, { _T("exit"),		cmd_exit	},
	{ _T("d"),		cmd_disass	}, { _T("dasm"),		cmd_disass	},
	{ _T("m"),		cmd_memdump	}, { _T("mem"),			cmd_memdump	},

	{ _T("g"),		cmd_go		}, { _T("go"),			cmd_go		}, { _T("go!"),		cmd_go		},
	{ _T("h"),		cmd_halt	}, { _T("halt"),		cmd_halt	},

	{ _T("ss"),		cmd_single	}, { _T("step"),		cmd_single	},
	{ _T("si"),		cmd_into	}, { _T("step into"),	cmd_into	},
	{ _T("sk"),		cmd_skip	}, { _T("skip"),		cmd_skip	},

	{ _T("sf"),		cmd_frame	}, { _T("step frame"),	cmd_frame	},

	{ _T("sb"),		cmd_setbp	},

	{ NULL,		NULL }
};

static int ExecuteCommand(TCHAR* cmd)
{
	TCHAR cmdonly[8] = _T("");
	TCHAR* pos = cmd;

	SKIP_WS(pos);

	for (int i = 0; *pos && !isspace(*pos); i++) {
		cmdonly[i] = *pos++;
	}

	SKIP_WS(pos);

	for (int i = 0; CommandList[i].cmdstring; i++) {
		if (!_tcsicmp(cmdonly, CommandList[i].cmdstring)) {
			if (CommandList[i].cmdfunction) {
				return CommandList[i].cmdfunction(pos);
			}

			return 0;
		}
	}

	return 1;
}

// ----------------------------------------------------------------------------
// Message processing

static BOOL CALLBACK DialogProc(HWND hDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	if (Msg == WM_INITDIALOG) {

		hDbgDlg = hDlg;

		nDisassWindow = IDC_DBG_MAINWN1;
		nMemdumpWindow = IDC_DBG_MAINWN2;

		nActiveWindow = nDisassWindow;

		SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &nMouseWheelScrollLines, 0);

		SendDlgItemMessage(hDlg, IDC_DBG_COMMAND, EM_SETTEXTMODE, TM_PLAINTEXT, 0);
		SendDlgItemMessage(hDlg, IDC_DBG_COMMAND, EM_LIMITTEXT, 63, 0);

		hCommandFont = CreateFont(bLargeWindow ? 18 : 14, 0, 0, 0, FW_NORMAL, 0, 0, 0, 0, 0, 0, ANTIALIASED_QUALITY, FF_MODERN, _T("Courier New"));
		SendDlgItemMessage(hDlg, IDC_DBG_COMMAND, WM_SETFONT, (WPARAM)hCommandFont, (LPARAM)0);

		SendDlgItemMessage(hDlg, IDC_DBG_MAINWN1, EM_SETMARGINS, EC_LEFTMARGIN, 3);
		SendDlgItemMessage(hDlg, IDC_DBG_MAINWN2, EM_SETMARGINS, EC_LEFTMARGIN, 3);
		SendDlgItemMessage(hDlg, IDC_DBG_INFOWN1, EM_SETMARGINS, EC_LEFTMARGIN, 3);
		SendDlgItemMessage(hDlg, IDC_DBG_INFOWN2, EM_SETMARGINS, EC_LEFTMARGIN, 3);
		SendDlgItemMessage(hDlg, IDC_DBG_COMMAND, EM_SETMARGINS, EC_LEFTMARGIN, 3);

		SendDlgItemMessage(hDlg, IDC_DBG_MAINWN1, EM_SETEVENTMASK, 0, ENM_KEYEVENTS | ENM_MOUSEEVENTS | ENM_SCROLLEVENTS | ENM_PROTECTED);
		SendDlgItemMessage(hDlg, IDC_DBG_MAINWN2, EM_SETEVENTMASK, 0, ENM_KEYEVENTS | ENM_MOUSEEVENTS | ENM_SCROLLEVENTS | ENM_PROTECTED);
		SendDlgItemMessage(hDlg, IDC_DBG_INFOWN1, EM_SETEVENTMASK, 0, ENM_KEYEVENTS | ENM_MOUSEEVENTS | ENM_SCROLLEVENTS | ENM_PROTECTED);
		SendDlgItemMessage(hDlg, IDC_DBG_INFOWN2, EM_SETEVENTMASK, 0, ENM_KEYEVENTS | ENM_MOUSEEVENTS | ENM_SCROLLEVENTS | ENM_PROTECTED);
		SendDlgItemMessage(hDlg, IDC_DBG_COMMAND, EM_SETEVENTMASK, 0, ENM_KEYEVENTS | ENM_MOUSEEVENTS | ENM_SCROLLEVENTS);

		// Initialise feedback window
		InitFeedback(GetDlgItem(hDlg, IDC_DBG_INFOWN2));

		// Get the list of memory areas
		GetMemoryAreas();

		// Initialise disassembly window
		SekOpen(0);
		SetupDisass(0);

		// Initialise memory dump window
		SetupMemdump(0);

		UpdateCPUState(&prev_m68000);
		CreateCPUInfo(GetDlgItem(hDlg, IDC_DBG_INFOWN1), true, &curr_m68000, &prev_m68000);

		SekClose();

		SendDlgItemMessage(hDlg, IDC_DBG_MAINWN1_DRP1, CB_SETCURSEL, (WPARAM)0, (LPARAM)0);

		SendMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hDlg, IDC_DBG_COMMAND), TRUE);

		WndInMid(hDlg, NULL);
		ShowWindow(hDlg, SW_NORMAL);

		return FALSE;
	}

	if (Msg == WM_CLOSE) {
		DebugExit();

		return 0;
	}

	if (Msg == WM_COMMAND) {

		// Exit the debugger
		if (LOWORD(wParam) == IDCANCEL && HIWORD(wParam) == BN_CLICKED) {
			SendMessage(hDlg, WM_CLOSE, 0, 0);
			return 0;
		}

		if (LOWORD(wParam) == IDC_DBG_GO && HIWORD(wParam) == BN_CLICKED) {
			if (bRunPause || bBreakpointHit) {
				ExecuteCommand(_T("g"));
			} else {
				ExecuteCommand(_T("h"));
			}
			return 0;
		}

		if (LOWORD(wParam) == IDC_DBG_STEP && HIWORD(wParam) == BN_CLICKED) {
			ExecuteCommand(_T("ss"));
			return 0;
		}
		if (LOWORD(wParam) == IDC_DBG_STEPINTO && HIWORD(wParam) == BN_CLICKED) {
			ExecuteCommand(_T("si"));
			return 0;
		}
		if (LOWORD(wParam) == IDC_DBG_SKIP && HIWORD(wParam) == BN_CLICKED) {
			ExecuteCommand(_T("sk"));
			return 0;
		}

		if (LOWORD(wParam) == IDC_DBG_STEPFRAME && HIWORD(wParam) == BN_CLICKED) {
			ExecuteCommand(_T("sf"));
			return 0;
		}
		if (LOWORD(wParam) == IDC_DBG_SHOT && HIWORD(wParam) == BN_CLICKED) {
			MakeScreenShot();
			return 0;
		}

		// Execute the comnmand entered in the command text control
		if (LOWORD(wParam) == IDOK) {
			TCHAR CommandText[64] = _T("");
			SendDlgItemMessage(hDlg, IDC_DBG_COMMAND, WM_GETTEXT, (WPARAM)64, (LPARAM)CommandText);
			SendDlgItemMessage(hDlg, IDC_DBG_COMMAND, WM_SETTEXT, (WPARAM)0, (LPARAM)_T(""));

			ExecuteCommand(CommandText);

			SendMessage(hDlg, WM_NEXTDLGCTL, (WPARAM)GetDlgItem(hDlg, IDC_DBG_COMMAND), TRUE);
		}

		if (HIWORD(wParam) == CBN_SELENDOK) {
			if (LOWORD(wParam) == IDC_DBG_MAINWN1_DRP1) {
				SetupDisass(SendMessage((HWND)lParam, CB_GETCURSEL, (WPARAM)0, (LPARAM)0));
			}
			if (LOWORD(wParam) == IDC_DBG_MAINWN1_DRP2) {
				nDisassAttrib = SendMessage((HWND)lParam, CB_GETCURSEL, (WPARAM)0, (LPARAM)0);
				CreateDisass(GetDlgItem(hDlg, nDisassWindow), nDisassAddress[0], nDisassCPUType);
			}
			if (LOWORD(wParam) == IDC_DBG_MAINWN2_DRP1) {
				SetupMemdump(SendMessage((HWND)lParam, CB_GETCURSEL, (WPARAM)0, (LPARAM)0));
			}
			if (LOWORD(wParam) == IDC_DBG_MAINWN2_DRP2) {
				nMemdumpAttrib = SendMessage((HWND)lParam, CB_GETCURSEL, (WPARAM)0, (LPARAM)0);
				CreateMemdump(GetDlgItem(hDlg, nMemdumpWindow), nMemdumpArea, nMemdumpAddress);
			}
		}
	}

	if (Msg == WM_NOTIFY) {
		int nPageSize = bLargeWindow ? 16 : 12;
		unsigned int nWindow = 0;
		bool bAffectCaret = true;
		int nScrollLines = 0;

		if (((NMHDR*)lParam)->code == EN_MSGFILTER) {
			nWindow = ((NMHDR*)lParam)->idFrom;

			if (nWindow == IDC_DBG_COMMAND) {
				nWindow = nActiveWindow;

				bAffectCaret = false;
			}
		}

		if (((NMHDR*)lParam)->code == EN_MSGFILTER && ((MSGFILTER*)lParam)->msg == WM_MOUSEWHEEL) {
			static int nMouseWheelDelta;

//			if (wParam & (MK_SHIFT | MK_CONTROL)) {
//				break;
//			}

			nMouseWheelDelta -= (short)HIWORD(((MSGFILTER*)lParam)->wParam);

			if (abs(nMouseWheelDelta) >= WHEEL_DELTA) {
				if (nMouseWheelScrollLines == WHEEL_PAGESCROLL) {
					nScrollLines = 2 * (nPageSize - ((nWindow == nDisassWindow) ? 1 : 0));
					if (nMouseWheelDelta < 0) {
						nScrollLines = -nScrollLines;
					}
				} else {
					nScrollLines = 2 * (nMouseWheelDelta / WHEEL_DELTA);
				}
				nMouseWheelDelta %= WHEEL_DELTA;

				bAffectCaret = false;
			}
		}

		if ((bBreakpointHit || bRunPause) && ((NMHDR*)lParam)->code == EN_MSGFILTER && ((MSGFILTER*)lParam)->msg == WM_KEYDOWN || ((MSGFILTER*)lParam)->msg == WM_SYSKEYDOWN) {

			if (((NMHDR*)lParam)->idFrom != IDC_DBG_COMMAND && ((NMHDR*)lParam)->idFrom != nDisassWindow && ((NMHDR*)lParam)->idFrom != nMemdumpWindow) {
				nWindow = 0;
			}

			if (((MSGFILTER*)lParam)->wParam == VK_UP) {
				if (KEY_DOWN(VK_CONTROL)) {
					nScrollLines = -1;
				} else {
					nScrollLines = -2;
				}
			}
			if (((MSGFILTER*)lParam)->wParam == VK_DOWN) {
				if (KEY_DOWN(VK_CONTROL)) {
					nScrollLines = 1;
				} else {
					nScrollLines = 2;
				}
			}
			if (((MSGFILTER*)lParam)->wParam == VK_PRIOR) {
				nScrollLines = -2 * (nPageSize - ((nWindow == nDisassWindow) ? 1 : 0));
			}
			if (((MSGFILTER*)lParam)->wParam == VK_NEXT) {
				nScrollLines = 2 * (nPageSize - ((nWindow == nDisassWindow) ? 1 : 0));
			}
		}

		if (nScrollLines && nWindow) {
			DWORD dwStart = 0, dwEnd = 0;
			bool bResetCaret = false;

			if (nWindow == nDisassWindow) {

				SendDlgItemMessage(hDlg, nWindow, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);

				if (nScrollLines < 0) {
					int nLine = SendDlgItemMessage(hDlg, nWindow, EM_LINEFROMCHAR, (WPARAM)((dwStart != (DWORD)-1) ? dwStart : dwEnd), (LPARAM)0);

					if (nScrollLines & 1) {
						CreateDisass(GetDlgItem(hDlg, nWindow), nDisassAddress[0] < 2 ? 0 : nDisassAddress[0] - 2, nDisassCPUType);

						dwStart = dwEnd = 2 * nPageSize + SendDlgItemMessage(hDlg, nWindow, EM_LINEINDEX, nLine, 0);
						bResetCaret = true;
					} else {
						if (!bAffectCaret || nScrollLines < -2 || nLine == 0) {
							CreateDisass(GetDlgItem(hDlg, nWindow), TraceBack(nDisassAddress[0], -nScrollLines / 2, nDisassCPUType), nDisassCPUType);

							if (bAffectCaret) {
								dwStart = dwEnd = 2 * nPageSize;
							}
							bResetCaret = true;
						}
					}
				}
				if (nScrollLines > 0) {
					int nLine = SendDlgItemMessage(hDlg, nWindow, EM_LINEFROMCHAR, (WPARAM)dwEnd, (LPARAM)0);

					if (nScrollLines & 1) {
						CreateDisass(GetDlgItem(hDlg, nWindow), nDisassAddress[0] >= 0x00FFFFFE ? 0x00FFFFFE : nDisassAddress[0] + 2, nDisassCPUType);

						dwStart = dwEnd = 32 + SendDlgItemMessage(hDlg, nWindow, EM_LINEINDEX, nLine, 0);
						bResetCaret = true;
					} else {
						if (!bAffectCaret || nScrollLines > 2 || SendDlgItemMessage(hDlg, nWindow, EM_LINEFROMCHAR, (WPARAM)dwEnd, (LPARAM)0) >= (nPageSize - 1)) {

							// Make sure we don't go past the end of memory
							if (nDisassAddress[0] >= 0x00FFFFFE) {
								nDisassAddress[1] = 0x00FFFFFE;
							}

							CreateDisass(GetDlgItem(hDlg, nWindow), nDisassAddress[nScrollLines / 2], nDisassCPUType);

							if (bAffectCaret) {
								dwStart = dwEnd = 32 + SendDlgItemMessage(hDlg, nWindow, EM_LINEINDEX, nPageSize - 1, 0);
							}
							bResetCaret = true;
						}
					}
				}
			}

			if (nWindow == nMemdumpWindow) {
				int nAreaSize = nMemdumpArea ? DbgMemoryAreaInfo[nMemdumpArea].nLen : 0x01000000;
				bool bRefresh = true;

				SendDlgItemMessage(hDlg, nWindow, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);

				if (nScrollLines < 0) {
					if (nScrollLines & 1) {
						nMemdumpAddress -= 1;
					} else {
						if (!bAffectCaret || nScrollLines < -2 || SendDlgItemMessage(hDlg, nWindow, EM_LINEFROMCHAR, (WPARAM)dwStart, (LPARAM)0) == 0) {
							nMemdumpAddress += 8 * nScrollLines;
						} else {
							bRefresh = false;
						}
					}
				}
				if (nScrollLines > 0) {
					if (nScrollLines & 1) {
						nMemdumpAddress += 1;
					} else {
						if (!bAffectCaret || nScrollLines > 2 || SendDlgItemMessage(hDlg, nWindow, EM_LINEFROMCHAR, (WPARAM)dwEnd, (LPARAM)0) >= nPageSize - 1) {
							nMemdumpAddress += 8 * nScrollLines;
						} else {
							bRefresh = false;
						}
					}
				}

				if (bRefresh) {
					// Make sure we don't go past the end of memory
					if (nMemdumpAddress > nAreaSize - nPageSize * 16) {
						nMemdumpAddress = nAreaSize - nPageSize * 16;
					}
					if (nMemdumpAddress < 0) {
						nMemdumpAddress = 0;
					}

					CreateMemdump(GetDlgItem(hDlg, nWindow), nMemdumpArea, nMemdumpAddress);
					bResetCaret = true;
				}
			}

			// If we've re-filled the RichEdit control, the caret position is reset as well, so restore it
			if (bResetCaret) {
				SendDlgItemMessage(hDlg, nWindow, EM_SETSEL, (WPARAM)dwStart, (LPARAM)dwEnd);
			}
		}
	}

	return 0;
}

// ----------------------------------------------------------------------------
// Public functions

#endif

int DebugExit()
{

#if defined (FBA_DEBUG)

	free(DbgMemoryAreaInfo);
	DbgMemoryAreaInfo = NULL;

	EnableWindow(hScrnWnd, TRUE);
	DestroyWindow(hDbgDlg);

	DeleteObject(hCommandFont);
	hCommandFont = NULL;

	SekDbgDisableBreakpoints();
	if (bBreakpointHit) {
		bBreakpointHit = false;
		POST_INITIALISE_MESSAGE;
	}

	SetPauseMode(bOldPause);
	MenuUpdate();

#endif

	return 0;
}

int DebugCreate()
{

#if defined (FBA_DEBUG)

	if (bDrvOkay == 0) {
		return 1;
	}

	AudBlankSound();

	DestroyWindow(hDbgDlg);

	SystemParametersInfo(SPI_GETWORKAREA, 0, &SystemWorkArea, 0);
	bLargeWindow = (SystemWorkArea.right - SystemWorkArea.left >= 1024 && SystemWorkArea.bottom - SystemWorkArea.top >= 768) ? true : false;
	hDbgDlg = FBACreateDialog(hAppInst, MAKEINTRESOURCE(bLargeWindow ? IDD_DEBUG_LRG : IDD_DEBUG_SML), hScrnWnd, DialogProc);
	if (hDbgDlg == NULL) {
		return 1;
	}

	SekDbgBreakpointHandlerRead = BreakpointHitRead;
	SekDbgBreakpointHandlerFetch = BreakpointHitFetch;
	SekDbgBreakpointHandlerWrite = BreakpointHitWrite;

	bOldPause = bRunPause;

	SetPauseMode(true);

	return 0;

#else

	POST_INITIALISE_MESSAGE;

	return 1;

#endif

}
