/*
 *   DebugDll.c
 *
 *   This file is part of Emu48
 *
 *   Copyright (C) 2000 Christoph Gießelink
 *
 */
#include "pch.h"
#include "resource.h"
#include "Emu48.h"
#include "Opcodes.h"
#include "ops.h"
#include "debugger.h"

#include "Emu48Dll.h"

#define MAXBREAKPOINTS  256					// max. number of breakpoints

typedef struct								// type of breakpoint table
{
	UINT	nType;							// breakpoint type
	DWORD	dwAddr;							// breakpoint address
} BP_T;

static DWORD *pdwLinArray;					// last instruction linear array

static DWORD dwRefRstkp;					// reference stack pointer content

static WORD  wBreakpointCount = 0;			// number of breakpoints
static BP_T  sBreakpoint[MAXBREAKPOINTS];	// breakpoint table

static BOOL  bDbgNotify = FALSE;			// debugger notify flag
static BOOL  bDbgRpl;						// Flag for ASM or RPL breakpoint

// callback function notify debugger breakpoint
static VOID (CALLBACK *pEmuDbgNotify)(INT nBreaktype) = NULL;

// callback function notify hardware stack changed
static BOOL (CALLBACK *pEmuStackNotify)(VOID) = NULL;



//################
//#
//#    Static functions
//#
//################

//
// convert nibble register to DWORD
//
static DWORD RegToDWORD(BYTE *pReg, WORD wNib)
{
	WORD  i;
	DWORD dwRegister = 0;

	for (i = 0;i < wNib;++i)
	{
		dwRegister <<= 4;
		dwRegister += pReg[wNib-i-1];
	}
	return dwRegister;
}

//
// convert DWORD to nibble register
//
static VOID DWORDToReg(BYTE *pReg, WORD wNib, DWORD dwReg)
{
	int i;

	for (i = 0;i < wNib;++i)
	{
		pReg[i] = (BYTE) (dwReg & 0xF);
		dwReg >>= 4;						// next nibble
	}
	return;
}


//################
//#
//#    Public internal functions
//#
//################

//
// handle upper 32 bit of cpu cycle counter
//
VOID UpdateDbgCycleCounter(VOID)
{
	return;									// not necessary here, cycle counter has 64 bit in DLL version
}

//
// check for code breakpoints
//
BOOL CheckBreakpoint(DWORD dwAddr, DWORD dwRange, UINT nType)
{
	INT  i;
	BOOL bBreak = FALSE;					// don't break
	DWORD dwRomAddr = -1;					// no valid ROM address

	// stack changed notify function defined
	if (nType == BP_EXEC && pEmuStackNotify != NULL)
	{
		// hardware stack changed
		if (dwRefRstkp != -1 && dwRefRstkp != Chipset.rstkp)
			bBreak = pEmuStackNotify();		// inform debugger

		dwRefRstkp = Chipset.rstkp;			// save current stack level
	}

	// absolute ROM adress breakpoints
	if (nType == BP_EXEC)					// only on code breakpoints
	{
		LPBYTE I = FASTPTR(dwAddr);			// get opcode stream

		// adress in ROM area
		if (I >= pbyRom && I < pbyRom + dwRomSize)
			dwRomAddr = I - pbyRom;			// linear ROM address
	}

	for (i = 0; i < wBreakpointCount; ++i)	// scan all breakpoints
	{
		// check for absolute ROM adress breakpoint
		if (   sBreakpoint[i].dwAddr >= dwRomAddr && sBreakpoint[i].dwAddr < dwRomAddr + dwRange
			&& (sBreakpoint[i].nType & BP_ROM) != 0)
			return TRUE;

		// check address range and type
		if (   sBreakpoint[i].dwAddr >= dwAddr && sBreakpoint[i].dwAddr < dwAddr + dwRange
			&& (sBreakpoint[i].nType & nType) != 0)
			return TRUE;
	}
	return bBreak;
}

//
// notify debugger that emulation stopped
//
VOID NotifyDebugger(INT nType)				// update registers
{
	nDbgState = DBG_STEPINTO;				// state "step into"
	dwDbgStopPC = -1;						// disable "cursor stop address"

	_ASSERT(pEmuDbgNotify);					// notify function defined from caller
	pEmuDbgNotify(nType);					// notify caller
	bDbgNotify = TRUE;						// emulation stopped
	return;
}

//
// disable debugger
//
VOID DisableDebugger(VOID)
{
	nDbgState = DBG_OFF;					// disable debugger
	bInterrupt = TRUE;						// exit opcode loop
	SetEvent(hEventDebug);
	return;
}


//################
//#
//#    Dummy functions for linking
//#
//################

//
// entry from message loop
//
LRESULT OnToolDebug(VOID)
{
	return 0;
}

//
// load breakpoint list
//
VOID LoadBreakpointList(HANDLE hFile)
{
	return;
	UNREFERENCED_PARAMETER(hFile);
}

//
// save breakpoint list
//
VOID SaveBreakpointList(HANDLE hFile)
{
	return;
	UNREFERENCED_PARAMETER(hFile);
}

//
// create a copy of the breakpoint list
//
VOID CreateBackupBreakpointList(VOID)
{
	return;
}

//
// restore the breakpoint list from the copy
//
VOID RestoreBackupBreakpointList(VOID)
{
	return;
}


//################
//#
//#    Public external functions
//#
//################

/****************************************************************************
* EmuInitLastInstr
*****************************************************************************
*
* @func   init a circular buffer area for saving the last instruction
*         addresses
*
* @xref   none
*
* @rdesc  BOOL: FALSE = OK, TRUE = Error
*
****************************************************************************/

DECLSPEC BOOL CALLBACK EmuInitLastInstr(
	WORD wNoInstr,							// @parm number of saved instructions,
											//       0 = frees the memory buffer
	DWORD *pdwArray)						// @parm pointer to linear array
{
	if (pdwInstrArray)						// circular buffer defined
	{
		EnterCriticalSection(&csDbgLock);
		{
			free(pdwInstrArray);			// free memory
			pdwInstrArray = NULL;
		}
		LeaveCriticalSection(&csDbgLock);
	}

	if (wNoInstr)							// new size
	{
		pdwLinArray = pdwArray;				// save address of linear array
		wInstrSize = wNoInstr + 1;			// size of circular buffer
		wInstrWp = wInstrRp = 0;			// init write/read pointer

		pdwInstrArray = malloc(wInstrSize*sizeof(*pdwInstrArray));
		if (pdwInstrArray == NULL)			// allocation error
			return TRUE;
	}
	return FALSE;
}

/****************************************************************************
* EmuGetLastInstr
*****************************************************************************
*
* @func   return number of valid entries in the last instruction array,
*         each entry contents a PC address, array[0] contents the oldest,
*         array[*pwNoEntries-1] the last PC address
*
* @xref   none
*
* @rdesc  BOOL: FALSE = OK, TRUE = Error
*
****************************************************************************/

DECLSPEC BOOL CALLBACK EmuGetLastInstr(
	WORD *pwNoEntries)						// @parm return number of valid entries in array
{
	WORD i;

	if (pdwInstrArray == NULL) return TRUE;	// circular buffer not defined

	// copy data to linear buffer
	*pwNoEntries = 0;
	for (i = wInstrRp; i != wInstrWp; i = (i + 1) % wInstrSize)
		pdwLinArray[(*pwNoEntries)++] = pdwInstrArray[i];

	return FALSE;
}

/****************************************************************************
* EmuRun
*****************************************************************************
*
* @func   run emulation
*
* @xref   none
*
* @rdesc  BOOL: FALSE = OK
*               TRUE  = Error, Emu48 is running
*
****************************************************************************/

DECLSPEC BOOL CALLBACK EmuRun(VOID)
{
	BOOL bErr = (nDbgState == DBG_RUN);
	if (nDbgState != DBG_RUN)				// emulation stopped
	{
		bDbgNotify = FALSE;					// reset debugger notify flag
		nDbgState = DBG_RUN;				// state "run"
		SetEvent(hEventDebug);				// run emulation
	}
	return bErr;
}

/****************************************************************************
* EmuRunPC
*****************************************************************************
*
* @func   run emulation until stop address
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuRunPC(
	DWORD dwAddressPC)						// @parm breakpoint address
{
	if (nDbgState != DBG_RUN)				// emulation stopped
	{
		dwDbgStopPC = dwAddressPC;			// stop address
		EmuRun();							// run emulation
	}
	return;
}

/****************************************************************************
* EmuStep
*****************************************************************************
*
* @func   execute one ASM instruction and return to caller
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuStep(VOID)
{
	if (nDbgState != DBG_RUN)				// emulation stopped
	{
		bDbgNotify = FALSE;					// reset debugger notify flag
		nDbgState = DBG_STEPINTO;			// state "step into"
		SetEvent(hEventDebug);				// run emulation
	}
	return;
}

/****************************************************************************
* EmuStepOver
*****************************************************************************
*
* @func   execute one ASM instruction but skip GOSUB, GOSUBL, GOSBVL
*         subroutines
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuStepOver(VOID)
{
	if (nDbgState != DBG_RUN)				// emulation stopped
	{
		LPBYTE I;

		bDbgNotify = FALSE;					// reset debugger notify flag

		I = FASTPTR(Chipset.pc);

		dwDbgRstkp = Chipset.rstkp;			// save stack level

		nDbgState = DBG_STEPINTO;			// state "step into"
		if (I[0] == 0x7)					// GOSUB  7aaa
		{
			nDbgState = DBG_STEPOVER;		// state "step over"
		}
		if (I[0] == 0x8)					// GOSUBL or GOSBVL
		{
			if (I[1] == 0xE)				// GOSUBL 8Eaaaa
			{
				nDbgState = DBG_STEPOVER;	// state "step over"
			}
			if (I[1] == 0xF)				// GOSBVL 8Eaaaaa
			{
				nDbgState = DBG_STEPOVER;	// state "step over"
			}
		}
		SetEvent(hEventDebug);				// run emulation
	}
	return;
}

/****************************************************************************
* EmuStepOut
*****************************************************************************
*
* @func   run emulation until a RTI, RTN, RTNC, RTNCC, RTNNC, RTNSC, RTNSXN,
*         RTNYES instruction is found above the current stack level
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuStepOut(VOID)
{
	if (nDbgState != DBG_RUN)				// emulation stopped
	{
		bDbgNotify = FALSE;					// reset debugger notify flag
		dwDbgRstkp = (Chipset.rstkp-1)&7;	// save stack data
		dwDbgRstk  = Chipset.rstk[dwDbgRstkp];
		nDbgState = DBG_STEPOUT;			// state "step out"
		SetEvent(hEventDebug);				// run emulation
	}
	return;
}

/****************************************************************************
* EmuStop
*****************************************************************************
*
* @func   break emulation
*
* @xref   none
*
* @rdesc  BOOL: FALSE = OK
*               TRUE  = Error, no debug notify handler
*
****************************************************************************/

DECLSPEC BOOL CALLBACK EmuStop(VOID)
{
	if (pEmuDbgNotify && nDbgState != DBG_STEPINTO) // emulation running
	{
		bDbgNotify = FALSE;					// reset debugger notify flag
		nDbgState = DBG_STEPINTO;			// state "step into"
		if (Chipset.Shutdn)					// cpu thread stopped
			SetEvent(hEventShutdn);			// goto debug session
		while (!bDbgNotify) Sleep(0);		// wait until emulation stopped
	}
	return nDbgState != DBG_STEPINTO;
}

/****************************************************************************
* EmuCallBackDebugNotify
*****************************************************************************
*
* @func   init CallBack handler to notify caller on debugger breakpoint
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuCallBackDebugNotify(
    VOID (CALLBACK *EmuDbgNotify)(INT nBreaktype)) // @parm CallBack function notify Debug breakpoint
{
	if(EmuDbgNotify != NULL)				// debugger enabled
	{
		dwDbgStopPC = -1;					// no stop address for goto cursor
		dwDbgRplPC = -1;					// no stop address for RPL breakpoint
		pEmuDbgNotify = EmuDbgNotify;		// set new handler
		nDbgState = DBG_RUN;				// enable debugger
	}
	else
	{
		nDbgState = DBG_OFF;				// disable debugger
		pEmuDbgNotify = EmuDbgNotify;		// remove handler
		bInterrupt = TRUE;					// exit opcode loop
		SetEvent(hEventDebug);
	}
	return;
}

/****************************************************************************
* EmuCallBackStackNotify
*****************************************************************************
*
* @func   init CallBack handler to notify caller on hardware stack change;
*         if the CallBack function return TRUE, emulation stops behind the
*         opcode changed hardware stack content, otherwise, if the CallBack
*         function return FALSE, no breakpoint is set
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuCallBackStackNotify(
	BOOL (CALLBACK *EmuStackNotify)(VOID))	// @parm CallBack function notify stack changed
{
	dwRefRstkp = -1;						// reference stack pointer not initialized
	pEmuStackNotify = EmuStackNotify;		// set new handler
	return;
}

/****************************************************************************
* EmuGetRegister
*****************************************************************************
*
* @func   read a 32 bit register
*
* @xref   none
*
* @rdesc  DWORD: 32 bit value of register
*
****************************************************************************/

DECLSPEC DWORD CALLBACK EmuGetRegister(
	UINT uRegister)							// @parm index of register
{
	DWORD dwResult;

	switch(uRegister)
	{
	case EMU_REGISTER_PC:
		dwResult = Chipset.pc;
		break;
	case EMU_REGISTER_D0:
		dwResult = Chipset.d0;
		break;
	case EMU_REGISTER_D1:
		dwResult = Chipset.d1;
		break;
	case EMU_REGISTER_DUMMY:
	case EMU_REGISTER_R5L:
	case EMU_REGISTER_R5H:
	case EMU_REGISTER_R6L:
	case EMU_REGISTER_R6H:
	case EMU_REGISTER_R7L:
	case EMU_REGISTER_R7H:
		dwResult = 0;						// dummy return
		break;
	case EMU_REGISTER_AL:
		dwResult = RegToDWORD(&Chipset.A[0],8);
		break;
	case EMU_REGISTER_AH:
		dwResult = RegToDWORD(&Chipset.A[8],8);
		break;
	case EMU_REGISTER_BL:
		dwResult = RegToDWORD(&Chipset.B[0],8);
		break;
	case EMU_REGISTER_BH:
		dwResult = RegToDWORD(&Chipset.B[8],8);
		break;
	case EMU_REGISTER_CL:
		dwResult = RegToDWORD(&Chipset.C[0],8);
		break;
	case EMU_REGISTER_CH:
		dwResult = RegToDWORD(&Chipset.C[8],8);
		break;
	case EMU_REGISTER_DL:
		dwResult = RegToDWORD(&Chipset.D[0],8);
		break;
	case EMU_REGISTER_DH:
		dwResult = RegToDWORD(&Chipset.D[8],8);
		break;
	case EMU_REGISTER_R0L:
		dwResult = RegToDWORD(&Chipset.R0[0],8);
		break;
	case EMU_REGISTER_R0H:
		dwResult = RegToDWORD(&Chipset.R0[8],8);
		break;
	case EMU_REGISTER_R1L:
		dwResult = RegToDWORD(&Chipset.R1[0],8);
		break;
	case EMU_REGISTER_R1H:
		dwResult = RegToDWORD(&Chipset.R1[8],8);
		break;
	case EMU_REGISTER_R2L:
		dwResult = RegToDWORD(&Chipset.R2[0],8);
		break;
	case EMU_REGISTER_R2H:
		dwResult = RegToDWORD(&Chipset.R2[8],8);
		break;
	case EMU_REGISTER_R3L:
		dwResult = RegToDWORD(&Chipset.R3[0],8);
		break;
	case EMU_REGISTER_R3H:
		dwResult = RegToDWORD(&Chipset.R3[8],8);
		break;
	case EMU_REGISTER_R4L:
		dwResult = RegToDWORD(&Chipset.R4[0],8);
		break;
	case EMU_REGISTER_R4H:
		dwResult = RegToDWORD(&Chipset.R4[8],8);
		break;
	case EMU_REGISTER_FLAGS:
	   /**
		*  "FLAGS" register format :
		*
		*  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		*  |               ST              |S|x|x|x|K|I|C|M|  HST  |   P   |
		*  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		*  M : Mode (0:Hex, 1:Dec)
		*  C : Carry
		*  I : Interrupt pending
		*  K : KDN Interrupts Enabled
		*  S : Shutdn Flag (read only)
		*  x : reserved
		*/
		dwResult = RegToDWORD(Chipset.ST,4);
		dwResult <<= 1;
		dwResult |= Chipset.Shutdn ? 1 : 0;
		dwResult <<= (3+1);
		dwResult |= Chipset.intk ? 1 : 0;
		dwResult <<= 1;
		dwResult |= Chipset.inte ? 1 : 0;
		dwResult <<= 1;
		dwResult |= Chipset.carry ? 1 : 0;
		dwResult <<= 1;
		dwResult |= Chipset.mode_dec ? 1 : 0;
		dwResult <<= 4;
		dwResult |= Chipset.HST;
		dwResult <<= 4;
		dwResult |= Chipset.P;
		break;
	case EMU_REGISTER_OUT:
		dwResult = Chipset.out;
		break;
	case EMU_REGISTER_IN:
		dwResult = Chipset.in;
		break;
	case EMU_REGISTER_VIEW1:
		dwResult = ((Chipset.Bank_FF >> 1) & 0x3F) >> 4;
		break;
	case EMU_REGISTER_VIEW2:
		dwResult = ((Chipset.Bank_FF >> 1) & 0x3F) & 0xF;
		break;
	case EMU_REGISTER_RSTKP:
		dwResult = Chipset.rstkp;
		break;
	case EMU_REGISTER_RSTK0:
		dwResult = Chipset.rstk[0];
		break;
	case EMU_REGISTER_RSTK1:
		dwResult = Chipset.rstk[1];
		break;
	case EMU_REGISTER_RSTK2:
		dwResult = Chipset.rstk[2];
		break;
	case EMU_REGISTER_RSTK3:
		dwResult = Chipset.rstk[3];
		break;
	case EMU_REGISTER_RSTK4:
		dwResult = Chipset.rstk[4];
		break;
	case EMU_REGISTER_RSTK5:
		dwResult = Chipset.rstk[5];
		break;
	case EMU_REGISTER_RSTK6:
		dwResult = Chipset.rstk[6];
		break;
	case EMU_REGISTER_RSTK7:
		dwResult = Chipset.rstk[7];
		break;
	case EMU_REGISTER_CLKL:
		dwResult = (DWORD) (Chipset.cycles & 0xFFFFFFFF);
		break;
	case EMU_REGISTER_CLKH:
		dwResult = (DWORD) (Chipset.cycles >> 32);
		break;
	case EMU_REGISTER_CRC:
		dwResult = Chipset.crc;
		break;
	default:
		dwResult = 0;						// default return
		_ASSERT(FALSE);						// illegal entry
	}
	return dwResult;
}

/****************************************************************************
* EmuSetRegister
*****************************************************************************
*
* @func   write a 32 bit register
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuSetRegister(
	UINT uRegister,							// @parm index of register
	DWORD dwValue)							// @parm new 32 bit value
{
	switch(uRegister)
	{
	case EMU_REGISTER_PC:
		Chipset.pc = dwValue;
		break;
	case EMU_REGISTER_D0:
		Chipset.d0 = dwValue;
		break;
	case EMU_REGISTER_D1:
		Chipset.d1 = dwValue;
		break;
	case EMU_REGISTER_DUMMY:
	case EMU_REGISTER_R5L:
	case EMU_REGISTER_R5H:
	case EMU_REGISTER_R6L:
	case EMU_REGISTER_R6H:
	case EMU_REGISTER_R7L:
	case EMU_REGISTER_R7H:
		break;								// dummy return
	case EMU_REGISTER_AL:
		DWORDToReg(&Chipset.A[0],8,dwValue);
		break;
	case EMU_REGISTER_AH:
		DWORDToReg(&Chipset.A[8],8,dwValue);
		break;
	case EMU_REGISTER_BL:
		DWORDToReg(&Chipset.B[0],8,dwValue);
		break;
	case EMU_REGISTER_BH:
		DWORDToReg(&Chipset.B[8],8,dwValue);
		break;
	case EMU_REGISTER_CL:
		DWORDToReg(&Chipset.C[0],8,dwValue);
		break;
	case EMU_REGISTER_CH:
		DWORDToReg(&Chipset.C[8],8,dwValue);
		break;
	case EMU_REGISTER_DL:
		DWORDToReg(&Chipset.D[0],8,dwValue);
		break;
	case EMU_REGISTER_DH:
		DWORDToReg(&Chipset.D[8],8,dwValue);
		break;
	case EMU_REGISTER_R0L:
		DWORDToReg(&Chipset.R0[0],8,dwValue);
		break;
	case EMU_REGISTER_R0H:
		DWORDToReg(&Chipset.R0[8],8,dwValue);
		break;
	case EMU_REGISTER_R1L:
		DWORDToReg(&Chipset.R1[0],8,dwValue);
		break;
	case EMU_REGISTER_R1H:
		DWORDToReg(&Chipset.R1[8],8,dwValue);
		break;
	case EMU_REGISTER_R2L:
		DWORDToReg(&Chipset.R2[0],8,dwValue);
		break;
	case EMU_REGISTER_R2H:
		DWORDToReg(&Chipset.R2[8],8,dwValue);
		break;
	case EMU_REGISTER_R3L:
		DWORDToReg(&Chipset.R3[0],8,dwValue);
		break;
	case EMU_REGISTER_R3H:
		DWORDToReg(&Chipset.R3[8],8,dwValue);
		break;
	case EMU_REGISTER_R4L:
		DWORDToReg(&Chipset.R4[0],8,dwValue);
		break;
	case EMU_REGISTER_R4H:
		DWORDToReg(&Chipset.R4[8],8,dwValue);
		break;
	case EMU_REGISTER_FLAGS:
	   /**
		*  "FLAGS" register format :
		*
		*  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		*  |               ST              |S|x|x|x|K|I|C|M|  HST  |   P   |
		*  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
		*  M : Mode (0:Hex, 1:Dec)
		*  C : Carry
		*  I : Interrupt pending
		*  K : KDN Interrupts Enabled
		*  S : Shutdn Flag (read only)
		*  x : reserved
		*/
		Chipset.P = (BYTE) (dwValue & 0xF);
		dwValue >>= 4;
		Chipset.HST = (BYTE) (dwValue & 0xF);
		dwValue >>= 4;
		Chipset.mode_dec = (dwValue & 0x1) ? TRUE : FALSE;
		dwValue >>= 1;
		Chipset.carry = (dwValue & 0x1) ? TRUE : FALSE;
		dwValue >>= 1;
		Chipset.inte = (dwValue & 0x1) ? TRUE : FALSE;
		dwValue >>= 1;
		Chipset.intk = (dwValue & 0x1) ? TRUE : FALSE;
		dwValue >>= (1+4);
		DWORDToReg(Chipset.ST,sizeof(Chipset.ST),dwValue);
		PCHANGED;
		break;
	case EMU_REGISTER_OUT:
		Chipset.out = (WORD) (dwValue & 0xFFFF);
		break;
	case EMU_REGISTER_IN:
		Chipset.in = (WORD) (dwValue & 0xFFFF);
		break;
	case EMU_REGISTER_VIEW1:
		Chipset.Bank_FF &= 0x1F;
		Chipset.Bank_FF |= (dwValue & 0x03) << (4+1);
		RomSwitch(Chipset.Bank_FF);
		break;
	case EMU_REGISTER_VIEW2:
		Chipset.Bank_FF &= 0x61;
		Chipset.Bank_FF |= (dwValue & 0x0F) << 1;
		RomSwitch(Chipset.Bank_FF);
		break;
	case EMU_REGISTER_RSTKP:
		Chipset.rstkp = dwValue;
		break;
	case EMU_REGISTER_RSTK0:
		Chipset.rstk[0] = dwValue;
		break;
	case EMU_REGISTER_RSTK1:
		Chipset.rstk[1] = dwValue;
		break;
	case EMU_REGISTER_RSTK2:
		Chipset.rstk[2] = dwValue;
		break;
	case EMU_REGISTER_RSTK3:
		Chipset.rstk[3] = dwValue;
		break;
	case EMU_REGISTER_RSTK4:
		Chipset.rstk[4] = dwValue;
		break;
	case EMU_REGISTER_RSTK5:
		Chipset.rstk[5] = dwValue;
		break;
	case EMU_REGISTER_RSTK6:
		Chipset.rstk[6] = dwValue;
		break;
	case EMU_REGISTER_RSTK7:
		Chipset.rstk[7] = dwValue;
		break;
	case EMU_REGISTER_CLKL:
	case EMU_REGISTER_CLKH:
		break;								// not allowed to change
	case EMU_REGISTER_CRC:
		Chipset.crc = (WORD) (dwValue & 0xFFFF);
		break;
	default:
		_ASSERT(FALSE);						// illegal entry
	}
	return;
}

/****************************************************************************
* EmuGetMem
*****************************************************************************
*
* @func   read one nibble from the specified mapped address
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuGetMem(
	DWORD dwMapAddr,						// @parm mapped address of Saturn CPU
	BYTE  *pbyValue)						// @parm readed nibble
{
	Npeek(pbyValue,dwMapAddr,1);
	return;
}

/****************************************************************************
* EmuSetMem
*****************************************************************************
*
* @func   write one nibble to the specified mapped address
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuSetMem(
	DWORD dwMapAddr,						// @parm mapped address of Saturn CPU
	BYTE  byValue)							// @parm nibble to write
{
	Nwrite(&byValue,dwMapAddr,1);
	return;
}

/****************************************************************************
* EmuGetRom
*****************************************************************************
*
* @func   return size and base address of mapped ROM
*
* @xref   none
*
* @rdesc  LPBYTE: base address of ROM (pointer to original data)
*
****************************************************************************/

DECLSPEC LPBYTE CALLBACK EmuGetRom(
	DWORD *pdwRomSize)						// @parm return size of ROM in nibbles
{
	*pdwRomSize = dwRomSize;
	return pbyRom;
}

/****************************************************************************
* EmuSetBreakpoint
*****************************************************************************
*
* @func   set ASM code/data breakpoint
*
* @xref   none
*
* @rdesc  BOOL: TRUE  = Error, Breakpoint table full
*               FALSE = OK, Breakpoint set
*
****************************************************************************/

DECLSPEC BOOL CALLBACK EmuSetBreakpoint(
	DWORD dwAddress,						// @parm breakpoint address to set
	UINT  nBreakpointType)					// @parm breakpoint type to set
{
	INT   i;

	_ASSERT(   nBreakpointType == BP_EXEC	// illegal breakpoint type
			|| nBreakpointType == BP_READ
			|| nBreakpointType == BP_WRITE
			|| nBreakpointType == BP_RPL
			|| nBreakpointType == BP_ACCESS
			|| nBreakpointType == BP_ROM);

	for (i = 0; i < wBreakpointCount; ++i)	// search for breakpoint
	{
		// breakpoint already set
		if (   sBreakpoint[i].dwAddr == dwAddress
			&& sBreakpoint[i].nType  == nBreakpointType)
			return FALSE;
	}

	if (wBreakpointCount >= MAXBREAKPOINTS)	// breakpoint buffer full
		return TRUE;

	sBreakpoint[wBreakpointCount].dwAddr = dwAddress;
	sBreakpoint[wBreakpointCount].nType  = nBreakpointType;
	++wBreakpointCount;
	return FALSE;
}

/****************************************************************************
* EmuClearBreakpoint
*****************************************************************************
*
* @func   clear ASM code/data breakpoint
*
* @xref   none
*
* @rdesc  BOOL: TRUE  = Error, Breakpoint not found
*               FALSE = OK, Breakpoint cleared
*
****************************************************************************/

DECLSPEC BOOL CALLBACK EmuClearBreakpoint(
	DWORD dwAddress,						// @parm breakpoint address to clear
	UINT  nBreakpointType)					// @parm breakpoint type to clear
{
	INT   i;

	_ASSERT(   nBreakpointType == BP_EXEC	// illegal breakpoint type
			|| nBreakpointType == BP_READ
			|| nBreakpointType == BP_WRITE
			|| nBreakpointType == BP_RPL
			|| nBreakpointType == BP_ACCESS
			|| nBreakpointType == BP_ROM);

	for (i = 0; i < wBreakpointCount; ++i)	// search for breakpoint
	{
		// breakpoint found
		if (   sBreakpoint[i].dwAddr == dwAddress
			&& sBreakpoint[i].nType  == nBreakpointType)
		{
			// move rest to top
			for (++i; i < wBreakpointCount; ++i)
				sBreakpoint[i-1] = sBreakpoint[i];

			--wBreakpointCount;
			return FALSE;					// breakpoint found
		}
	}
	return TRUE;							// breakpoint not found
}

/****************************************************************************
* EmuClearAllBreakpoints
*****************************************************************************
*
* @func   clear all breakpoints
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuClearAllBreakpoints(VOID)
{
	wBreakpointCount = 0;
	return;
}

/****************************************************************************
* EmuEnableNop3Breakpoint
*****************************************************************************
*
* @func   enable/disable NOP3 breakpoint
*         stop emulation at Opcode 420 for GOC + (next line)
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuEnableNop3Breakpoint(
	BOOL bEnable)							// @parm stop on NOP3 opcode
{
	bDbgNOP3 = bEnable;						// change stop on NOP3 breakpoint flag
	return;
}

/****************************************************************************
* EmuEnableDocodeBreakpoint
*****************************************************************************
*
* @func   enable/disable DOCODE breakpoint
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuEnableDoCodeBreakpoint(
	BOOL bEnable)							// @parm stop on DOCODE entry
{
	bDbgCode = bEnable;						// change stop on DOCODE breakpoint flag
	return;
}

/****************************************************************************
* EmuEnableRplBreakpoint
*****************************************************************************
*
* @func   enable/disable RPL breakpoint
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuEnableRplBreakpoint(
	BOOL bEnable)							// @parm stop on RPL exit
{
	bDbgRPL = bEnable;						// change stop on RPL exit flag
	return;
}

/****************************************************************************
* EmuEnableSkipInterruptCode
*****************************************************************************
*
* @func   enable/disable skip single step execution inside the interrupt
*         handler, this option has no effect on code and data breakpoints
*
* @xref   none
*
* @rdesc  VOID
*
****************************************************************************/

DECLSPEC VOID CALLBACK EmuEnableSkipInterruptCode(
	BOOL bEnable)							// @parm TRUE = skip code instructions
											//              inside interrupt service routine
{
	bDbgSkipInt = bEnable;					// change skip interrupt code flag
	return;
}
