//
// (C) 2005 Mike Brent aka Tursi aka HarmlessLion.com
// This software is provided AS-IS. No warranty
// express or implied is provided.
//
// This notice defines the entire license for this code.
// All rights not explicity granted here are reserved by the
// author.
//
// You may redistribute this software provided the original
// archive is UNCHANGED and a link back to my web page,
// http://harmlesslion.com, is provided as the author's site.
// It is acceptable to link directly to a subpage at harmlesslion.com
// provided that page offers a URL for that purpose
//
// Source code, if available, is provided for educational purposes
// only. You are welcome to read it, learn from it, mock
// it, and hack it up - for your own use only.
//
// Please contact me before distributing derived works or
// ports so that we may work out terms. I don't mind people
// using my code but it's been outright stolen before. In all
// cases the code must maintain credit to the original author(s).
//
// -COMMERCIAL USE- Contact me first. I didn't make
// any money off it - why should you? ;) If you just learned
// something from this, then go ahead. If you just pinched
// a routine or two, let me know, I'll probably just ask
// for credit. If you want to derive a commercial tool
// or use large portions, we need to talk. ;)
//
// If this, itself, is a derived work from someone else's code,
// then their original copyrights and licenses are left intact
// and in full force.
//
// http://harmlesslion.com - visit the web page for contact info
//
///////////////////////////////////////////////////////
// Classic99 VDP Routines
// M.Brent
///////////////////////////////////////////////////////

#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0400

#include <stdio.h>
#include <windows.h>
#include <ddraw.h>

#include "tiemul.h"
#include "resource.h"
#include "2xSaI\2xSaI.h"
#include "FilterDLL\sms_ntsc.h"

// 16-bit 0rrrgggbbb values
int TIPALETTE[16]={ 
	0x0000, 0x0000, 0x1328, 0x2f6f, 0x295d, 0x3ddf, 0x6949, 0x23be,
	0x7d4a, 0x7def, 0x6b0a, 0x7330, 0x12c7, 0x6577, 0x6739, 0x7fff,
};

int SIT;									// Screen Image Table
int CT;										// Color Table
int PDT;									// Pattern Descriptor Table
int SAL;									// Sprite Allocation Table
int SDT;									// Sprite Descriptor Table
int CTsize;									// Color Table size in Bitmap Mode
int PDTsize;								// Pattern Descriptor Table size in Bitmap Mode

Byte VDP[16384];							// Video RAM
Byte SprColBuf[256][192];					// Sprite Collision Buffer
int SprColFlag;								// Sprite collision flag

IDirectDraw7 *lpdd=NULL;					// DirectDraw object
LPDIRECTDRAWSURFACE7 lpdds=NULL;			// Primary surface
LPDIRECTDRAWSURFACE7 ddsBack=NULL;			// Back buffer
LPDIRECTDRAWCLIPPER  lpDDClipper=NULL;		// Window clipper
int FullScreenMode=0;						// Current full screen mode
int FilterMode=0;							// Current filter mode
int TVFiltersAvailable=0;					// Depends on whether we can load the Filter DLL
int TVScanLines=1;							// Whether to draw scanlines or not
int VDPDebug=0;								// When set, displays all 256 chars

sms_ntsc_t tvFilter;						// TV Filter structure
sms_ntsc_setup_t tvSetup;					// TV Setup structure
HMODULE hFilterDLL;							// Handle to Filter DLL
void (*sms_ntsc_init)(sms_ntsc_t *ntsc, sms_ntsc_setup_t const *setup);	// pointer to init function
void (*sms_ntsc_blit)(sms_ntsc_t const *ntsc, unsigned short const *sms_in, long in_row_width, int in_width, int height, void *rgb_out, long out_pitch);
																		// pointer to blit function
void (*sms_ntsc_scanlines)(void *pFrame, int nWidth, int nStride, int nHeight);


HANDLE Video_hdl[2];						// Handles for Display/Blit events
unsigned short *framedata;					// The actual pixel data
unsigned short *framedata2;					// Filtered pixel data
BITMAPINFO myInfo;							// Bitmapinfo header for the DIB functions
BITMAPINFO myInfo2;							// Bitmapinfo header for the DIB functions
HDC tmpDC;									// Temporary DC for StretchBlt to work from

int redraw_needed;							// redraw flag
int interrupt_needed;						// interrupt flag
int skip_interrupt;							// flag for some instructions
Byte VDPREG[9];								// VDP read-only registers
Byte VDPS;									// VDP Status register
Word VDPADD;								// VDP Address counter
Word tmpVDPADD;								// Temp VDP address
int vdpaccess;								// VDP Write count
Byte vdpprefetch;							// VDP Prefetch
unsigned long hVideoThread;					// thread handle
Byte hzRate;								// flag for 50 or 60hz
Byte Recording;								// Flag for AVI recording
Byte RecordFrame;							// Current frame recorded (currently we only write 1/4 of the frames)
int MaintainAspect;							// Flag for Aspect ratio
int StretchMode;							// Setting for video stretching

extern int fontX, fontY;					// Font dimensions
extern Word disasm[];						// execution trace


//////////////////////////////////////////////////////////
// Helpers for the TV controls
//////////////////////////////////////////////////////////
void GetTVValues(double *hue, double *sat, double *cont, double *bright, double *sharp) {
	*hue=tvSetup.hue;
	*sat=tvSetup.saturation;
	*cont=tvSetup.contrast;
	*bright=tvSetup.brightness;
	*sharp=tvSetup.sharpness;
}

void SetTVValues(double hue, double sat, double cont, double bright, double sharp) {
	tvSetup.hue=hue;
	tvSetup.saturation=sat;
	tvSetup.contrast=cont;
	tvSetup.brightness=bright;
	tvSetup.sharpness=sharp;
	if (sms_ntsc_init) {
		if (WAIT_OBJECT_0 != WaitForSingleObject(MyMutex, 0)) {
			return;		// do it later
		}
		sms_ntsc_init(&tvFilter, &tvSetup);
		ReleaseMutex(MyMutex);
	}
}

//////////////////////////////////////////////////////////
// Get table addresses from Registers
//////////////////////////////////////////////////////////
void gettables()
{
	/* Screen Image Table */
	SIT=((VDPREG[2]&0x0f)<<10);
	/* Sprite Attribute List */
	SAL=((VDPREG[5]&0x7f)<<7);
	/* Sprite Descriptor Table */
	SDT=((VDPREG[6]&0x07)<<11);

	// The normal math for table addresses isn't quite right in bitmap mode
	// The PDT and CT have different math and a size setting
	if (VDPREG[0]&0x02) {
		CT=(VDPREG[3]&0x80) ? 0x2000 : 0;
		CTsize=((VDPREG[3]&0x7f)<<6)|0x3f;
		PDT=(VDPREG[4]&0x04) ? 0x2000 : 0;
		PDTsize=((VDPREG[4]&0x03)<<11);
		if (VDPREG[1]&0x18) {	// in Bitmap text or multicolor mode, we fill bits with 1
			PDTsize|=0x7ff;
		} else {
			PDTsize|=CTsize;	// In other bitmap modes we get bits from the color table mask
		}
	} else {
		/* Colour Table */
		CT=VDPREG[3]<<6;
		/* Pattern Descriptor Table */
		PDT=((VDPREG[4]&0x07)<<11);
		CTsize=32;
		PDTsize=2048;
	}
}

////////////////////////////////////////////////////////////
// Startup and run VDP graphics interface
////////////////////////////////////////////////////////////
void VDPmain()
{	
	DWORD ret;
	HDC myDC;

	Init_2xSaI(555);		// Classic99 uses 555 15-bit mode internally

	// load the Filter DLL
	TVFiltersAvailable=0;
	hFilterDLL=LoadLibrary("FilterDll.dll");
	if (NULL == hFilterDLL) {
		debug_write("Failed to load filter library.");
	} else {
		sms_ntsc_init=(void (*)(sms_ntsc_t*,sms_ntsc_setup_t const *))GetProcAddress(hFilterDLL, "sms_ntsc_init");
		sms_ntsc_blit=(void (*)(sms_ntsc_t const *, unsigned short const *, long, int, int, void *, long))GetProcAddress(hFilterDLL, "sms_ntsc_blit");
		sms_ntsc_scanlines=(void (*)(void *, int, int, int))GetProcAddress(hFilterDLL, "sms_ntsc_scanlines");
		if ((NULL == sms_ntsc_blit) || (NULL == sms_ntsc_init) || (NULL == sms_ntsc_scanlines)) {
			debug_write("Failed to find entry points in filter library.");
			FreeLibrary(hFilterDLL);
			sms_ntsc_blit=NULL;
			sms_ntsc_init=NULL;
			sms_ntsc_scanlines=NULL;
			hFilterDLL=NULL;
		} else {
			// Some of these are set up by SetTVValues()
//			tvSetup.hue=0;			// -1.0 to +1.0
//			tvSetup.saturation=0;	// -1.0 to +1.0
//			tvSetup.contrast=0;		// -1.0 to +1.0
//			tvSetup.brightness=0;	// -1.0 to +1.0
//			tvSetup.sharpness=0;	// -1.0 to +1.0
			tvSetup.gamma=0;		// -1.0 to +1.0
			tvSetup.resolution=0;	// -1.0 to +1.0
			tvSetup.artifacts=0;	// -1.0 to +1.0
			tvSetup.fringing=0;		// -1.0 to +1.0
			tvSetup.bleed=0;		// -1.0 to +1.0
			tvSetup.decoder_matrix=NULL;
			tvSetup.palette_out=NULL;
			sms_ntsc_init(&tvFilter, &tvSetup);
			TVFiltersAvailable=1;
		}
	}

	myInfo.bmiHeader.biSize=sizeof(myInfo.bmiHeader);
	myInfo.bmiHeader.biWidth=256+16;
	myInfo.bmiHeader.biHeight=192+16;
	myInfo.bmiHeader.biPlanes=1;
	myInfo.bmiHeader.biBitCount=16;
	myInfo.bmiHeader.biCompression=BI_RGB;
	myInfo.bmiHeader.biSizeImage=0;
	myInfo.bmiHeader.biXPelsPerMeter=1;
	myInfo.bmiHeader.biYPelsPerMeter=1;
	myInfo.bmiHeader.biClrUsed=0;
	myInfo.bmiHeader.biClrImportant=0;

	memcpy(&myInfo2, &myInfo, sizeof(myInfo));
	myInfo2.bmiHeader.biWidth=512+32;
	myInfo2.bmiHeader.biHeight=384+29;

	myDC=GetDC(myWnd);
	tmpDC=CreateCompatibleDC(myDC);
	ReleaseDC(myWnd, myDC);

	SetupDirectDraw(false);

	// now we create a waitable object and sit on it - the main thread
	// will tell us when we should redraw the screen.
	DisplayEvent=CreateEvent(NULL, false, false, NULL);
	if (NULL == DisplayEvent)
		debug_write("Video Event Creation failed");
	BlitEvent=CreateEvent(NULL, false, false, NULL);
	if (NULL == BlitEvent)
		debug_write("Blit Event Creation failed");

	debug_write("Starting video loop");
	MaintainAspect=1;
	redraw_needed=1;

	while (quitflag==0)
	{
		if ((ret=WaitForMultipleObjects(2, Video_hdl, false, 100)) != WAIT_TIMEOUT)
		{
			if (WAIT_FAILED==ret)
				ret=GetLastError();

			if (WAIT_OBJECT_0 == ret)
				VDPdisplay();

			doBlit();
			ResetEvent(Video_hdl[1]);	// in case we fell through from above
		}
	}

	if (NULL != hFilterDLL) {
		sms_ntsc_blit=NULL;
		sms_ntsc_init=NULL;
		sms_ntsc_scanlines=NULL;
		FreeLibrary(hFilterDLL);
		hFilterDLL=NULL;
	}
	takedownDirectDraw();
	DeleteDC(tmpDC);
	CloseHandle(DisplayEvent);
}

//////////////////////////////////////////////////////////
// Perform drawing of a single frame
// Determines which screen mode to draw
//////////////////////////////////////////////////////////
void VDPdisplay()
{
	int idx;
	unsigned short *ptmp;
	unsigned short col;
	DWORD longcol;
	DWORD *plong;

	redraw_needed=0;

	gettables();

	// blank screen
	plong=(DWORD*)framedata;
	col=TIPALETTE[VDPREG[7]&0xf];
	longcol=(col<<16)|col;
	for (idx=0; idx<((256+16)*(192+16))/2; idx++) {
		*(plong++)=longcol;
	}

	if (!(VDPREG[1] & 0x40))		// Disable display
	{
		return;
	}

	if ((VDPREG[1] & 0x18)==0x18)	// MODE BITS 2 and 1
	{
		VDPillegal();
		return;
	}

	if (VDPREG[1] & 0x10)			// MODE BIT 2
	{
		if (VDPREG[0] & 0x02) {		// BITMAP MODE BIT
			VDPtextII();
		} else {
			VDPtext();
		}
		return;
	}

	if (VDPREG[1] & 0x08)			// MODE BIT 1
	{
		if (VDPREG[0] & 0x02) {		// BITMAP MODE BIT
			VDPmulticolorII();
		} else {
			VDPmulticolor();
		}
		return;
	}

	if (VDPREG[0] & 0x02) {			// BITMAP MODE BIT
		VDPgraphicsII();
	} else {
		VDPgraphics();
	}
}

//////////////////////////////////////////////////////
// Draw a debug screen 
//////////////////////////////////////////////////////
void draw_debug()
{
	char buf1[80], buf2[80];
	int myPC, idx;
	int tmpPC;

	// We'll put these in their own window now and draw them
	// with the standard GDI stuff
	if (quitflag) return;
	
	// Disassembly window - 20 lines of history and 10 lines of future
	if (asmWnd) {
		HDC myDC;
		RECT myRect;
		int len;
		
		myDC=GetDC(asmWnd);
		SelectObject(myDC, GetStockObject(ANSI_FIXED_FONT));	// select non-proportional font
		
		myRect.top=0;
		myRect.bottom=9999;
		myRect.left=0;
		myRect.right=9999;

		sprintf(buf1, "  %04x   %-30s ", 0, "");
		len=strlen(buf1);

		for (idx=0; idx<20; idx++) {
			Dasm9900(buf2, disasm[idx]);
			sprintf(buf1, "  %04x   %-30s ", disasm[idx], buf2);
			DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
			myRect.top+=fontY;
		}

		myPC=PC;
		
		tmpPC = myPC;
		myPC += Dasm9900(buf2, myPC);
		sprintf(buf1, "> %04x   %-30s ", tmpPC, buf2);
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		for (idx=0; idx<10; idx++) {
			tmpPC = myPC;
			myPC += Dasm9900(buf2, myPC);
			sprintf(buf1, "  %04x   %-30s ", tmpPC, buf2);
			DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
			myRect.top+=fontY;
		}

		ReleaseDC(asmWnd, myDC);
	}

	// Debug window - 30 lines of debug information
	if (dbgWnd) {
		HDC myDC;
		RECT myRect;
		
		myDC=GetDC(dbgWnd);
		SelectObject(myDC, GetStockObject(ANSI_FIXED_FONT));	// select non-proportional font
		
		myRect.top=0;
		myRect.bottom=9999;
		myRect.left=0;
		myRect.right=9999;

		// debug text
		for (idx=0; idx<30; idx++) {
			DrawText(myDC, &lines[idx][0], DEBUGLEN, &myRect, DT_LEFT | DT_TOP);
			myRect.top+=fontY;
		}

		for (idx=0; idx<DEBUGLEN; idx++) {
			buf1[idx]=' ';
		}
		buf1[idx]='\0';
		DrawText(myDC, buf1, DEBUGLEN, &myRect, DT_LEFT | DT_TOP);

		ReleaseDC(regWnd, myDC);
	}

	// Register window - registers, pointers and table addresses
	if (regWnd) {
		HDC myDC;
		RECT myRect;
		int len, c;
		char buf3[10];

		len=strlen("........................................");
		
		// draw the current register settings and watches
		// Registers and VDP registers
		myDC=GetDC(regWnd);
 		
		SelectObject(myDC, GetStockObject(ANSI_FIXED_FONT));	// select non-proportional font
		
		myRect.top=0;
		myRect.bottom=9999;
		myRect.left=0;
		myRect.right=9999;

		// Registers (16xCPU, 8xVDP)
		for (idx=0; idx<8; idx++)
		{
			sprintf(buf1,"R%-2d - %04x   R%-2d - %04x   VDPREG %1d - %02x  ",idx, GetSafeWord(WP+idx*2), idx+8, GetSafeWord(WP+(idx+8)*2), idx, VDPREG[idx]);
			DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
			myRect.top+=fontY;
		}

		// Various Registers and memory pointers
		sprintf(buf1, "VDP - %04x   GRM - %04x   VDPS- %02x      ", VDPADD, GRMADD, VDPS);
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		sprintf(buf1, "PC  - %04x   WP  - %04x   ST  - %04x    ", PC, WP, ST);
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		sprintf(buf1, "SIT - %04x   SAL - %04x   SDT - %04x    ", SIT, SAL, SDT);
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		sprintf(buf1, "PDT - %04x   Size- %04x   Bank- %04x    ", PDT, PDTsize, (xb<<8)|(bank));
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		sprintf(buf1, "CT  - %04x   Size- %04x   DSR - %04x    ", CT, CTsize, (Word)(0x1000|(nCurrentDSR<<8)+nDSRBank[nCurrentDSR]));
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		// Memory dump - 16 bytes each for CPU, VDP, GROM
		// CPU must not call the read function, as it may call the memory
		// mapped devices and affect them. This code does not take into
		// account scratchpad or cartridge ROM banks
		strcpy(buf1, "CPU: ");
		strcpy(buf3, "");
		tmpPC=romword(WP);		// read at R0 for now
		for (idx=0; idx<8; idx++) {
			c=GetSafeByte(tmpPC+idx);
			sprintf(buf2, "%02x ", c);
			strcat(buf1, buf2);
			if ((c>=32)&&(c<=126)) {
				sprintf(buf2, "%c", c);
			} else {
				strcpy(buf2, ".");
			}
			strcat(buf3, buf2);
		}
		strcat(buf1, buf3);
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		strcpy(buf1, "     ");
		for (idx=8; idx<16; idx++) {
			c=GetSafeByte(tmpPC+idx);
			sprintf(buf2, "%02x ", c);
			strcat(buf1, buf2);
			if ((c>=32)&&(c<=126)) {
				sprintf(buf2, "%c", c);
			} else {
				strcpy(buf2, ".");
			}
			strcat(buf3, buf2);
		}
		strcat(buf1, buf3);
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		// VDP and GROM *must not* call the read functions, as it will
		// change the address!! (But, since we don't do bank switching in them...)

		// VDP
		strcpy(buf1, "VDP: ");
		strcpy(buf3, "");
		tmpPC=VDPADD;
		for (idx=0; idx<8; idx++) {
			c=VDP[tmpPC+idx];
			sprintf(buf2, "%02x ", c);
			strcat(buf1, buf2);
			if ((c>=32)&&(c<=126)) {
				sprintf(buf2, "%c", c);
			} else {
				strcpy(buf2, ".");
			}
			strcat(buf3, buf2);
		}
		strcat(buf1, buf3);
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		strcpy(buf1, "     ");
		for (idx=8; idx<16; idx++) {
			c=VDP[tmpPC+idx];
			sprintf(buf2, "%02x ", c);
			strcat(buf1, buf2);
			if ((c>=32)&&(c<=126)) {
				sprintf(buf2, "%c", c);
			} else {
				strcpy(buf2, ".");
			}
			strcat(buf3, buf2);
		}
		strcat(buf1, buf3);
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		// GROM
		strcpy(buf1, "GRM: ");
		strcpy(buf3, "");
		tmpPC=GRMADD;
		for (idx=0; idx<8; idx++) {
			c=GROM[tmpPC+idx];
			sprintf(buf2, "%02x ", c);
			strcat(buf1, buf2);
			if ((c>=32)&&(c<=126)) {
				sprintf(buf2, "%c", c);
			} else {
				strcpy(buf2, ".");
			}
			strcat(buf3, buf2);
		}
		strcat(buf1, buf3);
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		strcpy(buf1, "     ");
		for (idx=8; idx<16; idx++) {
			c=GROM[tmpPC+idx];
			sprintf(buf2, "%02x ", c);
			strcat(buf1, buf2);
			if ((c>=32)&&(c<=126)) {
				sprintf(buf2, "%c", c);
			} else {
				strcpy(buf2, ".");
			}
			strcat(buf3, buf2);
		}
		strcat(buf1, buf3);
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);
		myRect.top+=fontY;

		// blank line to clean up any partial line at the bottom
		strcpy(buf1, "                                        ");
		DrawText(myDC, buf1, len, &myRect, DT_LEFT | DT_TOP);

		ReleaseDC(regWnd, myDC);
	}

#if 0
	if (max_cpf == 0) {										/* singlestepping: memory monitor */

	  if (key[KEY_0]) {
		if (key[KEY_LSHIFT])	// shift-0: clear address
		  debugadr = 0;
		else
		  debugadr = (debugadr << 4);
		key[KEY_0] = 0;
	  }
	  if (key[KEY_1]) {
		debugadr = (debugadr << 4) | 1;
		key[KEY_1] = 0;
	  }
	  if (key[KEY_2]) {
		debugadr = (debugadr << 4) | 2;
		key[KEY_2] = 0;
	  }
	  if (key[KEY_3]) {
		debugadr = (debugadr << 4) | 3;
		key[KEY_3] = 0;
	  }
	  if (key[KEY_4]) {
		debugadr = (debugadr << 4) | 4;
		key[KEY_4] = 0;
	  }
	  if (key[KEY_5]) {
		debugadr = (debugadr << 4) | 5;
		key[KEY_5] = 0;
	  }
	  if (key[KEY_6]) {
		debugadr = (debugadr << 4) | 6;
		key[KEY_6] = 0;
	  }
	  if (key[KEY_7]) {
		debugadr = (debugadr << 4) | 7;
		key[KEY_7] = 0;
	  }
	  if (key[KEY_8]) {
		debugadr = (debugadr << 4) | 8;
		key[KEY_8] = 0;
	  }
	  if (key[KEY_9]) {
		debugadr = (debugadr << 4) | 9;
		key[KEY_9] = 0;
	  }
	  if (key[KEY_A]) {
		debugadr = (debugadr << 4) | 0x0a;
		key[KEY_A] = 0;
	  }
	  if (key[KEY_B]) {
		debugadr = (debugadr << 4) | 0x0b;
		key[KEY_B] = 0;
	  }
	  if (key[KEY_C]) {
		debugadr = (debugadr << 4) | 0x0c;
		key[KEY_C] = 0;
	  }
	  if (key[KEY_D]) {
		debugadr = (debugadr << 4) | 0x0d;
		key[KEY_D] = 0;
	  }
	  if (key[KEY_E]) {
		debugadr = (debugadr << 4) | 0x0e;
		key[KEY_E] = 0;
	  }
	  if (key[KEY_F]) {
		debugadr = (debugadr << 4) | 0x0f;
		key[KEY_F] = 0;
	  }

	  debugadr &= 0x3ffff;
	  sprintf(buf1, "adr=0x%05x  -  breakpoint=0x%04x", debugadr, breakpoint);
	  textout(buf1, 0, 64+yoff+(idx++)*FONTHEIGHT);

	  if (key[KEY_M]) {			// set memory watch
		if (key[KEY_LSHIFT])	// with shift: write CPU byte - HANDLE WITH CARE!
		  CPU[debugadrm] = debugadr;
		else
		  debugadrm = debugadr;
		key[KEY_M] = 0;
	  }
	  if (key[KEY_V]) {
		if (key[KEY_LSHIFT]) {
		  VDP[debugadrv] = debugadr;
		  redraw_needed = 1;
		} else
		  debugadrv = debugadr;
		key[KEY_V] = 0;
	  }
	  if (key[KEY_G]) {
		if (key[KEY_LSHIFT])
		  GROM[debugadrg] = debugadr;
		else
		  debugadrg = debugadr;
		key[KEY_G] = 0;
	  }
	  if (key[KEY_R]) {			/* bReak */
		breakpoint = debugadr;
		key[KEY_R] = 0;
	  }
	  if (key[KEY_U]) {			/* Unbreak */
		breakpoint = 0;
		key[KEY_U] = 0;
	  }
	  if (key[KEY_O] && key[KEY_LSHIFT]) { // with shift: gOto - HANDLE WITH CARE!
		PC = debugadr & 0xffff;
		key[KEY_O] = 0;
	  }
	  if (key[KEY_I]) {			/* dIsassemble */
	    myPC = debugadr & 0xffff;
	    curline = 0;
	    for (idx=0; idx<(hti<256?30:50); idx++)
	      {
			tmpPC = myPC;
			myPC += Dasm9900(buf2, myPC);
			sprintf(buf1, "%04x ", tmpPC, buf2);
			for (i=0; i<3; i++)
			  if (tmpPC+2*i < myPC)
				sprintf(buf1+5+4*i, "%04x  ", romword(tmpPC+2*i));
			  else
				sprintf(buf1+5+4*i, "      ");
			sprintf(buf1+17, "%-32s", buf2);
			textout(buf1, xoff, idx*8);
	      }
	    key[KEY_I] = 0;
	  }
	} /* max_cpf==0 */
#endif
}

/////////////////////////////////////////////////////////
// Draw graphics mode
/////////////////////////////////////////////////////////
void VDPgraphics()
{
	int t,o;						// temp variables
	int i1,i2,i3;					// temp variables
	int p_add;
	int fgc, bgc, c;
	unsigned char ch=0xff;

	o=0;							// offset in SIT

	for (i1=0; i1<192; i1+=8)		// y loop
	{ 
		for (i2=0; i2<256; i2+=8)	// x loop
		{ 
			if (VDPDebug) {
				ch++;
			} else {
				ch=VDP[SIT+o];
			}
			p_add=PDT+(ch<<3);
			c = ch>>3;
			fgc=VDP[CT+c];
			bgc=fgc&0x0f;
			fgc>>=4;
			o++;

			for (i3=0; i3<8; i3++)
			{	
				t=VDP[p_add++];
	
				pixel(i2,i1+i3,(t&0x80 ? fgc : bgc ));
				pixel(i2+1,i1+i3,(t&0x40 ? fgc : bgc ));
				pixel(i2+2,i1+i3,(t&0x20 ? fgc : bgc ));
				pixel(i2+3,i1+i3,(t&0x10 ? fgc : bgc ));
				pixel(i2+4,i1+i3,(t&0x08 ? fgc : bgc ));
				pixel(i2+5,i1+i3,(t&0x04 ? fgc : bgc ));
				pixel(i2+6,i1+i3,(t&0x02 ? fgc : bgc ));
				pixel(i2+7,i1+i3,(t&0x01 ? fgc : bgc ));
			}
		}
	}

	DrawSprites();

}

/////////////////////////////////////////////////////////
// Draw bitmap graphics mode
/////////////////////////////////////////////////////////
void VDPgraphicsII()
{
	int t,o;						// temp variables
	int i1,i2,i3;					// temp variables
	int p_add, c_add;
	int fgc, bgc;
	int table, Poffset, Coffset;
	unsigned char ch=0xff;

	o=0;							// offset in SIT
	table=0; Poffset=0; Coffset=0;

	for (i1=0; i1<192; i1+=8)		// y loop
	{ 
		if ((i1==64)||(i1==128)) {
			table++;
			Poffset=table*0x800;
			Coffset=table*0x800;
		}

		for (i2=0; i2<256; i2+=8)	// x loop
		{ 
#if 0
			// This is not the right way to debug bitmap mode ;)
			if (VDPDebug) {
				ch++;
			} else {
				ch=VDP[SIT+o];
			}
			
			ch<<=3;
			p_add=PDT+((ch+Poffset)&PDTsize);
			c_add=CT+((ch+Coffset)&CTsize);
#else
			p_add=PDT+(((VDP[SIT+o]<<3)+Poffset)&PDTsize);
			c_add=CT+(((VDP[SIT+o]<<3)+Coffset)&CTsize);
#endif
			o++;

			for (i3=0; i3<8; i3++)
			{	
				t=VDP[p_add++];
				fgc=VDP[c_add++];
				bgc=fgc&0x0f;
				fgc>>=4;
	
				pixel(i2,i1+i3,(t&0x80 ? fgc : bgc ));
				pixel(i2+1,i1+i3,(t&0x40 ? fgc : bgc ));
				pixel(i2+2,i1+i3,(t&0x20 ? fgc : bgc ));
				pixel(i2+3,i1+i3,(t&0x10 ? fgc : bgc ));
				pixel(i2+4,i1+i3,(t&0x08 ? fgc : bgc ));
				pixel(i2+5,i1+i3,(t&0x04 ? fgc : bgc ));
				pixel(i2+6,i1+i3,(t&0x02 ? fgc : bgc ));
				pixel(i2+7,i1+i3,(t&0x01 ? fgc : bgc ));
			}
		}
	}

	DrawSprites();

}

////////////////////////////////////////////////////////////////////////
// Draw text mode 40x24
////////////////////////////////////////////////////////////////////////
void VDPtext()
{ 
	int t,o;
	int i1,i2,i3;
	int c1, c2, p_add;
	unsigned char ch=0xff;

	t=VDPREG[7];
	c1=t&0xf;
	c2=t>>4;

	// erase border area
	for (i1=0; i1<8; i1++) {
		for (i2=0; i2<192; i2++) {
			pixel(i1, i2, c1);
			pixel(i1+248, i2, c1);
		}
	}

	o=0;										// offset in SIT
	for (i1=0; i1<192; i1+=8)					// y loop
	{ 
		for (i2=8; i2<248; i2+=6)				// x loop
		{ 
			if (VDPDebug) {
				ch++;
			} else {
				ch=VDP[SIT+o];
			}

			p_add=PDT+(ch<<3);
			o++;

			for (i3=0; i3<8; i3++)		// 6 pixels wide
			{	
				t=VDP[p_add++];
				pixel(i2,i1+i3,(t&0x80 ? c2 : c1 ));
				pixel(i2+1,i1+i3,(t&0x40 ? c2 : c1 ));
				pixel(i2+2,i1+i3,(t&0x20 ? c2 : c1 ));
				pixel(i2+3,i1+i3,(t&0x10 ? c2 : c1 ));
				pixel(i2+4,i1+i3,(t&0x08 ? c2 : c1 ));
				pixel(i2+5,i1+i3,(t&0x04 ? c2 : c1 ));
			}
		}
	}
	// no sprites in text mode
}

////////////////////////////////////////////////////////////////////////
// Draw bitmap text mode 40x24
////////////////////////////////////////////////////////////////////////
void VDPtextII()
{ 
	int t,o;
	int i1,i2,i3;
	int c1, c2, p_add;
	int table, Poffset;
	unsigned char ch=0xff;

	t=VDPREG[7];
	c1=t&0xf;
	c2=t>>4;

	// erase border area
	for (i1=0; i1<8; i1++) {
		for (i2=0; i2<192; i2++) {
			pixel(i1, i2, c1);
			pixel(i1+248, i2, c1);
		}
	}

	o=0;							// offset in SIT
	table=0; Poffset=0;

	for (i1=0; i1<192; i1+=8)					// y loop
	{ 
		if ((i1==64)||(i1==128)) {
			table++;
			Poffset=table*0x800;
		}

		for (i2=8; i2<248; i2+=6)				// x loop
		{ 
			if (VDPDebug) {
				ch++;
			} else {
				ch=VDP[SIT+o];
			}

			p_add=PDT+(((ch<<3)+Poffset)&PDTsize);
			o++;

			for (i3=0; i3<8; i3++)		// 6 pixels wide
			{	
				t=VDP[p_add++];
				pixel(i2,i1+i3,(t&0x80 ? c2 : c1 ));
				pixel(i2+1,i1+i3,(t&0x40 ? c2 : c1 ));
				pixel(i2+2,i1+i3,(t&0x20 ? c2 : c1 ));
				pixel(i2+3,i1+i3,(t&0x10 ? c2 : c1 ));
				pixel(i2+4,i1+i3,(t&0x08 ? c2 : c1 ));
				pixel(i2+5,i1+i3,(t&0x04 ? c2 : c1 ));
			}
		}
	}
	// no sprites in text mode
}

////////////////////////////////////////////////////////////////////////
// Draw Illegal mode (similar to text mode)
////////////////////////////////////////////////////////////////////////
void VDPillegal()
{ 
	int t;
	int i1,i2,i3;
	int c1, c2;

	t=VDPREG[7];
	c1=t&0xf;
	c2=t>>4;

	// erase border area
	for (i1=0; i1<8; i1++) {
		for (i2=0; i2<192; i2++) {
			pixel(i1, i2, c1);
			pixel(i1+248, i2, c1);
		}
	}

	// Each character is made up of rows of 4 pixels foreground, 2 pixels background

	for (i1=0; i1<192; i1+=8)					// y loop
	{ 
		for (i2=8; i2<248; i2+=6)				// x loop
		{ 
			for (i3=0; i3<8; i3++)				// 6 pixels wide
			{	
				pixel(i2,i1+i3,c2);
				pixel(i2+1,i1+i3,c2);
				pixel(i2+2,i1+i3,c2);
				pixel(i2+3,i1+i3,c2);
				pixel(i2+4,i1+i3,c1);
				pixel(i2+5,i1+i3,c1);
			}
		}
	}
	// no sprites in this mode
}

/////////////////////////////////////////////////////
// Draw Multicolor Mode
/////////////////////////////////////////////////////
void VDPmulticolor() 
{
	int o;								// temp variables
	int i1,i2,i3, i4;					// temp variables
	int p_add;
	int fgc, bgc;
	int off;
	unsigned char ch=0xff;

	o=0;							// offset in SIT
	off=0;							// offset in pattern

	for (i1=0; i1<192; i1+=8)									// y loop
	{ 
		for (i2=0; i2<256; i2+=8)								// x loop
		{ 
			if (VDPDebug) {
				ch++;
			} else {
				ch=VDP[SIT+o];
			}

			p_add=PDT+(ch<<3)+off;
			o++;

			for (i3=0; i3<7; i3+=4)
			{	
				fgc=VDP[p_add++];
				bgc=fgc&0x0f;
				fgc>>=4;
	
				for (i4=0; i4<4; i4++) {
					pixel(i2,i1+i3+i4,fgc);
					pixel(i2+1,i1+i3+i4,fgc);
					pixel(i2+2,i1+i3+i4,fgc);
					pixel(i2+3,i1+i3+i4,fgc);
					pixel(i2+4,i1+i3+i4,bgc);
					pixel(i2+5,i1+i3+i4,bgc);
					pixel(i2+6,i1+i3+i4,bgc);
					pixel(i2+7,i1+i3+i4,bgc);
				}
			}
		}
		off+=2;
		if (off>7) off=0;
	}

	DrawSprites();

	return;
}

/////////////////////////////////////////////////////
// Draw Bitmap Multicolor Mode
/////////////////////////////////////////////////////
void VDPmulticolorII() 
{
	int o;								// temp variables
	int i1,i2,i3, i4;					// temp variables
	int p_add;
	int fgc, bgc;
	int off;
	int table, Poffset;
	unsigned char ch=0xff;

	o=0;							// offset in SIT
	off=0;							// offset in pattern
	table=0; Poffset=0;

	for (i1=0; i1<192; i1+=8)									// y loop
	{ 
		if ((i1==64)||(i1==128)) {
			table++;
			Poffset=table*0x800;
		}

		for (i2=0; i2<256; i2+=8)								// x loop
		{ 
			if (VDPDebug) {
				ch++;
			} else {
				ch=VDP[SIT+o];
			}

			p_add=PDT+(((ch<<3)+Poffset)&PDTsize);
			o++;

			for (i3=0; i3<7; i3+=4)
			{	
				fgc=VDP[p_add++];
				bgc=fgc&0x0f;
				fgc>>=4;
	
				for (i4=0; i4<4; i4++) {
					pixel(i2,i1+i3+i4,fgc);
					pixel(i2+1,i1+i3+i4,fgc);
					pixel(i2+2,i1+i3+i4,fgc);
					pixel(i2+3,i1+i3+i4,fgc);
					pixel(i2+4,i1+i3+i4,bgc);
					pixel(i2+5,i1+i3+i4,bgc);
					pixel(i2+6,i1+i3+i4,bgc);
					pixel(i2+7,i1+i3+i4,bgc);
				}
			}
		}
		off+=2;
		if (off>7) off=0;
	}

	DrawSprites();

	return;
}

////////////////////////////////////////////////////////////////
// Stretch-blit the buffer into the active window
////////////////////////////////////////////////////////////////
void doBlit()
{
	RECT rect1, rect2;
	int x,y;
	HRESULT ret;

	if (WAIT_OBJECT_0 != WaitForSingleObject(MyMutex, 0)) {
		return;		// do it later
	}

	GetClientRect(myWnd, &rect1);
	myDC=GetDC(myWnd);
	SetStretchBltMode(myDC, COLORONCOLOR);

	if (FilterMode == 4) {
		if ((!TVFiltersAvailable) || (NULL == sms_ntsc_blit)) {
			MessageBox(myWnd, "Filter DLL not available - reverting to no filter.", "Classic99 Error", MB_OK);
			PostMessage(myWnd, WM_COMMAND, ID_VIDEO_FILTERMODE_NONE, 0);
			ReleaseDC(myWnd, myDC);
			ReleaseMutex(MyMutex);
			return;
		}
	}

	// Do the filtering - we throw away the top and bottom 3 scanlines due to some garbage there - it's border anyway
	switch (FilterMode) {
	case 1: // 2xSaI
		_2xSaI((uint8*) framedata+((256+16)*2), ((256+16)*2), NULL, (uint8*)framedata2, (512+32)*2, 256+16, 191+16);
		break;
	case 2: // Super2xSaI
		Super2xSaI((uint8*) framedata+((256+16)*2), ((256+16)*2), NULL, (uint8*)framedata2, (512+32)*2, 256+16, 191+16);
		break;
	case 3: // SuperEagle
		SuperEagle((uint8*) framedata+((256+16)*2), ((256+16)*2), NULL, (uint8*)framedata2, (512+32)*2, 256+16, 191+16);
		break;
	case 4:	// TV filter
		// This filter can only take in 230 pixels for 512+32 out horizontally
		// But that's cool, cause then we can replicate the TV cutting off the edges ;)
		sms_ntsc_blit(&tvFilter, framedata+(20), 256+16, 256+16-40, 192+16, framedata2, (512+32)*2*2);
		if (TVScanLines) {
			sms_ntsc_scanlines(framedata2, 512+32, (512+32)*2, 384+29);
		} else {
			// Duplicate every line instead
			for (int y=1; y<384+29; y+=2) {
				for (int x=0; x<512+32; x++) {
					framedata2[y*(512+32)+x]=framedata2[(y-1)*(512+32)+x];
				}
			}
		}
	}


	switch (StretchMode) {
	case 1:	// DIB
		if (FilterMode == 0) {
			StretchDIBits(myDC, 0, 0, rect1.right-rect1.left, rect1.bottom-rect1.top, 0, 0, 256+16, 192+16, framedata, &myInfo, 0, SRCCOPY);
		} else {
			// trim a bit from the edges for the TV filter
			StretchDIBits(myDC, 0, 0, rect1.right-rect1.left, rect1.bottom-rect1.top, 0, 0, 512+32, 384+29, framedata2, &myInfo2, 0, SRCCOPY);
		}
		break;

	case 2: // DX
		if (NULL == lpdd) {
			SetupDirectDraw(false);
			if (NULL == lpdd) {
				StretchMode=0;
				break;
			}
		}

		if (DD_OK != lpdd->TestCooperativeLevel()) {
			break;
		}

		if (NULL == ddsBack) {
			StretchMode=0;
			break;
		}

		if (DD_OK == ddsBack->GetDC(&tmpDC)) {	// color depth translation
			if (FilterMode == 0) {
				SetDIBitsToDevice(tmpDC, 0, 0, 256+16, 192+16, 0, 0, 0, 192+16, framedata, &myInfo, DIB_RGB_COLORS);
			} else {
				SetDIBitsToDevice(tmpDC, 0, 0, 512+32, 384+29, 0, 0, 0, 384+29, framedata2, &myInfo2, DIB_RGB_COLORS);
			}
		}
		ddsBack->ReleaseDC(tmpDC);
		GetWindowRect(myWnd, &rect2);
		x=GetSystemMetrics(SM_CXSIZEFRAME)+rect2.left;
		y=GetSystemMetrics(SM_CYSIZEFRAME)+GetSystemMetrics(SM_CYCAPTION)+GetSystemMetrics(SM_CYMENU)+rect2.top;
		rect1.top+=y;
		rect1.bottom+=y;
		rect1.left+=x;
		rect1.right+=x;
		if (DDERR_SURFACELOST == lpdds->Blt(&rect1, ddsBack, NULL, DDBLT_DONOTWAIT, NULL)) {	// Just go as quick as we can, don't bother waiting
			lpdd->RestoreAllSurfaces();
		}
		break;

	case 3: // DX Full
		if (NULL == lpdd) {
			SetupDirectDraw(FullScreenMode);
			if (NULL == lpdd) {
				StretchMode=0;
				break;
			}
		}

		if (DD_OK != lpdd->TestCooperativeLevel()) {
			break;
		}
		
		if (NULL == ddsBack) {
			StretchMode=0;
			break;
		}
		if (DD_OK == ddsBack->GetDC(&tmpDC)) {	// color depth translation
			if (FilterMode == 0) {
				SetDIBitsToDevice(tmpDC, 0, 0, 256+16, 192+16, 0, 0, 0, 192+16, framedata, &myInfo, DIB_RGB_COLORS);
			} else {
				SetDIBitsToDevice(tmpDC, 0, 0, 512+32, 384+29, 0, 0, 0, 384+29, framedata2, &myInfo2, DIB_RGB_COLORS);
			}
		}
		ddsBack->ReleaseDC(tmpDC);
		if (DD_OK != (ret=lpdds->Blt(NULL, ddsBack, NULL, DDBLT_DONOTWAIT, NULL))) {
			if (DDERR_SURFACELOST == ret) {
				lpdd->RestoreAllSurfaces();
			}
		}
		break;

	default:// None
		// Center it in the window, whatever size
		if (FilterMode == 0) {
			x=(rect1.right-rect1.left-(256+16))/2;
			y=(rect1.bottom-rect1.top-(192+16))/2;
			x=SetDIBitsToDevice(myDC, x, y, 256+16, 192+16, 0, 0, 0, 192+16, framedata, &myInfo, DIB_RGB_COLORS);
			y=GetLastError();
		} else {
			x=(rect1.right-rect1.left-(512+32))/2;
			y=(rect1.bottom-rect1.top-(384+29))/2;
			SetDIBitsToDevice(myDC, x, y, 512+32, 384+29, 0, 0, 0, 384+29, framedata2, &myInfo2, DIB_RGB_COLORS);
		}
		break;
	}

	ReleaseDC(myWnd, myDC);

	ReleaseMutex(MyMutex);
}

/////////////////////////////////////////////////////////////////////////
// Write a string of text to the video card
/////////////////////////////////////////////////////////////////////////
void textout(char * /*s*/, int /*x*/, int /*y*/)
{
//	g_pDisplay->GetBackBuffer()->DrawText(x, y, s, false);
}

//////////////////////////////////////////////////////////
// Draw Sprites into the backbuffer
//////////////////////////////////////////////////////////
void DrawSprites()
{
	int i1, i3, xx, yy, pat, col, p_add, t;
	int highest;
	int curSAL;

	memset(SprColBuf, 0, 256*192);
	SprColFlag=0;
	
	highest=31;

	// find the highest active sprite
	for (i1=0; i1<32; i1++)			// 32 sprites 
	{
		yy=VDP[SAL+(i1<<2)];
		if (yy==0xd0)
		{
			highest=i1-1;
			break;
		}
	}

	for (i1=highest; i1>=0; i1--)	
	{	
		curSAL=SAL+(i1<<2);
		yy=VDP[curSAL++]+1;				// sprite Y, it's stupid, cause 255 is line 0 
		if (yy>225) yy-=255;			// fade in from top
		xx=VDP[curSAL++];				// sprite X 
		pat=VDP[curSAL++];				// sprite pattern
		if (VDPREG[1] & 0x2)
			pat=pat&0xfc;				// if double-sized, it must be a multiple of 4
		col=VDP[curSAL]&0xf;			// sprite color 
	
		if (VDP[curSAL++]&0x80)			// early clock
			xx-=32;

		// Even transparent sprites get drawn into the collision buffer
		p_add=SDT+(pat<<3);
		
		if (VDPREG[1]&0x01)	{		// magnified sprites
			for (i3=0; i3<16; i3+=2)
			{	
				t=VDP[p_add++];

				if (t&0x80) 
					bigpixel(xx, yy+i3, col);
				if (t&0x40)
					bigpixel(xx+2, yy+i3, col);
				if (t&0x20)
					bigpixel(xx+4, yy+i3, col);
				if (t&0x10)
					bigpixel(xx+6, yy+i3, col);
				if (t&0x08)
					bigpixel(xx+8, yy+i3, col);
				if (t&0x04)
					bigpixel(xx+10, yy+i3, col);
				if (t&0x02)
					bigpixel(xx+12, yy+i3, col);
				if (t&0x01)
					bigpixel(xx+14, yy+i3, col);

				if (VDPREG[1]&0x02)		// double-size sprites, need to draw 3 more chars 
				{	
					t=VDP[p_add+7];
					if (t&0x80)
						bigpixel(xx, yy+i3+16, col);
					if (t&0x40)
						bigpixel(xx+2, yy+i3+16, col);
					if (t&0x20)
						bigpixel(xx+4, yy+i3+16, col);
					if (t&0x10)
						bigpixel(xx+6, yy+i3+16, col);
					if (t&0x08)
						bigpixel(xx+8, yy+i3+16, col);
					if (t&0x04)
						bigpixel(xx+10, yy+i3+16, col);
					if (t&0x02)
						bigpixel(xx+12, yy+i3+16, col);
					if (t&0x01)
						bigpixel(xx+14, yy+i3+16, col);

					t=VDP[p_add+15];
					if (t&0x80)
						bigpixel(xx+16, yy+i3, col);
					if (t&0x40)
						bigpixel(xx+18, yy+i3, col);
					if (t&0x20)
						bigpixel(xx+20, yy+i3, col);
					if (t&0x10)	
						bigpixel(xx+22, yy+i3, col);
					if (t&0x08)
						bigpixel(xx+24, yy+i3, col);
					if (t&0x04)
						bigpixel(xx+26, yy+i3, col);
					if (t&0x02)
						bigpixel(xx+28, yy+i3, col);
					if (t&0x01)
						bigpixel(xx+30, yy+i3, col);

					t=VDP[p_add+23];
					if (t&0x80)
						bigpixel(xx+16, yy+i3+16, col);
					if (t&0x40)
						bigpixel(xx+18, yy+i3+16, col);
					if (t&0x20)
						bigpixel(xx+20, yy+i3+16, col);
					if (t&0x10)
						bigpixel(xx+22, yy+i3+16, col);
					if (t&0x08)
						bigpixel(xx+24, yy+i3+16, col);
					if (t&0x04)
						bigpixel(xx+26, yy+i3+16, col);
					if (t&0x02)
						bigpixel(xx+28, yy+i3+16, col);
					if (t&0x01)
						bigpixel(xx+30, yy+i3+16, col);
				}
			}
		} else {
			for (i3=0; i3<8; i3++)
			{	
				t=VDP[p_add++];

				if (t&0x80)
					spritepixel(xx, yy+i3, col);
				if (t&0x40)
					spritepixel(xx+1, yy+i3, col);
				if (t&0x20)
					spritepixel(xx+2, yy+i3, col);
				if (t&0x10)
					spritepixel(xx+3, yy+i3, col);
				if (t&0x08)
					spritepixel(xx+4, yy+i3, col);
				if (t&0x04)
					spritepixel(xx+5, yy+i3, col);
				if (t&0x02)
					spritepixel(xx+6, yy+i3, col);
				if (t&0x01)
					spritepixel(xx+7, yy+i3, col);

				if (VDPREG[1]&0x02)		// double-size sprites, need to draw 3 more chars 
				{	
					t=VDP[p_add+7];
					if (t&0x80)
						spritepixel(xx, yy+i3+8, col);
					if (t&0x40)
						spritepixel(xx+1, yy+i3+8, col);
					if (t&0x20)
						spritepixel(xx+2, yy+i3+8, col);
					if (t&0x10)
						spritepixel(xx+3, yy+i3+8, col);
					if (t&0x08)
						spritepixel(xx+4, yy+i3+8, col);
					if (t&0x04)
						spritepixel(xx+5, yy+i3+8, col);
					if (t&0x02)
						spritepixel(xx+6, yy+i3+8, col);
					if (t&0x01)
						spritepixel(xx+7, yy+i3+8, col);

					t=VDP[p_add+15];
					if (t&0x80)
						spritepixel(xx+8, yy+i3, col);
					if (t&0x40)
						spritepixel(xx+9, yy+i3, col);
					if (t&0x20)
						spritepixel(xx+10, yy+i3, col);
					if (t&0x10)	
						spritepixel(xx+11, yy+i3, col);
					if (t&0x08)
						spritepixel(xx+12, yy+i3, col);
					if (t&0x04)
						spritepixel(xx+13, yy+i3, col);
					if (t&0x02)
						spritepixel(xx+14, yy+i3, col);
					if (t&0x01)
						spritepixel(xx+15, yy+i3, col);

					t=VDP[p_add+23];
					if (t&0x80)
						spritepixel(xx+8, yy+i3+8, col);
					if (t&0x40)
						spritepixel(xx+9, yy+i3+8, col);
					if (t&0x20)
						spritepixel(xx+10, yy+i3+8, col);
					if (t&0x10)
						spritepixel(xx+11, yy+i3+8, col);
					if (t&0x08)
						spritepixel(xx+12, yy+i3+8, col);
					if (t&0x04)
						spritepixel(xx+13, yy+i3+8, col);
					if (t&0x02)
						spritepixel(xx+14, yy+i3+8, col);
					if (t&0x01)
						spritepixel(xx+15, yy+i3+8, col);
				}
			}
		}
	}
	// Set the VDP collision bit
	if (SprColFlag) {
		VDPS|=0x20;
	} else {
		VDPS&=0xdf;
	}
}

////////////////////////////////////////////////////////////
// Draw a pixel onto the backbuffer surface
////////////////////////////////////////////////////////////
void pixel(int x, int y, int c)
{
	framedata[((199-y)<<8)+((199-y)<<4)+x+8]=TIPALETTE[c];
}

////////////////////////////////////////////////////////////
// Draw a range-checked pixel onto the backbuffer surface
////////////////////////////////////////////////////////////
void spritepixel(int x, int y, int c)
{
	if ((y>191)||(y<0)) return;
	if ((x>255)||(x<0)) return;
	
	if (SprColBuf[x][y]) {
		SprColFlag=1;
	} else {
		SprColBuf[x][y]=1;
	}
	if (!c) return;		// don't DRAW transparent
	framedata[((199-y)<<8)+((199-y)<<4)+x+8]=TIPALETTE[c];
	return;
}

////////////////////////////////////////////////////////////
// Draw a magnified pixel onto the backbuffer surface
////////////////////////////////////////////////////////////
void bigpixel(int x, int y, int c)
{
	spritepixel(x,y,c);
	spritepixel(x+1,y,c);
	spritepixel(x,y+1,c);
	spritepixel(x+1,y+1,c);
}

////////////////////////////////////////////////////////////
// DirectX full screen enumeration callback
////////////////////////////////////////////////////////////
HRESULT WINAPI myCallBack(LPDDSURFACEDESC2 ddSurface, LPVOID pData) {
	int *c;

	c=(int*)pData;

	if (ddSurface->ddpfPixelFormat.dwRGBBitCount == (DWORD)*c) {
		*c=(*c)|0x80;
		return DDENUMRET_CANCEL;
	}
	return DDENUMRET_OK;
}

////////////////////////////////////////////////////////////
// Setup DirectDraw, with the requested fullscreen mode
// In order for Fullscreen to work, only the main thread
// may call this function!
////////////////////////////////////////////////////////////
void SetupDirectDraw(int fullscreen) {
	int x,y,c;
	RECT myRect;

	WaitForSingleObject(MyMutex, INFINITE);

	if (DirectDrawCreateEx(NULL, (void**)&lpdd, IID_IDirectDraw7, NULL)!=DD_OK) {
		MessageBox(myWnd, "Unable to initialize DirectDraw 7\nClassic99 Requires DirectX 7 for DX and Full screen modes", "Classic99 Error", MB_OK);
		lpdd=NULL;
		StretchMode=0;
	} else {
		if (fullscreen) {
			DDSURFACEDESC2 myDesc;

			GetWindowRect(myWnd, &myRect);

			switch (fullscreen) {
			case 1: x=320; y=240; c=8; break;
			case 2: x=320; y=240; c=16; break;
			case 3: x=320; y=240; c=24; break;
			case 4: x=320; y=200; c=8; break;
			case 5: x=640; y=480; c=8; break;
			case 6: x=640; y=480; c=16; break;
			case 7: x=640; y=480; c=24; break;
			case 8: x=800; y=600; c=16; break;
			case 9: x=800; y=600; c=24; break;
			default:x=320; y=240; c=8; break;
			}

			// Check if mode is legal
			ZeroMemory(&myDesc, sizeof(myDesc));
			myDesc.dwSize=sizeof(myDesc);
			myDesc.dwFlags=DDSD_HEIGHT | DDSD_WIDTH;
			myDesc.dwWidth=x;
			myDesc.dwHeight=y;
			lpdd->EnumDisplayModes(0, &myDesc, (void*)&c, myCallBack);
			// If a valid mode was found, 'c' has 0x80 ORd with it
			if (0 == (c&0x80)) {
				MessageBox(myWnd, "Requested graphics mode is not supported on the primary display.", "Classic99 Error", MB_OK);
				if (lpdd) lpdd->Release();
				lpdd=NULL;
				StretchMode=0;
				MoveWindow(myWnd, myRect.left, myRect.top, myRect.right-myRect.left, myRect.bottom-myRect.top, true);
				goto optout;
			}

			c&=0x7f;	// Remove the flag bit

			if (lpdd->SetCooperativeLevel(myWnd, DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT | DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX)!=DD_OK) {
				MessageBox(myWnd, "Unable to set cooperative level\nFullscreen DX is not available", "Classic99 Error", MB_OK);
				if (lpdd) lpdd->Release();
				lpdd=NULL;
				StretchMode=0;
				MoveWindow(myWnd, myRect.left, myRect.top, myRect.right-myRect.left, myRect.bottom-myRect.top, true);
				goto optout;
			}

			if (lpdd->SetDisplayMode(x,y,c,0,0) != DD_OK) {
				MessageBox(myWnd, "Unable to set display mode.\nRequested DX mode is not available", "Classic99 Error", MB_OK);
				MoveWindow(myWnd, myRect.left, myRect.top, myRect.right-myRect.left, myRect.bottom-myRect.top, true);
				StretchMode=0;
				goto optout;
			}
		} else {
			if (lpdd->SetCooperativeLevel(myWnd, DDSCL_NORMAL)!=DD_OK) {
				MessageBox(myWnd, "Unable to set cooperative level\nDX mode is not available", "Classic99 Error", MB_OK);
				if (lpdd) lpdd->Release();
				lpdd=NULL;
				StretchMode=0;
				goto optout;
			}
		}

		DDSURFACEDESC2 ddsd;

		ZeroMemory(&ddsd, sizeof(ddsd));
		ddsd.dwSize=sizeof(ddsd);
		ddsd.dwFlags=DDSD_CAPS;
		ddsd.ddsCaps.dwCaps=DDSCAPS_PRIMARYSURFACE;

		if (lpdd->CreateSurface(&ddsd, &lpdds, NULL) !=DD_OK) {
			MessageBox(myWnd, "Unable to create primary surface\nDX mode is not available", "Classic99 Error", MB_OK);
			if (lpdd) lpdd->Release();
			lpdd=NULL;
			StretchMode=0;
			goto optout;
		}

		ZeroMemory(&ddsd, sizeof(ddsd));
		ddsd.dwSize=sizeof(ddsd);
		ddsd.dwFlags=DDSD_HEIGHT | DDSD_WIDTH;
		if (FilterMode == 0) {
			ddsd.dwWidth=256+16;
			ddsd.dwHeight=192+16;
		} else {
			ddsd.dwWidth=512+32;
			ddsd.dwHeight=384+29;
		}

		if (lpdd->CreateSurface(&ddsd, &ddsBack, NULL) !=DD_OK) {
			MessageBox(myWnd, "Unable to create back buffer surface\nDX mode is not available", "Classic99 Error", MB_OK);
			ddsBack=NULL;
			lpdds->Release();
			lpdds=NULL;
			lpdd->Release();
			lpdd=NULL;
			StretchMode=0;
			goto optout;
		}

		if (!fullscreen) {
			if (lpdd->CreateClipper(0, &lpDDClipper, NULL) != DD_OK) {
				MessageBox(myWnd, "Warning: Unable to create Direct Draw Clipper", "Classic99 Warning", MB_OK);
			} else {
				if (lpDDClipper->SetHWnd(0, myWnd) != DD_OK) {
					MessageBox(myWnd, "Warning: Unable to set Clipper Window", "Classic99 Warning", MB_OK);
					lpDDClipper->Release();
					lpDDClipper=NULL;
				} else {
					if (DD_OK != lpdds->SetClipper(lpDDClipper)) {
						MessageBox(myWnd, "Warning: Unable to attach Clipper", "Classic99 Warning", MB_OK);
						lpDDClipper->Release();
						lpDDClipper=NULL;
					}
				}
			}
		}

optout: ;
	}
	ReleaseMutex(MyMutex);
}

////////////////////////////////////////////////////////////
// Release all references to DirectDraw objects
////////////////////////////////////////////////////////////
void takedownDirectDraw() {	
	WaitForSingleObject(MyMutex, INFINITE);
	if (NULL != lpDDClipper) lpDDClipper->Release();
	lpDDClipper=NULL;
	if (NULL != ddsBack) ddsBack->Release();
	ddsBack=NULL;
	if (NULL != lpdds) lpdds->Release();
	lpdds=NULL;
	if (NULL != lpdd) lpdd->Release();
	lpdd=NULL;
	ReleaseMutex(MyMutex);
}

////////////////////////////////////////////////////////////
// Resize the back buffer
////////////////////////////////////////////////////////////
int ResizeBackBuffer(int w, int h) {
	DDSURFACEDESC2 ddsd;
	
	WaitForSingleObject(MyMutex, INFINITE);
	if (NULL != ddsBack) ddsBack->Release();
	ddsBack=NULL;

	ZeroMemory(&ddsd, sizeof(ddsd));
	ddsd.dwSize=sizeof(ddsd);
	ddsd.dwFlags=DDSD_HEIGHT | DDSD_WIDTH;
	ddsd.dwWidth=w;
	ddsd.dwHeight=h;

	if (lpdd->CreateSurface(&ddsd, &ddsBack, NULL) !=DD_OK) {
		MessageBox(myWnd, "Unable to create back buffer surface\nDX mode is not available", "Classic99 Error", MB_OK);
		ddsBack=NULL;
		StretchMode=0;
		ReleaseMutex(MyMutex);
		return 1;
	}

	ReleaseMutex(MyMutex);
	return 0;
}
