/* FCE Ultra - NES/Famicom Emulator
 *
 * Copyright notice for this file:
 *  Copyright (C) 1998 BERO
 *  Copyright (C) 2003 Xodnizel
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "types.h"
#include "x6502.h"
#include "fceu.h"
#include "ppu.h"
#include "nsf.h"
#include "sound.h"
#include "file.h"
#include "utils/endian.h"
#include "utils/memory.h"
		 
#include "cart.h"
#include "palette.h"
#include "state.h"
#include "video.h"
#include "input.h"
#include "driver.h"
#include "debug.h"
		 
#include <cstring>
#include <cstdio>
#include <cstdlib>

#define VBlankON    (PPU[0] & 0x80)	//Generate VBlank NMI
#define Sprite16    (PPU[0] & 0x20)	//Sprites 8x16/8x8
#define BGAdrHI     (PPU[0] & 0x10)	//BG pattern adr $0000/$1000
#define SpAdrHI     (PPU[0] & 0x08)	//Sprite pattern adr $0000/$1000
#define INC32       (PPU[0] & 0x04)	//auto increment 1/32

#define SpriteON    (PPU[1] & 0x10)	//Show Sprite
#define ScreenON    (PPU[1] & 0x08)	//Show screen
#define PPUON       (PPU[1] & 0x18)	//PPU should operate
#define GRAYSCALE   (PPU[1] & 0x01)	//Grayscale (AND palette entries with 0x30)

#define SpriteLeft8 (PPU[1] & 0x04)
#define BGLeft8     (PPU[1] & 0x02)

#define PPU_status  (PPU[2])

#define READPALNOGS(ofs)    (PALRAM[(ofs)])
#define READPAL(ofs)    (PALRAM[(ofs)] & (GRAYSCALE ? 0x30 : 0xFF))
#define READUPAL(ofs)   (UPALRAM[(ofs)] & (GRAYSCALE ? 0x30 : 0xFF))

static void FetchSpriteData(void);
static void RefreshLine(int lastpixel);
static void RefreshSprites(void);
static void CopySprites(uint8 *target);

static void Fixit1(void);
static uint32 ppulut1[256];
static uint32 ppulut2[256];
static uint32 ppulut3[128];

static bool new_ppu_reset = false;

int test = 0;

template<typename T, int BITS>
struct BITREVLUT {
	T* lut;
	BITREVLUT() {
		int bits = BITS;
		int n = 1 << BITS;
		lut = new T[n];

		int m = 1;
		int a = n >> 1;
		int j = 2;

		lut[0] = 0;
		lut[1] = a;

		while (--bits) {
			m <<= 1;
			a >>= 1;
			for (int i = 0; i < m; i++)
				lut[j++] = lut[i] + a;
		}
	}

	T operator[](int index) {
		return lut[index];
	}
};
BITREVLUT<uint8, 8> bitrevlut;

struct PPUSTATUS {
	int32 sl;
	int32 cycle, end_cycle;
};

struct SPRITE_READ {
	int32 num;
	int32 count;
	int32 fetch;
	int32 found;
	int32 found_pos[8];
	int32 ret;
	int32 last;
	int32 mode;

	void reset() {
		num = count = fetch = found = ret = last = mode = 0;
		found_pos[0] = found_pos[1] = found_pos[2] = found_pos[3] = 0;
		found_pos[4] = found_pos[5] = found_pos[6] = found_pos[7] = 0;
	}

	void start_scanline() {
		num = 1;
		found = 0;
		fetch = 1;
		count = 0;
		last = 64;
		mode = 0;
		found_pos[0] = found_pos[1] = found_pos[2] = found_pos[3] = 0;
		found_pos[4] = found_pos[5] = found_pos[6] = found_pos[7] = 0;
	}
};

//doesn't need to be savestated as it is just a reflection of the current position in the ppu loop
PPUPHASE ppuphase;

//this needs to be savestated since a game may be trying to read from this across vblanks
SPRITE_READ spr_read;

//definitely needs to be savestated
uint8 idleSynch = 1;

//uses the internal counters concept at http://nesdev.icequake.net/PPU%20addressing.txt
struct PPUREGS {
	//normal clocked regs. as the game can interfere with these at any time, they need to be savestated
	uint32 fv;	//3
	uint32 v;	//1
	uint32 h;	//1
	uint32 vt;	//5
	uint32 ht;	//5

	//temp unlatched regs (need savestating, can be written to at any time)
	uint32 _fv, _v, _h, _vt, _ht;

	//other regs that need savestating
	uint32 fh;	//3 (horz scroll)
	uint32 s;	//1 ($2000 bit 4: "Background pattern table address (0: $0000; 1: $1000)")

	//other regs that don't need saving
	uint32 par;	//8 (sort of a hack, just stored in here, but not managed by this system)

	//cached state data. these are always reset at the beginning of a frame and don't need saving
	//but just to be safe, we're gonna save it
	PPUSTATUS status;

	void reset() {
		fv = v = h = vt = ht = 0;
		fh = par = s = 0;
		_fv = _v = _h = _vt = _ht = 0;
		status.cycle = 0;
		status.end_cycle = 341;
		status.sl = 241;
	}

	void install_latches() {
		fv = _fv;
		v = _v;
		h = _h;
		vt = _vt;
		ht = _ht;
	}

	void install_h_latches() {
		ht = _ht;
		h = _h;
	}

	void clear_latches() {
		_fv = _v = _h = _vt = _ht = 0;
		fh = 0;
	}

	void increment_hsc() {
		//The first one, the horizontal scroll counter, consists of 6 bits, and is
		//made up by daisy-chaining the HT counter to the H counter. The HT counter is
		//then clocked every 8 pixel dot clocks (or every 8/3 CPU clock cycles).
		ht++;
		h += (ht >> 5);
		ht &= 31;
		h &= 1;
	}

	void increment_vs() {
		fv++;
		int fv_overflow = (fv >> 3);
		vt += fv_overflow;
		vt &= 31;	//fixed tecmo super bowl
		if (vt == 30 && fv_overflow == 1) {	//caution here (only do it at the exact instant of overflow) fixes p'radikus conflict
			v++;
			vt = 0;
		}
		fv &= 7;
		v &= 1;
	}

	uint32 get_ntread() {
		return 0x2000 | (v << 0xB) | (h << 0xA) | (vt << 5) | ht;
	}

	uint32 get_2007access() {
		return ((fv & 3) << 0xC) | (v << 0xB) | (h << 0xA) | (vt << 5) | ht;
	}

	//The PPU has an internal 4-position, 2-bit shifter, which it uses for
	//obtaining the 2-bit palette select data during an attribute table byte
	//fetch. To represent how this data is shifted in the diagram, letters a..c
	//are used in the diagram to represent the right-shift position amount to
	//apply to the data read from the attribute data (a is always 0). This is why
	//you only see bits 0 and 1 used off the read attribute data in the diagram.
	uint32 get_atread() {
		return 0x2000 | (v << 0xB) | (h << 0xA) | 0x3C0 | ((vt & 0x1C) << 1) | ((ht & 0x1C) >> 2);
	}

	//address line 3 relates to the pattern table fetch occuring (the PPU always makes them in pairs).
	uint32 get_ptread() {
		return (s << 0xC) | (par << 0x4) | fv;
	}

	void increment2007(bool rendering, bool by32) {

		if (rendering)
		{
			//don't do this:
			//if (by32) increment_vs();
			//else increment_hsc();
			//do this instead:
			increment_vs();  //yes, even if we're moving by 32
			return;
		}

		//If the VRAM address increment bit (2000.2) is clear (inc. amt. = 1), all the
		//scroll counters are daisy-chained (in the order of HT, VT, H, V, FV) so that
		//the carry out of each counter controls the next counter's clock rate. The
		//result is that all 5 counters function as a single 15-bit one. Any access to
		//2007 clocks the HT counter here.
		//
		//If the VRAM address increment bit is set (inc. amt. = 32), the only
		//difference is that the HT counter is no longer being clocked, and the VT
		//counter is now being clocked by access to 2007.
		if (by32) {
			vt++;
		} else {
			ht++;
			vt += (ht >> 5) & 1;
		}
		h += (vt >> 5);
		v += (h >> 1);
		fv += (v >> 1);
		ht &= 31;
		vt &= 31;
		h &= 1;
		v &= 1;
		fv &= 7;
	}

	void debug_log()
	{
		FCEU_printf("ppur: fv(%d), v(%d), h(%d), vt(%d), ht(%d)\n",fv,v,h,vt,ht);
		FCEU_printf("      _fv(%d), _v(%d), _h(%d), _vt(%d), _ht(%d)\n",_fv,_v,_h,_vt,_ht);
		FCEU_printf("      fh(%d), s(%d), par(%d)\n",fh,s,par);
		FCEU_printf("      .status cycle(%d), end_cycle(%d), sl(%d)\n",status.cycle,status.end_cycle,status.sl);
	}
} ppur;

int newppu_get_scanline() { return ppur.status.sl; }
int newppu_get_dot() { return ppur.status.cycle; }
void newppu_hacky_emergency_reset()
{
	if(ppur.status.end_cycle == 0)
		ppur.reset();
}

static void makeppulut(void) {
	int x;
	int y;
	int cc, xo, pixel;


	for (x = 0; x < 256; x++) {
		ppulut1[x] = 0;
		for (y = 0; y < 8; y++)
			ppulut1[x] |= ((x >> (7 - y)) & 1) << (y * 4);
		ppulut2[x] = ppulut1[x] << 1;
	}

	for (cc = 0; cc < 16; cc++) {
		for (xo = 0; xo < 8; xo++) {
			ppulut3[xo | (cc << 3)] = 0;
			for (pixel = 0; pixel < 8; pixel++) {
				int shiftr;
				shiftr = (pixel + xo) / 8;
				shiftr *= 2;
				ppulut3[xo | (cc << 3)] |= ((cc >> shiftr) & 3) << (2 + pixel * 4);
			}
		}
	}
}

static int ppudead = 1;
static int kook = 0;
int fceuindbg = 0;

//mbg 6/23/08
//make the no-bg fill color configurable
//0xFF shall indicate to use palette[0]
uint8 gNoBGFillColor = 0xFF;

int MMC5Hack = 0;
uint32 MMC5HackVROMMask = 0;
uint8 *MMC5HackExNTARAMPtr = 0;
uint8 *MMC5HackVROMPTR = 0;
uint8 MMC5HackCHRMode = 0;
uint8 MMC5HackSPMode = 0;
uint8 MMC50x5130 = 0;
uint8 MMC5HackSPScroll = 0;
uint8 MMC5HackSPPage = 0;

int PEC586Hack = 0;

int QTAIHack = 0;
uint8 QTAINTRAM[2048];
uint8 qtaintramreg;

uint8 VRAMBuffer = 0, PPUGenLatch = 0;
uint8 *vnapage[4];
uint8 PPUNTARAM = 0;
uint8 PPUCHRRAM = 0;

//Color deemphasis emulation.  Joy...
static uint8 deemp = 0;
static int deempcnt[8];

void (*GameHBIRQHook)(void), (*GameHBIRQHook2)(void);
void (*PPU_hook)(uint32 A);

uint8 vtoggle = 0;
uint8 XOffset = 0;
uint8 SpriteDMA = 0; // $4014 / Writing $xx copies 256 bytes by reading from $xx00-$xxFF and writing to $2004 (OAM data)

uint32 TempAddr = 0, RefreshAddr = 0, DummyRead = 0, NTRefreshAddr = 0;

static int maxsprites = 8;

//scanline is equal to the current visible scanline we're on.
int scanline;
int g_rasterpos;
static uint32 scanlines_per_frame;

uint8 PPU[4];
uint8 PPUSPL;
uint8 NTARAM[0x800], PALRAM[0x20], SPRAM[0x100], SPRBUF[0x100];
uint8 UPALRAM[0x03];//for 0x4/0x8/0xC addresses in palette, the ones in
					//0x20 are 0 to not break fceu rendering.

#define MMC5SPRVRAMADR(V)   &MMC5SPRVPage[(V) >> 10][(V)]
#define VRAMADR(V)          &VPage[(V) >> 10][(V)]

uint8* MMC5BGVRAMADR(uint32 A);

uint8 READPAL_MOTHEROFALL(uint32 A)
{
	if(!(A & 3)) {
		if(!(A & 0xC))
			return READPAL(0x00);
		else
			return READUPAL(((A & 0xC) >> 2) - 1);
	}
	else
		return READPAL(A & 0x1F);
}

//this duplicates logic which is embedded in the ppu rendering code
//which figures out where to get CHR data from depending on various hack modes
//mostly involving mmc5.
//this might be incomplete.
uint8* FCEUPPU_GetCHR(uint32 vadr, uint32 refreshaddr) {
	if (MMC5Hack) {
		if (MMC5HackCHRMode == 1) {
			uint8 *C = MMC5HackVROMPTR;
			C += (((MMC5HackExNTARAMPtr[refreshaddr & 0x3ff]) & 0x3f & MMC5HackVROMMask) << 12) + (vadr & 0xfff);
			C += (MMC50x5130 & 0x3) << 18;	//11-jun-2009 for kuja_killer
			return C;
		} else {
			return MMC5BGVRAMADR(vadr);
		}
	} else return VRAMADR(vadr);
}

//likewise for ATTR
int FCEUPPU_GetAttr(int ntnum, int xt, int yt) {
	int attraddr = 0x3C0 + ((yt >> 2) << 3) + (xt >> 2);
	int temp = (((yt & 2) << 1) + (xt & 2));
	int refreshaddr = xt + yt * 32;
	if (MMC5Hack && MMC5HackCHRMode == 1)
		return (MMC5HackExNTARAMPtr[refreshaddr & 0x3ff] & 0xC0) >> 6;
	else
		return (vnapage[ntnum][attraddr] & (3 << temp)) >> temp;
}

//new ppu-----
inline void FFCEUX_PPUWrite_Default(uint32 A, uint8 V) {
	uint32 tmp = A;

	if (PPU_hook) PPU_hook(A);

	if (tmp < 0x2000) {
		if (PPUCHRRAM & (1 << (tmp >> 10)))
			VPage[tmp >> 10][tmp] = V;
	} else if (tmp < 0x3F00) {
		if (QTAIHack && (qtaintramreg & 1)) {
			QTAINTRAM[((((tmp & 0xF00) >> 10) >> ((qtaintramreg >> 1)) & 1) << 10) | (tmp & 0x3FF)] = V;
		} else {
			if (PPUNTARAM & (1 << ((tmp & 0xF00) >> 10)))
				vnapage[((tmp & 0xF00) >> 10)][tmp & 0x3FF] = V;
		}
	} else {
		if (!(tmp & 3)) {
			if (!(tmp & 0xC)) {
				PALRAM[0x00] = PALRAM[0x04] = PALRAM[0x08] = PALRAM[0x0C] = V & 0x3F;
				PALRAM[0x10] = PALRAM[0x14] = PALRAM[0x18] = PALRAM[0x1C] = V & 0x3F;
			}
			else
				UPALRAM[((tmp & 0xC) >> 2) - 1] = V & 0x3F;
		} else
			PALRAM[tmp & 0x1F] = V & 0x3F;
	}
}

volatile int rendercount, vromreadcount, undefinedvromcount, LogAddress = -1;
unsigned char *cdloggervdata = NULL;
unsigned int cdloggerVideoDataSize = 0;

int GetCHRAddress(int A) 
{
	if (cdloggerVideoDataSize) 
	{
		int result = -1;
		if ( (A >= 0) && (A < 0x2000) )
		{
			result = &VPage[A >> 10][A] - CHRptr[0];
		}
		if ((result >= 0) && (result < (int)cdloggerVideoDataSize))
		{
			return result;
		}
	}
	else
	{
		if ( (A >= 0) && (A < 0x2000) ) return A;
	}
	return -1;
}

int GetCHROffset(uint8 *ptr) {
	int result = ptr - CHRptr[0];
	if (cdloggerVideoDataSize) {
		if ((result >= 0) && (result < (int)cdloggerVideoDataSize))
			return result;
	} else {
		if ((result >= 0) && (result < 0x2000))
			return result;
	}
	return -1;
}

#define RENDER_LOG(tmp) { \
		if (debug_loggingCD) \
		{ \
			int addr = GetCHRAddress(tmp); \
			if (addr != -1)	\
			{ \
				if (!(cdloggervdata[addr] & 1))	\
				{ \
					cdloggervdata[addr] |= 1; \
					if(cdloggerVideoDataSize) { \
						if (!(cdloggervdata[addr] & 2)) undefinedvromcount--; \
						rendercount++; \
					} \
				} \
			} \
		} \
}

#define RENDER_LOGP(tmp) { \
		if (debug_loggingCD) \
		{ \
			int addr = GetCHROffset(tmp); \
			if (addr != -1)	\
			{ \
				if (!(cdloggervdata[addr] & 1))	\
				{ \
					cdloggervdata[addr] |= 1; \
					if(cdloggerVideoDataSize) { \
						if (!(cdloggervdata[addr] & 2)) undefinedvromcount--; \
						rendercount++; \
					} \
				} \
			} \
		} \
}

uint8 FASTCALL FFCEUX_PPURead_Default(uint32 A) {
	uint32 tmp = A;

	if (PPU_hook) PPU_hook(A);

	if (tmp < 0x2000) {
		return VPage[tmp >> 10][tmp];
	} else if (tmp < 0x3F00) {
		return vnapage[(tmp >> 10) & 0x3][tmp & 0x3FF];
	} else {
		uint8 ret;
		if (!(tmp & 3)) {
			if (!(tmp & 0xC))
				ret = READPAL(0x00);
			else
				ret = READUPAL(((tmp & 0xC) >> 2) - 1);
		} else
			ret = READPAL(tmp & 0x1F);
		return ret;
	}
}


uint8 (FASTCALL *FFCEUX_PPURead)(uint32 A) = 0;
void (*FFCEUX_PPUWrite)(uint32 A, uint8 V) = 0;

#define CALL_PPUREAD(A) (FFCEUX_PPURead(A))

#define CALL_PPUWRITE(A, V) (FFCEUX_PPUWrite ? FFCEUX_PPUWrite(A, V) : FFCEUX_PPUWrite_Default(A, V))

//whether to use the new ppu
int newppu = 0;

void ppu_getScroll(int &xpos, int &ypos) {
	if (newppu) {
		ypos = ppur._vt * 8 + ppur._fv + ppur._v * 256;
		xpos = ppur._ht * 8 + ppur.fh + ppur._h * 256;
	} else {
		xpos = ((RefreshAddr & 0x400) >> 2) | ((RefreshAddr & 0x1F) << 3) | XOffset;

		ypos = ((RefreshAddr & 0x3E0) >> 2) | ((RefreshAddr & 0x7000) >> 12);
		if (RefreshAddr & 0x800) ypos += 240;
	}
}
//---------------

static DECLFR(A2002) {
	if (newppu) {
		//once we thought we clear latches here, but that caused midframe glitches.
		//i think we should only reset the state machine for 2005/2006
		//ppur.clear_latches();
	}

	uint8 ret;

	FCEUPPU_LineUpdate();
	ret = PPU_status;
	ret |= PPUGenLatch & 0x1F;

#ifdef FCEUDEF_DEBUGGER
	if (!fceuindbg)
#endif
	{
		vtoggle = 0;
		PPU_status &= 0x7F;
		PPUGenLatch = ret;
	}

	return ret;
}

static DECLFR(A2004) {
	if (newppu) {
		if ((ppur.status.sl < 241) && PPUON) {
			// from cycles 0 to 63, the
			// 32 byte OAM buffer gets init
			// to 0xFF
			if (ppur.status.cycle < 64)
				return spr_read.ret = 0xFF;
			else {
				for (int i = spr_read.last;
					 i != ppur.status.cycle; ++i) {
					if (i < 256) {
						switch (spr_read.mode) {
						case 0:
							if (spr_read.count < 2)
								spr_read.ret = (PPU[3] & 0xF8) + (spr_read.count << 2);
							else
								spr_read.ret = spr_read.count << 2;

							spr_read.found_pos[spr_read.found] = spr_read.ret;
							spr_read.ret = SPRAM[spr_read.ret];

							if (i & 1) {
								//odd cycle
								//see if in range
								if (!((ppur.status.sl - 1 - spr_read.ret) & ~(Sprite16 ? 0xF : 0x7))) {
									++spr_read.found;
									spr_read.fetch = 1;
									spr_read.mode = 1;
								} else {
									if (++spr_read.count == 64) {
										spr_read.mode = 4;
										spr_read.count = 0;
									} else if (spr_read.found == 8) {
										spr_read.fetch = 0;
										spr_read.mode = 2;
									}
								}
							}
							break;
						case 1:	//sprite is in range fetch next 3 bytes
							if (i & 1) {
								++spr_read.fetch;
								if (spr_read.fetch == 4) {
									spr_read.fetch = 1;
									if (++spr_read.count == 64) {
										spr_read.count = 0;
										spr_read.mode = 4;
									} else if (spr_read.found == 8) {
										spr_read.fetch = 0;
										spr_read.mode = 2;
									} else
										spr_read.mode = 0;
								}
							}

							if (spr_read.count < 2)
								spr_read.ret = (PPU[3] & 0xF8) + (spr_read.count << 2);
							else
								spr_read.ret = spr_read.count << 2;

							spr_read.ret = SPRAM[spr_read.ret | spr_read.fetch];
							break;
						case 2:	//8th sprite fetched
							spr_read.ret = SPRAM[(spr_read.count << 2) | spr_read.fetch];
							if (i & 1) {
								if (!((ppur.status.sl - 1 - SPRAM[((spr_read.count << 2) | spr_read.fetch)]) & ~((Sprite16) ? 0xF : 0x7))) {
									spr_read.fetch = 1;
									spr_read.mode = 3;
								} else {
									if (++spr_read.count == 64) {
										spr_read.count = 0;
										spr_read.mode = 4;
									}
									spr_read.fetch =
										(spr_read.fetch + 1) & 3;
								}
							}
							spr_read.ret = spr_read.count;
							break;
						case 3:	//9th sprite overflow detected
							spr_read.ret = SPRAM[spr_read.count | spr_read.fetch];
							if (i & 1) {
								if (++spr_read.fetch == 4) {
									spr_read.count = (spr_read.count + 1) & 63;
									spr_read.mode = 4;
								}
							}
							break;
						case 4:	//read OAM[n][0] until hblank
							if (i & 1)
								spr_read.count = (spr_read.count + 1) & 63;
							spr_read.fetch = 0;
							spr_read.ret = SPRAM[spr_read.count << 2];
							break;
						}
					} else if (i < 320) {
						spr_read.ret = (i & 0x38) >> 3;
						if (spr_read.found < (spr_read.ret + 1)) {
							if (spr_read.num) {
								spr_read.ret = SPRAM[252];
								spr_read.num = 0;
							} else
								spr_read.ret = 0xFF;
						} else if ((i & 7) < 4) {
							spr_read.ret =
								SPRAM[spr_read.found_pos[spr_read.ret] | spr_read.fetch++];
							if (spr_read.fetch == 4)
								spr_read.fetch = 0;
						} else
							spr_read.ret = SPRAM[spr_read.found_pos [spr_read.ret | 3]];
					} else {
						if (!spr_read.found)
							spr_read.ret = SPRAM[252];
						else
							spr_read.ret = SPRAM[spr_read.found_pos[0]];
						break;
					}
				}
				spr_read.last = ppur.status.cycle;
				return spr_read.ret;
			}
		} else
			return SPRAM[PPU[3]];
	} else {
		FCEUPPU_LineUpdate();
		return PPUGenLatch;
	}
}

static DECLFR(A200x) {	/* Not correct for $2004 reads. */
	FCEUPPU_LineUpdate();
	return PPUGenLatch;
}

static DECLFR(A2007) {
	uint8 ret;
	uint32 tmp = RefreshAddr & 0x3FFF;

	if (debug_loggingCD) {
		if (!DummyRead && (LogAddress != -1)) {
			if (!(cdloggervdata[LogAddress] & 2)) {
				cdloggervdata[LogAddress] |= 2;
				if ((!(cdloggervdata[LogAddress] & 1)) && cdloggerVideoDataSize) undefinedvromcount--;
				vromreadcount++;
			}
		} else
			DummyRead = 0;
	}

	if (newppu) {
		ret = VRAMBuffer;
		RefreshAddr = ppur.get_2007access() & 0x3FFF;
		if ((RefreshAddr & 0x3F00) == 0x3F00) {
			//if it is in the palette range bypass the
			//delayed read, and what gets filled in the temp
			//buffer is the address - 0x1000, also
			//if grayscale is set then the return is AND with 0x30
			//to get a gray color reading
			if (!(tmp & 3)) {
				if (!(tmp & 0xC))
					ret = READPAL(0x00);
				else
					ret = READUPAL(((tmp & 0xC) >> 2) - 1);
			} else
				ret = READPAL(tmp & 0x1F);
			VRAMBuffer = CALL_PPUREAD(RefreshAddr - 0x1000);
		} else {
			if (debug_loggingCD && (RefreshAddr < 0x2000))
				LogAddress = GetCHRAddress(RefreshAddr);
			VRAMBuffer = CALL_PPUREAD(RefreshAddr);
		}
		ppur.increment2007(ppur.status.sl >= 0 && ppur.status.sl < 241 && PPUON, INC32 != 0);
		RefreshAddr = ppur.get_2007access();
		return ret;
	} else {

		//OLDPPU
		FCEUPPU_LineUpdate();

		if (tmp >= 0x3F00) {	// Palette RAM tied directly to the output data, without VRAM buffer
			if (!(tmp & 3)) {
				if (!(tmp & 0xC))
					ret = READPAL(0x00);
				else
					ret = READUPAL(((tmp & 0xC) >> 2) - 1);
			} else
				ret = READPAL(tmp & 0x1F);
			#ifdef FCEUDEF_DEBUGGER
			if (!fceuindbg)
			#endif
			{
				if ((tmp - 0x1000) < 0x2000)
					VRAMBuffer = VPage[(tmp - 0x1000) >> 10][tmp - 0x1000];
				else
					VRAMBuffer = vnapage[((tmp - 0x1000) >> 10) & 0x3][(tmp - 0x1000) & 0x3FF];
				if (PPU_hook) PPU_hook(tmp);
			}
		} else {
			ret = VRAMBuffer;
			#ifdef FCEUDEF_DEBUGGER
			if (!fceuindbg)
			#endif
			{
				if (PPU_hook) PPU_hook(tmp);
				PPUGenLatch = VRAMBuffer;
				if (tmp < 0x2000) {

					if (debug_loggingCD)
						LogAddress = GetCHRAddress(tmp);
					if(MMC5Hack && newppu)
						VRAMBuffer = *MMC5BGVRAMADR(tmp);
					else
						VRAMBuffer = VPage[tmp >> 10][tmp];

				} else if (tmp < 0x3F00)
					VRAMBuffer = vnapage[(tmp >> 10) & 0x3][tmp & 0x3FF];
			}
		}

	#ifdef FCEUDEF_DEBUGGER
		if (!fceuindbg)
	#endif
		{
			if ((ScreenON || SpriteON) && (scanline < 240)) {
				uint32 rad = RefreshAddr;
				if ((rad & 0x7000) == 0x7000) {
					rad ^= 0x7000;
					if ((rad & 0x3E0) == 0x3A0)
						rad ^= 0xBA0;
					else if ((rad & 0x3E0) == 0x3e0)
						rad ^= 0x3e0;
					else
						rad += 0x20;
				} else
					rad += 0x1000;
				RefreshAddr = rad;
			} else {
				if (INC32)
					RefreshAddr += 32;
				else
					RefreshAddr++;
			}
			if (PPU_hook) PPU_hook(RefreshAddr & 0x3fff);
		}
		return ret;
	}
}

static DECLFW(B2000) {
	FCEUPPU_LineUpdate();
	PPUGenLatch = V;

	if (!(PPU[0] & 0x80) && (V & 0x80) && (PPU_status & 0x80))
		TriggerNMI2();

	PPU[0] = V;
	TempAddr &= 0xF3FF;
	TempAddr |= (V & 3) << 10;

	ppur._h = V & 1;
	ppur._v = (V >> 1) & 1;
	ppur.s = (V >> 4) & 1;
}

static DECLFW(B2001) {
	FCEUPPU_LineUpdate();
	if (paldeemphswap)
		V = (V&0x9F)|((V&0x40)>>1)|((V&0x20)<<1);
	PPUGenLatch = V;
	PPU[1] = V;
	if (V & 0xE0)
		deemp = V >> 5;
}

static DECLFW(B2002) {
	PPUGenLatch = V;
}

static DECLFW(B2003) {
	PPUGenLatch = V;
	PPU[3] = V;
	PPUSPL = V & 0x7;
}

static DECLFW(B2004) {
	PPUGenLatch = V;
	if (newppu) {
		//the attribute upper bits are not connected
		//so AND them out on write, since reading them
		//should return 0 in those bits.
		if ((PPU[3] & 3) == 2)
			V &= 0xE3;
		SPRAM[PPU[3]] = V;
		PPU[3] = (PPU[3] + 1) & 0xFF;
	} else {
		if (PPUSPL >= 8) {
			if (PPU[3] >= 8)
				SPRAM[PPU[3]] = V;
		} else {
			SPRAM[PPUSPL] = V;
		}
		PPU[3]++;
		PPUSPL++;
	}
}

static DECLFW(B2005) {
	uint32 tmp = TempAddr;
	FCEUPPU_LineUpdate();
	PPUGenLatch = V;
	if (!vtoggle) {
		tmp &= 0xFFE0;
		tmp |= V >> 3;
		XOffset = V & 7;
		ppur._ht = V >> 3;
		ppur.fh = V & 7;
	} else {
		tmp &= 0x8C1F;
		tmp |= ((V & ~0x7) << 2);
		tmp |= (V & 7) << 12;
		ppur._vt = V >> 3;
		ppur._fv = V & 7;
	}
	TempAddr = tmp;
	vtoggle ^= 1;
}


static DECLFW(B2006) {
	FCEUPPU_LineUpdate();

	PPUGenLatch = V;
	if (!vtoggle) {
		TempAddr &= 0x00FF;
		TempAddr |= (V & 0x3f) << 8;

		ppur._vt &= 0x07;
		ppur._vt |= (V & 0x3) << 3;
		ppur._h = (V >> 2) & 1;
		ppur._v = (V >> 3) & 1;
		ppur._fv = (V >> 4) & 3;
	} else {
		TempAddr &= 0xFF00;
		TempAddr |= V;

		RefreshAddr = TempAddr;
		DummyRead = 1;
		if (PPU_hook)
			PPU_hook(RefreshAddr);

		ppur._vt &= 0x18;
		ppur._vt |= (V >> 5);
		ppur._ht = V & 31;

		ppur.install_latches();
	}

	vtoggle ^= 1;
}

static DECLFW(B2007) {
	uint32 tmp = RefreshAddr & 0x3FFF;

	if (debug_loggingCD) {
		if(!cdloggerVideoDataSize && (tmp < 0x2000))
			cdloggervdata[tmp] = 0;
	}

	if (newppu) {
		PPUGenLatch = V;
		RefreshAddr = ppur.get_2007access() & 0x3FFF;
		CALL_PPUWRITE(RefreshAddr, V);
		ppur.increment2007(ppur.status.sl >= 0 && ppur.status.sl < 241 && PPUON, INC32 != 0);
		RefreshAddr = ppur.get_2007access();
	} else {
		PPUGenLatch = V;
		if (tmp < 0x2000) {
			if (PPUCHRRAM & (1 << (tmp >> 10)))
				VPage[tmp >> 10][tmp] = V;
		} else if (tmp < 0x3F00) {
			if (QTAIHack && (qtaintramreg & 1)) {
				QTAINTRAM[((((tmp & 0xF00) >> 10) >> ((qtaintramreg >> 1)) & 1) << 10) | (tmp & 0x3FF)] = V;
			} else {
				if (PPUNTARAM & (1 << ((tmp & 0xF00) >> 10)))
					vnapage[((tmp & 0xF00) >> 10)][tmp & 0x3FF] = V;
			}
		} else {
			if (!(tmp & 3)) {
				if (!(tmp & 0xC))
					PALRAM[0x00] = PALRAM[0x04] = PALRAM[0x08] = PALRAM[0x0C] = V & 0x3F;
				else
					UPALRAM[((tmp & 0xC) >> 2) - 1] = V & 0x3F;
			} else
				PALRAM[tmp & 0x1F] = V & 0x3F;
		}
		if (INC32)
			RefreshAddr += 32;
		else
			RefreshAddr++;
		if (PPU_hook)
			PPU_hook(RefreshAddr & 0x3fff);
	}
}

static DECLFW(B4014) {
	uint32 t = V << 8;
	int x;

	for (x = 0; x < 256; x++)
		X6502_DMW(0x2004, X6502_DMR(t + x));
	SpriteDMA = V;
}

#define PAL(c)  ((c) + cc)

#define GETLASTPIXEL    (PAL ? ((timestamp * 48 - linestartts) / 15) : ((timestamp * 48 - linestartts) >> 4))

static uint8 *Pline, *Plinef;
static int firsttile;
int linestartts;	//no longer static so the debugger can see it
static int tofix = 0;

static void ResetRL(uint8 *target) {
	memset(target, 0xFF, 256);
	InputScanlineHook(0, 0, 0, 0);
	Plinef = target;
	Pline = target;
	firsttile = 0;
	linestartts = timestamp * 48 + X.count;
	tofix = 0;
	FCEUPPU_LineUpdate();
	tofix = 1;
}

static uint8 sprlinebuf[256 + 8];

void FCEUPPU_LineUpdate(void) {
	if (newppu)
		return;

#ifdef FCEUDEF_DEBUGGER
	if (!fceuindbg)
#endif
	if (Pline) {
		int l = GETLASTPIXEL;
		RefreshLine(l);
	}
}

static bool rendersprites = true, renderbg = true;

void FCEUI_SetRenderPlanes(bool sprites, bool bg) {
	rendersprites = sprites;
	renderbg = bg;
}

void FCEUI_GetRenderPlanes(bool& sprites, bool& bg) {
	sprites = rendersprites;
	bg = renderbg;
}

static void CheckSpriteHit(int p);

static void EndRL(void) {
	RefreshLine(272);
	if (tofix)
		Fixit1();
	CheckSpriteHit(272);
	Pline = 0;
}

static int32 sphitx;
static uint8 sphitdata;

static void CheckSpriteHit(int p) {
	int l = p - 16;
	int x;

	if (sphitx == 0x100) return;

	for (x = sphitx; x < (sphitx + 8) && x < l; x++) {
		if ((sphitdata & (0x80 >> (x - sphitx))) && !(Plinef[x] & 64) && x < 255) {
			PPU_status |= 0x40;
			sphitx = 0x100;
			break;
		}
	}
}

//spork the world.  Any sprites on this line? Then this will be set to 1.
//Needed for zapper emulation and *gasp* sprite emulation.
static int spork = 0;

// lasttile is really "second to last tile."
static void RefreshLine(int lastpixel) {
	static uint32 pshift[2];
	static uint32 atlatch;
	uint32 smorkus = RefreshAddr;

	#define RefreshAddr smorkus
	uint32 vofs;
	int X1;

	uint8 *P = Pline;
	int lasttile = lastpixel >> 3;
	int numtiles;
	static int norecurse = 0;	// Yeah, recursion would be bad.
								// PPU_hook() functions can call
								// mirroring/chr bank switching functions,
								// which call FCEUPPU_LineUpdate, which call this
								// function.
	if (norecurse) return;

	if (sphitx != 0x100 && !(PPU_status & 0x40)) {
		if ((sphitx < (lastpixel - 16)) && !(sphitx < ((lasttile - 2) * 8)))
			lasttile++;
	}

	if (lasttile > 34) lasttile = 34;
	numtiles = lasttile - firsttile;

	if (numtiles <= 0) return;

	P = Pline;

	vofs = 0;

	if(PEC586Hack)
		vofs = ((RefreshAddr & 0x200) << 3) | ((RefreshAddr >> 12) & 7);
	else
		vofs = ((PPU[0] & 0x10) << 8) | ((RefreshAddr >> 12) & 7);

	if (!ScreenON && !SpriteON) {
		uint32 tem;
		tem = READPAL(0) | (READPAL(0) << 8) | (READPAL(0) << 16) | (READPAL(0) << 24);
		tem |= 0x40404040;
		FCEU_dwmemset(Pline, tem, numtiles * 8);
		P += numtiles * 8;
		Pline = P;

		firsttile = lasttile;

		#define TOFIXNUM (272 - 0x4)
		if (lastpixel >= TOFIXNUM && tofix) {
			Fixit1();
			tofix = 0;
		}

		if ((lastpixel - 16) >= 0) {
			InputScanlineHook(Plinef, spork ? sprlinebuf : 0, linestartts, lasttile * 8 - 16);
		}
		return;
	}

	//Priority bits, needed for sprite emulation.
	PALRAM[0] |= 64;
	PALRAM[4] |= 64;
	PALRAM[8] |= 64;
	PALRAM[0xC] |= 64;

	//This high-level graphics MMC5 emulation code was written for MMC5 carts in "CL" mode.
	//It's probably not totally correct for carts in "SL" mode.

#define PPUT_MMC5
	if (MMC5Hack && geniestage != 1) {
		if (MMC5HackCHRMode == 0 && (MMC5HackSPMode & 0x80)) {
			int tochange = MMC5HackSPMode & 0x1F;
			tochange -= firsttile;
			for (X1 = firsttile; X1 < lasttile; X1++) {
				if ((tochange <= 0 && MMC5HackSPMode & 0x40) || (tochange > 0 && !(MMC5HackSPMode & 0x40))) {
					#define PPUT_MMC5SP
					#include "pputile.inc"
					#undef PPUT_MMC5SP
				} else {
					#include "pputile.inc"
				}
				tochange--;
			}
		} else if (MMC5HackCHRMode == 1 && (MMC5HackSPMode & 0x80)) {
			int tochange = MMC5HackSPMode & 0x1F;
			tochange -= firsttile;

			#define PPUT_MMC5SP
			#define PPUT_MMC5CHR1
			for (X1 = firsttile; X1 < lasttile; X1++) {
				#include "pputile.inc"
			}
			#undef PPUT_MMC5CHR1
			#undef PPUT_MMC5SP
		} else if (MMC5HackCHRMode == 1) {
			#define PPUT_MMC5CHR1
			for (X1 = firsttile; X1 < lasttile; X1++) {
				#include "pputile.inc"
			}
			#undef PPUT_MMC5CHR1
		} else {
			for (X1 = firsttile; X1 < lasttile; X1++) {
				#include "pputile.inc"
			}
		}
	}
	#undef PPUT_MMC5
	else if (PPU_hook) {
		norecurse = 1;
		#define PPUT_HOOK
		if (PEC586Hack) {
			#define PPU_BGFETCH
			for (X1 = firsttile; X1 < lasttile; X1++) {
				#include "pputile.inc"
			}
			#undef PPU_BGFETCH
		} else {
			for (X1 = firsttile; X1 < lasttile; X1++) {
				#include "pputile.inc"
			}
		}
		#undef PPUT_HOOK
		norecurse = 0;
	} else {
		if (PEC586Hack) {
			#define PPU_BGFETCH
			for (X1 = firsttile; X1 < lasttile; X1++) {
				#include "pputile.inc"
			}
			#undef PPU_BGFETCH
		} if (QTAIHack) {
			#define PPU_VRC5FETCH
			for (X1 = firsttile; X1 < lasttile; X1++) {
				#include "pputile.inc"
			}
			#undef PPU_VRC5FETCH
		} else {
			for (X1 = firsttile; X1 < lasttile; X1++) {
				#include "pputile.inc"
			}
		}
	}

#undef vofs
#undef RefreshAddr

	//Reverse changes made before.
	PALRAM[0] &= 63;
	PALRAM[4] &= 63;
	PALRAM[8] &= 63;
	PALRAM[0xC] &= 63;

	RefreshAddr = smorkus;
	if (firsttile <= 2 && 2 < lasttile && !(PPU[1] & 2)) {
		uint32 tem;
		tem = READPAL(0) | (READPAL(0) << 8) | (READPAL(0) << 16) | (READPAL(0) << 24);
		tem |= 0x40404040;
		*(uint32*)Plinef = *(uint32*)(Plinef + 4) = tem;
	}

	if (!ScreenON) {
		uint32 tem;
		int tstart, tcount;
		tem = READPAL(0) | (READPAL(0) << 8) | (READPAL(0) << 16) | (READPAL(0) << 24);
		tem |= 0x40404040;

		tcount = lasttile - firsttile;
		tstart = firsttile - 2;
		if (tstart < 0) {
			tcount += tstart;
			tstart = 0;
		}
		if (tcount > 0)
			FCEU_dwmemset(Plinef + tstart * 8, tem, tcount * 8);
	}

	if (lastpixel >= TOFIXNUM && tofix) {
		Fixit1();
		tofix = 0;
	}

	//This only works right because of a hack earlier in this function.
	CheckSpriteHit(lastpixel);

	if ((lastpixel - 16) >= 0) {
		InputScanlineHook(Plinef, spork ? sprlinebuf : 0, linestartts, lasttile * 8 - 16);
	}
	Pline = P;
	firsttile = lasttile;
}

static INLINE void Fixit2(void) {
	if (ScreenON || SpriteON) {
		uint32 rad = RefreshAddr;
		rad &= 0xFBE0;
		rad |= TempAddr & 0x041f;
		RefreshAddr = rad;
	}
}

static void Fixit1(void) {
	if (ScreenON || SpriteON) {
		uint32 rad = RefreshAddr;

		if ((rad & 0x7000) == 0x7000) {
			rad ^= 0x7000;
			if ((rad & 0x3E0) == 0x3A0)
				rad ^= 0xBA0;
			else if ((rad & 0x3E0) == 0x3e0)
				rad ^= 0x3e0;
			else
				rad += 0x20;
		} else
			rad += 0x1000;
		RefreshAddr = rad;
	}
}

void MMC5_hb(int);		//Ugh ugh ugh.
static void DoLine(void) {
	if (scanline >= 240 && scanline != totalscanlines) {
		X6502_Run(256 + 69);
		scanline++;
		X6502_Run(16);
		return;
	}

	int x;
	uint8 *target = XBuf + ((scanline < 240 ? scanline : 240) << 8);
	u8* dtarget = XDBuf + ((scanline < 240 ? scanline : 240) << 8);

	if (MMC5Hack) MMC5_hb(scanline);

	X6502_Run(256);
	EndRL();

	if (!renderbg) {// User asked to not display background data.
		uint32 tem;
		uint8 col;
		if (gNoBGFillColor == 0xFF)
			col = READPAL(0);
		else col = gNoBGFillColor;
		tem = col | (col << 8) | (col << 16) | (col << 24);
		tem |= 0x40404040; 
		FCEU_dwmemset(target, tem, 256);
	}

	if (SpriteON)
		CopySprites(target);

	//greyscale handling (mask some bits off the color) ? ? ?
	if (ScreenON || SpriteON)
	{
		if (PPU[1] & 0x01) {
			for (x = 63; x >= 0; x--)
				*(uint32*)&target[x << 2] = (*(uint32*)&target[x << 2]) & 0x30303030;
		}
	}

	//some pathetic attempts at deemph
	if ((PPU[1] >> 5) == 0x7) {
		for (x = 63; x >= 0; x--)
			*(uint32*)&target[x << 2] = ((*(uint32*)&target[x << 2]) & 0x3f3f3f3f) | 0xc0c0c0c0;
	} else if (PPU[1] & 0xE0)
		for (x = 63; x >= 0; x--)
			*(uint32*)&target[x << 2] = (*(uint32*)&target[x << 2]) | 0x40404040;
	else
		for (x = 63; x >= 0; x--)
			*(uint32*)&target[x << 2] = ((*(uint32*)&target[x << 2]) & 0x3f3f3f3f) | 0x80808080;

	//write the actual deemph
	for (x = 63; x >= 0; x--)
		*(uint32*)&dtarget[x << 2] = ((PPU[1]>>5)<<0)|((PPU[1]>>5)<<8)|((PPU[1]>>5)<<16)|((PPU[1]>>5)<<24);

	sphitx = 0x100;

	if (ScreenON || SpriteON)
		FetchSpriteData();

	if (GameHBIRQHook && (ScreenON || SpriteON) && ((PPU[0] & 0x38) != 0x18)) {
		X6502_Run(6);
		Fixit2();
		X6502_Run(4);
		GameHBIRQHook();
		X6502_Run(85 - 16 - 10);
	} else {
		X6502_Run(6);	// Tried 65, caused problems with Slalom(maybe others)
		Fixit2();
		X6502_Run(85 - 6 - 16);

		// A semi-hack for Star Trek: 25th Anniversary
		if (GameHBIRQHook && (ScreenON || SpriteON) && ((PPU[0] & 0x38) != 0x18))
			GameHBIRQHook();
	}

	DEBUG(FCEUD_UpdateNTView(scanline, 0));

	if (SpriteON)
		RefreshSprites();
	if (GameHBIRQHook2 && (ScreenON || SpriteON))
		GameHBIRQHook2();
	scanline++;
	if (scanline < 240) {
		ResetRL(XBuf + (scanline << 8));
	}
	X6502_Run(16);
}

#define V_FLIP  0x80
#define H_FLIP  0x40
#define SP_BACK 0x20

typedef struct {
	uint8 y, no, atr, x;
} SPR;

typedef struct {
	uint8 ca[2], atr, x;
} SPRB;

void FCEUI_DisableSpriteLimitation(int a) {
	maxsprites = a ? 64 : 8;
}

static uint8 numsprites, SpriteBlurp;
static void FetchSpriteData(void) {
	uint8 ns, sb;
	SPR *spr;
	uint8 H;
	int n;
	int vofs;
	uint8 P0 = PPU[0];

	spr = (SPR*)SPRAM;
	H = 8;

	ns = sb = 0;

	vofs = (uint32)(P0 & 0x8 & (((P0 & 0x20) ^ 0x20) >> 2)) << 9;
	H += (P0 & 0x20) >> 2;

	if (!PPU_hook)
		for (n = 63; n >= 0; n--, spr++) {
			if ((uint32)(scanline - spr->y) >= H) continue;
			if (ns < maxsprites) {
				if (n == 63) sb = 1;

				{
					SPRB dst;
					uint8 *C;
					int t;
					uint32 vadr;

					t = (int)scanline - (spr->y);

					if (Sprite16)
						vadr = ((spr->no & 1) << 12) + ((spr->no & 0xFE) << 4);
					else
						vadr = (spr->no << 4) + vofs;

					if (spr->atr & V_FLIP) {
						vadr += 7;
						vadr -= t;
						vadr += (P0 & 0x20) >> 1;
						vadr -= t & 8;
					} else {
						vadr += t;
						vadr += t & 8;
					}

					/* Fix this geniestage hack */
					if (MMC5Hack && geniestage != 1)
						C = MMC5SPRVRAMADR(vadr);
					else
						C = VRAMADR(vadr);

					if (SpriteON)
						RENDER_LOGP(C);
					dst.ca[0] = C[0];
					if (SpriteON)
						RENDER_LOGP(C + 8);
					dst.ca[1] = C[8];
					dst.x = spr->x;
					dst.atr = spr->atr;

					*(uint32*)&SPRBUF[ns << 2] = *(uint32*)&dst;
				}

				ns++;
			} else {
				PPU_status |= 0x20;
				break;
			}
		}
	else
		for (n = 63; n >= 0; n--, spr++) {
			if ((uint32)(scanline - spr->y) >= H) continue;

			if (ns < maxsprites) {
				if (n == 63) sb = 1;

				{
					SPRB dst;
					uint8 *C;
					int t;
					uint32 vadr;

					t = (int)scanline - (spr->y);

					if (Sprite16)
						vadr = ((spr->no & 1) << 12) + ((spr->no & 0xFE) << 4);
					else
						vadr = (spr->no << 4) + vofs;

					if (spr->atr & V_FLIP) {
						vadr += 7;
						vadr -= t;
						vadr += (P0 & 0x20) >> 1;
						vadr -= t & 8;
					} else {
						vadr += t;
						vadr += t & 8;
					}

					if (MMC5Hack)
						C = MMC5SPRVRAMADR(vadr);
					else
						C = VRAMADR(vadr);
					if (SpriteON)
						RENDER_LOGP(C);
					dst.ca[0] = C[0];
					if (ns < 8) {
						PPU_hook(0x2000);
						PPU_hook(vadr);
					}
					if (SpriteON)
						RENDER_LOGP(C + 8);
					dst.ca[1] = C[8];
					dst.x = spr->x;
					dst.atr = spr->atr;


					*(uint32*)&SPRBUF[ns << 2] = *(uint32*)&dst;
				}

				ns++;
			} else {
				PPU_status |= 0x20;
				break;
			}
		}

	//Handle case when >8 sprites per scanline option is enabled.
	if (ns > 8) PPU_status |= 0x20;
	else if (PPU_hook) {
		for (n = 0; n < (8 - ns); n++) {
			PPU_hook(0x2000);
			PPU_hook(vofs);
		}
	}
	numsprites = ns;
	SpriteBlurp = sb;
}

static void RefreshSprites(void) {
	int n;
	SPRB *spr;

	spork = 0;
	if (!numsprites) return;

	FCEU_dwmemset(sprlinebuf, 0x80808080, 256);
	numsprites--;
	spr = (SPRB*)SPRBUF + numsprites;

	for (n = numsprites; n >= 0; n--, spr--) {
		uint32 pixdata;
		uint8 J, atr;

		int x = spr->x;
		uint8 *C;
		int VB;

		pixdata = ppulut1[spr->ca[0]] | ppulut2[spr->ca[1]];
		J = spr->ca[0] | spr->ca[1];
		atr = spr->atr;

		if (J) {
			if (n == 0 && SpriteBlurp && !(PPU_status & 0x40)) {
				sphitx = x;
				sphitdata = J;
				if (atr & H_FLIP)
					sphitdata = ((J << 7) & 0x80) |
								((J << 5) & 0x40) |
								((J << 3) & 0x20) |
								((J << 1) & 0x10) |
								((J >> 1) & 0x08) |
								((J >> 3) & 0x04) |
								((J >> 5) & 0x02) |
								((J >> 7) & 0x01);
			}

			C = sprlinebuf + x;
			VB = (0x10) + ((atr & 3) << 2);

			if (atr & SP_BACK) {
				if (atr & H_FLIP) {
					if (J & 0x80) C[7] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x40) C[6] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x20) C[5] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x10) C[4] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x08) C[3] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x04) C[2] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x02) C[1] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x01) C[0] = READPAL(VB | pixdata) | 0x40;
				} else {
					if (J & 0x80) C[0] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x40) C[1] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x20) C[2] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x10) C[3] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x08) C[4] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x04) C[5] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x02) C[6] = READPAL(VB | (pixdata & 3)) | 0x40;
					pixdata >>= 4;
					if (J & 0x01) C[7] = READPAL(VB | pixdata) | 0x40;
				}
			} else {
				if (atr & H_FLIP) {
					if (J & 0x80) C[7] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x40) C[6] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x20) C[5] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x10) C[4] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x08) C[3] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x04) C[2] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x02) C[1] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x01) C[0] = READPAL(VB | pixdata);
				} else {
					if (J & 0x80) C[0] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x40) C[1] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x20) C[2] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x10) C[3] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x08) C[4] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x04) C[5] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x02) C[6] = READPAL(VB | (pixdata & 3));
					pixdata >>= 4;
					if (J & 0x01) C[7] = READPAL(VB | pixdata);
				}
			}
		}
	}
	SpriteBlurp = 0;
	spork = 1;
}

static void CopySprites(uint8 *target) {
	uint8 *P = target;

	if (!spork) return;
	spork = 0;

	if (!rendersprites) return;	//User asked to not display sprites.

	if(!SpriteON) return;
	
	int start=8;
	if(PPU[1] & 0x04)
		start = 0;

	for(int i=start;i<256;i++)
	{
		uint8 t = sprlinebuf[i];
		if(!(t&0x80))
			if (!(t & 0x40) || (P[i] & 0x40))		// Normal sprite || behind bg sprite
				P[i] = t;
	}
}

void FCEUPPU_SetVideoSystem(int w) {
	if (w) {
		scanlines_per_frame = dendy ? 262: 312;
		FSettings.FirstSLine = FSettings.UsrFirstSLine[1];
		FSettings.LastSLine = FSettings.UsrLastSLine[1];
		//paldeemphswap = 1; // dendy has pal ppu, and pal ppu has these swapped
	} else {
		scanlines_per_frame = 262;
		FSettings.FirstSLine = FSettings.UsrFirstSLine[0];
		FSettings.LastSLine = FSettings.UsrLastSLine[0];
		//paldeemphswap = 0;
	}
}

//Initializes the PPU
void FCEUPPU_Init(void) {
	makeppulut();
}

void PPU_ResetHooks() {
	FFCEUX_PPURead = FFCEUX_PPURead_Default;
}

void FCEUPPU_Reset(void) {
	VRAMBuffer = PPU[0] = PPU[1] = PPU_status = PPU[3] = 0;
	PPUSPL = 0;
	PPUGenLatch = 0;
	RefreshAddr = TempAddr = 0;
	vtoggle = 0;
	ppudead = 2;
	kook = 0;
	idleSynch = 1;

	new_ppu_reset = true; // delay reset of ppur/spr_read until it's ready to start a new frame
}

void FCEUPPU_Power(void) {
	int x;

	// initialize PPU memory regions according to settings
	FCEU_MemoryRand(NTARAM, 0x800, true);
	FCEU_MemoryRand(PALRAM, 0x20, true);
	FCEU_MemoryRand(SPRAM, 0x100, true);
	// palettes can only store values up to $3F, and PALRAM X4/X8/XC are mirrors of X0 for rendering purposes (UPALRAM is used for $2007 readback)
	for (x = 0; x < 0x20; ++x) PALRAM[x] &= 0x3F;
	UPALRAM[0] = PALRAM[0x04];
	UPALRAM[1] = PALRAM[0x08];
	UPALRAM[2] = PALRAM[0x0C];
	PALRAM[0x0C] = PALRAM[0x08] = PALRAM[0x04] = PALRAM[0x00];
	PALRAM[0x1C] = PALRAM[0x18] = PALRAM[0x14] = PALRAM[0x10];
	FCEUPPU_Reset();

	for (x = 0x2000; x < 0x4000; x += 8) {
		ARead[x] = A200x;
		BWrite[x] = B2000;
		ARead[x + 1] = A200x;
		BWrite[x + 1] = B2001;
		ARead[x + 2] = A2002;
		BWrite[x + 2] = B2002;
		ARead[x + 3] = A200x;
		BWrite[x + 3] = B2003;
		ARead[x + 4] = A2004;
		BWrite[x + 4] = B2004;
		ARead[x + 5] = A200x;
		BWrite[x + 5] = B2005;
		ARead[x + 6] = A200x;
		BWrite[x + 6] = B2006;
		ARead[x + 7] = A2007;
		BWrite[x + 7] = B2007;
	}
	BWrite[0x4014] = B4014;
}

int FCEUPPU_Loop(int skip) {
	if ((newppu) && (GameInfo->type != GIT_NSF)) {
		int FCEUX_PPU_Loop(int skip);
		return FCEUX_PPU_Loop(skip);
	}

	//Needed for Knight Rider, possibly others.
	if (ppudead) {
		memset(XBuf, 0x80, 256 * 240);
		X6502_Run(scanlines_per_frame * (256 + 85));
		ppudead--;
	} else {
		X6502_Run(256 + 85);
		PPU_status |= 0x80;

		//Not sure if this is correct.  According to Matt Conte and my own tests, it is.
		//Timing is probably off, though.
		//NOTE:  Not having this here breaks a Super Donkey Kong game.
		PPU[3] = PPUSPL = 0;

		//I need to figure out the true nature and length of this delay.
		X6502_Run(12);
		if (GameInfo->type == GIT_NSF)
			DoNSFFrame();
		else {
			if (VBlankON)
				TriggerNMI();
		}
		X6502_Run((scanlines_per_frame - 242) * (256 + 85) - 12);
		if (overclock_enabled && vblankscanlines) {
			if (!DMC_7bit || !skip_7bit_overclocking) {
				overclocking = 1;
				X6502_Run(vblankscanlines * (256 + 85) - 12);
				overclocking = 0;
			}
		}
		PPU_status &= 0x1f;
		X6502_Run(256);

		{
			int x;

			if (ScreenON || SpriteON) {
				if (GameHBIRQHook && ((PPU[0] & 0x38) != 0x18))
					GameHBIRQHook();
				if (PPU_hook)
					for (x = 0; x < 42; x++) {
						PPU_hook(0x2000); PPU_hook(0);
					}
				if (GameHBIRQHook2)
					GameHBIRQHook2();
			}
			X6502_Run(85 - 16);
			if (ScreenON || SpriteON) {
				RefreshAddr = TempAddr;
				if (PPU_hook) PPU_hook(RefreshAddr & 0x3fff);
			}

			//Clean this stuff up later.
			spork = numsprites = 0;
			ResetRL(XBuf);

			X6502_Run(16 - kook);
			kook ^= 1;
		}
		if (GameInfo->type == GIT_NSF)
			X6502_Run((256 + 85) * normalscanlines);
		#ifdef FRAMESKIP
		else if (skip) {
			int y;

			y = SPRAM[0];
			y++;

			PPU_status |= 0x20;	// Fixes "Bee 52".  Does it break anything?
			if (GameHBIRQHook) {
				X6502_Run(256);
				for (scanline = 0; scanline < 240; scanline++) {
					if (ScreenON || SpriteON)
						GameHBIRQHook();
					if (scanline == y && SpriteON) PPU_status |= 0x40;
					X6502_Run((scanline == 239) ? 85 : (256 + 85));
				}
			} else if (y < 240) {
				X6502_Run((256 + 85) * y);
				if (SpriteON) PPU_status |= 0x40;	// Quick and very dirty hack.
				X6502_Run((256 + 85) * (240 - y));
			} else
				X6502_Run((256 + 85) * 240);
		}
		#endif
		else {
			deemp = PPU[1] >> 5;

			// manual samples can't play correctly with overclocking
			if (DMC_7bit && skip_7bit_overclocking) // 7bit sample started before 240th line
				totalscanlines = normalscanlines;
			else
				totalscanlines = normalscanlines + (overclock_enabled ? postrenderscanlines : 0);

			for (scanline = 0; scanline < totalscanlines; ) {	//scanline is incremented in  DoLine.  Evil. :/
				deempcnt[deemp]++;

				if (scanline < 240)
					DEBUG(FCEUD_UpdatePPUView(scanline, 1));

				DoLine();

				if (scanline < normalscanlines || scanline == totalscanlines)
					overclocking = 0;
				else {
					if (DMC_7bit && skip_7bit_overclocking) // 7bit sample started after 240th line
						break;
					overclocking = 1;
				}
			}
			DMC_7bit = 0;

			if (MMC5Hack) MMC5_hb(scanline);

			//deemph nonsense, kept for complicated reasons (see SetNESDeemph_OldHacky implementation)
			int maxref = 0;
			for (int x = 1, max = 0; x < 7; x++) {
				if (deempcnt[x] > max) {
					max = deempcnt[x];
					maxref = x;
				}
				deempcnt[x] = 0;
			}
			SetNESDeemph_OldHacky(maxref, 0);
		}
	}	//else... to if(ppudead)

	#ifdef FRAMESKIP
	if (skip) {
		FCEU_PutImageDummy();
		return(0);
	} else
	#endif
	{
		return(1);
	}
}

int (*PPU_MASTER)(int skip) = FCEUPPU_Loop;

static uint16 TempAddrT, RefreshAddrT;

void FCEUPPU_LoadState(int version) {
	TempAddr = TempAddrT;
	RefreshAddr = RefreshAddrT;
}

SFORMAT FCEUPPU_STATEINFO[] = {
	{ NTARAM, 0x800, "NTAR" },
	{ PALRAM, 0x20, "PRAM" },
	{ SPRAM, 0x100, "SPRA" },
	{ PPU, 0x4, "PPUR" },
	{ &kook, 1, "KOOK" },
	{ &ppudead, 1, "DEAD" },
	{ &PPUSPL, 1, "PSPL" },
	{ &XOffset, 1, "XOFF" },
	{ &vtoggle, 1, "VTGL" },
	{ &RefreshAddrT, 2 | FCEUSTATE_RLSB, "RADD" },
	{ &TempAddrT, 2 | FCEUSTATE_RLSB, "TADD" },
	{ &VRAMBuffer, 1, "VBUF" },
	{ &PPUGenLatch, 1, "PGEN" },
	{ 0 }
};

SFORMAT FCEU_NEWPPU_STATEINFO[] = {
	{ &idleSynch, 1, "IDLS" },
	{ &spr_read.num, 4 | FCEUSTATE_RLSB, "SR_0" },
	{ &spr_read.count, 4 | FCEUSTATE_RLSB, "SR_1" },
	{ &spr_read.fetch, 4 | FCEUSTATE_RLSB, "SR_2" },
	{ &spr_read.found, 4 | FCEUSTATE_RLSB, "SR_3" },
	{ &spr_read.found_pos[0], 4 | FCEUSTATE_RLSB, "SRx0" },
	{ &spr_read.found_pos[0], 4 | FCEUSTATE_RLSB, "SRx1" },
	{ &spr_read.found_pos[0], 4 | FCEUSTATE_RLSB, "SRx2" },
	{ &spr_read.found_pos[0], 4 | FCEUSTATE_RLSB, "SRx3" },
	{ &spr_read.found_pos[0], 4 | FCEUSTATE_RLSB, "SRx4" },
	{ &spr_read.found_pos[0], 4 | FCEUSTATE_RLSB, "SRx5" },
	{ &spr_read.found_pos[0], 4 | FCEUSTATE_RLSB, "SRx6" },
	{ &spr_read.found_pos[0], 4 | FCEUSTATE_RLSB, "SRx7" },
	{ &spr_read.ret, 4 | FCEUSTATE_RLSB, "SR_4" },
	{ &spr_read.last, 4 | FCEUSTATE_RLSB, "SR_5" },
	{ &spr_read.mode, 4 | FCEUSTATE_RLSB, "SR_6" },
	{ &ppur.fv, 4 | FCEUSTATE_RLSB, "PFVx" },
	{ &ppur.v, 4 | FCEUSTATE_RLSB, "PVxx" },
	{ &ppur.h, 4 | FCEUSTATE_RLSB, "PHxx" },
	{ &ppur.vt, 4 | FCEUSTATE_RLSB, "PVTx" },
	{ &ppur.ht, 4 | FCEUSTATE_RLSB, "PHTx" },
	{ &ppur._fv, 4 | FCEUSTATE_RLSB, "P_FV" },
	{ &ppur._v, 4 | FCEUSTATE_RLSB, "P_Vx" },
	{ &ppur._h, 4 | FCEUSTATE_RLSB, "P_Hx" },
	{ &ppur._vt, 4 | FCEUSTATE_RLSB, "P_VT" },
	{ &ppur._ht, 4 | FCEUSTATE_RLSB, "P_HT" },
	{ &ppur.fh, 4 | FCEUSTATE_RLSB, "PFHx" },
	{ &ppur.s, 4 | FCEUSTATE_RLSB, "PSxx" },
	{ &ppur.status.sl, 4 | FCEUSTATE_RLSB, "PST0" },
	{ &ppur.status.cycle, 4 | FCEUSTATE_RLSB, "PST1" },
	{ &ppur.status.end_cycle, 4 | FCEUSTATE_RLSB, "PST2" },
	{ 0 }
};

void FCEUPPU_SaveState(void) {
	TempAddrT = TempAddr;
	RefreshAddrT = RefreshAddr;
}

uint32 FCEUPPU_PeekAddress()
{
	if (newppu)
	{
		return ppur.get_2007access() & 0x3FFF;
	}

	return RefreshAddr & 0x3FFF;
}

//---------------------
int pputime = 0;
int totpputime = 0;
const int kLineTime = 341;
const int kFetchTime = 2;

void runppu(int x) {
	ppur.status.cycle = (ppur.status.cycle + x) % ppur.status.end_cycle;
	if (!new_ppu_reset) // if resetting, suspend CPU until the first frame
	{
		X6502_Run(x);
	}
}

//todo - consider making this a 3 or 4 slot fifo to keep from touching so much memory
struct BGData {
	struct Record {
		uint8 nt, pecnt, at, pt[2], qtnt;
		uint8 ppu1[8];

		INLINE void Read() {
			NTRefreshAddr = RefreshAddr = ppur.get_ntread();
			if (PEC586Hack)
				ppur.s = (RefreshAddr & 0x200) >> 9;
			else if (QTAIHack) {
				qtnt = QTAINTRAM[((((RefreshAddr >> 10) & 3) >> ((qtaintramreg >> 1)) & 1) << 10) | (RefreshAddr & 0x3FF)];
				ppur.s = qtnt & 0x3F;
			}
			pecnt = (RefreshAddr & 1) << 3;
			nt = CALL_PPUREAD(RefreshAddr);
			ppu1[0] = PPU[1];
			runppu(1);
			ppu1[1] = PPU[1];
			runppu(1);



			RefreshAddr = ppur.get_atread();
			at = CALL_PPUREAD(RefreshAddr);

			//modify at to get appropriate palette shift
			if (ppur.vt & 2) at >>= 4;
			if (ppur.ht & 2) at >>= 2;
			at &= 0x03;
			at <<= 2;
			//horizontal scroll clocked at cycle 3 and then
			//vertical scroll at 251
			ppu1[2] = PPU[1];
			runppu(1);
			if (PPUON) {
				ppur.increment_hsc();
				if (ppur.status.cycle == 251)
					ppur.increment_vs();
			}
			ppu1[3] = PPU[1];
			runppu(1);

			ppur.par = nt;
			RefreshAddr = ppur.get_ptread();
			if (PEC586Hack) {
				pt[0] = CALL_PPUREAD(RefreshAddr | pecnt);
				ppu1[4] = PPU[1];
				runppu(1);
				ppu1[5] = PPU[1];
				runppu(1);
				pt[1] = CALL_PPUREAD(RefreshAddr | pecnt);
				ppu1[6] = PPU[1];
				runppu(1);
				ppu1[7] = PPU[1];
				runppu(1);
			} else if (QTAIHack && (qtnt & 0x40)) {
				pt[0] = *(CHRptr[0] + RefreshAddr);
				ppu1[4] = PPU[1];
				runppu(1);
				ppu1[5] = PPU[1];
				runppu(1);
				RefreshAddr |= 8;
				pt[1] = *(CHRptr[0] + RefreshAddr);
				ppu1[6] = PPU[1];
				runppu(1);
				ppu1[7] = PPU[1];
				runppu(1);
			} else {
				if (ScreenON)
					RENDER_LOG(RefreshAddr);
				pt[0] = CALL_PPUREAD(RefreshAddr);
				ppu1[4] = PPU[1];
				runppu(1);
				ppu1[5] = PPU[1];
				runppu(1);
				RefreshAddr |= 8;
				if (ScreenON)
					RENDER_LOG(RefreshAddr);
				pt[1] = CALL_PPUREAD(RefreshAddr);
				ppu1[6] = PPU[1];
				runppu(1);
				ppu1[7] = PPU[1];
				runppu(1);
			}
		}
	};

	Record main[34];	//one at the end is junk, it can never be rendered
} bgdata;

static inline int PaletteAdjustPixel(int pixel) {
	if ((PPU[1] >> 5) == 0x7)
		return (pixel & 0x3f) | 0xc0;
	else if (PPU[1] & 0xE0)
		return pixel | 0x40;
	else
		return (pixel & 0x3F) | 0x80;
}

int framectr = 0;
int FCEUX_PPU_Loop(int skip) {

	if (new_ppu_reset) // first frame since reset, time to initialize
	{
		ppur.reset();
		spr_read.reset();
		new_ppu_reset = false;
	}

	//262 scanlines
	if (ppudead) {
		// not quite emulating all the NES power up behavior
		// since it is known that the NES ignores writes to some
		// register before around a full frame, but no games
		// should write to those regs during that time, it needs
		// to wait for vblank
		ppur.status.sl = 241;
		if (PAL)
			runppu(70 * kLineTime);
		else
			runppu(20 * kLineTime);
		ppur.status.sl = 0;
		runppu(242 * kLineTime);
		--ppudead;
		goto finish;
	}

	{
		PPU_status |= 0x80;
		ppuphase = PPUPHASE_VBL;

		//Not sure if this is correct.  According to Matt Conte and my own tests, it is.
		//Timing is probably off, though.
		//NOTE:  Not having this here breaks a Super Donkey Kong game.
		PPU[3] = PPUSPL = 0;
		const int delay = 20;	//fceu used 12 here but I couldnt get it to work in marble madness and pirates.

		ppur.status.sl = 241;	//for sprite reads

		//formerly: runppu(delay);
		for(int dot=0;dot<delay;dot++)
			runppu(1);

		if (VBlankON) TriggerNMI();
		int sltodo = PAL?70:20;
		
		//formerly: runppu(20 * (kLineTime) - delay);
		for(int S=0;S<sltodo;S++)
		{
			for(int dot=(S==0?delay:0);dot<kLineTime;dot++)
				runppu(1);
			ppur.status.sl++;
		}

		//this seems to run just before the dummy scanline begins
		PPU_status = 0;
		//this early out caused metroid to fail to boot. I am leaving it here as a reminder of what not to do
		//if(!PPUON) { runppu(kLineTime*242); goto finish; }

		//There are 2 conditions that update all 5 PPU scroll counters with the
		//contents of the latches adjacent to them. The first is after a write to
		//2006/2. The second, is at the beginning of scanline 20, when the PPU starts
		//rendering data for the first time in a frame (this update won't happen if
		//all rendering is disabled via 2001.3 and 2001.4).

		//if(PPUON)
		//	ppur.install_latches();

		static uint8 oams[2][64][8];//[7] turned to [8] for faster indexing
		static int oamcounts[2] = { 0, 0 };
		static int oamslot = 0;
		static int oamcount;

		//capture the initial xscroll
		//int xscroll = ppur.fh;
		//render 241/291 scanlines (1 dummy at beginning, dendy's 50 at the end)
		//ignore overclocking!
		for (int sl = 0; sl < normalscanlines; sl++) 
		{
			spr_read.start_scanline();

			g_rasterpos = 0;
			ppur.status.sl = sl;

			linestartts = timestamp * 48 + X.count; // pixel timestamp for debugger

			const int yp = sl - 1;
			ppuphase = PPUPHASE_BG;

			if (sl != 0 && sl < 241)  // ignore the invisible
			{
				DEBUG(FCEUD_UpdatePPUView(scanline = yp, 1));
				DEBUG(FCEUD_UpdateNTView(scanline = yp, 1));
			}

			//hack to fix SDF ship intro screen with split. is it right?
			//well, if we didnt do this, we'd be passing in a negative scanline, so that's a sign something is fishy..
			if(sl != 0)
				if (MMC5Hack) MMC5_hb(yp);


			//twiddle the oam buffers
			const int scanslot = oamslot ^ 1;
			const int renderslot = oamslot;
			oamslot ^= 1;

			oamcount = oamcounts[renderslot];

			//the main scanline rendering loop:
			//32 times, we will fetch a tile and then render 8 pixels.
			//two of those tiles were read in the last scanline.
			for (int xt = 0; xt < 32; xt++) {
				bgdata.main[xt + 2].Read();

				const uint8 blank = (gNoBGFillColor == 0xFF) ? READPAL(0) : gNoBGFillColor;

				//ok, we're also going to draw here.
				//unless we're on the first dummy scanline
				if (sl != 0 && sl < 241) { // cape at 240 for dendy, its PPU does nothing afterwards
					int xstart = xt << 3;
					oamcount = oamcounts[renderslot];
					uint8 * const target = XBuf + (yp << 8) + xstart;
					uint8 * const dtarget = XDBuf + (yp << 8) + xstart;
					uint8 *ptr = target;
					uint8 *dptr = dtarget;
					int rasterpos = xstart;

					//check all the conditions that can cause things to render in these 8px
					const bool renderspritenow = SpriteON && (xt > 0 || SpriteLeft8);
					const bool renderbgnow = ScreenON && (xt > 0 || BGLeft8);
					for (int xp = 0; xp < 8; xp++, rasterpos++, g_rasterpos++) {
						//bg pos is different from raster pos due to its offsetability.
						//so adjust for that here
						const int bgpos = rasterpos + ppur.fh;
						const int bgpx = bgpos & 7;
						const int bgtile = bgpos >> 3;

						uint8 pixel = 0;
						uint8 pixelcolor = blank;

						//according to qeed's doc, use palette 0 or $2006's value if it is & 0x3Fxx
						if (!ScreenON && !SpriteON)
						{
							// if there's anything wrong with how we're doing this, someone please chime in
							int addr = ppur.get_2007access();
							if ((addr & 0x3F00) == 0x3F00)
							{
								pixel = addr & 0x1F;
							}
							pixelcolor = READPAL_MOTHEROFALL(pixel);
						}

						//generate the BG data
						if (renderbgnow) {
							uint8* pt = bgdata.main[bgtile].pt;
							pixel = ((pt[0] >> (7 - bgpx)) & 1) | (((pt[1] >> (7 - bgpx)) & 1) << 1) | bgdata.main[bgtile].at;
						}
						if (renderbg)
							pixelcolor = READPALNOGS(pixel);

						//look for a sprite to be drawn
						bool havepixel = false;
						for (int s = 0; s < oamcount; s++) {
							uint8* oam = oams[renderslot][s];
							int x = oam[3];
							if (rasterpos >= x && rasterpos < x + 8) {
								//build the pixel.
								//fetch the LSB of the patterns
								uint8 spixel = oam[4] & 1;
								spixel |= (oam[5] & 1) << 1;

								//shift down the patterns so the next pixel is in the LSB
								oam[4] >>= 1;
								oam[5] >>= 1;

								if (!renderspritenow) continue;

								//bail out if we already have a pixel from a higher priority sprite
								if (havepixel) continue;

								//transparent pixel bailout
								if (spixel == 0) continue;

								//spritehit:
								//1. is it sprite#0?
								//2. is the bg pixel nonzero?
								//then, it is spritehit.
								if (oam[6] == 0 && (pixel & 3) != 0 &&
									rasterpos < 255) {
									PPU_status |= 0x40;
								}
								havepixel = true;

								//priority handling
								if (oam[2] & 0x20) {
									//behind background:
									if ((pixel & 3) != 0) continue;
								}

								//bring in the palette bits and palettize
								spixel |= (oam[2] & 3) << 2;

								if (rendersprites)
									pixelcolor = READPALNOGS(0x10 + spixel);
							}
						}

						//apply grayscale.. kind of clunky
						//really we need to read the entire palette instead of just ppu1
						//this will be needed for special color effects probably (very fine rainbows and whatnot?)
						//are you allowed to chang the palette mid-line anyway? well you can definitely change the grayscale flag as we know from the FF1 "polygon" effect
						if(bgdata.main[xt+2].ppu1[xp]&1)
							pixelcolor &= 0x30;

						//this does deemph stuff inside it.. which is probably wrong...
						*ptr = PaletteAdjustPixel(pixelcolor);

						ptr++;

						//grab deemph..
						//I guess this works the same way as the grayscale, ideally?
						*dptr++ = bgdata.main[xt+2].ppu1[xp]>>5;
					}
				}
			}

			//look for sprites (was supposed to run concurrent with bg rendering)
			oamcounts[scanslot] = 0;
			oamcount = 0;
			const int spriteHeight = Sprite16 ? 16 : 8;
			for (int i = 0; i < 64; i++) {
				oams[scanslot][oamcount][7] = 0;
				uint8* spr = SPRAM + i * 4;
				if (yp >= spr[0] && yp < spr[0] + spriteHeight) {
					//if we already have maxsprites, then this new one causes an overflow,
					//set the flag and bail out.
					if (oamcount >= 8 && PPUON) {
						PPU_status |= 0x20;
						if (maxsprites == 8)
							break;
					}

					//just copy some bytes into the internal sprite buffer
					for (int j = 0; j < 4; j++)
						oams[scanslot][oamcount][j] = spr[j];
					oams[scanslot][oamcount][7] = 1;

					//note that we stuff the oam index into [6].
					//i need to turn this into a struct so we can have fewer magic numbers
					oams[scanslot][oamcount][6] = (uint8)i;
					oamcount++;
				}
			}
			oamcounts[scanslot] = oamcount;

			//FV is clocked by the PPU's horizontal blanking impulse, and therefore will increment every scanline.
			//well, according to (which?) tests, maybe at the end of hblank.
			//but, according to what it took to get crystalis working, it is at the beginning of hblank.

			//this is done at cycle 251
			//rendering scanline, it doesn't need to be scanline 0,
			//because on the first scanline when the increment is 0, the vs_scroll is reloaded.
			//if(PPUON && sl != 0)
			//	ppur.increment_vs();

			//todo - think about clearing oams to a predefined value to force deterministic behavior

			ppuphase = PPUPHASE_OBJ;

			//fetch sprite patterns
			for (int s = 0; s < maxsprites; s++) {
				//if we have hit our eight sprite pattern and we dont have any more sprites, then bail
				if (s == oamcount && s >= 8)
					break;

				//if this is a real sprite sprite, then it is not above the 8 sprite limit.
				//this is how we support the no 8 sprite limit feature.
				//not that at some point we may need a virtual CALL_PPUREAD which just peeks and doesnt increment any counters
				//this could be handy for the debugging tools also
				const bool realSprite = (s < 8);

				uint8* const oam = oams[scanslot][s];
				uint32 line = yp - oam[0];
				if (oam[2] & 0x80)	//vflip
					line = spriteHeight - line - 1;

				uint32 patternNumber = oam[1];
				uint32 patternAddress;

				//create deterministic dummy fetch pattern
				if (!oam[7]) {
					patternNumber = 0;
					line = 0;
				}

				//8x16 sprite handling:
				if (Sprite16) {
					uint32 bank = (patternNumber & 1) << 12;
					patternNumber = patternNumber & ~1;
					patternNumber |= (line >> 3);
					patternAddress = (patternNumber << 4) | bank;
				} else {
					patternAddress = (patternNumber << 4) | (SpAdrHI << 9);
				}

				//offset into the pattern for the current line.
				//tricky: tall sprites have already had lines>8 taken care of by getting a new pattern number above.
				//so we just need the line offset for the second pattern
				patternAddress += line & 7;

				//garbage nametable fetches
				int garbage_todo = 2;
				if (PPUON)
				{
					if (sl == 0 && ppur.status.cycle == 304)
					{
						runppu(1);
						if (PPUON) ppur.install_latches();
						runppu(1);
						garbage_todo = 0;
					}
					if ((sl != 0 && sl < 241) && ppur.status.cycle == 256)
					{
						runppu(1);
						//at 257: 3d world runner is ugly if we do this at 256
						if (PPUON) ppur.install_h_latches();
						runppu(1);
						garbage_todo = 0;
					}
				}
				if (realSprite) runppu(garbage_todo);

				//Dragon's Lair (Europe version mapper 4)
				//does not set SpriteON in the beginning but it does
				//set the bg on so if using the conditional SpriteON the MMC3 counter
				//the counter will never count and no IRQs will be fired so use PPUON
				if (((PPU[0] & 0x38) != 0x18) && s == 2 && PPUON) {
					//(The MMC3 scanline counter is based entirely on PPU A12, triggered on rising edges (after the line remains low for a sufficiently long period of time))
					//http://nesdevwiki.org/wiki/index.php/Nintendo_MMC3
					//test cases for timing: SMB3, Crystalis
					//crystalis requires deferring this til somewhere in sprite [1,3]
					//kirby requires deferring this til somewhere in sprite [2,5..
					//if (PPUON && GameHBIRQHook) {
					if (GameHBIRQHook) {
						GameHBIRQHook();
					}
				}

				//blind attempt to replicate old ppu functionality
				if(s == 2 && PPUON)
				{
					if (GameHBIRQHook2) {
						GameHBIRQHook2();
					}
				}

				if (realSprite) runppu(kFetchTime);


				//pattern table fetches
				RefreshAddr = patternAddress;
				if (SpriteON)
					RENDER_LOG(RefreshAddr);
				oam[4] = CALL_PPUREAD(RefreshAddr);
				if (realSprite) runppu(kFetchTime);

				RefreshAddr += 8;
				if (SpriteON)
					RENDER_LOG(RefreshAddr);
				oam[5] = CALL_PPUREAD(RefreshAddr);
				if (realSprite) runppu(kFetchTime);

				//hflip
				if (!(oam[2] & 0x40)) {
					oam[4] = bitrevlut[oam[4]];
					oam[5] = bitrevlut[oam[5]];
				}
			}

			ppuphase = PPUPHASE_BG;

			//fetch BG: two tiles for next line
			for (int xt = 0; xt < 2; xt++)
				bgdata.main[xt].Read();

			//I'm unclear of the reason why this particular access to memory is made.
			//The nametable address that is accessed 2 times in a row here, is also the
			//same nametable address that points to the 3rd tile to be rendered on the
			//screen (or basically, the first nametable address that will be accessed when
			//the PPU is fetching background data on the next scanline).
			//(not implemented yet)
			runppu(kFetchTime);
			if (sl == 0) {
				if (idleSynch && PPUON && !PAL)
					ppur.status.end_cycle = 340;
				else
					ppur.status.end_cycle = 341;
				idleSynch ^= 1;
			} else
				ppur.status.end_cycle = 341;
			runppu(kFetchTime);

			//After memory access 170, the PPU simply rests for 4 cycles (or the
			//equivelant of half a memory access cycle) before repeating the whole
			//pixel/scanline rendering process. If the scanline being rendered is the very
			//first one on every second frame, then this delay simply doesn't exist.
			if (ppur.status.end_cycle == 341)
				runppu(1);
		}	//scanline loop

		DMC_7bit = 0;

		if (MMC5Hack) MMC5_hb(240);

		//idle for one line
		runppu(kLineTime);
		framectr++;
	}

finish:
	return 0;
}
