
#include "pgm.h"
#include "uni_vmm.h"
#include "arm7_intf.h"


#define M68K_CYCS_PER_FRAME	((20000000 * 100) / nBurnFPS)
#define Z80_CYCS_PER_FRAME	(( 8468000 * 100) / nBurnFPS)
#define ARM7_CYCS_PER_FRAME	((20000000 * 100) / nBurnFPS)

#define	PGM_INTER_LEAVE	2

#define M68K_CYCS_PER_INTER	(M68K_CYCS_PER_FRAME / PGM_INTER_LEAVE)
#define Z80_CYCS_PER_INTER	(Z80_CYCS_PER_FRAME  / PGM_INTER_LEAVE)
#define ARM7_CYCS_PER_INTER	(ARM7_CYCS_PER_FRAME / PGM_INTER_LEAVE)

unsigned char PgmJoy1[8] = {0,0,0,0,0,0,0,0};
unsigned char PgmJoy2[8] = {0,0,0,0,0,0,0,0};
unsigned char PgmJoy3[8] = {0,0,0,0,0,0,0,0};
unsigned char PgmJoy4[8] = {0,0,0,0,0,0,0,0};
unsigned char PgmBtn1[8] = {0,0,0,0,0,0,0,0};
unsigned char PgmBtn2[8] = {0,0,0,0,0,0,0,0};
unsigned char PgmInput[9] = {0,0,0,0,0,0,0,0,0};
static int PgmCoinTimer[4];
static int coin_counter = 0;
unsigned char PgmReset = 0;

int nPGM68KROMLen = 0;
int nPGMTileROMLen = 0;
int nPGMSPRColROMLen = 0;
int nPGMSPRMaskROMLen = 0;
int nPGMSNDROMLen = 0;
int nPGMExternalARMLen = 0;

int nPGMEnableIRQ4 = 0;

unsigned int *RamBg, *RamTx, *RamCurPal;
unsigned short *RamRs, *RamPal, *RamVReg, *RamSpr;
static unsigned char *RamZ80;
unsigned char *PGM68KRAM;

static unsigned char *Mem = NULL, *MemEnd = NULL;
static unsigned char *RamStart, *RamEnd;
static unsigned char *PGMSprBuf;
unsigned char *PGMUSER0 = NULL; // User regions
unsigned char *PGM68KBIOS, *PGM68KROM, *PGMTileROM, *PGMTileROMExp, *PGMSPRColROM, *PGMSPRMaskROM, *PGMARMROM;
unsigned char *PGMARMRAM0, *PGMARMRAM1, *PGMARMRAM2, *PGMARMShareRAM, *PGMARMShareRAM2;

int nEnableArm7 = 0;
int nPGMArm7Type = 0;

unsigned char nPgmPalRecalc = 0;
unsigned char nPgmZ80Work = 0;

void (*pPgmResetCallback)() = NULL;
void (*pPgmInitCallback)() = NULL;
void (*pPgmProtCallback)() = NULL;
int (*pPgmScanCallback)(int, int*) = NULL;

static int nPgmCurrentBios = -1;

static int pgmMemIndex()
{
	unsigned char *Next; Next = Mem;

	PGM68KBIOS	= Next;		// 68000 BIOS

	if (!(BurnDrvGetHardwareCode() & HARDWARE_IGS_JAMMAPCB_NO68KBIOS))
	{
		Next += 0x0080000;
	}

	PGM68KROM	= Next; Next += nPGM68KROMLen;	// 68000 PRG (max 0x400000)

	if (BurnDrvGetHardwareCode() & HARDWARE_IGS_USE_ARM_CPU) {
		PGMARMROM	= Next; Next += 0x0004000;
	}

	RamStart	= Next;

	if (BurnDrvGetHardwareCode() & HARDWARE_IGS_USE_ARM_CPU) {
		PGMARMShareRAM	= Next; Next += 0x0000400;
		PGMARMRAM0	= Next; Next += 0x0000400; // minimum map is 0x1000 - should be 0x400
		PGMARMRAM2	= Next; Next += 0x0000400; // minimum map is 0x1000 - should be 0x400
	}

	PGM68KRAM	= Next; Next += 0x0020000;						// 128K Main RAM
	RamBg		= (unsigned int *) Next; Next += 0x0004000;
	RamTx		= (unsigned int *) Next; Next += 0x0002000;
	RamRs		= (unsigned short *) Next; Next += 0x0000800;	// Row Scroll
	RamPal		= (unsigned short *) Next; Next += 0x0001200;	// Palette R5G5B5
	RamVReg		= (unsigned short *) Next; Next += 0x0010000;	// Video Regs inc. Zoom Table
	RamZ80		= Next; Next += 0x0010000;
	PGMSprBuf	= Next; Next += 0xa00;
	
	RamEnd		= Next;
	
	RamSpr		= (unsigned short *) PGMSprBuf;	// first 0xa00 of main ram = sprites, seems to be buffered, DMA? 
	RamCurPal	= (unsigned int *) Next; Next += 0x00900 * sizeof(unsigned int);
	
	MemEnd		= Next;
	return 0;
}

static int pgmGetRoms(bool bLoad)
{
	char* pRomName;
	struct BurnRomInfo ri;
	struct BurnRomInfo pi;

	unsigned char *PGM68KROMLoad = PGM68KROM;
	unsigned char *PGMTileROMLoad = PGMTileROMExp + 0x180000;
	unsigned char *PGMSPRMaskROMLoad = PGMSPRMaskROM;
	unsigned char *PGMSNDROMLoad = ICSSNDROM + 0x200000;

	for (int i = 0; !BurnDrvGetRomName(&pRomName, i, 0); i++) {

		BurnDrvGetRomInfo(&ri, i);

		if ((ri.nType & BRF_PRG) && (ri.nType & 0x0f) == 1)
		{
			if (bLoad) {
				BurnDrvGetRomInfo(&pi, i+1);

				if (ri.nLen == 0x80000 && pi.nLen == 0x80000)
				{
					BurnLoadRom(PGM68KROMLoad + 0, i + 0, 2);
					BurnLoadRom(PGM68KROMLoad + 1, i + 1, 2);
					PGM68KROMLoad += pi.nLen;
					i += 1;
				}
				else
				{
					BurnLoadRom(PGM68KROMLoad, i, 1);
				}
				PGM68KROMLoad += ri.nLen;				
			} else {
				nPGM68KROMLen += ri.nLen;
			}
			continue;
		}

		if ((ri.nType & BRF_GRA) && (ri.nType & 0x0f) == 2)
		{
			if (bLoad) {
				BurnLoadRom(PGMTileROMLoad, i, 1);
				PGMTileROMLoad += ri.nLen;
			} else {
				nPGMTileROMLen += ri.nLen;
			}
			continue;
		}

		if ((ri.nType & BRF_GRA) && (ri.nType & 0x0f) == 4)
		{
			if (bLoad) {
				BurnLoadRom(PGMSPRMaskROMLoad, i, 1);
				PGMSPRMaskROMLoad += ri.nLen;
			} else {
				nPGMSPRMaskROMLen += ri.nLen;
			}
			continue;
		}

		if ((ri.nType & BRF_SND) && (ri.nType & 0x0f) == 5)
		{
			if (bLoad) {
				BurnLoadRom(PGMSNDROMLoad, i, 1);
				PGMSNDROMLoad += ri.nLen;
			} else {
				nPGMSNDROMLen += ri.nLen;
			}
			continue;
		}

		if ((ri.nType & BRF_PRG) && (ri.nType & 0x0f) == 7)
		{
			if (bLoad) {
				if (BurnDrvGetHardwareCode() & HARDWARE_IGS_USE_ARM_CPU) {
					BurnLoadRom(PGMARMROM, i, 1);
				}
			}
			continue;
		}

#ifdef ARM7
		if ((ri.nType & BRF_PRG) && (ri.nType & 0x0f) == 8)
		{
			if (BurnDrvGetHardwareCode() & HARDWARE_IGS_USE_ARM_CPU) {
				if (bLoad) {
					BurnLoadRom(PGMUSER0Load, i, 1);
					PGMUSER0Load += ri.nLen;
				} else {
					nPGMExternalARMLen += ri.nLen;
				}
			}
			continue;
		}
#endif

		if ((ri.nType & BRF_PRG) && (ri.nType & 0x0f) == 9)
		{
			if (bLoad) {
				BurnLoadRom(PGM68KRAM, i, 1);
			}
		}
	}

	if (!bLoad) nPGMTileROMLen += 0x180000;
	if (!bLoad) nPGMSNDROMLen += 0x200000;

	return 0;
}

/* Calendar Emulation */

static unsigned char CalVal, CalMask, CalCom=0, CalCnt=0;

static unsigned char bcd(unsigned char data)
{
	return ((data / 10) << 4) | (data % 10);
}

static unsigned char pgm_calendar_r()
{
	unsigned char calr;
	calr = (CalVal & CalMask) ? 1 : 0;
	CalMask <<= 1;
	return calr;
}

static void pgm_calendar_w(unsigned short data)
{
	// initialize the time, otherwise it crashes
	time_t nLocalTime = time(NULL);
	tm* tmLocalTime = localtime(&nLocalTime);

	CalCom <<= 1;
	CalCom |= data & 1;
	++CalCnt;
	if(CalCnt==4)
	{
		CalMask = 1;
		CalVal = 1;
		CalCnt = 0;
		
		switch(CalCom & 0xf)
		{
			case 1: case 3: case 5: case 7: case 9: case 0xb: case 0xd:
				CalVal++;
				break;
			case 0:
				CalVal=bcd(tmLocalTime->tm_wday); //??
				break;
			case 2:  //Hours
				CalVal=bcd(tmLocalTime->tm_hour);
				break;
			case 4:  //Seconds
				CalVal=bcd(tmLocalTime->tm_sec);
				break;
			case 6:  //Month
				CalVal=bcd(tmLocalTime->tm_mon + 1); //?? not bcd in MVS
				break;
			case 8:
				CalVal=0; //Controls blinking speed, maybe milliseconds
				break;
			case 0xa: //Day
				CalVal=bcd(tmLocalTime->tm_mday);
				break;
			case 0xc: //Minute
				CalVal=bcd(tmLocalTime->tm_min);
				break;
			case 0xe:  //Year
				CalVal=bcd(tmLocalTime->tm_year % 100);
				break;
			case 0xf:  //Load Date
				tmLocalTime = localtime(&nLocalTime);
				break;
		}
	}
}


inline static unsigned int CalcCol(unsigned short nColour)
{
	int r, g, b;

	r = (nColour & 0x001F) << 3;	// Red
	r |= r >> 5;
	g = (nColour & 0x03E0) >> 2;  // Green
	g |= g >> 5;
	b = (nColour & 0x7C00) >> 7;	// Blue
	b |= b >> 5;

	return BurnHighCol(b, g, r, 0);
}

/* memory handler */

unsigned char __fastcall PgmReadByte(unsigned int sekAddress)
{
	switch (sekAddress) {
		//case 0xC00005:
		//	return ics2115_soundlatch_r(1);

		case 0xC00007:
			return pgm_calendar_r();

//		default:
//			bprintf(PRINT_NORMAL, _T("Attempt to read byte value of location %x\n"), sekAddress);
	}

	return 0;
}

unsigned short __fastcall PgmReadWord(unsigned int sekAddress)
{
	switch (sekAddress) {
		case 0xC00004:
			return ics2115_soundlatch_r(1);
		case 0xC08000:	// p1+p2 controls
			return ~(PgmInput[0] | (PgmInput[1] << 8));
		case 0xC08002:  // p3+p4 controls
			return ~(PgmInput[2] | (PgmInput[3] << 8));
		case 0xC08004:  // extra controls
			return ~(PgmInput[4] | (PgmInput[5] << 8));
		case 0xC08006: // dipswitches
			return ~(PgmInput[6]) | 0xffe0;

//		default:
//			bprintf(PRINT_NORMAL, _T("Attempt to read word value of location %x\n"), sekAddress);
	}
	return 0;
}

void __fastcall PgmWriteWord(unsigned int sekAddress, unsigned short wordValue)
{
	switch (sekAddress) {
			
		case 0x700006:	// Watchdog ???
			//bprintf(PRINT_NORMAL, _T("Watchdog write %04x\n"), wordValue);
			break;
			
		case 0xC00002:	// m68k_l1_w
			ics2115_soundlatch_w(0, wordValue);
			if(nPgmZ80Work) ZetNmi();
			break;
		case 0xC00004:	// soundlatch2_word_w
			ics2115_soundlatch_w(1, wordValue);
			break;
		case 0xC00006:
			pgm_calendar_w(wordValue);
			break;
		case 0xC00008:	// z80_reset_w
//			bprintf(PRINT_NORMAL, _T("z80_reset_w(%04x)  %4.1f%%\n"), wordValue, 6.0 * SekTotalCycles() / 20000.0);
			if (wordValue == 0x5050) {
				ics2115_reset();
				nPgmZ80Work = 1;
				
				ZetReset();
			} else {
				/* this might not be 100% correct, but several of the games (ddp2, puzzli2 etc. expect the z80 to be turned
           		   off during data uploads, they write here before the upload */
				nPgmZ80Work = 0;
			}
			break;
		case 0xC0000A:	// z80_ctrl_w
			break;
		case 0xC0000C:	// soundlatch3_word_w
			ics2115_soundlatch_w(2, wordValue);
			break;	
		
		case 0xC08006:
			coin_counter = wordValue;
			break;

//		default:
//			bprintf(PRINT_NORMAL, _T("Attempt to write word value %x to location %x\n"), wordValue, sekAddress);
	}
}

void __fastcall PgmPalWriteWord(unsigned int sekAddress, unsigned short wordValue)
{
	// 0xA00000 ~ 0xA011FF: 2304 color Palette (X1R5G5B5)
	sekAddress -= 0xA00000;
	sekAddress >>= 1;
	RamPal[sekAddress] = wordValue;
	RamCurPal[sekAddress] = CalcCol(wordValue);
}

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

//		default:
//			bprintf(PRINT_NORMAL, _T("Attempt to read byte value of location %x\n"), sekAddress);
	}
	return 0;
}

unsigned short __fastcall PgmZ80ReadWord(unsigned int sekAddress)
{
	sekAddress -= 0xC10000;
	return (RamZ80[sekAddress] << 8) | RamZ80[sekAddress+1];
}

void __fastcall PgmZ80WriteWord(unsigned int sekAddress, unsigned short wordValue)
{
	sekAddress -= 0xC10000;
	RamZ80[sekAddress] = wordValue >> 8;
	RamZ80[sekAddress+1] = wordValue & 0xFF;
}

unsigned char __fastcall PgmZ80PortRead(unsigned short p)
{
	switch (p >> 8) {
		case 0x80:
			return ics2115read(p & 0xff);
		case 0x81:
			return ics2115_soundlatch_r(2) & 0xff;
		case 0x82:
			return ics2115_soundlatch_r(0) & 0xff;
		case 0x84:
			return ics2115_soundlatch_r(1) & 0xff;
//		default:
//			bprintf(PRINT_NORMAL, _T("Z80 Attempt to read port %04x\n"), p);
	}
	return 0;
}

void __fastcall PgmZ80PortWrite(unsigned short p, unsigned char v)
{
	switch (p >> 8) {
		case 0x80:
			ics2115write(p&0xff, v);
			break;
		case 0x81:
			ics2115_soundlatch_w(2, v);
			break;
		case 0x82:
			ics2115_soundlatch_w(0, v);
			break;	
		case 0x84:
			ics2115_soundlatch_w(1, v);
			break;
//		default:
//			bprintf(PRINT_NORMAL, _T("Z80 Attempt to write %02x to port %04x\n"), v, p);
	}
}

int PgmDoReset()
{
	if (nPgmCurrentBios != PgmInput[8]) {	// Load the 68k bios
		if (!(BurnDrvGetHardwareCode() & HARDWARE_IGS_JAMMAPCB_NO68KBIOS)) { // ketsui, espgal
			nPgmCurrentBios = (BurnDrvGetHardwareCode() & HARDWARE_IGS_JAMMAPCB) ? 0 : PgmInput[8]; // ddp3,thegladpcb : everything else
			BurnLoadRom(PGM68KBIOS, 0x00082 + nPgmCurrentBios, 1);	// 68k bios
		}
	}

	memset (PgmCoinTimer, 0, 4 * sizeof(int));

	SekOpen(0);
	SekReset();
	SekClose();

	ZetOpen(0);
	nPgmZ80Work = 0;
	ZetReset();
	ZetClose();	

	if (nEnableArm7) {
		Arm7Open(0);
		Arm7Reset();
		Arm7Close();
	}

	ics2115_reset();

	if (pPgmResetCallback) {
		pPgmResetCallback();
	}

	return 0;
}

static void expand_gfx()
{
	memcpy (PGMTileROM, PGMTileROMExp, 0x200000);

	for (int i = nPGMTileROMLen/5-1; i >= 0 ; i --) {
		PGMTileROMExp[0+8*i] = ((PGMTileROMExp[0+5*i] >> 0) & 0x1f);
		PGMTileROMExp[1+8*i] = ((PGMTileROMExp[0+5*i] >> 5) & 0x07) | ((PGMTileROMExp[1+5*i] << 3) & 0x18);
		PGMTileROMExp[2+8*i] = ((PGMTileROMExp[1+5*i] >> 2) & 0x1f );
		PGMTileROMExp[3+8*i] = ((PGMTileROMExp[1+5*i] >> 7) & 0x01) | ((PGMTileROMExp[2+5*i] << 1) & 0x1e);
		PGMTileROMExp[4+8*i] = ((PGMTileROMExp[2+5*i] >> 4) & 0x0f) | ((PGMTileROMExp[3+5*i] << 4) & 0x10);
		PGMTileROMExp[5+8*i] = ((PGMTileROMExp[3+5*i] >> 1) & 0x1f );
		PGMTileROMExp[6+8*i] = ((PGMTileROMExp[3+5*i] >> 6) & 0x03) | ((PGMTileROMExp[4+5*i] << 2) & 0x1c);
		PGMTileROMExp[7+8*i] = ((PGMTileROMExp[4+5*i] >> 3) & 0x1f );
	}
}

static void expand_colgfx(unsigned char *src, int len)
{
	for (int cnt = (len / 2)-1; cnt >= 0; cnt--)
	{
		unsigned short colpack = ((src[cnt*2]) | (src[cnt*2+1] << 8));
		src[cnt*3+0] = (colpack >> 0 ) & 0x1f;
		src[cnt*3+1] = (colpack >> 5 ) & 0x1f;
		src[cnt*3+2] = (colpack >> 10) & 0x1f;
	}
}

static int pgmGetSpriteColRoms(bool bLoad)
{
	char* pRomName;
	struct BurnRomInfo ri;
	int prevlen = 0;

	for (int i = 0; !BurnDrvGetRomName(&pRomName, i, 0); i++) {

		BurnDrvGetRomInfo(&ri, i);

		if ((ri.nType & BRF_GRA) && (ri.nType & 7) == 3)
		{
			if (bLoad) {
				int len = ri.nLen;

				unsigned char *tmp = (unsigned char*)malloc((len * 3) / 2);

				BurnLoadRom(tmp, i, 1);

				// fix for 2x size b060x rom
               			if (strcmp(BurnDrvGetTextA(DRV_NAME), "kovsh") == 0 ||
					strcmp(BurnDrvGetTextA(DRV_NAME), "kovsh103") == 0) {
					if (len == 0x400000 && prevlen == 0x800000) {
						len -= 0x200000;
					}
				}

				prevlen = len;

				UniversalVMMInitBlockAdd(tmp, 0, len);

				expand_colgfx(tmp, len);

				UniversalVMMInitBlockAdd(tmp, 1, (len * 3)/2);

				free (tmp);

			} else {
				nPGMSPRColROMLen += ri.nLen;
			}
			continue;
		}
	}

	return 0;
}

int pgmInit()
{
	Mem = NULL;

	{
		pgmGetSpriteColRoms(false);

		UniversalVMMInitBlockStart(0, 0x400000, 1024 * 4, 120);
		UniversalVMMInitBlockStart(1, 0x100000, 1024 * 1, 30);

		pgmGetSpriteColRoms(true);

		UniversalVMMInitBlockFinish(0);
		UniversalVMMInitBlockFinish(1);

		nPGMSPRColROMLen = (nPGMSPRColROMLen * 3) / 2;
	}

	pgmGetRoms(false);

	nICSSNDROMMask = 1;
	while (nICSSNDROMMask < nPGMSNDROMLen) nICSSNDROMMask <<= 1;
	nICSSNDROMMask -= 1;

	PGMTileROM      = (unsigned char*)malloc(0x200000);		// 8x8 Text Tiles + 32x32 BG Tiles
	PGMTileROMExp   = (unsigned char*)malloc((nPGMTileROMLen / 5) * 8);	// Expanded 8x8 Text Tiles and 32x32 BG Tiles
	PGMSPRMaskROM	= (unsigned char*)malloc(nPGMSPRMaskROMLen);
	ICSSNDROM	= (unsigned char*)malloc(nPGMSNDROMLen);

	pgmMemIndex();
	int nLen = MemEnd - (unsigned char *)0;
	if ((Mem = (unsigned char *)malloc(nLen)) == NULL) return 1;
	memset(Mem, 0, nLen);
	pgmMemIndex();

	// load bios roms
	BurnLoadRom(PGMTileROMExp,	0x00080, 1);	// Bios Text and Tiles
	BurnLoadRom(ICSSNDROM,		0x00081, 1);	// Bios Intro Sounds

	pgmGetRoms(true);

	expand_gfx();

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

		// ketsui and espgaluda
		if (BurnDrvGetHardwareCode() & HARDWARE_IGS_JAMMAPCB_NO68KBIOS)
		{
			SekMapMemory(PGM68KROM,			0x000000, (nPGM68KROMLen-1), SM_ROM);			// 68000 ROM (no bios)
		}
		else
		{
			SekMapMemory(PGM68KBIOS,		0x000000, 0x01ffff, SM_ROM);				// 68000 BIOS
			SekMapMemory(PGM68KROM,			0x100000, (nPGM68KROMLen-1)+0x100000, SM_ROM);		// 68000 ROM
		}

		// Ripped from FBA Shuffle. Thanks guys! :)
                for (int i = 0; i < 0x100000; i+=0x20000) {           // Main Ram + Mirrors...
                        SekMapMemory(PGM68KRAM,            	0x800000 | i, 0x81ffff | i, SM_RAM);
                }

                for (int i = 0; i < 0x100000; i+=0x08000) {          // Video Ram + Mirrors...
                        SekMapMemory((unsigned char *)RamBg,    0x900000 | i, 0x903fff | i, SM_RAM);
                        SekMapMemory((unsigned char *)RamTx,    0x904000 | i, 0x905fff | i, SM_RAM);
                        SekMapMemory((unsigned char *)RamRs,    0x907000 | i, 0x9077ff | i, SM_RAM);
                }

		SekMapMemory((unsigned char *)RamPal,		0xA00000, 0xA011FF, SM_ROM);
		SekMapMemory((unsigned char *)RamVReg,		0xB00000, 0xB0FFFF, SM_RAM);
		
		SekMapHandler(1,				0xA00000, 0xA011FF, SM_WRITE);
		SekMapHandler(2,				0xC10000, 0xC1FFFF, SM_READ | SM_WRITE);
		
		SekSetReadWordHandler(0, PgmReadWord);
		SekSetReadByteHandler(0, PgmReadByte);
		SekSetWriteWordHandler(0, PgmWriteWord);
//		SekSetWriteByteHandler(0, PgmWriteByte);
		
		SekSetWriteWordHandler(1, PgmPalWriteWord);
		
		SekSetReadWordHandler(2, PgmZ80ReadWord);
//		SekSetReadByteHandler(2, PgmZ80ReadByte);
		SekSetWriteWordHandler(2, PgmZ80WriteWord);
//		SekSetWriteByteHandler(2, PgmZ80WriteByte);

		SekClose();
	}

	{
		ZetInit(1);
		ZetOpen(0);
		ZetMapArea(0x0000, 0xFFFF, 0, RamZ80);
		ZetMapArea(0x0000, 0xFFFF, 1, RamZ80);
		ZetMapArea(0x0000, 0xFFFF, 2, RamZ80);
		ZetSetOutHandler(PgmZ80PortWrite);
		ZetSetInHandler(PgmZ80PortRead);
		ZetMemEnd();
		ZetClose();
	}

	if (pPgmInitCallback) {
		pPgmInitCallback();
	}

	if (pPgmProtCallback) {
		pPgmProtCallback();
	}

	ics2115_init();

	PgmDoReset();

	return 0;
}

int pgmExit()
{
	SekExit();
	ZetExit();

	if (nEnableArm7) {
		Arm7Exit();
	}

	UniversalVMMExit();

	free(Mem);
	Mem = NULL;

	ics2115_exit();
	
	free (PGMTileROM);
	free (PGMTileROMExp);
	free (PGMSPRMaskROM);

	PGM68KBIOS = NULL;
	PGM68KROM = NULL;
	PGMTileROM = NULL;
	PGMTileROMExp = NULL;
	PGMSPRColROM = NULL;
	PGMSPRMaskROM = NULL;

	if (PGMUSER0) {
		free (PGMUSER0);
		PGMUSER0 = NULL;
	}

	nPGM68KROMLen = 0;
	nPGMTileROMLen = 0;
	nPGMSPRColROMLen = 0;
	nPGMSPRMaskROMLen = 0;
	nPGMSNDROMLen = 0;
	nPGMExternalARMLen = 0;

	pPgmInitCallback = NULL;
	pPgmProtCallback = NULL;
	pPgmScanCallback = NULL;
	pPgmResetCallback = NULL;

	nEnableArm7 = 0;
	nPGMEnableIRQ4 = 0;
	nPGMArm7Type = 0;

	nPgmCurrentBios = -1;

	return 0;
}

int pgmFrame()
{
	if (PgmReset) 
		PgmDoReset();
	
	if (nPgmPalRecalc) {
		for (int i=0;i<(0x1200/2);i++)
			RamCurPal[i] = CalcCol(RamPal[i]);
		nPgmPalRecalc = 0;
	}

	// Compile digital inputs
	{
		memset (PgmInput, 0, 6);
		for (int i = 0; i < 8; i++) {
			PgmInput[0] ^= (PgmJoy1[i] & 1) << i;
			PgmInput[1] ^= (PgmJoy2[i] & 1) << i;
			PgmInput[2] ^= (PgmJoy3[i] & 1) << i;
			PgmInput[3] ^= (PgmJoy4[i] & 1) << i;
			PgmInput[4] ^= (PgmBtn1[i] & 1) << i;
			PgmInput[5] ^= (PgmBtn2[i] & 1) << i;
		}

		// clear opposites
		if ((PgmInput[0] & 0x06) == 0x06) PgmInput[0] &= 0xf9; // up/down
		if ((PgmInput[0] & 0x18) == 0x18) PgmInput[0] &= 0xe7; // left/right
		if ((PgmInput[1] & 0x06) == 0x06) PgmInput[1] &= 0xf9;
		if ((PgmInput[1] & 0x18) == 0x18) PgmInput[1] &= 0xe7;
		if ((PgmInput[2] & 0x06) == 0x06) PgmInput[2] &= 0xf9;
		if ((PgmInput[2] & 0x18) == 0x18) PgmInput[2] &= 0xe7;
		if ((PgmInput[3] & 0x06) == 0x06) PgmInput[3] &= 0xf9;
		if ((PgmInput[3] & 0x18) == 0x18) PgmInput[3] &= 0xe7;

		// only allow coin to be held for 7 frames before we clear it
		// this fixes coin insertion problems with killbld and orlegend
		PgmCoinTimer[0] = PgmBtn1[0] ? (PgmCoinTimer[0] + 1) : 0;
		PgmCoinTimer[1] = PgmBtn1[1] ? (PgmCoinTimer[1] + 1) : 0;
		PgmCoinTimer[2] = PgmBtn1[2] ? (PgmCoinTimer[2] + 1) : 0;
		PgmCoinTimer[3] = PgmBtn1[3] ? (PgmCoinTimer[3] + 1) : 0;
	
		if (PgmCoinTimer[0] > 7) PgmInput[4] &= 0xfe;
		if (PgmCoinTimer[1] > 7) PgmInput[4] &= 0xfd;
		if (PgmCoinTimer[2] > 7) PgmInput[4] &= 0xfb;
		if (PgmCoinTimer[3] > 7) PgmInput[4] &= 0xf7;
	}

	int nCyclesDone[3] = {0, 0, 0};
	int nCyclesNext[3] = {0, 0, 0};
	
	SekNewFrame();
	ZetNewFrame();
	Arm7NewFrame();

	if (nEnableArm7) // region hacks
	{
		switch (nPGMArm7Type)
		{
			case 1: // kov/kovsh/kovshp/photoy2k/puzlstar/puzzli2/oldsplus/py2k2
				PGMARMShareRAM[0x008] = PgmInput[7];
			break;

			case 2: // martmast/kov2/ddp2/dw2001
				PGMARMShareRAM[0x138] = PgmInput[7];
			break;

			case 3: // svg/killbldp/dmnfrnt/theglad/happy6in1
				// unknown...
			break;
		}
	}

	SekOpen(0);
	ZetOpen(0);
	Arm7Open(0);

	for (int i = 0; i < PGM_INTER_LEAVE; i++)
	{
		nCyclesNext[0] += M68K_CYCS_PER_INTER;
		nCyclesNext[1] += Z80_CYCS_PER_INTER;
		nCyclesNext[2] += ARM7_CYCS_PER_INTER;

		int cycles = nCyclesNext[0] - nCyclesDone[0];
		if (i == 0) cycles -= 200;

		if (cycles > 0) {
			nCyclesDone[0] += SekRun(cycles);
		}

		cycles = nCyclesNext[2] - Arm7TotalCycles();

		if (cycles > 0 && nEnableArm7) {
			nCyclesDone[2] += Arm7Run(cycles);
		}

		if (nPgmZ80Work) {
			nCyclesDone[1] += ZetRun( nCyclesNext[1] - nCyclesDone[1] );
		} else
			nCyclesDone[1] += nCyclesNext[1] - nCyclesDone[1];

		if (i == ((PGM_INTER_LEAVE / 2)-1) && nPGMEnableIRQ4 != 0) {
			SekSetIRQLine(4, SEK_IRQSTATUS_AUTO);
		}
	}

	SekSetIRQLine(6, SEK_IRQSTATUS_AUTO);

	ics2115_frame();

	Arm7Close();
	SekClose();
	ZetClose();

	ics2115_update(nBurnSoundLen);

	if (pBurnDraw) pgmDraw();

	memcpy (PGMSprBuf, PGM68KRAM, 0xa00);

	return 0;
}

int pgmScan(int nAction,int *pnMin)
{
	struct BurnArea ba;

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

	if (nAction & ACB_MEMORY_ROM) {	
		if (BurnDrvGetHardwareCode() & HARDWARE_IGS_JAMMAPCB_NO68KBIOS) {
			ba.Data		= PGM68KROM;
			ba.nLen		= nPGM68KROMLen;
			ba.nAddress	= 0;
			ba.szName	= "68K ROM";
			BurnAcb(&ba);
		} else {
			ba.Data		= PGM68KBIOS;
			ba.nLen		= 0x0020000;
			ba.nAddress	= 0;
			ba.szName	= "BIOS ROM";
			BurnAcb(&ba);

			ba.Data		= PGM68KROM;
			ba.nLen		= nPGM68KROMLen;
			ba.nAddress	= 0x100000;
			ba.szName	= "68K ROM";
			BurnAcb(&ba);
		}
	}

	if (nAction & ACB_NVRAM) {								// Scan nvram
		ba.Data		= PGM68KRAM;
		ba.nLen		= 0x0020000;
		ba.nAddress = 0;
		ba.szName	= "68K RAM";
		BurnAcb(&ba);
	}

	if (nAction & ACB_MEMORY_RAM) {						// Scan memory, devices & variables
		ba.Data		= RamBg;
		ba.nLen		= 0x0004000;
		ba.nAddress = 0;
		ba.szName	= "Bg RAM";
		BurnAcb(&ba);

		ba.Data		= RamTx;
		ba.nLen		= 0x0002000;
		ba.nAddress = 0;
		ba.szName	= "Tx RAM";
		BurnAcb(&ba);

		ba.Data		= RamRs;
		ba.nLen		= 0x0000800;
		ba.nAddress = 0;
		ba.szName	= "Row Scroll";
		BurnAcb(&ba);

		ba.Data		= RamPal;
		ba.nLen		= 0x0001200;
		ba.nAddress = 0;
		ba.szName	= "Palette";
		BurnAcb(&ba);

		ba.Data		= RamVReg;
		ba.nLen		= 0x0010000;
		ba.nAddress = 0;
		ba.szName	= "Video Regs";
		BurnAcb(&ba);
		
		ba.Data		= RamZ80;
		ba.nLen		= 0x0010000;
		ba.nAddress = 0;
		ba.szName	= "Z80 RAM";
		BurnAcb(&ba);
		
	}

	if (nAction & ACB_DRIVER_DATA) {
	
		SekScan(nAction);										// Scan 68000 state
		ZetScan(nAction);										// Scan Z80 state

		// Scan critical driver variables
		SCAN_VAR(PgmInput);

		if (nAction & ACB_WRITE)
			nPgmPalRecalc = 1;
		
		SCAN_VAR(nPgmZ80Work);
		ics2115_scan(nAction, pnMin);

		SCAN_VAR(nPgmCurrentBios);

		SCAN_VAR(CalVal);
		SCAN_VAR(CalMask);
		SCAN_VAR(CalCom);
		SCAN_VAR(CalCnt);
	}

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


 	return 0;
}
