/*
 * FB Alpha Neo Geo module
 *
 * The video frequencies of MVS hardware are:
 * hsync: 15625    Hz
 * vsync:   ~59.18 Hz (264 scanlines make up a single frame)
 * The image occupies 224 scanlines, spanning scanlines 24 to 248.
 *
 * The memory card interface occupies 128KB of memory. There are 2 kinds of
 * memory cards that can be used -- 16bit or 8bit. For games, a maximum of 16kB
 * data is stored on either, but 16bit cards can be used to transfer bookkeeping
 * information. For 8bit cards, only odd bytes are used to address memory and
 * the BIOS tests the size of the inserted card by writing to the first byte of
 * each 2KB block. The interface used is JEIDA 3.0.
 *
 * bit 6 of memory location 0x300081 = 0						 -> 1/2 slot MVS
 *									 = 1 + bit 5 of 0x320001 = 0 -> 4 	slot MVS
 *									 						 = 1 -> 6   slot MVS
 *
 * On MVS hardware with multiple slots, address 0x380021 is used to select the
 * active slot.
 *
 * The watchdog timer will reset the system after ~0.13 seconds
 *
 *     On an MV-1F system, the following code was used to test:
 *         000100  203C 0001 4F51             MOVE.L   #0x14F51,D0
 *         000106  13C0 0030 0001             MOVE.B   D0,0x300001
 *         00010C  5380                       SUBQ.L   #1,D0
 *         00010E  64FC                       BCC.S    *-0x2 [0x10C]
 *         000110  13C0 0030 0001             MOVE.B   D0,0x300001
 *         000116  60F8                       BRA.S    *-0x6 [0x110]
 *     This code loops long enough to sometimes cause a reset, sometimes not.
 *     The move takes 16 cycles, subq 8, bcc 10 if taken and 8 if not taken, so:
 *     (0x14F51 * 18 + 14) cycles / 12000000 cycles per second = 0.12876 seconds
 *
 *     Newer games force a reset using the following code (this from kof99):
 *         009CDA  203C 0003 0D40             MOVE.L   #0x30D40,D0
 *         009CE0  5380                       SUBQ.L   #1,D0
 *         009CE2  64FC                       BCC.S    *-0x2 [0x9CE0]
 *     Note however that there is a valid code path after this loop.
 *
 * On AES hardware, reading unmapped memory returns the last value that was on
 * the data-bus.
 *
 * Raster interrupts can be generated by specifying an offset, in pixels.
 * This offset can be added to the following (depending on a control register):
 *  - the current position of the electron beam
 *  - the position of the electron beam whenever interrupt 2 is triggered
 *  - the position of the electron beam when interrupt 4 (vblank) is triggered
 *
 * Note that the offset is *always* relative; when setting the offset from the
 * vblank (scanline 248), to trigger an interrupt at scanline 24, you would
 * point the offset to 24 scanlines past the screen end at line 264, and set it
 * to 15360 (40 scanlines * 384 pixels per scanline).
 *
 */

#include "neogeo.h"
#include "burn_ym2610.h"
#include "bitswap.h"

// #define LOG_IRQ2
// #define LOG_DRAW

#define NEO_HREFRESH (15625.0)
#define NEO_VREFRESH (NEO_HREFRESH / 264.0)

// If defined, reset the Z80 when switching between the Z80 BIOS/cartridge ROM
// #define Z80_RESET_ON_BANKSWITCH

// If defined, adjust the Z80 speed along with the 68000 when overclocking
#define Z80_SPEED_ADJUST

// If defined, use kludges to better align raster effects in some games (e.g. mosyougi)
#define RASTER_KLUDGE

// If defined, use the bAllowRasters variable to enable/disable raster effects
// #define RASTERS_OPTIONAL

#ifdef Z80_SPEED_ADJUST
 static int nZ80Clockspeed;
#else
 static const int nZ80Clockspeed = 4000000;
#endif

#ifdef RASTER_KLUDGE
 static unsigned short nScanlineOffset;
#else
 // 0xF8 is correct as verified on MVS hardware
 static const unsigned short nScanlineOffset = 0xF8;
#endif

#ifdef RASTERS_OPTIONAL
 static bool bAllowRasters = false;
#endif

// The number of carteidge slots on the emulated MVS hardware (can be 1, 2, 4, or 6)
unsigned char nNeoNumSlots = 2;

unsigned char NeoButton1[32] = { 0, };
unsigned char NeoButton2[8]  = { 0, };
unsigned char NeoButton3[8]  = { 0, };
unsigned char NeoButton4[8]  = { 0, };
unsigned char NeoJoy1[8]	 = { 0, };
unsigned char NeoJoy2[8]     = { 0, };
unsigned char NeoJoy3[8]     = { 0, };
unsigned char NeoJoy4[8]     = { 0, };
unsigned short NeoAxis[2]	 = { 0, };
unsigned char NeoInput[32]   = { 0, };
unsigned char NeoDiag[2]	 = { 0, };
unsigned char NeoDebugDip[2] = { 0, };
unsigned char NeoReset = 0, NeoSystem = 0;

static unsigned char OldDebugDip[2];

// Which 68K BIOS to use
static int nBIOS;

// Joyports are multiplexed
static int nJoyport0[8] = { 0, };
static int nJoyport1[8] = { 0, };

// Ports always mirror the joystick, except when other controllers are hooked up:
//
// Joyport0: 0x00		 : trackball X in irrmaze / p1 paddle in popbounc
//			 0x01		 : trackball Y in irrmaze
//			 0x09		 : mahjong controller
//			 0x12		 : mahjong controller (BIOS controls)
//			 0x1B		 : Always has p1 joystick & buttons
//			 0x24		 : mahjong controller
//			 0x20 & 0x21 : read by irrmaze instead of 0x00 & 0x01 when you lose a life
//						   (activates air-jets)
// Joyport1: 0x00		 : p2 paddle in popbounc
//			 0x1B		 : Always has p2 joystick & buttons

// ----------------------------------------------------------------------------
// Variables that need to be included in savestates

static int nCyclesExtra[2];
static int nPrevBurnCPUSpeedAdjust;

bool bNeoEnableGraphics;

unsigned int nNeo68KROMBank;

static int nIRQAcknowledge;

static int nIRQ2Control;
static bool bSRAMWritable;

static bool b68KBoardROMBankedIn;
static bool bZ80BoardROMBankedIn;
static int nZ80Bank0, nZ80Bank1, nZ80Bank2, nZ80Bank3;

static unsigned char* NeoGraphicsRAMBank;
static unsigned short NeoGraphicsRAMPointer;
static int nNeoGraphicsModulo;

int nNeoSpriteFrame;

static int nSpriteFrameSpeed;
static int nSpriteFrameTimer;

static unsigned char nSoundLatch;
static unsigned char nSoundReply;
static unsigned int nSoundStatus;

#if 1 && defined USE_SPEEDHACKS
static int nSoundPrevReply;
#endif

static int nInputSelect;
static unsigned char* NeoInputBank;
static unsigned int nAnalogAxis[2];

static unsigned int nuPD4990ATicks;

static unsigned int nIRQ2Offset;

#define NO_IRQ_PENDING (0x7FFFFFFF)
static int nIRQCycles;

static int nNeoWatchdog;

// ----------------------------------------------------------------------------

static bool bMemoryCardInserted, bMemoryCardWritable;

void (*pNeoInitCallback)() = NULL;
void (*pNeoBankswitchCallback)() = NULL;
int (*pNeoScanCallback)(int, int*) = NULL;

static int nCyclesTotal[2];
static int nCyclesSegment;
static int nCyclesVBlank;
static int nCycles68KSync;

unsigned int nNeoSlotDrvNum[MAX_NEO_SLOTS];

static unsigned char *AllRAM = NULL, *RAMEnd = NULL, *AllROM = NULL, *ROMEnd = NULL;

unsigned char *Neo68KROM, *NeoZ80ROM,  *Neo68KBIOS, *Neo68KRAM;
static unsigned char *NeoZ80RAM, *NeoNVRAM, *NeoNVRAM2, *NeoMemoryCard, *Neo68KVectors, *NeoZ80BIOS;

unsigned int nSpriteSize;
static unsigned int nCodeSize;

unsigned char* NeoGraphicsRAM;

unsigned char* YM2610ADPCMAROM = NULL;
unsigned char* YM2610ADPCMBROM = NULL;

static int nYM2610ADPCMASize = 0;
static int nYM2610ADPCMBSize = 0;

static bool bIRQ2Enabled;

static bool bRenderImage;

static bool bRenderLineByLine;

static bool bForcePartialRender;
static bool bForceUpdateOnStatusRead;

static int nNeoControlConfig;

static bool bAESBIOS;
static bool bZ80BIOS;

// This function is called once to determine how much memory is needed (RAMEnd-(unsigned char *)0),
// then a second time after the memory is allocated to set up all the pointers.
static int RAMIndex()
{
	unsigned char* Next; Next = AllRAM;
	NeoPalSrc[0]	= Next; Next += 0x002000;			// Palette RAM Bank 0
	NeoPalSrc[1]	= Next; Next += 0x002000;			// Palette RAM Bank 1

	NeoGraphicsRAM	= Next; Next += 0x020000;			// Graphics controller RAM (2 64KB banks)

	Neo68KRAM		= Next; Next += 0x010000;			// 68K work RAM
	NeoZ80RAM		= Next; Next += 0x000800;			// Z80 RAM

	NeoNVRAM		= Next; Next += 0x010000;			// Battery backed SRAM
	if ((BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK) == HARDWARE_SNK_GAMBLING) {
		NeoNVRAM2	= Next; Next += 0x002000;			// Extra SRAM for vliner/jockeygp
	}
	NeoMemoryCard	= Next; Next += 0x020000;			// Memory card

	RAMEnd			= Next;

	return 0;
}

// This function is called once to determine how much memory is needed (ROMEnd-(unsigned char*)0),
// then a second time after the memory is allocated to set up all the pointers.
static int ROMIndex()
{
	unsigned char* Next; Next = AllROM;
	Neo68KBIOS		= Next; Next += 0x080000;			// 68K boardROM
	Neo68KROM		= Next; Next += nCodeSize;
	Neo68KVectors	= Next; Next += 0x000400;			// Copy of 68K cartridge ROM with boardROM vector table

	NeoZ80BIOS		= Next; Next += 0x020000;			// Z80 boardROM
	NeoZ80ROM		= Next; Next += 0x080000;

	NeoZoomROM		= Next; Next += 0x020000;			// Y Zoom table

	ROMEnd			= Next;

	return 0;
}

// -----------------------------------------------------------------------------
// ROM loading

static inline bool NeoCheckAESBIOS()
{
	if (nBIOS == -1 || nBIOS == 6 || nBIOS == 8 || ((NeoSystem & 0x74) == 0x20)) {
		return true;
	}

	return false;
}

static int NeoLoad68KBIOS(int nNewBIOS)
{
	switch (NeoSystem & 0x30) {
		case 0x00: {
			switch (nNewBIOS) {
				// MVS
				case 0x00:
				case 0x01:
				case 0x02:
				// AES
				case 0x04:
				case 0x06:
					break;
				default:
					nNewBIOS = -1;
			}
			break;
		}
		case 0x10: {
			nNewBIOS = 0x08;
			break;
		}
		case 0x20: {
			nBIOS = -1;			// !!! Remove when patches for unibios are removed
			nNewBIOS = 0x0C;
			break;
		}
		default: {
			nNewBIOS = -1;
		}
	}

	// Create copy of 68K with BIOS vector table
	memcpy(Neo68KVectors + 0x80, Neo68KROM + 0x80, 0x0380);

	if ((BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK) == HARDWARE_SNK_TRACKBALL) {
		nNewBIOS = 9;
	}

	if ((BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK) == HARDWARE_SNK_PCB) {
		nNewBIOS = 10;
	}

	// The most recent MVS models doesn't have a Z80 BIOS
	bZ80BIOS = (nNewBIOS != 2) ? true : false;

	// Check if we need to load a new BIOS
	if (nNewBIOS == nBIOS) {
		return 0;
	}

	nBIOS = nNewBIOS;

	// Load the BIOS ROMs
	if (nBIOS >= 0) {
		BurnLoadRom(Neo68KBIOS, 0x00080 + nBIOS, 1);
	} else {
		BurnLoadRom(Neo68KBIOS, 0x00080 +     6, 1);
	}

	if (!strcmp(BurnDrvGetTextA(DRV_NAME), "kf2k3pcb") || !strcmp(BurnDrvGetTextA(DRV_NAME), "k2k3pcbd")) kf2k3pcb_bios_decode();

	// Patch out checksum test (only needed for the irrmaze BIOS)
	if (nBIOS == 9) {
		*((unsigned short*)(Neo68KBIOS + 0x010D8C)) = 0x4E71;
		*((unsigned short*)(Neo68KBIOS + 0x010D8E)) = 0x4E71;
	}

#if 0
	if (nBIOS == 0x0C && (NeoSystem & 0x04) == 0) {
		// Patch UniBIOS v2.0 for AES mode
		*((unsigned short*)(Neo68KBIOS + 0x0125DE)) = 0x6706;
		*((unsigned short*)(Neo68KBIOS + 0x000080)) = 0xBFBF + 0x0101;

		// Patch out checksum in UniBIOS v2.0
		*((unsigned short*)(Neo68KBIOS + 0x011C62)) = 0x4E71;
		*((unsigned short*)(Neo68KBIOS + 0x011C64)) = 0x4E71;
	}
#endif

	// Create copy of 68K with BIOS vector table
	memcpy(Neo68KVectors + 0x00, Neo68KBIOS,       0x0080);

	return 0;
}

static int FindType(const char* pName)
{
	int i = 0;

	while (pName[i] && pName[i] != '-' && pName[i] != '_') {
		i++;
	}

	return i + 1;
}

static int FindROMs(unsigned int nType, int* pOffset, int* pNum)
{
	int nOffset = -1;
	int nNum = -1;

	struct BurnRomInfo ri;
	ri.nType = 0;
	ri.nLen = 0;

	// Invalidate indices
	if (pOffset) {
		*pOffset = -1;
	}
	if (pNum) {
		*pNum = 0;
	}

	do {
		BurnDrvGetRomInfo(&ri, ++nOffset);
	} while ((ri.nType & 7) != nType && nOffset < 0x80);

	if (nOffset >= 0x7F) {
		return 1;
	}

	do {
		BurnDrvGetRomInfo(&ri, nOffset + ++nNum);
	} while ((ri.nType & 7) == nType && nOffset < 0x80);

	if (pOffset) {
		*pOffset = nOffset;
	}
	if (pNum) {
		*pNum = nNum >= 0 ? nNum : 0;
	}

	return 0;
}

static int LoadRoms(NeoGameInfo* pInfo)
{
//	NeoSpriteROM = (unsigned char*)malloc(nSpriteSize);
	NeoSpriteROM = (unsigned char*)malloc(nSpriteSize < (nNeoTileMask << 7) ? ((nNeoTileMask + 1) << 7) : nSpriteSize);
	if (NeoSpriteROM == NULL) {
		return 1;
	}

	if (BurnDrvGetHardwareCode() & (HARDWARE_SNK_ENCRYPTED_A | HARDWARE_SNK_ENCRYPTED_B)) {
		double fRange = (double)pInfo->nSpriteNum / 4.0;
		if (fRange < 1.5) {
			fRange = 1.5;
		}
		BurnSetProgressRange(1.0 / fRange);
	} else {
		BurnSetProgressRange(1.0 / pInfo->nSpriteNum);
	}

	// Load sprite data
	NeoLoadSprites(pInfo->nSpriteOffset, pInfo->nSpriteNum, NeoSpriteROM, nSpriteSize);

	NeoTextROM = (unsigned char*)malloc(nNeoTextROMSize + 0x020000);
	if (NeoTextROM == NULL) {
		return 1;
	}

	// Load Text layer tiledata
	{
		// Load boardROM data
		BurnLoadRom(NeoTextROM,	0x00080 + 0x14, 1);

		if (pInfo->nTextOffset != -1) {
			// Load S ROM data
			BurnLoadRom(NeoTextROM + 0x020000, pInfo->nTextOffset, 1);
		} else {
			// Extract data from the end of C ROMS
			BurnUpdateProgress(0.0, _T("Generating text layer graphics..."), 0);
			NeoExtractSData(NeoSpriteROM, NeoTextROM + 0x020000, nSpriteSize, nNeoTextROMSize);

			if ((BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK) == HARDWARE_SNK_PCB) {
				for (int i = 0; i < nNeoTextROMSize; i++) {
					NeoTextROM[0x20000 + i] = BITSWAP08(NeoTextROM[0x20000 + i] ^ 0xd2, 4, 0, 7, 2, 5, 1, 6, 3);
				}
			}
		}
	}

	// Allocate all memory is needed for ROM
	{
		int nLen;

		ROMIndex();													// Get amount of memory needed
		nLen = ROMEnd - (unsigned char*)0;
		if ((AllROM = (unsigned char*)malloc(nLen)) == NULL) {		// Allocate memory
			return 1;
		}
		memset(AllROM, 0, nLen);									// Initialise memory
		ROMIndex();													// Index the allocated memory
	}

	// Load the roms into memory
	if (BurnDrvGetHardwareCode() & HARDWARE_SNK_SMA_PROTECTION) {
		BurnLoadRom(Neo68KROM + 0x0C0000, 0, 1);
		NeoLoadCode(pInfo->nCodeOffset + 1, pInfo->nCodeNum - 1, Neo68KROM + 0x100000);
	} else {
		NeoLoadCode(pInfo->nCodeOffset, pInfo->nCodeNum, Neo68KROM);
	}

	BurnLoadRom(NeoZ80ROM, pInfo->nSoundOffset, 1);
	
	if (BurnDrvGetHardwareCode() & HARDWARE_SNK_ENCRYPTED_M1) {
		neogeo_cmc50_m1_decrypt();
	}

	if (NeoLoad68KBIOS((NeoSystem & 0x07) ^ 4)) {
		return 1;
	}

	if (pNeoInitCallback) {
		pNeoInitCallback();
	}

	if (pInfo->nADPCMANum) {
		unsigned char* pADPCMData;

		YM2610ADPCMAROM	= (unsigned char*)malloc(nYM2610ADPCMASize);
		if (YM2610ADPCMAROM == NULL) {
			return 1;
		}

		pADPCMData = YM2610ADPCMAROM;

		if (!strcmp(BurnDrvGetTextA(DRV_NAME), "pbobblna")) {
			YM2610ADPCMAROM = (unsigned char*)realloc(YM2610ADPCMAROM, 0x380000);
			nYM2610ADPCMASize += 0x200000;
			pADPCMData = YM2610ADPCMAROM + 0x200000;
 		}

		NeoLoadADPCM(pInfo->nADPCMOffset, pInfo->nADPCMANum, pADPCMData);
	}

	if (pInfo->nADPCMBNum) {
		YM2610ADPCMBROM	= (unsigned char*)malloc(nYM2610ADPCMBSize);
		if (YM2610ADPCMBROM == NULL) {
			return 1;
		}

		NeoLoadADPCM(pInfo->nADPCMOffset + pInfo->nADPCMANum, pInfo->nADPCMBNum, YM2610ADPCMBROM);
	} else {
		YM2610ADPCMBROM = YM2610ADPCMAROM;
		nYM2610ADPCMBSize = nYM2610ADPCMASize;
	}

	// Decode text data
	BurnUpdateProgress(0.0, _T("Decoding text layer graphics..."), 0);
	NeoDecodeText(NeoTextROM, nNeoTextROMSize);

	// Save decrypted Cs
	if (bSaveCRoms) NeoSaveDecryptedCRoms();

	// Decode sprite data
	NeoDecodeSprites(NeoSpriteROM, nSpriteSize);

	BurnLoadRom(NeoZ80BIOS,	0x00080 + 0x10, 1);
	BurnLoadRom(NeoZoomROM,	0x00080 + 0x18, 1);

	return 0;
}

// ----------------------------------------------------------------------------
// Bankswitch / memory map functions

static void NeoZ80SetBank0(int nBank)
{
	nBank &= 0x0F;
	if (nBank != nZ80Bank0) {
		unsigned char* nStartAddress = NeoZ80ROM + (nBank << 14);
		ZetMapArea(0x8000, 0xBFFF, 0, nStartAddress);
		ZetMapArea(0x8000, 0xBFFF, 2, nStartAddress);

		nZ80Bank0 = nBank;
	}

	return;
}

static void NeoZ80SetBank1(int nBank)
{
	nBank &= 0x1F;
	if (nBank != nZ80Bank1) {
		unsigned char* nStartAddress = NeoZ80ROM + (nBank << 13);
		ZetMapArea(0xC000, 0xDFFF, 0, nStartAddress);
		ZetMapArea(0xC000, 0xDFFF, 2, nStartAddress);

		nZ80Bank1 = nBank;
	}

	return;
}

static void NeoZ80SetBank2(int nBank)
{
	nBank &= 0x3F;
	if (nBank != nZ80Bank2) {
		unsigned char* nStartAddress = NeoZ80ROM + (nBank << 12);
		ZetMapArea(0xE000, 0xEFFF, 0, nStartAddress);
		ZetMapArea(0xE000, 0xEFFF, 2, nStartAddress);

		nZ80Bank2 = nBank;
	}

	return;
}

static void NeoZ80SetBank3(int nBank)
{
	nBank &= 0x7F;
	if (nBank != nZ80Bank3) {
		unsigned char* nStartAddress = NeoZ80ROM + (nBank << 11);
		ZetMapArea(0xF000, 0xF7FF, 0, nStartAddress);
		ZetMapArea(0xF000, 0xF7FF, 2, nStartAddress);

		nZ80Bank3 = nBank;
	}

	return;
}

static void NeoZ80MapROM(bool bMapBoardROM)
{
	if (bMapBoardROM && bZ80BIOS) {
		// Bank in the Z80 boardROM
		ZetMapArea(0x0000, 0x7FFF, 0, NeoZ80BIOS);
		ZetMapArea(0x0000, 0x7FFF, 2, NeoZ80BIOS);
	} else {
		// Bank in the Z80 cartridge ROM
		ZetMapArea(0x0000, 0x7FFF, 0, NeoZ80ROM);
		ZetMapArea(0x0000, 0x7FFF, 2, NeoZ80ROM);
	}
}

static void MapVectorTable(bool bMapBoardROM)
{
	if (bMapBoardROM) {
		SekMapMemory(Neo68KVectors,	0x000000, 0x0003FF, SM_ROM);
	} else {
		SekMapMemory(Neo68KROM,		0x000000, 0x0003FF, SM_ROM);
	}
}

inline static void MapPalette(int nBank)
{
	if (nNeoPaletteBank != nBank) {
		nNeoPaletteBank = nBank;
		SekMapMemory(NeoPalSrc[nBank], 0x400000, 0x401FFF, SM_ROM);

		NeoSetPalette();
	}
}

void NeoMapBank()
{
	SekMapMemory(Neo68KROM + nNeo68KROMBank, 0x200000, 0x2FFFFF, SM_ROM);
}

void Bankswitch(unsigned int nBank)
{
	nBank = 0x100000 + ((nBank & 7) << 20);
	if (nBank >= nCodeSize) {
		nBank = 0x100000;
	}

	if (nBank != nNeo68KROMBank) {
//		bprintf(PRINT_NORMAL, _T("Bankswitched main ROM, new address is 0x%08X.\n"), nBank);
		nNeo68KROMBank = nBank;
		SekMapMemory(Neo68KROM + nNeo68KROMBank, 0x200000, 0x2FFFFF, SM_ROM);
	}
}

// -----------------------------------------------------------------------------
// Savestate support

int NeoScan(int nAction, int* pnMin)
{
	int nOldBIOS = nBIOS;
	struct BurnArea ba;

	if (pnMin) {												// Return minimum compatible version
		*pnMin =  0x029521;
	}

	// Make sure we have the correct value for nBIOS
	if (nAction & ACB_DRIVER_DATA) {
		SCAN_VAR(nBIOS);
	}

	if (nAction & ACB_MEMORY_ROM) {
		ba.Data		= Neo68KBIOS;
		ba.nLen		= 0x00020000;
		ba.nAddress = 0;
		ba.szName	= "68K BIOS";
		BurnAcb(&ba);

		ba.Data		= Neo68KROM;
		ba.nLen		= nCodeSize;
		ba.nAddress = 0;
		ba.szName	= "68K ROM";
		BurnAcb(&ba);

		ba.Data		= NeoZ80BIOS;
		ba.nLen		= 0x00020000;
		ba.nAddress = 0;
		ba.szName	= "Z80 BIOS";
		BurnAcb(&ba);

		ba.Data		= NeoZ80ROM;
		ba.nLen		= 0x00080000;
		ba.nAddress = 0;
		ba.szName	= "Z80 ROM";
		BurnAcb(&ba);
	}

	if (nAction & ACB_MEMCARD) {								// Scan memory card

		if (pnMin && (nAction & ACB_TYPEMASK) == ACB_MEMCARD) {	// Return minimum compatible version
			*pnMin = 0x029523;
		}

		ba.Data		= NeoMemoryCard;
		ba.nLen		= 0x020000;
		ba.nAddress = 0;
		ba.szName	= "Memory card";

		if ((nAction & ACB_TYPEMASK) == ACB_MEMCARD) {
			if (nAction & ACB_WRITE) {
				bMemoryCardInserted = true;
			}
			if (nAction & ACB_READ) {
				bMemoryCardInserted = false;

				// If a card is inserted, determine the size
				if (*((unsigned short*)NeoMemoryCard) != 0x8000) {
					int nSize = (NeoMemoryCard[21] << 8) | NeoMemoryCard[23];
					if (nSize >= 0x1000) {
						ba.nLen = nSize;
					}
				}
			}
		}

		BurnAcb(&ba);
	}
	if (!NeoCheckAESBIOS() && (nAction & ACB_NVRAM)) {			// Scan non-volatile memory

		if (pnMin && (nAction & ACB_TYPEMASK) == ACB_NVRAM) {	// Return minimum compatible version
			*pnMin = 0x029402;
		}

		ba.Data		= NeoNVRAM;
		ba.nLen		= 0x00010000;
		ba.nAddress = 0;
		ba.szName	= "NVRAM";
		BurnAcb(&ba);

		if ((BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK) == HARDWARE_SNK_GAMBLING) {
			ba.Data		= NeoNVRAM2;
			ba.nLen		= 0x00002000;
			ba.nAddress = 0;
			ba.szName	= "Extra NVRAM";
			BurnAcb(&ba);
		}
	}
	if (nAction & ACB_MEMORY_RAM) {								// Scan RAM
		ba.Data		= Neo68KRAM;
		ba.nLen		= 0x00010000;
		ba.nAddress = 0;
		ba.szName	= "68K RAM";
		BurnAcb(&ba);

    	ba.Data		= NeoZ80RAM;
		ba.nLen		= 0x00000800;
		ba.nAddress = 0;
		ba.szName	= "Z80 RAM";
		BurnAcb(&ba);

    	ba.Data		= NeoPalSrc[0];
		ba.nLen		= 0x000002000;
		ba.nAddress = 0;
		ba.szName	= "Palette 0";
		BurnAcb(&ba);
    	ba.Data		= NeoPalSrc[1];
		ba.nLen		= 0x000002000;
		ba.nAddress = 0;
		ba.szName	= "Palette 1";
		BurnAcb(&ba);

    	ba.Data		= NeoGraphicsRAM;
		ba.nLen		= 0x00020000;
		ba.nAddress = 0;
		ba.szName	= "Graphics RAM";
		BurnAcb(&ba);

		if (pNeoScanCallback) {
			pNeoScanCallback(nAction, pnMin);
		}
	}

	if (nAction & ACB_DRIVER_DATA) {						// Scan driver data

		SekScan(nAction);									// Scan 68000 state
		ZetScan(nAction);									// Scan Z80 state

		BurnYM2610Scan(nAction, pnMin);

		if (!NeoCheckAESBIOS()) {
			uPD4990AScan(nAction, pnMin);
		}

		if (pNeoScanCallback) {
			pNeoScanCallback(nAction, pnMin);
		}

		SCAN_VAR(nCyclesExtra);

		SCAN_VAR(bNeoEnableGraphics);
		SCAN_VAR(nIRQAcknowledge);

		SCAN_VAR(nIRQ2Control);	SCAN_VAR(nIRQ2Offset); SCAN_VAR(nIRQCycles);
		SCAN_VAR(bSRAMWritable);

		SCAN_VAR(nNeoWatchdog);

		SCAN_VAR(b68KBoardROMBankedIn);
		SCAN_VAR(bBIOSTextROMEnabled);
		SCAN_VAR(nZ80Bank0); SCAN_VAR(nZ80Bank1); SCAN_VAR(nZ80Bank2); SCAN_VAR(nZ80Bank3);
		SCAN_VAR(nNeo68KROMBank);

		NeoGraphicsRAMBank -= (unsigned int)NeoGraphicsRAM;
		SCAN_VAR(NeoGraphicsRAMBank); SCAN_VAR(NeoGraphicsRAMPointer); SCAN_VAR(nNeoGraphicsModulo);
		NeoGraphicsRAMBank += (unsigned int)NeoGraphicsRAM;

		SCAN_VAR(nNeoSpriteFrame); SCAN_VAR(nSpriteFrameSpeed); SCAN_VAR(nSpriteFrameTimer);

		SCAN_VAR(nNeoPaletteBank);

		SCAN_VAR(nSoundLatch);
		SCAN_VAR(nSoundReply);
		SCAN_VAR(nSoundStatus);

#if 1 && defined USE_SPEEDHACKS
		SCAN_VAR(nSoundPrevReply);
#endif

		SCAN_VAR(nInputSelect);

		NeoInputBank -= (unsigned int)NeoInput;
		SCAN_VAR(NeoInputBank);
		NeoInputBank += (unsigned int)NeoInput;

		SCAN_VAR(nAnalogAxis);

		SCAN_VAR(nuPD4990ATicks);

		if (nAction & ACB_WRITE) {
			int nNewBIOS = nBIOS;
			int nBank;

			MapVectorTable(b68KBoardROMBankedIn);
			NeoZ80MapROM(bZ80BoardROMBankedIn);

			nBank = nZ80Bank0; nZ80Bank0 = -1;
			NeoZ80SetBank0(nBank);
			nBank = nZ80Bank1; nZ80Bank1 = -1;
			NeoZ80SetBank1(nBank);
			nBank = nZ80Bank2; nZ80Bank2 = -1;
			NeoZ80SetBank2(nBank);
			nBank = nZ80Bank3; nZ80Bank3 = -1;
			NeoZ80SetBank3(nBank);

			if (pNeoBankswitchCallback) {
				pNeoBankswitchCallback();
			} else {
				if ((BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK) != HARDWARE_SNK_GAMBLING) {
					SekMapMemory(Neo68KROM + nNeo68KROMBank, 0x200000, 0x2FFFFF, SM_ROM);
				}
			}

			nBank = nNeoPaletteBank; nNeoPaletteBank = -1;
			MapPalette(nBank);

			NeoRecalcPalette = 1;

			nBIOS = nOldBIOS;
			NeoLoad68KBIOS(nNewBIOS);

			bAESBIOS = NeoCheckAESBIOS();

			nPrevBurnCPUSpeedAdjust = -1;
		}
	}

	return 0;
}

// ----------------------------------------------------------------------------
// CPU synchronisation

static inline void neogeoSynchroniseZ80(int nExtraCycles)
{
#ifdef Z80_SPEED_ADJUST
	int nCycles = SekTotalCycles() / 3 + nExtraCycles;
#else
	int nCycles = ((long long)SekTotalCycles() * nCyclesTotal[1] / nCyclesTotal[0]) + nExtraCycles;
#endif

	if (nCycles <= ZetTotalCycles()) {
		return;
	}

	nCycles68KSync = nCycles - nExtraCycles;

	BurnTimerUpdate(nCycles);
}

// Callbacks for the FM chip

static void neogeoFMIRQHandler(int, int nStatus)
{
//	bprintf(PRINT_NORMAL, _T("    YM2610 IRQ status: 0x%02X (%6i cycles)\n"), nStatus, ZetTotalCycles());

	if (nStatus & 1) {
		ZetSetIRQLine(0xFF, ZET_IRQSTATUS_ACK);
	} else {
		ZetSetIRQLine(0,    ZET_IRQSTATUS_NONE);
	}
}

static int neogeoSynchroniseStream(int nSoundRate)
{
	return (long long)ZetTotalCycles() * nSoundRate / nZ80Clockspeed;
}

static double neogeoGetTime()
{
	return (double)ZetTotalCycles() / nZ80Clockspeed;
}

// -----------------------------------------------------------------------------
// Z80 handlers

unsigned char __fastcall neogeoZ80In(unsigned short nAddress)
{
	switch (nAddress & 0xFF) {
		case 0x00:									// Read sound command
//			bprintf(PRINT_NORMAL, _T("  - Sound command received (0x%02X).\n"), nSoundLatch);
			nSoundStatus = 1;
#if 1 && defined USE_SPEEDHACKS
			nSoundPrevReply = -1;
#endif
			return nSoundLatch;

		case 0x04:
			return BurnYM2610Read(0);
		case 0x05:
			return BurnYM2610Read(1);
		case 0x06:
			return BurnYM2610Read(2);

		case 0x08:
//			bprintf(PRINT_NORMAL, "  - Z80 bank 3 -> 0x%02X\n", nAddress >> 8);
			NeoZ80SetBank3(nAddress >> 8);
			break;
		case 0x09:
//			bprintf(PRINT_NORMAL, "  - Z80 bank 2 -> 0x%02X\n", nAddress >> 8);
			NeoZ80SetBank2(nAddress >> 8);
			break;
		case 0x0A:
//			bprintf(PRINT_NORMAL, "  - Z80 bank 1 -> 0x%02X\n", nAddress >> 8);
			NeoZ80SetBank1(nAddress >> 8);
			break;
		case 0x0B:
//			bprintf(PRINT_NORMAL, "  - Z80 bank 0 -> 0x%02X\n", nAddress >> 8);
			NeoZ80SetBank0(nAddress >> 8);
			break;

//		default: {
//			bprintf(PRINT_NORMAL, "  - Z80 read port 0x%04X.\n", nAddress);
//		}
	}

	return 0;
}

void __fastcall neogeoZ80Out(unsigned short nAddress, unsigned char nValue)
{
	switch (nAddress & 0x0FF) {
		case 0x00:
//			bprintf(PRINT_NORMAL, "  - Z80 port 0x00 -> 0x%02X.\n", nValue);
			break;
		case 0x04:
		case 0x05:
		case 0x06:
		case 0x07:
			BurnYM2610Write(nAddress & 3, nValue);
			break;

		case 0x0C:									// Write reply to sound commands
//			bprintf(PRINT_NORMAL, _T("  - Sound reply sent (0x%02X).\n"), nValue);
			nSoundReply = nValue;

#if 1 && defined USE_SPEEDHACKS
			if (nSoundPrevReply != nValue) {
				nSoundPrevReply = nValue;

				// s1945p replies a 0x00, then an 0xFF;
				// the 68K loops until it has read both
				if (nSoundReply == 0) {
					nSoundStatus &= ~2;
				} else {
					nSoundStatus |=  2;
				}
			} else {
				nSoundStatus |= 2;
			}

			if (ZetTotalCycles() > nCycles68KSync) {

//				bprintf(PRINT_NORMAL, _T("    %i\n"), ZetTotalCycles());
				BurnTimerUpdateEnd();
//				bprintf(PRINT_NORMAL, _T("    %i - %i\n"), ZetTotalCycles(), nCycles68KSync);
			}
#endif

			break;

//		default: {
//			bprintf(PRINT_NORMAL, "  - Z80 port 0x%04X -> 0x%02X.\n", nAddress, nValue);
//		}
	}
}

// -----------------------------------------------------------------------------
// 68K handlers

static inline int NeoConvertIRQPosition(int nOffset)
{
	unsigned long long nNewPosition = (((unsigned long long)nOffset) << 9) / nBurnCPUSpeedAdjust;

	return (nNewPosition < NO_IRQ_PENDING) ? nNewPosition : NO_IRQ_PENDING;
}

static inline void NeoIRQAcknowledge(unsigned short wordValue)
{
	nIRQAcknowledge |= (wordValue & 7);

//	bprintf(PRINT_NORMAL, "  - IRQ Ack -> %02X (at line %3i).\n", wordValue, SekCurrentScanline());

	if (nIRQAcknowledge == 7) {
		SekSetIRQLine(7, SEK_IRQSTATUS_NONE);
	} else {
		if ((nIRQAcknowledge & 1) == 0) {
			SekSetIRQLine(3, SEK_IRQSTATUS_ACK);
		}
		if ((nIRQAcknowledge & 2) == 0) {
			SekSetIRQLine(2, SEK_IRQSTATUS_ACK);
		}
		if ((nIRQAcknowledge & 4) == 0) {
			SekSetIRQLine(1, SEK_IRQSTATUS_ACK);
		}
	}

//	bprintf(PRINT_NORMAL, "  - IRQ Ack -> %02X   (at line %3i).\n", wordValue, SekCurrentScanline());
}

static inline void SendSoundCommand(const unsigned char nCommand)
{
//	bprintf(PRINT_NORMAL, _T("  - Sound command sent (0x%02X).\n"), nCommand);

	neogeoSynchroniseZ80(0);

	nSoundStatus &= ~1;
	nSoundLatch = nCommand;

	ZetNmi();

#if 1 && defined USE_SPEEDHACKS
	neogeoSynchroniseZ80(0x0200);
#endif
}

unsigned char __fastcall neogeoReadByte(unsigned int sekAddress)
{
	switch (sekAddress) {

		case 0x300000:
//			bprintf(PRINT_NORMAL, _T(" -- bank %d inputP0[0x%02X] read (%i).\n"), 0, nInputSelect, SekTotalCycles());
			return ~NeoInputBank[nJoyport0[nInputSelect & 0x07]];

		case 0x300001:
			return ~NeoInputBank[4];

		case 0x300081:
			return  NeoInputBank[5];

		case 0x320000: {
			int nReply = nSoundReply;

#if 1 && defined USE_SPEEDHACKS
			// nSoundStatus: &1 = sound latch read, &2 = response written
			if (nSoundStatus != 3) {
				neogeoSynchroniseZ80(0x0100);
			}
#endif

			if (nSoundStatus & 1) {
//				bprintf(PRINT_NORMAL, _T("  - Sound reply read (0x%02X).\n"),  nSoundReply);

				return nReply;
			} else {
//				bprintf(PRINT_NORMAL, _T("  - Sound reply read while sound pending (0x%02X).\n"),  nSoundReply);

				return nReply & 0x7F;
			}
		}

		case 0x320001: {
			if (!bAESBIOS) {
				unsigned char nuPD4990AOutput = uPD4990ARead(SekTotalCycles() - nuPD4990ATicks);
				nuPD4990ATicks = SekTotalCycles();
				return (~NeoInputBank[3] & 0x3F) | (nuPD4990AOutput << 6);
			}

			return (~NeoInputBank[3] & 0x7F) & 0xE7;
		}

		case 0x340000:
//			bprintf(PRINT_NORMAL, _T(" -- bank %d inputP1[0x%02X] read.\n"), 0, nInputSelect);
			return ~NeoInputBank[nJoyport1[nInputSelect & 0x07]];

		case 0x380000:
//			bprintf(PRINT_NORMAL, " -- input 2 read.\n");
			return ~NeoInputBank[2];

		case 0x3c007b:		// kof2002 'how to play' reads here, expects 0xff back
		case 0x3c007d:
		case 0x3c007f:
		case 0x3c0081:
		case 0x3c0085:
		case 0x3c0124:
		case 0x3c0125:
		case 0x3c01ba:
		case 0x3c01bb:
			return 0xff;

//		default:
//			bprintf(PRINT_NORMAL, _T("  - 0x%08X byte (word, PC: %08X)\n"), sekAddress, SekGetPC(-1));
	}

	return 0;
}

unsigned short __fastcall neogeoReadWord(unsigned int sekAddress)
{
	switch (sekAddress) {
		case 0x300000:
//			bprintf(PRINT_NORMAL, _T(" -- bank %d inputP0[0x%02X] read.\n"), 0, nInputSelect);
			return ~((NeoInputBank[nJoyport0[nInputSelect & 0x07]] << 8) | NeoInputBank[4]);
		case 0x340000:
//			bprintf(PRINT_NORMAL, _T(" -- bank %d inputP1[0x%02X] read.\n"), 0, nInputSelect);
			return ~(NeoInputBank[nJoyport1[nInputSelect & 0x07]] << 8);
		case 0x380000:
			return ~(NeoInputBank[2] << 8);

		case 0x3C0000:
		case 0x3C0002:
		case 0x3C000A:
//			bprintf(PRINT_NORMAL, "  - Graphics RAM read (Bank %i, address 0x%04X).\n", NeoGraphicsRAMPointer > 0xFFFF ? 1 : 0, NeoGraphicsRAMPointer & 0xFFFF);
			return *((unsigned short*)(NeoGraphicsRAMBank + NeoGraphicsRAMPointer));

		case 0x3C0004:
//			bprintf(PRINT_NORMAL, "  - Graphics RAM modulo read.\n");
			return (unsigned short)(nNeoGraphicsModulo >> 1);

		case 0x3C0006: {									// Display status
//			bprintf(PRINT_NORMAL, "  - Display status read, line: %3i, anim: %i\n", SekCurrentScanline(), nNeoSpriteFrame);

			bForcePartialRender |= bForceUpdateOnStatusRead;

			return ((SekCurrentScanline() + nScanlineOffset) << 7) | (nNeoSpriteFrame & 7);
		}

//		default:
//			bprintf(PRINT_NORMAL, _T("  - 0x%08X read (word, PC: %08X)\n"), sekAddress, SekGetPC(-1));
	}

	return 0;
}

void __fastcall neogeoWriteByte(unsigned int sekAddress, unsigned char byteValue)
{
	switch (sekAddress) {
		case 0x280051:										// Send command to RTC
		case 0x380051: {
			if (!bAESBIOS) {
				uPD4990AWrite(byteValue & 2, byteValue & 4, byteValue & 1);
			}
			break;
		}

		case 0x300001:										// Watchdog
//			bprintf(PRINT_NORMAL, _T("  - Watchdog timer reset (%02X, at scanline %i)\n"), byteValue, SekCurrentScanline());
			nNeoWatchdog = -SekTotalCycles();
			break;

		case 0x320000:
			SendSoundCommand(byteValue);
			break;

		case 0x380001: {									// Select the input returned at 0x300000
//			bprintf(PRINT_NORMAL, _T("  - InputP0/P1 0x%02X selected (%i).\n"), byteValue, SekTotalCycles());
			nInputSelect = byteValue;
			break;
		}

#if 0
		case 0x380021: {									// Select the active cartridge slot
			bprintf(PRINT_NORMAL, _T("  - Cartridge slot 0x%02X activated.\n"), byteValue);
			break;
		}
#endif

	static int blaat;

		case 0x380031: {									// Setting the bits to 0 sends the value written to 0x380041 to the appropriate output
															// Select LED output (bits 5/4 - numeric displays, bit 3 - marquee lights, one per slot)
//			bprintf(PRINT_NORMAL, _T("  - 0x%06X -> 0x%02X.\n"), sekAddress, byteValue);
			if (byteValue != 255) {
//				bprintf(PRINT_NORMAL, _T("  - LED %02X -> %02X\n"), ~byteValue & 255, ~blaat & 255);
			}
			break;
		}
		case 0x380041:										// LED output values
			// for numeric displays - digits displayed    = ~bytevalue
			// for start buttons    - highlighted marquee = ~bytevalue
//			bprintf(PRINT_NORMAL, _T("  - LED output -> 0x%02X.\n"), byteValue);
			blaat = byteValue;
			break;

		case 0x380061:										// Coin lockout chute 1 & input bank select
//			bprintf(PRINT_NORMAL, _T("  - Input bank 0 selected (0x%02X).\n"), byteValue);
			NeoInputBank = NeoInput + 0;
			break;
		case 0x3800E1:
//			bprintf(PRINT_NORMAL, _T("  - Input bank 1 selected (0x%02X).\n"), byteValue);
			NeoInputBank = NeoInput + 8;
			break;
		case 0x380063:										// Coin lockout chute 2
//			bprintf(PRINT_NORMAL, _T("  - Chute 2 coin lockout Off (0x%02X).\n"), byteValue);
			break;
		case 0x3800E3:
//			bprintf(PRINT_NORMAL, _T("  - Chute 2 coin lockout On (0x%02X).\n"), byteValue);
			break;

#if 0
		case 0x380065:										// Coin counter chute 1
			break;
		case 0x3800E5:
			break;
		case 0x380067:										// Coin counter chute 2
			break;
		case 0x3800E7:
			break;
#endif

		case 0x3A0001: {									// Enable display
			bNeoEnableGraphics = true;
//			bprintf(PRINT_NORMAL, _T("  - Display  enabled (0x%02X, at scanline %i).\n"), byteValue, SekCurrentScanline());
			break;
		}
		case 0x3A0011: {									// Disable display
			bNeoEnableGraphics = false;
//			bprintf(PRINT_NORMAL, _T("  - Display disabled (0x%02X, at scanline %i).\n"), byteValue, SekCurrentScanline());
			break;
		}

		case 0x3A0003: {									// Select BIOS vector table
			if (!b68KBoardROMBankedIn) {
				MapVectorTable(true);
				b68KBoardROMBankedIn = true;
			}
//			bprintf(PRINT_NORMAL, _T("  - BIOS vector table banked in (0x%02X).\n"), byteValue);
			break;
		}
		case 0x3A0013: {									// Select game vector table
			if (b68KBoardROMBankedIn) {
				MapVectorTable(false);
				b68KBoardROMBankedIn = false;
			}
//			bprintf(PRINT_NORMAL, _T("  - ROM vector table banked in (0x%02X).\n"), byteValue);
			break;
		}

		case 0x3A000B: {									// Select BIOS text ROM
//			bprintf(PRINT_NORMAL, _T("  - BIOS text/Z80 ROM banked in (0x%02X).\n"), byteValue);

			if (!bAESBIOS) {
				bBIOSTextROMEnabled = true;
			}

			if (bZ80BIOS) {
				if (!bZ80BoardROMBankedIn) {
					bZ80BoardROMBankedIn = true;
					NeoZ80MapROM(true);
				}

#ifdef Z80_RESET_ON_BANKSWITCH
				nSoundStatus |= 1;
				ZetReset();
#endif
			}

			break;
		}
		case 0x3A001B: {									// Select game text ROM
//			bprintf(PRINT_NORMAL, _T("  - Cartridge text/Z80 ROM banked in (0x%02X).\n"), byteValue);

			bBIOSTextROMEnabled = false;

			if (bZ80BIOS) {
				if (bZ80BoardROMBankedIn) {
					bZ80BoardROMBankedIn = false;
					NeoZ80MapROM(false);
				}

#ifdef Z80_RESET_ON_BANKSWITCH
				nSoundStatus |= 1;
				ZetReset();
#endif
			}

			break;
		}

		case 0x3A000D: {									// Write-protect SRAM
			bSRAMWritable = false;
//			bprintf(PRINT_NORMAL, _T("  - SRAM write-protected (0x%02X).\n"), byteValue);
			break;
		}
		case 0x3A001D: {									// Write-enable SRAM
			bSRAMWritable = true;
//			bprintf(PRINT_NORMAL, _T("  - SRAM writable (0x%02X).\n"), byteValue);
			break;
		}

		case 0x3C0006: {
			nSpriteFrameSpeed = byteValue;
//			bprintf(PRINT_NORMAL, _T("  - Sprite speed -> 0x%02X\n"), byteValue);
			break;
		}

		case 0x3A000F: {									// Select palette bank 1
//			bprintf(PRINT_NORMAL, _T("  - Palette 1 banked in (0x%02X).\n"), byteValue);
			MapPalette(1);
			break;
		}
		case 0x3A001F: {									// Select palette bank 0
//			bprintf(PRINT_NORMAL, _T("  - Palette 0 banked in (0x%02X).\n"), byteValue);
			MapPalette(0);
			break;
		}

//		case 0x3C0007:
//		case 0x3C000D: {
//			bprintf(PRINT_NORMAL, "  - Attempt to write byte 0x%06X ->   0x%02X\n", sekAddress, byteValue);
//			break;
//		}

//		default:
//			bprintf(PRINT_NORMAL, _T("  - Attempt to write byte 0x%06X ->   0x%02X\n"), sekAddress, byteValue);
	}
}

void __fastcall neogeoWriteWord(unsigned int sekAddress, unsigned short wordValue)
{
	switch (sekAddress) {
		case 0x320000:
			SendSoundCommand(wordValue >> 8);
			break;

		case 0x3C0000: {
			NeoGraphicsRAMPointer = wordValue << 1;
			NeoGraphicsRAMBank = NeoGraphicsRAM;
			if (wordValue & 0x8000) {
				NeoGraphicsRAMBank += 0x00010000;
			}
			break;
		}
		case 0x3C0002: {
			*((unsigned short*)(NeoGraphicsRAMBank + NeoGraphicsRAMPointer)) = wordValue;
			NeoGraphicsRAMPointer += nNeoGraphicsModulo;

#if 0
			if ((NeoGraphicsRAMBank == NeoGraphicsRAM) && NeoGraphicsRAMPointer >= 0xC000 && NeoGraphicsRAMPointer < 0xE000) {
				bprintf(PRINT_NORMAL, _T("VRAM bank 0 + 0x%04X -> %04X\n"), NeoGraphicsRAMPointer, wordValue);
			}
			if ((NeoGraphicsRAMBank != NeoGraphicsRAM) && NeoGraphicsRAMPointer >= 0x0C00) {
				bprintf(PRINT_NORMAL, _T("VRAM bank 1 + 0x%04X -> %04X\n"), NeoGraphicsRAMPointer, wordValue);
			}
#endif
			break;
		}
		case 0x3C0004: {
			nNeoGraphicsModulo = ((short)wordValue) << 1;
			break;
		}

		case 0x3C0006: {
			nSpriteFrameSpeed = (wordValue >> 8);

			if ((nIRQ2Control & 0x10) == 0 && wordValue & 0x10) {

#if 0 || defined LOG_IRQ2
				bprintf(PRINT_NORMAL, _T("  - IRQ2 enabled  (at line %3i, IRQControl: 0x%02X).\n"), SekCurrentScanline(), wordValue & 0xFF);
#endif

				if (nIRQCycles < nCyclesSegment) {
					SekRunAdjust(nIRQCycles - nCyclesSegment);
				}
			}

#if 0 || defined LOG_IRQ2
			if (nIRQ2Control & 0x10 && (wordValue & 0x10) == 0) {
				bprintf(PRINT_NORMAL, _T("  - IRQ2 disabled (at line %3i, IRQControl: 0x%02X).\n"), SekCurrentScanline(), wordValue & 0xFF);
			}
#endif

			nIRQ2Control = wordValue;
//			bprintf(PRINT_NORMAL, _T("  - Autoanim speed -> 0x%02X\n"), wordValue >> 8);
//			bprintf(PRINT_NORMAL, _T("  - IRQ2 control register -> 0x%02X (at line %3i)\n"), wordValue & 0xFF, SekCurrentScanline());
			break;
		}

		case 0x3C0008: {
//			bprintf(PRINT_NORMAL, "0x%06X -> 0x%04X\n", sekAddress, wordValue);
			// Bit 15 seems to be ignored
			nIRQ2Offset = (nIRQ2Offset & 0x0000FFFF) | ((wordValue & 0x7FFF) << 16);
			break;
		}
		case 0x3C000A: {
//			bprintf(PRINT_NORMAL, "0x%06X -> 0x%04X\n", sekAddress, wordValue);
			nIRQ2Offset = (nIRQ2Offset & 0xFFFF0000) | wordValue;

#if 0 || defined LOG_IRQ2
			bprintf(PRINT_NORMAL, _T("  - IRQ offs -> 0x%08X (at line %3i, IRQControl: 0x%02X).\n"), nIRQ2Offset, SekCurrentScanline(), nIRQ2Control);
#endif

			if (nIRQ2Control & 0x20) {

#if 0
				nIRQCycles = SekTotalCycles() + NeoConvertIRQPosition(nIRQ2Offset);
#else
				// Turfmast uses this to set the timing of the raster interrupts. Using the code below,
				// all raster effects in turfmast are correctly aligned and exhibit no flicker.
				nIRQCycles = SekCurrentScanline() * nSekCyclesScanline + NeoConvertIRQPosition(nIRQ2Offset + 8);
//				bprintf(PRINT_NORMAL, _T("   %i - %i\n"), SekCurrentScanline(), SekTotalCycles() % nSekCyclesScanline);
#endif

#if 0 || defined LOG_IRQ2
				bprintf(PRINT_NORMAL, _T("    IRQ Line -> %3i (at line %3i, relative).\n"), nIRQCycles / nSekCyclesScanline, SekCurrentScanline());
#endif

				if (nIRQCycles < 0) {
					nIRQCycles = NO_IRQ_PENDING;
				}
				if (nIRQCycles < nCyclesSegment) {
					SekRunAdjust(nIRQCycles - nCyclesSegment);
				}
			}

			break;
		}

		case 0x3C000C: {
			NeoIRQAcknowledge(wordValue);
			break;
		}

//		default:
//			bprintf(PRINT_NORMAL, _T("  - Attempt to write word 0x%06X -> 0x%04X\n"), sekAddress, wordValue);
	}
}

// ----------------------------------------------------------------------------
// Backup RAM on MVS hardware

void __fastcall neogeoWriteByteSRAM(unsigned int sekAddress, unsigned char byteValue)
{
 	if (bSRAMWritable) {
		NeoNVRAM[(sekAddress & 0xffff) ^ 1] = byteValue;
 	}
}

void __fastcall neogeoWriteWordSRAM(unsigned int sekAddress, unsigned short wordValue)
{
	if (bSRAMWritable) {
		*((unsigned short*)(NeoNVRAM + (sekAddress & 0xffff))) = wordValue;
	}
}

// ----------------------------------------------------------------------------

unsigned char __fastcall neogeoReadByteMemoryCard(unsigned int sekAddress)
{
//	if (sekAddress < 0x800100)
//	bprintf(PRINT_NORMAL, _T("  - Memcard 0x%04X read (PC: 0x%06X).\n"), sekAddress & 0x7FFF, SekGetPC(-1));

	if (bMemoryCardInserted) {
		if ((NeoSystem & 0x40) || (sekAddress & 1)) {
			return NeoMemoryCard[sekAddress & 0x01FFFF];
		}
	}

	return 0xFF;
}

void __fastcall neogeoWriteByteMemoryCard(unsigned int sekAddress, unsigned char byteValue)
{
//	if (sekAddress < 0x800100)
//	bprintf(PRINT_NORMAL, _T("  - Memcard 0x%04X -> 0x%02X (PC: 0x%06X).\n"), sekAddress & 0x7FFF, byteValue, SekGetPC(-1));

	if (bMemoryCardInserted && bMemoryCardWritable) {
		if ((NeoSystem & 0x40) || (sekAddress & 1)) {
			NeoMemoryCard[sekAddress & 0x01FFFF] = byteValue;
		}
	}
}

// ----------------------------------------------------------------------------
// 68K bankswitch for most games without SMA protection

void __fastcall neogeoWriteByteBankswitch(unsigned int sekAddress, unsigned char byteValue)
{
	if (sekAddress >= 0x2FFFF0) {

//		bprintf(PRINT_NORMAL, _T("  - Bankswitch: 0x%06X -> 0x%02X\n"), sekAddress, byteValue);

		Bankswitch(byteValue);
		return;
	}
}

void __fastcall neogeoWriteWordBankswitch(unsigned int sekAddress, unsigned short wordValue)
{
	if (sekAddress >= 0x2FFFF0) {

//		bprintf(PRINT_NORMAL, _T("  - Bankswitch: 0x%06X -> 0x%04X\n"), sekAddress, wordValue);

		Bankswitch(wordValue);
		return;
	}
}

// ----------------------------------------------------------------------------

unsigned char __fastcall neogeoReadByteGambling(unsigned int sekAddress)
{
	switch (sekAddress) {
		case 0x280001: {
			return 0xff - NeoInput[3];
		}
		
		case 0x2c0001: {
			return 0x03;
		}
	}
	
//	bprintf(PRINT_NORMAL, _T("Read Byte 0x%08X.\n"), sekAddress);
	
	return 0xff;
}

unsigned short __fastcall neogeoReadWordGambling(unsigned int sekAddress)
{
	switch (sekAddress) {
		case 0x280000: {
			return 0xff - NeoInput[3];
		}
		
		case 0x2c0000: {
			return 0x0003;
		}
	}
	
//	bprintf(PRINT_NORMAL, _T("Read Word 0x%08X.\n"), sekAddress);

	return 0xffff;
}

unsigned char __fastcall vliner_timing(unsigned int sekAddress)
{
	switch (sekAddress) {
		case 0x320000: {
			int nReply = nSoundReply;

#if 1 && defined USE_SPEEDHACKS
			// nSoundStatus: &1 = sound latch read, &2 = response written
			if (nSoundStatus != 3) {
				neogeoSynchroniseZ80(0x0100);
			}
#endif

			if (nSoundStatus & 1) {
//				bprintf(PRINT_NORMAL, _T("  - Sound reply read (0x%02X).\n"),  nSoundReply);

				return nReply;
			} else {
//				bprintf(PRINT_NORMAL, _T("  - Sound reply read while sound pending (0x%02X).\n"),  nSoundReply);

				return nReply & 0x7F;
			}
		}

		case 0x320001: {
			if (!bAESBIOS) {
				unsigned char nuPD4990AOutput = uPD4990ARead(SekTotalCycles() - nuPD4990ATicks);
				nuPD4990ATicks = SekTotalCycles();
				return 0x3F | (nuPD4990AOutput << 6);
			}

			return (0x3f) & 0xE7;
		}
	}
	
//	bprintf(PRINT_NORMAL, _T("Read Byte 0x%08X.\n"), sekAddress);
	return 0xff;
}

// ----------------------------------------------------------------------------

static int neogeoReset()
{
	NeoLoad68KBIOS((NeoSystem & 0x07) ^ 4);

	if (nBIOS == -1 || nBIOS == 9) {
		// Write system type & region code into BIOS ROM
		*((unsigned short*)(Neo68KBIOS + 0x000400)) = ((NeoSystem & 4) << 13) | (NeoSystem & 0x03);
	}

#if 1 && defined FBA_DEBUG
	bprintf(PRINT_IMPORTANT, _T("  - Emulating "));
	if (nBIOS < 8 || nBIOS == 9) {
		switch (NeoSystem & 0x03) {
			case 0x00:
				bprintf(PRINT_IMPORTANT, _T("Japanese"));
				break;
			case 0x01:
				bprintf(PRINT_IMPORTANT, _T("US"));
				break;
			case 0x02:
				bprintf(PRINT_IMPORTANT, _T("Asian / European"));
				break;
			default:
				bprintf(PRINT_IMPORTANT, _T("unknown"));
				break;
		}
	}
	if (nBIOS < 8 || nBIOS == 9 || nBIOS == 0x0C) {
		if (NeoSystem & 0x04) {
			bprintf(PRINT_IMPORTANT, _T(" MVS system"));
		} else {
			bprintf(PRINT_IMPORTANT, _T(" AES system"));
		}
	}
	if (nBIOS >= 8) {
		switch (nBIOS) {
			case 0x08:
				bprintf(PRINT_IMPORTANT, _T("development system"));
				break;
			case 0x09:
				bprintf(PRINT_IMPORTANT, _T(" with trackball control"));
				break;
			case 0x0C:
				bprintf(PRINT_IMPORTANT, _T(" with Universe BIOS v2.0"));
				break;
			default:
				bprintf(PRINT_IMPORTANT, _T("unknown system"));
				break;
		}
	}
	if ((BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK) == HARDWARE_SNK_PADDLE) {
		bprintf(PRINT_IMPORTANT, _T(" with paddle support"));
	}
	if ((BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK) == HARDWARE_SNK_PCB) {
		bprintf(PRINT_IMPORTANT, _T(" custom JAMMA PCB board BIOS"));
	}
	if (nBIOS == -1) {
		bprintf(PRINT_IMPORTANT, _T(" (patching region)"));
	}
	if (nBIOS == 9 && NeoSystem != 6) {
		bprintf(PRINT_IMPORTANT, _T(" (patching region and/or system type)"));
	}
	bprintf(PRINT_IMPORTANT, _T(".\n"));
#endif

	OldDebugDip[0] = NeoDebugDip[0] = 0;
	OldDebugDip[1] = NeoDebugDip[1] = 0;

	bAESBIOS = NeoCheckAESBIOS();

	bSRAMWritable = false;

	bNeoEnableGraphics = true;

	bBIOSTextROMEnabled = false;
	bZ80BoardROMBankedIn = false;
	b68KBoardROMBankedIn = true;

	nNeoPaletteBank = -1;

	nSpriteFrameSpeed = 4;
	nSpriteFrameTimer = 0;
	nNeoSpriteFrame = 0;

	nIRQAcknowledge = 6;
	bIRQ2Enabled = false;

	nSoundLatch = 0x00;
	nSoundReply = 0x00;
	nSoundStatus = 1;

#if 1 && defined USE_SPEEDHACKS
	nSoundPrevReply = -1;
#endif

	nIRQ2Offset = 0;
	nIRQ2Control = 0;

	nInputSelect = 0;
	NeoInputBank = NeoInput;

	nCyclesExtra[0] = nCyclesExtra[1] = 0;

	{
		SekOpen(0);

		if (bAESBIOS) {
			SekMapHandler(0,			0xD00000, 0xD0FFFF, SM_RAM);	// AES doesn't have the SRAM
		} else {
			SekMapMemory(NeoNVRAM,		0xD00000, 0xD0FFFF, SM_ROM);	// Battery backed SRAM
			SekMapHandler(1,			0xD00000, 0xD0FFFF, SM_WRITE);	//
		}

		MapVectorTable(b68KBoardROMBankedIn);

		// Set by a switch on the PCB
		if (!strcmp(BurnDrvGetTextA(DRV_NAME), "svcpcb") || !strcmp(BurnDrvGetTextA(DRV_NAME), "svcpcba") || !strcmp(BurnDrvGetTextA(DRV_NAME), "svcpcbnd") || !strcmp(BurnDrvGetTextA(DRV_NAME), "ms5pcb") || !strcmp(BurnDrvGetTextA(DRV_NAME), "ms5pcbnd")) {
			SekMapMemory(Neo68KBIOS + 0x20000 * (~NeoSystem & 1), 0xc00000, 0xc1ffff, SM_ROM);
		}
		
		SekReset();

		MapPalette(0);

		nNeo68KROMBank = 0;
		if (nCodeSize > 0x100000) {
			nNeo68KROMBank = 0x100000;
			if (pNeoBankswitchCallback) {
				pNeoBankswitchCallback();
			} else {
				if ((BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK) != HARDWARE_SNK_GAMBLING) {
					SekMapMemory(Neo68KROM + nNeo68KROMBank, 0x200000, 0x2FFFFF, SM_ROM);
				}
			}
		}

		SekClose();
	}

	{
		ZetOpen(0);

		NeoZ80MapROM(bZ80BoardROMBankedIn);

		nZ80Bank0 = nZ80Bank1 = nZ80Bank2 = nZ80Bank3 = -1;
		NeoZ80SetBank0(0x02);
		NeoZ80SetBank1(0x06);
		NeoZ80SetBank2(0x0E);
		NeoZ80SetBank3(0x1E);

		ZetReset();

		ZetClose();
	}

	BurnYM2610Reset();

	nNeoWatchdog = 0;

	nIRQCycles = NO_IRQ_PENDING;

	return 0;
}

int NeoInit()
{
	BurnSetRefreshRate(NEO_VREFRESH);
	{
		int nNeoScreenHeight; // not used
		BurnDrvGetFullSize(&nNeoScreenWidth, &nNeoScreenHeight);
	}

	NeoGameInfo info;
	NeoGameInfo* pInfo = &info;

	{
		struct BurnRomInfo ri;

		ri.nType = 0;
		ri.nLen = 0;

		// Find 'P' ROMs
		FindROMs(1, &pInfo->nCodeOffset, &pInfo->nCodeNum);
		// Find 'S' ROM
		FindROMs(2, &pInfo->nTextOffset, NULL);
		// Find 'C' ROMs
		FindROMs(3, &pInfo->nSpriteOffset, &pInfo->nSpriteNum);
		// Find 'M' ROM
		FindROMs(4, &pInfo->nSoundOffset, NULL);
		// Find 'V' ROMs
		FindROMs(5, &pInfo->nADPCMOffset, &pInfo->nADPCMANum);
		FindROMs(6, NULL, &pInfo->nADPCMBNum);

		if (pInfo->nADPCMBNum < 0) {
			pInfo->nADPCMBNum = 0;
		}

#if 1 && defined FBA_DEBUG
		bprintf(PRINT_IMPORTANT, _T("  - P: %i (%i);"), pInfo->nCodeOffset, pInfo->nCodeNum);
		if (pInfo->nTextOffset >= 0) {
			bprintf(PRINT_IMPORTANT, _T(" S: %i;"), pInfo->nTextOffset);
		} else {
			bprintf(PRINT_IMPORTANT, _T(" S: unused;"));
		}
		bprintf(PRINT_IMPORTANT, _T(" C: %i (%i); M: %i"), pInfo->nSpriteOffset, pInfo->nSpriteNum, pInfo->nSoundOffset);
		if (pInfo->nADPCMOffset >= 0) {
			bprintf(PRINT_IMPORTANT, _T(" V: %i (%i, %i)"), pInfo->nADPCMOffset, pInfo->nADPCMANum, pInfo->nADPCMBNum);
		} else {
			bprintf(PRINT_IMPORTANT, _T(" V: unused"));
		}
		bprintf(PRINT_IMPORTANT,_T("\n"));
#endif

		nCodeSize = 0;
		for (int i = 0; i < pInfo->nCodeNum; i++) {
			BurnDrvGetRomInfo(&ri, pInfo->nCodeOffset + i);
			nCodeSize += ri.nLen;
		}
		nCodeSize = (nCodeSize + 0x0FFFFF) & ~0x0FFFFF;

		nSpriteSize = 0;

		if (BurnDrvGetHardwareCode() & HARDWARE_SNK_SWAPC) {
			BurnDrvGetRomInfo(&ri, pInfo->nSpriteOffset);
			// for viewpoin, aof, ssideki
			if (pInfo->nSpriteNum == 2) {
				nSpriteSize = 0x600000;
			}
			// for kotm2
			if (pInfo->nSpriteNum == 4) {
				BurnDrvGetRomInfo(&ri, pInfo->nSpriteOffset + 2);
				if (ri.nLen == 0x080000) {
					nSpriteSize = 0x600000;
				}
			}
		}

		if (nSpriteSize == 0) {

			// Compute correct size taking gaps into account (kizuna)
			for (int i = 0; i < pInfo->nSpriteNum - 2; i += 2) {
				BurnDrvGetRomInfo(&ri, pInfo->nSpriteOffset + i);
				if (ri.nLen > nSpriteSize) {
					nSpriteSize = ri.nLen;
				}
			}
			nSpriteSize *= pInfo->nSpriteNum - 2;

			// The final 2 ROMs may have a different size
			BurnDrvGetRomInfo(&ri, pInfo->nSpriteOffset + pInfo->nSpriteNum - 2);
			nSpriteSize += ri.nLen * 2;
		}

		for (nNeoTileMask = 1; nNeoTileMask < nSpriteSize; nNeoTileMask <<= 1) {}
		nNeoTileMask = (nNeoTileMask >> 7) - 1;
		nNeoMaxTile = nSpriteSize >> 7;

		if (nNeoTextROMSize == -1) {
			if (pInfo->nTextOffset >= 0) {
				BurnDrvGetRomInfo(&ri, pInfo->nTextOffset);
				nNeoTextROMSize = ri.nLen;
			} else {
				nNeoTextROMSize = 0x020000;
			}
		}

		nYM2610ADPCMASize = nYM2610ADPCMBSize = 0;
		if (pInfo->nADPCMOffset >= 0) {
			if (pInfo->nADPCMANum) {
				ri.nLen = 0;
				BurnDrvGetRomInfo(&ri, pInfo->nADPCMOffset);
				nYM2610ADPCMASize = ri.nLen * pInfo->nADPCMANum;
			}
			if (pInfo->nADPCMBNum) {
				ri.nLen = 0;
				BurnDrvGetRomInfo(&ri, pInfo->nADPCMOffset + pInfo->nADPCMANum);
				nYM2610ADPCMBSize = ri.nLen * pInfo->nADPCMBNum;
			}				
 		}
	}

	nBIOS = 9999;
	if (LoadRoms(pInfo)) {
		return 1;
	}

	// Allocate all memory is needed for RAM
	{
		int nLen;

		RAMIndex();													// Get amount of memory needed
		nLen = RAMEnd - (unsigned char*)0;
		if ((AllRAM = (unsigned char*)malloc(nLen)) == NULL) {		// Allocate memory
			return 1;
		}
		memset(AllRAM, 0, nLen);									// Initialise memory
		RAMIndex();													// Index the allocated memory
	}

	{
		SekInit(0, 0x68000);										// Allocate 68000
		SekOpen(0);

		SekSetCyclesScanline((int)(12000000.0 / NEO_HREFRESH));

		// Map 68000 memory:

		if (nCodeSize <= 0x080000) {
			SekMapMemory(Neo68KROM, 0x000000, 0x07FFFF, SM_ROM);
			SekMapMemory(Neo68KROM, 0x080000, 0x0FFFFF, SM_ROM);
		} else {
			SekMapMemory(Neo68KROM,	0x000000, 0x0FFFFF, SM_ROM);	// 68K ROM bank 1
		}
		SekMapMemory(Neo68KRAM,		0x100000, 0x10FFFF, SM_RAM);	// 68K RAM
		SekMapMemory(NeoPalSrc[0],	0x6A0000, 0x6A1FFF, SM_RAM);	// Palette RAM bank 0 always accessed here
		SekMapMemory(Neo68KBIOS,	0xC00000, 0xC7FFFF, SM_ROM);	// BIOS ROM

		SekSetReadWordHandler(0, neogeoReadWord);
		SekSetReadByteHandler(0, neogeoReadByte);
		SekSetWriteWordHandler(0, neogeoWriteWord);
		SekSetWriteByteHandler(0, neogeoWriteByte);

		SekSetWriteWordHandler(1, neogeoWriteWordSRAM);
		SekSetWriteByteHandler(1, neogeoWriteByteSRAM);

		SekMapHandler(2,			0x800000, 0x81FFFF, SM_ROM);	// Memory card
		SekMapHandler(2,			0x800000, 0x81FFFF, SM_WRITE);	//

		SekSetReadByteHandler(2, neogeoReadByteMemoryCard);
		SekSetWriteByteHandler(2, neogeoWriteByteMemoryCard);

		SekMapHandler(3,	0x400000, 0x401FFF, SM_WRITE);			// Palette

		SekSetWriteWordHandler(3, NeoPalWriteWord);
		SekSetWriteByteHandler(3, NeoPalWriteByte);

		if ((BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK) == HARDWARE_SNK_GAMBLING) {
			SekMapMemory(NeoNVRAM2,	0x200000, 0x201FFF, SM_RAM);	// 68K RAM
			
			SekMapHandler(5,	0x202000, 0x2FFFFF, SM_READ);
			SekSetReadByteHandler(5,     neogeoReadByteGambling);
			SekSetReadWordHandler(5,     neogeoReadWordGambling);
			
			if (!strcmp(BurnDrvGetTextA(DRV_NAME), "vliner") || !strcmp(BurnDrvGetTextA(DRV_NAME), "vlinero")) {
				SekMapHandler(6,	0x320000, 0x320001, SM_READ);
				SekSetReadByteHandler(6,     vliner_timing);
			}
		} else {
			if (nCodeSize <= 0x100000) {
				if (nCodeSize <= 0x080000) {
					SekMapMemory(Neo68KROM, 0x200000, 0x27FFFF, SM_ROM);
					SekMapMemory(Neo68KROM, 0x280000, 0x2FFFFF, SM_ROM);
				} else {
					SekMapMemory(Neo68KROM, 0x200000, 0x2FFFFF, SM_ROM);
				}
			}
			if (!pNeoBankswitchCallback && nCodeSize > 0x100000) {
				SekMapHandler(4,	0x200000, 0x2FFFFF, SM_WRITE);	// 68K bankswitch

				SekSetWriteWordHandler(4, neogeoWriteWordBankswitch);
				SekSetWriteByteHandler(4, neogeoWriteByteBankswitch);
			}
		}

		SekClose();
	}

	{
		// Z80 setup
		ZetInit(1);
		ZetOpen(0);

		// Work RAM
		ZetMapArea(0xF800, 0xFFFF, 0, NeoZ80RAM);
		ZetMapArea(0xF800, 0xFFFF, 1, NeoZ80RAM);
		ZetMapArea(0xF800, 0xFFFF, 2, NeoZ80RAM);

		ZetMemEnd();

		ZetSetInHandler(neogeoZ80In);
		ZetSetOutHandler(neogeoZ80Out);

		ZetClose();
	}

	bRenderLineByLine = false;

#ifdef RASTER_KLUDGE
	nScanlineOffset = 0xF8;									// correct as verified on MVS hardware
#endif

	// These games rely on reading the line counter for synchronising raster effects
	if (!strcmp(BurnDrvGetTextA(DRV_NAME), "mosyougi")) {
		bRenderLineByLine = true;

#ifdef RASTER_KLUDGE
		nScanlineOffset = 0xFB;
#endif

	}
	if (!strcmp(BurnDrvGetTextA(DRV_NAME), "neodrift")) {
		bRenderLineByLine = true;
	}
	if (!strcmp(BurnDrvGetTextA(DRV_NAME), "zedblade")) {
		bRenderLineByLine = true;
	}

#if 0
	// These have problems with raster effects
	if (!strcmp(BurnDrvGetTextA(DRV_NAME), "tpgolf")) {
		bRenderLineByLine = true;
	}
	if (!strcmp(BurnDrvGetTextA(DRV_NAME), "trally")) {
		bRenderLineByLine = true;
	}
#endif

	nNeoControlConfig = BurnDrvGetHardwareCode() & HARDWARE_SNK_CONTROLMASK;

	for (int i = 0; i < 8; i++) {
		nJoyport0[i] = 0;
		nJoyport1[i] = 1;
	}

	switch (nNeoControlConfig) {
		case HARDWARE_SNK_PADDLE:									// Two Paddles + joysticks
			nJoyport0[0] = 6;
			nJoyport1[0] = 7;
			break;
		case HARDWARE_SNK_TRACKBALL:								// Trackball controller
			nJoyport0[0] = 6;
			nJoyport0[1] = 7;
			break;
		case HARDWARE_SNK_MAHJONG:									// Mahjong controller
			nJoyport0[1] = 16;
			nJoyport0[2] = 17;
			nJoyport0[4] = 18;
			break;
	}

	nZ80Clockspeed = 4000000;

	BurnYM2610Init(8000000, YM2610ADPCMAROM, &nYM2610ADPCMASize, YM2610ADPCMBROM, &nYM2610ADPCMBSize, &neogeoFMIRQHandler, neogeoSynchroniseStream, neogeoGetTime, 0);
	BurnTimerAttachZet(nZ80Clockspeed);

	NeoInitText();
	NeoInitSprites();

	NeoInitPalette();

	uPD4990AInit(12000000);
	nPrevBurnCPUSpeedAdjust = -1;

	bMemoryCardInserted = false;
	bMemoryCardWritable = false;

	neogeoReset();							// Reset machine

	return 0;
}

int NeoExit()
{
	uPD4990AExit();

	NeoExitPalette();

	NeoExitSprites();
	NeoExitText();

	BurnYM2610Exit();

	ZetExit();								// Deallocate Z80
	SekExit();								// Deallocate 68000

	// Deallocate all used memory
	free(NeoTextROM);						// Text ROM
	NeoTextROM = NULL;
	free(NeoSpriteROM);						// Sprite ROM
	NeoSpriteROM = NULL;

	if (nYM2610ADPCMASize) {				// ADPCM data
		free(YM2610ADPCMAROM);
	}
	if (YM2610ADPCMBROM != YM2610ADPCMAROM) {
		free(YM2610ADPCMBROM);
	}
	YM2610ADPCMAROM = NULL;
	YM2610ADPCMBROM = NULL;

	Neo68KROM = NULL;						// 68000 ROM

	free(AllROM);							// Misc ROM
	AllROM = NULL;

	free(AllRAM);							// Misc RAM
	AllRAM = NULL;

	nNeoTextROMSize = -1;

	nBIOS = 9999;

	pNeoInitCallback = NULL;
	pNeoBankswitchCallback = NULL;
	pNeoScanCallback = NULL;

	return 0;
}

int NeoRender()
{
	NeoUpdatePalette();						// Update the palette
	NeoClearScreen();

	if (bNeoEnableGraphics) {
		nSliceStart = 0x10; nSliceEnd = 0xF0;
		nSliceSize = nSliceEnd - nSliceStart;

#if 0 || defined LOG_DRAW
		bprintf(PRINT_NORMAL, _T(" -- Drawing slice: %3i - %3i.\n"), nSliceStart, nSliceEnd);
#endif

		NeoRenderSprites();					// Render sprites
		NeoRenderText();					// Render text layer
	}

	return 0;
}

inline static int CheckSleep(int)
{
	return 0;
}

inline static void NeoClearOpposites(unsigned char* nJoystickInputs)
{
	if ((*nJoystickInputs & 0x03) == 0x03) {
		*nJoystickInputs &= ~0x03;
	}
	if ((*nJoystickInputs & 0x0C) == 0x0C) {
		*nJoystickInputs &= ~0x0C;
	}
}

static void NeoStandardInputs(int nBank)
{
	if (nBank) {
		NeoInput[ 8] = 0x00;					   					// Player 1
		NeoInput[ 9] = 0x00;				   						// Player 2
		NeoInput[10] = 0x00;				   						// Buttons
		NeoInput[11] = 0x00;				   						//
		for (int i = 0; i < 8; i++) {
			NeoInput[ 8] |= (NeoJoy3[i] & 1) << i;
			NeoInput[ 9] |= (NeoJoy4[i] & 1) << i;
			NeoInput[10] |= (NeoButton3[i] & 1) << i;
			NeoInput[11] |= (NeoButton4[i] & 1) << i;
		}
		NeoClearOpposites(&NeoInput[ 8]);
		NeoClearOpposites(&NeoInput[ 9]);

		if (NeoDiag[1]) {
			NeoInput[13] &= ~0x80;
		}
	} else {
		NeoInput[ 0] = 0x00;					   					// Player 1
		NeoInput[ 1] = 0x00;					   					// Player 2
		NeoInput[ 2] = 0x00;					   					// Buttons
		NeoInput[ 3] = 0x00;					   					//
		for (int i = 0; i < 8; i++) {
			NeoInput[ 0] |= (NeoJoy1[i] & 1) << i;
			NeoInput[ 1] |= (NeoJoy2[i] & 1) << i;
			NeoInput[ 2] |= (NeoButton1[i] & 1) << i;
			NeoInput[ 3] |= (NeoButton2[i] & 1) << i;
		}
		NeoClearOpposites(&NeoInput[ 0]);
		NeoClearOpposites(&NeoInput[ 1]);

		if (NeoDiag[0]) {
			NeoInput[ 5] &= ~0x80;
		}
	}
}

int NeoFrame()
{
	if (NeoReset) {							   						// Reset machine
		neogeoReset();
	}

	if (bAESBIOS && !SekReadByte(0x10fcef))	{		// AES hack
		SekWriteByte(0x108000 + 0x7cef, 0xff);
	}

	NeoInput[ 5] |= 0x80;											// Default test switches to off
	NeoInput[13] |= 0x80;											//

	switch (nNeoControlConfig) {
		case HARDWARE_SNK_PADDLE: {									// Two Paddles + joysticks

			NeoStandardInputs(0);

			// Handle analog controls
			nAnalogAxis[0] -= NeoAxis[0];
			nAnalogAxis[1] -= NeoAxis[1];
			NeoInput[6] = (nAnalogAxis[0] >> 8) & 0xFF;
			NeoInput[7] = (nAnalogAxis[1] >> 8) & 0xFF;

			break;
		}

		case HARDWARE_SNK_TRACKBALL: {								// Trackball controller
			NeoInput[1] = 0x00;				   						// Buttons
			NeoInput[2] = 0x00;					   					//
			NeoInput[3] = 0x00;					   					//
			for (int i = 0; i < 8; i++) {
				NeoInput[1] |= (NeoJoy2[i] & 1) << i;
				NeoInput[2] |= (NeoButton1[i] & 1) << i;
				NeoInput[3] |= (NeoButton2[i] & 1) << i;
			}
			// Handle analog controls
			nAnalogAxis[0] += NeoAxis[0];
			nAnalogAxis[1] += NeoAxis[1];
			NeoInput[6] = (nAnalogAxis[0] >> 8) & 0xFF;
			NeoInput[7] = (nAnalogAxis[1] >> 8) & 0xFF;

			if (NeoDiag[0]) {
				NeoInput[5] &= ~0x80;
			}

			break;
		}

		case HARDWARE_SNK_4_JOYSTICKS: {							// Four joystick controllers

			NeoStandardInputs(0);
			NeoStandardInputs(1);

			break;
		}

		case HARDWARE_SNK_MAHJONG: {								// Mahjong controller

			NeoStandardInputs(0);

			NeoInput[16] = 0x00;
			NeoInput[17] = 0x00;
			NeoInput[18] = 0x00;
			for (int i = 0; i < 7; i++) {
				NeoInput[16] |= (NeoButton1[i +  8] & 1) << i;
				NeoInput[17] |= (NeoButton1[i + 16] & 1) << i;
				NeoInput[18] |= (NeoButton1[i + 24] & 1) << i;
			}

			break;
		}

		case HARDWARE_SNK_GAMBLING: {								// Gambling configuration

			// Special inputs
			NeoStandardInputs(0);
			break;
		}

		default: {													// Two joystick controllers
			NeoStandardInputs(0);
		}
	}

	bMemoryCardWritable = (NeoSystem & 0x80);

	if (bMemoryCardInserted) {
		NeoInput[2] |= 0xB0;
		if (bMemoryCardWritable) {
			NeoInput[2] |= 0x40;
		}
	} else {
		NeoInput[2] &= ~0xF0;
	}

	if (!bAESBIOS) {												// Report the appropriate number of slots on MVS hardware
		if (nNeoNumSlots > 2) {
			NeoInput[ 5] |= 0x40;
			NeoInput[13] |= 0x40;
			if (nNeoNumSlots > 4) {
				NeoInput[ 5] &= ~0x20;
				NeoInput[13] &= ~0x20;
			}
		}
	}

	if (OldDebugDip[0] != NeoDebugDip[0]) {
		SekWriteByte(SekReadLong(0x010E) + 0, NeoDebugDip[0]);
		OldDebugDip[0] = NeoDebugDip[0];
	}
	if (OldDebugDip[1] != NeoDebugDip[1]) {
		SekWriteByte(SekReadLong(0x010E) + 1, NeoDebugDip[1]);
		OldDebugDip[1] = NeoDebugDip[1];
	}

	if (nPrevBurnCPUSpeedAdjust != nBurnCPUSpeedAdjust) {
		// 68K CPU clock is 12MHz, modified by nBurnCPUSpeedAdjust
		nCyclesTotal[0] = (int)((long long)12000000 * nBurnCPUSpeedAdjust / (256.0 * NEO_VREFRESH));
#ifdef Z80_SPEED_ADJUST
		// Z80 CPU clock always 68K / 3
		nCyclesTotal[1] = nCyclesTotal[0] / 3;
		nZ80Clockspeed = (int)((long long)4000000 * nBurnCPUSpeedAdjust / 256);
		BurnTimerAttachZet(nZ80Clockspeed);
#else
		// Z80 CPU clock is always 4MHz
		nCyclesTotal[1] = 4000000.0 / NEO_VREFRESH;
#endif
		// 68K cycles executed each scanline
		SekSetCyclesScanline((int)(12000000.0 * nBurnCPUSpeedAdjust / (256.0 * NEO_HREFRESH)));

		// uPD499A ticks per second (same as 68K clock)
		uPD499ASetTicks((long long)12000000 * nBurnCPUSpeedAdjust / 256);

		nPrevBurnCPUSpeedAdjust = nBurnCPUSpeedAdjust;
	}

	// If the watchdog isn't reset every 8 frames, reset the system
	// This can't be 100% accurate, as the 68000 instruction timings are not 100%
	if (nNeoWatchdog > nCyclesTotal[0] * 8) {
		bprintf(PRINT_IMPORTANT, _T(" ** Watchdog triggered system reset\n"));
		neogeoReset();
	}

	bRenderImage = false;
	bForceUpdateOnStatusRead = false;

	if (pBurnDraw) {
		NeoUpdatePalette();											// Update the palette
		NeoClearScreen();
	}
	nSliceEnd = 0x10;

	SekNewFrame();
	ZetNewFrame();

	SekOpen(0);
	ZetOpen(0);

	// Compensate for extra cycles executed
	SekIdle(nCyclesExtra[0]);
	ZetIdle(nCyclesExtra[1]);

	nuPD4990ATicks = nCyclesExtra[0];

	// Run 68000

	nCyclesSegment = nSekCyclesScanline * 22;
	while (SekTotalCycles() < nCyclesSegment) {

		if ((nIRQ2Control & 0x10) && (nIRQCycles < NO_IRQ_PENDING) && (SekTotalCycles() >= nIRQCycles)) {
			nIRQAcknowledge &= ~2;
			SekSetIRQLine(2, SEK_IRQSTATUS_ACK);

#if 0 || defined LOG_IRQ2
			bprintf(PRINT_NORMAL, _T("  - IRQ triggered (line %3i, ctrl).\n"), SekCurrentScanline());
#endif

			if (nIRQ2Control & 0x80) {
				nIRQCycles += NeoConvertIRQPosition(nIRQ2Offset + 1);

#if 0 || defined LOG_IRQ2
				bprintf(PRINT_NORMAL, _T("  - IRQ Line -> %3i (at line %3i, autoload).\n"), nIRQCycles / nSekCyclesScanline, SekCurrentScanline());
#endif

			}
		}

		if (nCyclesSegment < nIRQCycles || SekTotalCycles() >= nIRQCycles) {
			SekRun(nCyclesSegment - SekTotalCycles());
		} else {
			SekRun(nIRQCycles - SekTotalCycles());
		}
	}

	bRenderImage = pBurnDraw != NULL && bNeoEnableGraphics;
	bForceUpdateOnStatusRead = bRenderImage && bRenderLineByLine;
	bForcePartialRender = false;

	// Display starts here

	nCyclesVBlank = nSekCyclesScanline * 248;
	if (bRenderLineByLine) {
		int nLastIRQ = nIRQCycles - 1;
		while (SekTotalCycles() < nCyclesVBlank) {

			if (bForcePartialRender) {
				nSliceStart = nSliceEnd;
				nSliceEnd = SekCurrentScanline() - 6;
			}

			if ((nIRQ2Control & 0x10) && (nIRQCycles < NO_IRQ_PENDING) && (nLastIRQ < nIRQCycles) && (SekTotalCycles() >= nIRQCycles)) {
				nLastIRQ = nIRQCycles;
				nIRQAcknowledge &= ~2;
				SekSetIRQLine(2, SEK_IRQSTATUS_ACK);
#if 0 || defined LOG_IRQ2
				bprintf(PRINT_NORMAL, _T("  - IRQ triggered (line %3i).\n"), SekCurrentScanline());
#endif

				if (nIRQ2Control & 0x80) {
					nIRQCycles += NeoConvertIRQPosition(nIRQ2Offset + 1);
#if 0 || defined LOG_IRQ2
					bprintf(PRINT_NORMAL, _T("  - IRQ Line -> %3i (at line %3i, autoload).\n"), nIRQCycles / nSekCyclesScanline, SekCurrentScanline());
#endif

				}

				bForcePartialRender = bRenderImage;
				nSliceEnd++;
			}

			if (bForcePartialRender) {

				if (nSliceEnd > 240) {
					nSliceEnd = 240;
				}
				nSliceSize = nSliceEnd - nSliceStart;
				if (nSliceSize > 0) {

#if 0 || defined LOG_DRAW
					bprintf(PRINT_NORMAL, _T(" -- Drawing slice: %3i - %3i.\n"), nSliceStart, nSliceEnd);
#endif

					NeoRenderSprites();								// Render sprites
				}
			}

			bForcePartialRender = false;

			if (SekTotalCycles() >= nCyclesSegment) {
				nCyclesSegment += nSekCyclesScanline;
			}
			if (nCyclesSegment < nIRQCycles || SekTotalCycles() >= nIRQCycles) {
				SekRun(nCyclesSegment - SekTotalCycles());
			} else {
				SekRun(nIRQCycles - SekTotalCycles());
			}
		}
	} else {
		nCyclesSegment = nCyclesVBlank;
		while (SekTotalCycles() < nCyclesVBlank) {

			if ((nIRQ2Control & 0x10) && (nIRQCycles < NO_IRQ_PENDING) && (SekTotalCycles() >= nIRQCycles)) {
				nIRQAcknowledge &= ~2;
				SekSetIRQLine(2, SEK_IRQSTATUS_ACK);

#if 0 || defined LOG_IRQ2
				bprintf(PRINT_NORMAL, _T("  - IRQ triggered (line %3i).\n"), SekCurrentScanline());
#endif

				if (nIRQ2Control & 0x80) {
					nIRQCycles += NeoConvertIRQPosition(nIRQ2Offset + 1);

#if 0 || defined LOG_IRQ2
					bprintf(PRINT_NORMAL, _T("  - IRQ Line -> %3i (at line %3i, autoload).\n"), nIRQCycles / nSekCyclesScanline, SekCurrentScanline());
#endif

				}

				bForcePartialRender = bRenderImage;
			}

			if (bForcePartialRender) {

				nSliceStart = nSliceEnd;
				nSliceEnd = SekCurrentScanline() - 5;

				if (nSliceEnd > 240) {
					nSliceEnd = 240;
				}
				nSliceSize = nSliceEnd - nSliceStart;
				if (nSliceSize > 0) {

#if 0 || defined LOG_DRAW
					bprintf(PRINT_NORMAL, _T(" -- Drawing slice: %3i - %3i.\n"), nSliceStart, nSliceEnd);
#endif

					NeoRenderSprites();								// Render sprites
				}
			}

			bForcePartialRender = false;

			if (nCyclesSegment < nIRQCycles || SekTotalCycles() >= nIRQCycles) {
				SekRun(nCyclesSegment - SekTotalCycles());
			} else {
				SekRun(nIRQCycles - SekTotalCycles());
			}
		}
	}

	if (bRenderImage) {
		if (nSliceEnd < 240) {
			nSliceStart = nSliceEnd;
			nSliceEnd = 240;
			nSliceSize = nSliceEnd - nSliceStart;

#if 0 || defined LOG_DRAW
			bprintf(PRINT_NORMAL, _T(" -- Drawing slice: %3i - %3i.\n"), nSliceStart, nSliceEnd);
#endif

			NeoRenderSprites();										// Render sprites
		}
		NeoRenderText();											// Render text layer
	}

	nIRQAcknowledge &= ~4;
	SekSetIRQLine(1, SEK_IRQSTATUS_ACK);

#if 0 || defined LOG_IRQ2
	bprintf(PRINT_NORMAL, _T("  - VBLank.\n"));
#endif

	// set IRQ scanline at line 248
	if (nIRQ2Control & 0x40) {
		if (NeoConvertIRQPosition(nIRQ2Offset) < NO_IRQ_PENDING) {
			nIRQCycles = nCyclesSegment + NeoConvertIRQPosition(nIRQ2Offset);
		}

#if 0 || defined LOG_IRQ2
		bprintf(PRINT_NORMAL, _T("  - IRQ Line -> %3i (at line %3i, VBlank).\n"), nIRQCycles / nSekCyclesScanline, 248);
#endif

	}

	nCyclesSegment = nCyclesTotal[0];
	while (SekTotalCycles() < nCyclesSegment) {

		if ((nIRQ2Control & 0x10) && (nIRQCycles < NO_IRQ_PENDING) && (SekTotalCycles() >= nIRQCycles)) {
			nIRQAcknowledge &= ~2;
			SekSetIRQLine(2, SEK_IRQSTATUS_ACK);

#if 0 || defined LOG_IRQ2
			bprintf(PRINT_NORMAL, _T("  - IRQ triggered (line %3i).\n"), SekCurrentScanline());
#endif

			if (nIRQ2Control & 0x80) {
				nIRQCycles += NeoConvertIRQPosition(nIRQ2Offset + 1);

#if 0 || defined LOG_IRQ2
				bprintf(PRINT_NORMAL, _T("  - IRQ Line -> %3i (at line %3i, autoload).\n"), nIRQCycles / nSekCyclesScanline, SekCurrentScanline());
#endif

			}
		}

		if (nCyclesSegment < nIRQCycles || SekTotalCycles() >= nIRQCycles) {
			SekRun(nCyclesSegment - SekTotalCycles());
		} else {
			SekRun(nIRQCycles - SekTotalCycles());
		}
	}

	if (nIRQCycles < NO_IRQ_PENDING) {
		nIRQCycles -= nCyclesTotal[0];
		if (nIRQCycles < 0) {
			nIRQCycles = NO_IRQ_PENDING;

#if 0 || defined LOG_IRQ2
		} else {
			bprintf(PRINT_NORMAL, _T("  - IRQ Line past screen end (IRQControl: %02X, line -> %3i).\n"), nIRQ2Control, nIRQCycles / nSekCyclesScanline);
#endif

		}
	}
//	bprintf(PRINT_NORMAL, " -- IRQControl: %02X, nIRQCycles / nSekCyclesScanline: %3i.\n", nIRQ2Control, nIRQCycles / nSekCyclesScanline);

	// Update the sound until the end of the frame

	nCycles68KSync = SekTotalCycles();
	BurnTimerEndFrame(nCyclesTotal[1]);
	BurnYM2610Update(pBurnSoundOut, nBurnSoundLen);

	// Update the uPD4990 until the end of the frame
	uPD4990AUpdate(SekTotalCycles() - nuPD4990ATicks);

	// Handle the watchdog
	nNeoWatchdog += SekTotalCycles();

	// Remember extra cycles executed
	nCyclesExtra[0] = SekTotalCycles() - nCyclesTotal[0];
	nCyclesExtra[1] = ZetTotalCycles() - nCyclesTotal[1];

	ZetClose();
	SekClose();

	if ((nIRQ2Control & 8) == 0) {
		if (++nSpriteFrameTimer > nSpriteFrameSpeed) {
			nSpriteFrameTimer = 0;
			nNeoSpriteFrame++;
		}
	}

	return 0;
}

void NeoZ80Cmd(UINT16 sound_code) {

//	SendSoundCommand(sound_code);
	nSoundLatch = sound_code;
	ZetNmi();

#if 1 && defined USE_SPEEDHACKS
	neogeoSynchroniseZ80(0x0200);
#endif

}
