////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia 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.
//
// Nestopia 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 Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
////////////////////////////////////////////////////////////////////////////////////////

#include <cstring>
#include <cstdlib>
#include "NstCpu.hpp"
#include "NstHook.hpp"
#include "NstState.hpp"
#include "api/NstApiUser.hpp"

namespace Nes
{
	namespace Core
	{
		dword Cpu::logged = 0;

		void (Cpu::*const Cpu::opcodes[0x100])() =
		{
			&Cpu::op0x00, &Cpu::op0x01, &Cpu::op0x02, &Cpu::op0x03,
			&Cpu::op0x04, &Cpu::op0x05, &Cpu::op0x06, &Cpu::op0x07,
			&Cpu::op0x08, &Cpu::op0x09, &Cpu::op0x0A, &Cpu::op0x0B,
			&Cpu::op0x0C, &Cpu::op0x0D, &Cpu::op0x0E, &Cpu::op0x0F,
			&Cpu::op0x10, &Cpu::op0x11, &Cpu::op0x12, &Cpu::op0x13,
			&Cpu::op0x14, &Cpu::op0x15, &Cpu::op0x16, &Cpu::op0x17,
			&Cpu::op0x18, &Cpu::op0x19, &Cpu::op0x1A, &Cpu::op0x1B,
			&Cpu::op0x1C, &Cpu::op0x1D, &Cpu::op0x1E, &Cpu::op0x1F,
			&Cpu::op0x20, &Cpu::op0x21, &Cpu::op0x22, &Cpu::op0x23,
			&Cpu::op0x24, &Cpu::op0x25, &Cpu::op0x26, &Cpu::op0x27,
			&Cpu::op0x28, &Cpu::op0x29, &Cpu::op0x2A, &Cpu::op0x2B,
			&Cpu::op0x2C, &Cpu::op0x2D, &Cpu::op0x2E, &Cpu::op0x2F,
			&Cpu::op0x30, &Cpu::op0x31, &Cpu::op0x32, &Cpu::op0x33,
			&Cpu::op0x34, &Cpu::op0x35, &Cpu::op0x36, &Cpu::op0x37,
			&Cpu::op0x38, &Cpu::op0x39, &Cpu::op0x3A, &Cpu::op0x3B,
			&Cpu::op0x3C, &Cpu::op0x3D, &Cpu::op0x3E, &Cpu::op0x3F,
			&Cpu::op0x40, &Cpu::op0x41, &Cpu::op0x42, &Cpu::op0x43,
			&Cpu::op0x44, &Cpu::op0x45, &Cpu::op0x46, &Cpu::op0x47,
			&Cpu::op0x48, &Cpu::op0x49, &Cpu::op0x4A, &Cpu::op0x4B,
			&Cpu::op0x4C, &Cpu::op0x4D, &Cpu::op0x4E, &Cpu::op0x4F,
			&Cpu::op0x50, &Cpu::op0x51, &Cpu::op0x52, &Cpu::op0x53,
			&Cpu::op0x54, &Cpu::op0x55, &Cpu::op0x56, &Cpu::op0x57,
			&Cpu::op0x58, &Cpu::op0x59, &Cpu::op0x5A, &Cpu::op0x5B,
			&Cpu::op0x5C, &Cpu::op0x5D, &Cpu::op0x5E, &Cpu::op0x5F,
			&Cpu::op0x60, &Cpu::op0x61, &Cpu::op0x62, &Cpu::op0x63,
			&Cpu::op0x64, &Cpu::op0x65, &Cpu::op0x66, &Cpu::op0x67,
			&Cpu::op0x68, &Cpu::op0x69, &Cpu::op0x6A, &Cpu::op0x6B,
			&Cpu::op0x6C, &Cpu::op0x6D, &Cpu::op0x6E, &Cpu::op0x6F,
			&Cpu::op0x70, &Cpu::op0x71, &Cpu::op0x72, &Cpu::op0x73,
			&Cpu::op0x74, &Cpu::op0x75, &Cpu::op0x76, &Cpu::op0x77,
			&Cpu::op0x78, &Cpu::op0x79, &Cpu::op0x7A, &Cpu::op0x7B,
			&Cpu::op0x7C, &Cpu::op0x7D, &Cpu::op0x7E, &Cpu::op0x7F,
			&Cpu::op0x80, &Cpu::op0x81, &Cpu::op0x82, &Cpu::op0x83,
			&Cpu::op0x84, &Cpu::op0x85, &Cpu::op0x86, &Cpu::op0x87,
			&Cpu::op0x88, &Cpu::op0x89, &Cpu::op0x8A, &Cpu::op0x8B,
			&Cpu::op0x8C, &Cpu::op0x8D, &Cpu::op0x8E, &Cpu::op0x8F,
			&Cpu::op0x90, &Cpu::op0x91, &Cpu::op0x92, &Cpu::op0x93,
			&Cpu::op0x94, &Cpu::op0x95, &Cpu::op0x96, &Cpu::op0x97,
			&Cpu::op0x98, &Cpu::op0x99, &Cpu::op0x9A, &Cpu::op0x9B,
			&Cpu::op0x9C, &Cpu::op0x9D, &Cpu::op0x9E, &Cpu::op0x9F,
			&Cpu::op0xA0, &Cpu::op0xA1, &Cpu::op0xA2, &Cpu::op0xA3,
			&Cpu::op0xA4, &Cpu::op0xA5, &Cpu::op0xA6, &Cpu::op0xA7,
			&Cpu::op0xA8, &Cpu::op0xA9, &Cpu::op0xAA, &Cpu::op0xAB,
			&Cpu::op0xAC, &Cpu::op0xAD, &Cpu::op0xAE, &Cpu::op0xAF,
			&Cpu::op0xB0, &Cpu::op0xB1, &Cpu::op0xB2, &Cpu::op0xB3,
			&Cpu::op0xB4, &Cpu::op0xB5, &Cpu::op0xB6, &Cpu::op0xB7,
			&Cpu::op0xB8, &Cpu::op0xB9, &Cpu::op0xBA, &Cpu::op0xBB,
			&Cpu::op0xBC, &Cpu::op0xBD, &Cpu::op0xBE, &Cpu::op0xBF,
			&Cpu::op0xC0, &Cpu::op0xC1, &Cpu::op0xC2, &Cpu::op0xC3,
			&Cpu::op0xC4, &Cpu::op0xC5, &Cpu::op0xC6, &Cpu::op0xC7,
			&Cpu::op0xC8, &Cpu::op0xC9, &Cpu::op0xCA, &Cpu::op0xCB,
			&Cpu::op0xCC, &Cpu::op0xCD, &Cpu::op0xCE, &Cpu::op0xCF,
			&Cpu::op0xD0, &Cpu::op0xD1, &Cpu::op0xD2, &Cpu::op0xD3,
			&Cpu::op0xD4, &Cpu::op0xD5, &Cpu::op0xD6, &Cpu::op0xD7,
			&Cpu::op0xD8, &Cpu::op0xD9, &Cpu::op0xDA, &Cpu::op0xDB,
			&Cpu::op0xDC, &Cpu::op0xDD, &Cpu::op0xDE, &Cpu::op0xDF,
			&Cpu::op0xE0, &Cpu::op0xE1, &Cpu::op0xE2, &Cpu::op0xE3,
			&Cpu::op0xE4, &Cpu::op0xE5, &Cpu::op0xE6, &Cpu::op0xE7,
			&Cpu::op0xE8, &Cpu::op0xE9, &Cpu::op0xEA, &Cpu::op0xEB,
			&Cpu::op0xEC, &Cpu::op0xED, &Cpu::op0xEE, &Cpu::op0xEF,
			&Cpu::op0xF0, &Cpu::op0xF1, &Cpu::op0xF2, &Cpu::op0xF3,
			&Cpu::op0xF4, &Cpu::op0xF5, &Cpu::op0xF6, &Cpu::op0xF7,
			&Cpu::op0xF8, &Cpu::op0xF9, &Cpu::op0xFA, &Cpu::op0xFB,
			&Cpu::op0xFC, &Cpu::op0xFD, &Cpu::op0xFE, &Cpu::op0xFF
		};

		const byte Cpu::writeClocks[0x100] =
		{
			0x1C, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x18, 0x18,
			0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x60,
			0x1C, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x18, 0x18,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x60,
			0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x18, 0x18,
			0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x60,
			0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x18, 0x18,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x60,
			0x00, 0x20, 0x00, 0x20, 0x04, 0x04, 0x04, 0x04,
			0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08,
			0x00, 0x20, 0x00, 0x00, 0x08, 0x08, 0x08, 0x08,
			0x00, 0x10, 0x00, 0x00, 0x10, 0x10, 0x10, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
			0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x18, 0x18,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x60,
			0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x18, 0x18,
			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x30, 0x30,
			0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x60, 0x60
		};

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		#if NST_MSVC >= 1200
		#pragma warning( push )
		#pragma warning( disable : 4355 )
		#endif

		Cpu::Cpu()
		:
		model ( CPU_RP2A03 ),
		apu   ( *this ),
		map   ( this, &Cpu::Peek_Overflow, &Cpu::Poke_Overflow )
		{
			cycles.UpdateTable( GetModel() );
			Reset( false, false );
		}

		#if NST_MSVC >= 1200
		#pragma warning( pop )
		#endif

		void Cpu::PowerOff()
		{
			Reset( false, true );
		}

		void Cpu::SetRamPowerState(uint powerstate)
		{
			ram.powerstate = powerstate;
		}

		void Cpu::Reset(bool hard)
		{
			Reset( true, hard );
		}

		void Cpu::Reset(const bool on,const bool hard)
		{
			if (!on || hard)
			{
				ram.Reset( GetModel() );

				a = 0x00;
				x = 0x00;
				y = 0x00;
				sp = 0xFD;

				flags.nz = 0U ^ 1U;
				flags.c = 0;
				flags.v = 0;
				flags.d = 0;
			}
			else
			{
				sp = (sp - 3) & 0xFF;
			}

			opcode  = 0;
			flags.i = Flags::I;
			jammed  = false;
			ticks   = 0;
			logged  = 0;

			pc = RESET_VECTOR;

			cycles.count  = 0;
			cycles.offset = 0;
			cycles.round  = 0;
			cycles.frame  = (model == CPU_RP2A03 ? PPU_RP2C02_HVSYNC : model == CPU_RP2A07 ? PPU_RP2C07_HVSYNC : PPU_DENDY_HVSYNC);

			interrupt.Reset();
			hooks.Clear();
			linker.Clear();

			if (on)
			{
				map( 0x0000, 0x07FF ).Set( &ram, &Ram::Peek_Ram_0, &Ram::Poke_Ram_0 );
				map( 0x0800, 0x0FFF ).Set( &ram, &Ram::Peek_Ram_1, &Ram::Poke_Ram_1 );
				map( 0x1000, 0x17FF ).Set( &ram, &Ram::Peek_Ram_2, &Ram::Poke_Ram_2 );
				map( 0x1800, 0x1FFF ).Set( &ram, &Ram::Peek_Ram_3, &Ram::Poke_Ram_3 );
				map( 0x2000, 0xFFFF ).Set( this, &Cpu::Peek_Nop,        &Cpu::Poke_Nop        );
				map( 0xFFFC         ).Set( this, &Cpu::Peek_Jam_1,      &Cpu::Poke_Nop        );
				map( 0xFFFD         ).Set( this, &Cpu::Peek_Jam_2,      &Cpu::Poke_Nop        );

				apu.Reset( hard );
			}
			else
			{
				map( 0x0000, 0xFFFF ).Set( this, &Cpu::Peek_Nop, &Cpu::Poke_Nop );

				if (hard)
					apu.PowerOff();
			}
		}

		void Cpu::Boot(const bool hard)
		{
			NST_VERIFY( pc == RESET_VECTOR );

			pc = map.Peek16( RESET_VECTOR );

			if (hard)
			{
				Poke(0x4017, 0x00);
				cycles.count = cycles.clock[RESET_CYCLES] + cycles.clock[0];
			}
			else
			{
				Poke(0x4017, apu.GetCtrl());
				cycles.count = cycles.clock[RESET_CYCLES] + cycles.clock[0];
			}
		}

		void Cpu::SetModel(const CpuModel m)
		{
			NST_ASSERT( pc == RESET_VECTOR && cycles.count == 0 );

			if (model != m)
			{
				model = m;
				cycles.UpdateTable( m );
			}
		}

		void Cpu::AddHook(const Hook& hook)
		{
			hooks.Add( hook );
		}

		void Cpu::RemoveHook(const Hook& hook)
		{
			hooks.Remove( hook );
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		bool Cpu::IsOddCycle() const
		{
			return uint((ticks + cycles.count) % cycles.clock[1]);
		}

		bool Cpu::IsWriteCycle(Cycle clock) const
		{
			if (const uint clocks = writeClocks[opcode])
			{
				clock = (clock - cycles.offset) / cycles.clock[0];

				if (clock < 8)
					return clocks >> clock & 0x1;
			}

			return false;
		}

		Cycle Cpu::GetClockBase() const
		{
			return model == CPU_RP2A07 || model == CPU_DENDY ? CLK_PAL : CLK_NTSC;
		}

		uint Cpu::GetClockDivider() const
		{
			return model == CPU_RP2A07 || model == CPU_DENDY ? CLK_PAL_DIV : CLK_NTSC_DIV;
		}

		dword Cpu::GetTime(Cycle clock) const
		{
			return
			(
				model == CPU_RP2A03 ? clock * qaword( CPU_RP2A03_CC * CLK_NTSC_DIV ) / CLK_NTSC :
				model == CPU_RP2A07 ? clock * qaword( CPU_RP2A07_CC * CLK_PAL_DIV  ) / CLK_PAL  :
                                      clock * qaword( CPU_DENDY_CC  * CLK_PAL_DIV  ) / CLK_PAL
			);
		}

		dword Cpu::GetFps() const
		{
			return
			(
				model == CPU_RP2A03 ? PPU_RP2C02_FPS :
				model == CPU_RP2A07 ? PPU_RP2C07_FPS :
                                      PPU_DENDY_FPS
			);
		}

		inline uint Cpu::Cycles::InterruptEdge() const
		{
			return clock[0] + clock[0] / 2;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		void Cpu::SaveState(State::Saver& state,const dword cpuChunk,const dword apuChunk) const
		{
			state.Begin( cpuChunk );

			{
				const byte data[7] =
				{
					pc & 0xFF,
					pc >> 8,
					sp,
					a,
					x,
					y,
					flags.Pack()
				};

				state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
			}

			state.Begin( AsciiId<'R','A','M'>::V ).Compress( ram.mem ).End();

			{
				const byte data[5] =
				{
					((interrupt.nmiClock != CYCLE_MAX) ? 0x01U : 0x00U) |
					((interrupt.low & IRQ_FRAME)       ? 0x02U : 0x00U) |
					((interrupt.low & IRQ_DMC)         ? 0x04U : 0x00U) |
					((interrupt.low & IRQ_EXT)         ? 0x08U : 0x00U) |
					(jammed                            ? 0x40U : 0x00U) |
					(model == CPU_RP2A07 ? 0x80U : model == CPU_DENDY ? 0x20U : 0x00U),
					cycles.count & 0xFF,
					cycles.count >> 8,
					(interrupt.nmiClock != CYCLE_MAX) ? interrupt.nmiClock+1 : 0,
					(interrupt.irqClock != CYCLE_MAX) ? interrupt.irqClock+1 : 0
				};

				state.Begin( AsciiId<'F','R','M'>::V ).Write( data ).End();
			}

			state.Begin( AsciiId<'C','L','K'>::V ).Write64( ticks ).End();

			state.End();

			apu.SaveState( state, apuChunk );
		}

		void Cpu::LoadState(State::Loader& state,const dword cpuChunk,const dword apuChunk,const dword baseChunk)
		{
			if (baseChunk == cpuChunk)
			{
				CpuModel stateModel = GetModel();
				ticks = 0;

				while (const dword chunk = state.Begin())
				{
					switch (chunk)
					{
						case AsciiId<'R','E','G'>::V:
						{
							State::Loader::Data<7> data( state );

							pc = data[0] | data[1] << 8;
							sp = data[2];
							a  = data[3];
							x  = data[4];
							y  = data[5];

							flags.Unpack( data[6] );
							break;
						}

						case AsciiId<'R','A','M'>::V:

							state.Uncompress( ram.mem );
							break;

						case AsciiId<'F','R','M'>::V:
						{
							State::Loader::Data<5> data( state );

							switch (data[0] & (0x80|0x20))
							{
								case 0x20: stateModel = CPU_DENDY;  break;
								case 0x80: stateModel = CPU_RP2A07; break;
								default:   stateModel = CPU_RP2A03; break;
							}

							interrupt.nmiClock = CYCLE_MAX;
							interrupt.irqClock = CYCLE_MAX;
							interrupt.low = 0;

							if (data[0] & (0x2|0x4|0x8))
							{
								interrupt.low =
								(
									((data[0] & 0x2) ? IRQ_FRAME : 0) |
									((data[0] & 0x4) ? IRQ_DMC   : 0) |
									((data[0] & 0x8) ? IRQ_EXT   : 0)
								);

								if (!flags.i)
									interrupt.irqClock = data[4] ? data[4] - 1 : 0;
							}

							cycles.count = data[1] | data[2] << 8;

							if (data[0] & 0x1)
								interrupt.nmiClock = data[3] ? data[3] - 1 : cycles.InterruptEdge();

							jammed = data[0] >> 6 & 0x1;

							if (jammed)
								interrupt.Reset();

							break;
						}

						case AsciiId<'C','L','K'>::V:

							ticks = state.Read64();
							break;
					}

					state.End();
				}

				const CpuModel actualModel = GetModel();

				if (stateModel != actualModel)
				{
					const uint clocks[2] =
					{
						stateModel  == CPU_RP2A03 ? CPU_RP2A03_CC : stateModel  == CPU_RP2A07 ? CPU_RP2A07_CC : CPU_DENDY_CC,
						actualModel == CPU_RP2A03 ? CPU_RP2A03_CC : actualModel == CPU_RP2A07 ? CPU_RP2A07_CC : CPU_DENDY_CC
					};

					cycles.count = cycles.count / clocks[0] * clocks[1];
					ticks = ticks / clocks[0] * clocks[1];

					if (interrupt.nmiClock != CYCLE_MAX)
						interrupt.nmiClock = interrupt.nmiClock / clocks[0] * clocks[1];

					if (interrupt.irqClock != CYCLE_MAX)
						interrupt.irqClock = interrupt.irqClock / clocks[0] * clocks[1];
				}

				NST_VERIFY( cycles.count < cycles.frame );

				if (cycles.count >= cycles.frame)
					cycles.count = 0;

				ticks -= (ticks + cycles.count) % cycles.clock[0];
			}
			else if (baseChunk == apuChunk)
			{
				apu.LoadState( state );
			}
		}

		void Cpu::NotifyOp(const char (&code)[4],const dword which)
		{
			if (!(logged & which))
			{
				logged |= which;
				Api::User::eventCallback( Api::User::EVENT_CPU_UNOFFICIAL_OPCODE, code );
			}
		}

		Cpu::Hooks::Hooks()
		: hooks(new Hook [2]), size(0), capacity(2) {}

		Cpu::Hooks::~Hooks()
		{
			delete [] hooks;
		}

		void Cpu::Hooks::Clear()
		{
			size = 0;
		}

		void Cpu::Hooks::Add(const Hook& hook)
		{
			for (uint i=0, n=size; i < n; ++i)
			{
				if (hooks[i] == hook)
					return;
			}

			if (size == capacity)
			{
				Hook* const NST_RESTRICT next = new Hook [capacity+1];
				++capacity;

				for (uint i=0, n=size; i < n; ++i)
					next[i] = hooks[i];

				delete [] hooks;
				hooks = next;
			}

			hooks[size++] = hook;
		}

		void Cpu::Hooks::Remove(const Hook& hook)
		{
			for (uint i=0, n=size; i < n; ++i)
			{
				if (hooks[i] == hook)
				{
					while (++i < n)
						hooks[i-1] = hooks[i];

					--size;
					return;
				}
			}
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		inline uint Cpu::Hooks::Size() const
		{
			return size;
		}

		inline const Hook* Cpu::Hooks::Ptr() const
		{
			return hooks;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		Cpu::Linker::Chain::Chain(const Port& p,uint a,uint l)
		: Port(p), address(a), level(l) {}

		Cpu::Linker::Linker()
		: chain(NULL) {}

		Cpu::Linker::~Linker()
		{
			Clear();
		}

		void Cpu::Linker::Clear()
		{
			if (Chain* next = chain)
			{
				chain = NULL;

				do
				{
					Chain* tmp = next->next;
					delete next;
					next = tmp;
				}
				while (next);
			}
		}

		const Io::Port* Cpu::Linker::Add(const Address address,const uint level,const Io::Port& port,IoMap& map)
		{
			NST_ASSERT( level );

			Chain* const entry = new Chain( port, address, level );

			for (Chain *it=chain, *prev=NULL; it; prev=it, it=it->next)
			{
				if (it->address == address)
				{
					NST_ASSERT( it->next && it->next->address == address );

					if (level > it->level)
					{
						entry->next = it;

						if (prev)
							prev->next = entry;
						else
							chain = entry;

						map(address) = port;

						return it;
					}
					else for (;;)
					{
						it = it->next;

						NST_ASSERT( level != it->level );

						if (level > it->level)
						{
							const Chain tmp( *it );
							*it = *entry;
							it->next = entry;
							*entry = tmp;

							return entry;
						}
					}
				}
			}

			entry->next = new Chain( map[address], address );
			entry->next->next = NULL;

			map(address) = port;

			if (Chain* it = chain)
			{
				while (it->next)
					it = it->next;

				it->next = entry;
			}
			else
			{
				chain = entry;
			}

			return entry->next;
		}

		void Cpu::Linker::Remove(const Address address,const Io::Port& port,IoMap& map)
		{
			for (Chain *it=chain, *prev=NULL; it; prev=it, it=it->next)
			{
				if (it->address == address && port == *it)
				{
					const Chain* const next = it->next;

					NST_ASSERT( it->level && next && next->address == address );

					*it = *next;
					delete next;

					if (map(address) == port)
						map(address) = *it;

					if (it->level == 0)
					{
						if (prev == NULL)
						{
							it = it->next;
							delete chain;
							chain = it;
						}
						else if (prev->address != address)
						{
							prev->next = it->next;
							delete it;
						}
					}

					break;
				}
			}
		}

		void Cpu::Cycles::UpdateTable(CpuModel model)
		{
			for (uint cc = (model == CPU_RP2A03 ? CPU_RP2A03_CC : model == CPU_RP2A07 ? CPU_RP2A07_CC : CPU_DENDY_CC), i=0; i < 8; ++i)
				clock[i] = (i+1) * cc;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		uint Cpu::Flags::Pack() const
		{
			NST_ASSERT( (i == 0 || i == I) && (c == 0 || c == C) && (d == 0 || d == D) );

			return
			(
				((nz | nz >> 1) & N) |
				((nz & 0xFF) ? 0 : Z) |
				c |
				(v ? V : 0) |
				i |
				d |
				R
			);
		}

		void Cpu::Flags::Unpack(const uint f)
		{
			nz = (~f & Z) | ((f & N) << 1);
			c =  f & C;
			v =  f & V;
			i =  f & I;
			d =  f & D;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		void Cpu::Interrupt::Reset()
		{
			nmiClock = CYCLE_MAX;
			irqClock = CYCLE_MAX;
			low = 0;
		}

		template<typename T,typename U>
		Cpu::IoMap::IoMap(Cpu* cpu,T peek,U poke)
		: Io::Map<SIZE_64K>( cpu, peek, poke ) {}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		inline uint Cpu::IoMap::Peek8(const uint address) const
		{
			NST_ASSERT( address < FULL_SIZE );
			return ports[address].Peek( address );
		}

		inline uint Cpu::IoMap::Peek16(const uint address) const
		{
			NST_ASSERT( address < FULL_SIZE-1 );
			return ports[address].Peek( address ) | ports[address + 1].Peek( address + 1 ) << 8;
		}

		inline void Cpu::IoMap::Poke8(const uint address,const uint data) const
		{
			NST_ASSERT( address < FULL_SIZE );
			ports[address].Poke( address, data );
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		void Cpu::Ram::Reset(const CpuModel model)
		{
			switch (powerstate) {
				case 1: std::memset( mem, 0xFF, sizeof(mem) ); break;
				case 2: std::memset( mem, byte(std::rand()), sizeof(mem) ); break;
				default: std::memset( mem, 0x00, sizeof(mem) ); break;
			}
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		NES_PEEK_A(Cpu::Ram,Ram_0) { return mem[address - 0x0000]; }
		NES_PEEK_A(Cpu::Ram,Ram_1) { return mem[address - 0x0800]; }
		NES_PEEK_A(Cpu::Ram,Ram_2) { return mem[address - 0x1000]; }
		NES_PEEK_A(Cpu::Ram,Ram_3) { return mem[address - 0x1800]; }

		NES_POKE_AD(Cpu::Ram,Ram_0) { mem[address - 0x0000] = data; }
		NES_POKE_AD(Cpu::Ram,Ram_1) { mem[address - 0x0800] = data; }
		NES_POKE_AD(Cpu::Ram,Ram_2) { mem[address - 0x1000] = data; }
		NES_POKE_AD(Cpu::Ram,Ram_3) { mem[address - 0x1800] = data; }

		NES_PEEK_A(Cpu,Nop)
		{
			return address >> 8;
		}

		NES_POKE(Cpu,Nop)
		{
		}

		NES_PEEK_A(Cpu,Overflow)
		{
			pc &= 0xFFFF;
			return ram.mem[address & 0x7FF];
		}

		NES_POKE_AD(Cpu,Overflow)
		{
			pc &= 0xFFFF;
			ram.mem[address & 0x7FF] = data;
		}

		NES_PEEK(Cpu,Jam_1)
		{
			pc = (pc - 1) & 0xFFFF;
			return 0xFC;
		}

		NES_PEEK(Cpu,Jam_2)
		{
			return 0xFF;
		}

		inline uint Cpu::FetchZpg16(const uint address) const
		{
			return ram.mem[address & 0xFF] | uint(ram.mem[(address+1) & 0xFF]) << 8;
		}

		inline uint Cpu::FetchPc8()
		{
			const uint data = map.Peek8( pc );
			++pc;
			return data;
		}

		inline uint Cpu::FetchPc16()
		{
			const uint data = map.Peek16( pc );
			pc += 2;
			return data;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// Immediate addressing
		////////////////////////////////////////////////////////////////////////////////////////

		inline uint Cpu::Imm_R()
		{
			const uint data = FetchPc8();
			cycles.count += cycles.clock[1];
			return data;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// Absolute addressing
		////////////////////////////////////////////////////////////////////////////////////////

		uint Cpu::Abs_R()
		{
			uint data = FetchPc16();
			cycles.count += cycles.clock[2];

			data = map.Peek8( data );
			cycles.count += cycles.clock[0];

			return data;
		}

		uint Cpu::Abs_RW(uint& data)
		{
			const uint address = FetchPc16();
			cycles.count += cycles.clock[2];

			data = map.Peek8( address );
			cycles.count += cycles.clock[0];

			map.Poke8( address, data );
			cycles.count += cycles.clock[0];

			return address;
		}

		inline uint Cpu::Abs_W()
		{
			const uint address = FetchPc16();
			cycles.count += cycles.clock[2];
			return address;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// Zero page addressing
		////////////////////////////////////////////////////////////////////////////////////////

		inline uint Cpu::Zpg_R()
		{
			const uint address = FetchPc8();
			cycles.count += cycles.clock[2];
			return ram.mem[address];
		}

		inline uint Cpu::Zpg_RW(uint& data)
		{
			const uint address = FetchPc8();
			cycles.count += cycles.clock[4];
			data = ram.mem[address];
			return address;
		}

		inline uint Cpu::Zpg_W()
		{
			const uint address = FetchPc8();
			cycles.count += cycles.clock[2];
			return address;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// Zero page indexed addressing
		////////////////////////////////////////////////////////////////////////////////////////

		inline uint Cpu::ZpgReg_R(uint indexed)
		{
			indexed = (indexed + FetchPc8()) & 0xFF;
			cycles.count += cycles.clock[3];
			return ram.mem[indexed];
		}

		inline uint Cpu::ZpgReg_RW(uint& data,uint indexed)
		{
			indexed = (indexed + FetchPc8()) & 0xFF;
			cycles.count += cycles.clock[5];
			data = ram.mem[indexed];
			return indexed;
		}

		inline uint Cpu::ZpgReg_W(uint indexed)
		{
			indexed = (indexed + FetchPc8()) & 0xFF;
			cycles.count += cycles.clock[3];
			return indexed;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// Zero page indexed addressing (X && Y)
		////////////////////////////////////////////////////////////////////////////////////////

		inline uint Cpu::ZpgX_R()            { return ZpgReg_R( x );        }
		inline uint Cpu::ZpgX_RW(uint& data) { return ZpgReg_RW( data, x ); }
		inline uint Cpu::ZpgX_W()            { return ZpgReg_W( x );        }
		inline uint Cpu::ZpgY_R()            { return ZpgReg_R( y );        }
		inline uint Cpu::ZpgY_RW(uint& data) { return ZpgReg_RW( data, y ); }
		inline uint Cpu::ZpgY_W()            { return ZpgReg_W( y );        }

		////////////////////////////////////////////////////////////////////////////////////////
		// Absolute indexed addressing
		////////////////////////////////////////////////////////////////////////////////////////

		uint Cpu::AbsReg_R(uint indexed)
		{
			uint data = pc;
			indexed += map.Peek8( data );
			data = (map.Peek8( data + 1 ) << 8) + indexed;
			cycles.count += cycles.clock[2];

			if (indexed & 0x100)
			{
				map.Peek8( data - 0x100 );
				cycles.count += cycles.clock[0];
			}

			data = map.Peek8( data );
			pc += 2;
			cycles.count += cycles.clock[0];

			return data;
		}

		uint Cpu::AbsReg_RW(uint& data,uint indexed)
		{
			uint address = pc;
			indexed += map.Peek8( address );
			address = (map.Peek8( address + 1 ) << 8) + indexed;

			map.Peek8( address - (indexed & 0x100) );
			pc += 2;
			cycles.count += cycles.clock[3];

			data = map.Peek8( address );
			cycles.count += cycles.clock[0];

			map.Poke8( address, data );
			cycles.count += cycles.clock[0];

			return address;
		}

		NST_FORCE_INLINE uint Cpu::AbsReg_W(uint indexed)
		{
			uint address = pc;
			indexed += map.Peek8( address );
			address = (map.Peek8( address + 1 ) << 8) + indexed;

			map.Peek8( address - (indexed & 0x100) );
			pc += 2;
			cycles.count += cycles.clock[3];

			return address;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// Absolute indexed addressing (X && Y)
		////////////////////////////////////////////////////////////////////////////////////////

		inline uint Cpu::AbsX_R()            { return AbsReg_R( x );        }
		inline uint Cpu::AbsY_R()            { return AbsReg_R( y );        }
		inline uint Cpu::AbsX_RW(uint& data) { return AbsReg_RW( data, x ); }
		inline uint Cpu::AbsY_RW(uint& data) { return AbsReg_RW( data, y ); }
		NST_FORCE_INLINE uint Cpu::AbsX_W()  { return AbsReg_W( x );        }
		NST_FORCE_INLINE uint Cpu::AbsY_W()  { return AbsReg_W( y );        }

		////////////////////////////////////////////////////////////////////////////////////////
		// Indexed indirect addressing
		////////////////////////////////////////////////////////////////////////////////////////

		uint Cpu::IndX_R()
		{
			uint data = FetchPc8() + x;
			cycles.count += cycles.clock[4];
			data = FetchZpg16( data );

			data = map.Peek8( data );
			cycles.count += cycles.clock[0];

			return data;
		}

		inline uint Cpu::IndX_RW(uint& data)
		{
			uint address = FetchPc8() + x;
			cycles.count += cycles.clock[4];
			address = FetchZpg16( address );

			data = map.Peek8( address );
			cycles.count += cycles.clock[0];

			map.Poke8( address, data );
			cycles.count += cycles.clock[0];

			return address;
		}

		inline uint Cpu::IndX_W()
		{
			const uint address = FetchPc8() + x;
			cycles.count += cycles.clock[4];
			return FetchZpg16( address );
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// Indirect indexed addressing
		////////////////////////////////////////////////////////////////////////////////////////

		uint Cpu::IndY_R()
		{
			uint data = FetchPc8();
			cycles.count += cycles.clock[3];

			const uint indexed = ram.mem[data] + y;
			data = (uint(ram.mem[(data + 1) & 0xFF]) << 8) + indexed;

			if (indexed & 0x100)
			{
				map.Peek8( data - 0x100 );
				cycles.count += cycles.clock[0];
			}

			data = map.Peek8( data );
			cycles.count += cycles.clock[0];

			return data;
		}

		inline uint Cpu::IndY_RW(uint& data)
		{
			uint address = FetchPc8();
			cycles.count += cycles.clock[4];

			const uint indexed = ram.mem[address] + y;
			address = (uint(ram.mem[(address + 1) & 0xFF]) << 8) + indexed;
			map.Peek8( address - (indexed & 0x100) );

			data = map.Peek8( address );
			cycles.count += cycles.clock[0];

			map.Poke8( address, data );
			cycles.count += cycles.clock[0];

			return address;
		}

		NST_FORCE_INLINE uint Cpu::IndY_W()
		{
			uint address = FetchPc8();
			cycles.count += cycles.clock[4];

			const uint indexed = ram.mem[address] + y;
			address = (uint(ram.mem[(address + 1) & 0xFF]) << 8) + indexed;

			map.Peek8( address - (indexed & 0x100) );

			return address;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// relative addressing
		////////////////////////////////////////////////////////////////////////////////////////

		template<bool STATE>
		NST_FORCE_INLINE void Cpu::Branch(uint tmp)
		{
			if ((!!tmp) == STATE)
			{
				pc = ((tmp=pc+1) + sign_extend_8(uint(map.Peek8( pc )))) & 0xFFFF;
				cycles.count += cycles.clock[2 + ((tmp^pc) >> 8 & 1)];
			}
			else
			{
				++pc;
				cycles.count += cycles.clock[1];
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// store data
		////////////////////////////////////////////////////////////////////////////////////////

		inline void Cpu::StoreMem(const uint address,const uint data)
		{
			map.Poke8( address, data );
			cycles.count += cycles.clock[0];
		}

		inline void Cpu::StoreZpg(const uint address,const uint data)
		{
			ram.mem[address] = data;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// stack management
		////////////////////////////////////////////////////////////////////////////////////////

		inline void Cpu::Push8(const uint data)
		{
			NST_ASSERT( sp <= 0xFF );

			const uint p = sp;
			sp = (sp - 1) & 0xFF;

			ram.mem[0x100+p] = data;
		}

		NST_FORCE_INLINE void Cpu::Push16(const uint data)
		{
			NST_ASSERT( sp <= 0xFF );

			const uint p0 = sp;
			const uint p1 = (p0 - 1) & 0xFF;
			sp = (p1 - 1) & 0xFF;

			ram.mem[0x100+p1] = data & 0xFF;
			ram.mem[0x100+p0] = data >> 8;
		}

		inline uint Cpu::Pull8()
		{
			NST_ASSERT( sp <= 0xFF );

			sp = (sp + 1) & 0xFF;

			return ram.mem[0x100+sp];
		}

		inline uint Cpu::Pull16()
		{
			NST_ASSERT( sp <= 0xFF );

			const uint p0 = (sp + 1) & 0xFF;
			const uint p1 = (p0 + 1) & 0xFF;
			sp = p1;

			return ram.mem[0x100+p0] | uint(ram.mem[0x100+p1]) << 8;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// load instructions
		////////////////////////////////////////////////////////////////////////////////////////

		inline void Cpu::Lda(const uint data) { a = data; flags.nz = data; }
		inline void Cpu::Ldx(const uint data) { x = data; flags.nz = data; }
		inline void Cpu::Ldy(const uint data) { y = data; flags.nz = data; }

		////////////////////////////////////////////////////////////////////////////////////////
		// store instructions
		////////////////////////////////////////////////////////////////////////////////////////

		inline uint Cpu::Sta() const { return a; }
		inline uint Cpu::Stx() const { return x; }
		inline uint Cpu::Sty() const { return y; }

		////////////////////////////////////////////////////////////////////////////////////////
		// transfer instructions
		////////////////////////////////////////////////////////////////////////////////////////

		NST_SINGLE_CALL void Cpu::Tax()
		{
			cycles.count += cycles.clock[1];
			x = a;
			flags.nz = a;
		}

		NST_SINGLE_CALL void Cpu::Tay()
		{
			cycles.count += cycles.clock[1];
			y = a;
			flags.nz = a;
		}

		NST_SINGLE_CALL void Cpu::Txa()
		{
			cycles.count += cycles.clock[1];
			a = x;
			flags.nz = x;
		}

		NST_SINGLE_CALL void Cpu::Tya()
		{
			cycles.count += cycles.clock[1];
			a = y;
			flags.nz = y;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// flow control instructions
		////////////////////////////////////////////////////////////////////////////////////////

		NST_SINGLE_CALL void Cpu::JmpAbs()
		{
			pc = map.Peek16( pc );
			cycles.count += cycles.clock[JMP_ABS_CYCLES-1];
		}

		NST_SINGLE_CALL void Cpu::JmpInd()
		{
			// 6502 trap, can't cross between pages

			const uint pos = map.Peek16( pc );
			pc = map.Peek8( pos ) | (map.Peek8( (pos & 0xFF00) | ((pos + 1) & 0x00FF) ) << 8);

			cycles.count += cycles.clock[JMP_IND_CYCLES-1];
		}

		NST_SINGLE_CALL void Cpu::Jsr()
		{
			// 6502 trap, return address pushed on the stack is
			// one byte prior to the next instruction

			Push16( pc + 1 );
			pc = map.Peek16( pc );
			cycles.count += cycles.clock[JSR_CYCLES-1];
		}

		NST_SINGLE_CALL void Cpu::Rts()
		{
			opcode = map.Peek8( pc );
			pc = Pull16() + 1;
			cycles.count += cycles.clock[RTS_CYCLES-1];
		}

		NST_SINGLE_CALL void Cpu::Rti()
		{
			cycles.count += cycles.clock[RTI_CYCLES-1];

			{
				const uint packed = Pull8();
				opcode = map.Peek8( pc );
				pc = Pull16();
				flags.Unpack( packed );
			}

			if (!interrupt.low || flags.i)
			{
				interrupt.irqClock = CYCLE_MAX;
			}
			else
			{
				interrupt.irqClock = 0;
				cycles.round = 0;
			}
		}

		NST_SINGLE_CALL void Cpu::Bne() { Branch< true  >( flags.nz & 0xFF  ); }
		NST_SINGLE_CALL void Cpu::Beq() { Branch< false >( flags.nz & 0xFF  ); }
		NST_SINGLE_CALL void Cpu::Bmi() { Branch< true  >( flags.nz & 0x180 ); }
		NST_SINGLE_CALL void Cpu::Bpl() { Branch< false >( flags.nz & 0x180 ); }
		NST_SINGLE_CALL void Cpu::Bcs() { Branch< true  >( flags.c          ); }
		NST_SINGLE_CALL void Cpu::Bcc() { Branch< false >( flags.c          ); }
		NST_SINGLE_CALL void Cpu::Bvs() { Branch< true  >( flags.v          ); }
		NST_SINGLE_CALL void Cpu::Bvc() { Branch< false >( flags.v          ); }

		////////////////////////////////////////////////////////////////////////////////////////
		// math operations
		////////////////////////////////////////////////////////////////////////////////////////

		inline void Cpu::Adc(const uint data)
		{
			NST_ASSERT( flags.c <= 1 );

			// the N2A03 has no BCD mode

			const uint tmp = a + data + flags.c;
			flags.v = ~(a ^ data) & (a ^ tmp) & 0x80;
			a = tmp & 0xFF;
			flags.nz = a;
			flags.c = tmp >> 8 & 0x1;
		}

		inline void Cpu::Sbc(const uint data)
		{
			Adc( data ^ 0xFF );
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// logical operations
		////////////////////////////////////////////////////////////////////////////////////////

		inline void Cpu::And(const uint data)
		{
			a &= data;
			flags.nz = a;
		}

		inline void Cpu::Ora(const uint data)
		{
			a |= data;
			flags.nz = a;
		}

		inline void Cpu::Eor(const uint data)
		{
			a ^= data;
			flags.nz = a;
		}

		inline void Cpu::Bit(const uint data)
		{
			flags.nz = ((data & a) != 0) | ((data & Flags::N) << 1);
			flags.v = data & Flags::V;
		}

		inline void Cpu::Cmp(uint data)
		{
			data = a - data;
			flags.nz = data & 0xFF;
			flags.c = ~data >> 8 & 0x1;
		}

		inline void Cpu::Cpx(uint data)
		{
			data = x - data;
			flags.nz = data & 0xFF;
			flags.c = ~data >> 8 & 0x1;
		}

		inline void Cpu::Cpy(uint data)
		{
			data = y - data;
			flags.nz = data & 0xFF;
			flags.c = ~data >> 8 & 0x1;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// shift operations
		////////////////////////////////////////////////////////////////////////////////////////

		inline uint Cpu::Asl(const uint data)
		{
			flags.c = data >> 7;
			flags.nz = data << 1 & 0xFF;
			return flags.nz;
		}

		inline uint Cpu::Lsr(const uint data)
		{
			flags.c = data & 0x01;
			flags.nz = data >> 1;
			return flags.nz;
		}

		inline uint Cpu::Rol(const uint data)
		{
			NST_ASSERT( flags.c <= 1 );

			flags.nz = (data << 1 & 0xFF) | flags.c;
			flags.c = data >> 7;

			return flags.nz;
		}

		inline uint Cpu::Ror(const uint data)
		{
			NST_ASSERT( flags.c <= 1 );

			flags.nz = (data >> 1) | (flags.c << 7);
			flags.c = data & 0x01;

			return flags.nz;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// increment and decrement operations
		////////////////////////////////////////////////////////////////////////////////////////

		inline uint Cpu::Dec(const uint data)
		{
			flags.nz = (data - 1) & 0xFF;
			return flags.nz;
		}

		inline uint Cpu::Inc(const uint data)
		{
			flags.nz = (data + 1) & 0xFF;
			return flags.nz;
		}

		NST_SINGLE_CALL void Cpu::Dex()
		{
			cycles.count += cycles.clock[1];
			x = (x - 1) & 0xFF;
			flags.nz = x;
		}

		NST_SINGLE_CALL void Cpu::Dey()
		{
			cycles.count += cycles.clock[1];
			y = (y - 1) & 0xFF;
			flags.nz = y;
		}

		NST_SINGLE_CALL void Cpu::Inx()
		{
			cycles.count += cycles.clock[1];
			x = (x + 1) & 0xFF;
			flags.nz = x;
		}

		NST_SINGLE_CALL void Cpu::Iny()
		{
			cycles.count += cycles.clock[1];
			y = (y + 1) & 0xFF;
			flags.nz = y;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// flags instructions
		////////////////////////////////////////////////////////////////////////////////////////

		NST_SINGLE_CALL void Cpu::Clc()
		{
			cycles.count += cycles.clock[1];
			flags.c = 0;
		}

		NST_SINGLE_CALL void Cpu::Sec()
		{
			cycles.count += cycles.clock[1];
			flags.c = Flags::C;
		}

		NST_SINGLE_CALL void Cpu::Cld()
		{
			cycles.count += cycles.clock[1];
			flags.d = 0;
		}

		NST_SINGLE_CALL void Cpu::Sed()
		{
			cycles.count += cycles.clock[1];
			flags.d = Flags::D;
		}

		NST_SINGLE_CALL void Cpu::Clv()
		{
			cycles.count += cycles.clock[1];
			flags.v = 0;
		}

		NST_SINGLE_CALL void Cpu::Sei()
		{
			cycles.count += cycles.clock[1];

			if (!flags.i)
			{
				flags.i = Flags::I;
				interrupt.irqClock = CYCLE_MAX;

				if (interrupt.low)
					DoISR( IRQ_VECTOR );
			}
		}

		NST_SINGLE_CALL void Cpu::Cli()
		{
			cycles.count += cycles.clock[1];

			if (flags.i)
			{
				flags.i = 0;

				NST_VERIFY( interrupt.irqClock == CYCLE_MAX );

				if (interrupt.low)
				{
					interrupt.irqClock = cycles.count + 1;
					cycles.NextRound( interrupt.irqClock );
				}
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// stack operations
		////////////////////////////////////////////////////////////////////////////////////////

		NST_SINGLE_CALL void Cpu::Pha()
		{
			cycles.count += cycles.clock[PHA_CYCLES-1];
			Push8( a );
		}

		NST_SINGLE_CALL void Cpu::Php()
		{
			// 6502 trap, B flag joins the club

			cycles.count += cycles.clock[PHP_CYCLES-1];
			Push8( flags.Pack() | Flags::B );
		}

		NST_SINGLE_CALL void Cpu::Pla()
		{
			cycles.count += cycles.clock[PLA_CYCLES-1];
			a = Pull8();
			flags.nz = a;
		}

		NST_SINGLE_CALL void Cpu::Plp()
		{
			cycles.count += cycles.clock[PLP_CYCLES-1];

			const uint i = flags.i;
			flags.Unpack( Pull8() );

			if (interrupt.low)
			{
				if (i > flags.i)
				{
					interrupt.irqClock = cycles.count + 1;
					cycles.NextRound( interrupt.irqClock );
				}
				else if (i < flags.i)
				{
					interrupt.irqClock = CYCLE_MAX;
					DoISR( IRQ_VECTOR );
				}
			}
		}

		NST_SINGLE_CALL void Cpu::Tsx()
		{
			cycles.count += cycles.clock[1];
			x = sp;
			flags.nz = sp;
		}

		NST_SINGLE_CALL void Cpu::Txs()
		{
			cycles.count += cycles.clock[1];
			sp = x;
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// undocumented instructions, rarely used
		////////////////////////////////////////////////////////////////////////////////////////

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		NST_NO_INLINE void Cpu::Anc(const uint data)
		{
			a &= data;
			flags.nz = a;
			flags.c = flags.nz >> 7;
			NotifyOp("ANC",1UL << 0);
		}

		NST_SINGLE_CALL void Cpu::Ane(const uint data)
		{
			a = (a | 0xEE) & x & data;
			flags.nz = a;
			NotifyOp("ANE",1UL << 1);
		}

		NST_SINGLE_CALL void Cpu::Arr(const uint data)
		{
			a = ((data & a) >> 1) | (flags.c << 7);
			flags.nz = a;
			flags.c = a >> 6 & 0x1;
			flags.v = (a >> 6 ^ a >> 5) & 0x1;
			NotifyOp("ARR",1UL << 2);
		}

		NST_SINGLE_CALL void Cpu::Asr(const uint data)
		{
			flags.c = data & a & 0x1;
			a = (data & a) >> 1;
			flags.nz = a;
			NotifyOp("ASR",1UL << 3);
		}

		NST_NO_INLINE uint Cpu::Dcp(uint data)
		{
			data = (data - 1) & 0xFF;
			Cmp( data );
			NotifyOp("DCP",1UL << 4);
			return data;
		}

		NST_NO_INLINE uint Cpu::Isb(uint data)
		{
			data = (data + 1) & 0xFF;
			Sbc( data );
			NotifyOp("ISB",1UL << 5);
			return data;
		}

		NST_SINGLE_CALL void Cpu::Las(const uint data)
		{
			sp &= data;
			x = sp;
			a = sp;
			flags.nz = sp;
			NotifyOp("LAS",1UL << 6);
		}

		NST_NO_INLINE void Cpu::Lax(const uint data)
		{
			a = data;
			x = data;
			flags.nz = data;
			NotifyOp("LAX",1UL << 7);
		}

		NST_SINGLE_CALL void Cpu::Lxa(const uint data)
		{
			a = data;
			x = data;
			flags.nz = data;
			NotifyOp("LXA",1UL << 8);
		}

		NST_NO_INLINE uint Cpu::Rla(uint data)
		{
			const uint carry = flags.c;
			flags.c = data >> 7;
			data = (data << 1 & 0xFF) | carry;
			a &= data;
			flags.nz = a;
			NotifyOp("RLA",1UL << 9);
			return data;
		}

		NST_NO_INLINE uint Cpu::Rra(uint data)
		{
			const uint carry = flags.c << 7;
			flags.c = data & 0x01;
			data = (data >> 1) | carry;
			Adc( data );
			NotifyOp("RRA",1UL << 10);
			return data;
		}

		NST_NO_INLINE uint Cpu::Sax()
		{
			const uint data = a & x;
			NotifyOp("SAX",1UL << 11);
			return data;
		}

		NST_SINGLE_CALL void Cpu::Sbx(uint data)
		{
			data = (a & x) - data;
			flags.c = (data <= 0xFF);
			x = data & 0xFF;
			flags.nz = x;
			NotifyOp("SBX",1UL << 12);
		}

		NST_NO_INLINE uint Cpu::Sha(uint address)
		{
			address = a & x & ((address >> 8) + 1);
			NotifyOp("SHA",1UL << 13);
			return address;
		}

		NST_SINGLE_CALL uint Cpu::Shs(uint address)
		{
			sp = a & x;
			address = sp & ((address >> 8) + 1);
			NotifyOp("SHS",1UL << 14);
			return address;
		}

		NST_SINGLE_CALL void Cpu::Shx(uint address)
		{
			uint newaddress = (address + y);
			uint data = x & ((address >> 8) + 1);
			Peek((address & 0xFF00) | (newaddress & 0x00FF)); // Dummy read

			if ((address ^ newaddress) & 0x100)
			{
				address = (newaddress & (x << 8)) | (newaddress & 0x00FF);
			}
			else
			{
				address = newaddress;
			}

			NotifyOp("SHX",1UL << 15);
			StoreMem(address, data);
		}

		NST_SINGLE_CALL void Cpu::Shy(uint address)
		{
			uint newaddress = (address + x);
			uint data = y & ((address >> 8) + 1);
			Peek((address & 0xFF00) | (newaddress & 0x00FF)); // Dummy read

			if ((address ^ newaddress) & 0x100)
			{
				address = (newaddress & (y << 8)) | (newaddress & 0x00FF);
			}
			else
			{
				address = newaddress;
			}

			NotifyOp("SHY",1UL << 16);
			StoreMem(address, data);
		}

		NST_NO_INLINE uint Cpu::Slo(uint data)
		{
			flags.c = data >> 7;
			data = data << 1 & 0xFF;
			a |= data;
			flags.nz = a;
			NotifyOp("SLO",1UL << 17);
			return data;
		}

		NST_NO_INLINE uint Cpu::Sre(uint data)
		{
			flags.c = data & 0x01;
			data >>= 1;
			a ^= data;
			flags.nz = a;
			NotifyOp("SRE",1UL << 18);
			return data;
		}

		void Cpu::Dop()
		{
			NotifyOp("DOP",1UL << 19);
		}

		void Cpu::Top(uint=0)
		{
			NotifyOp("TOP",1UL << 20);
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// interrupts
		////////////////////////////////////////////////////////////////////////////////////////

		NST_SINGLE_CALL void Cpu::Brk()
		{
			NST_DEBUG_MSG("6502 BRK");

			opcode = map.Peek8( pc );
			Push16( pc + 1 );
			Push8( flags.Pack() | Flags::B );
			flags.i = Flags::I;

			NST_VERIFY_MSG(interrupt.irqClock == CYCLE_MAX,"BRK -> IRQ collision!");
			interrupt.irqClock = CYCLE_MAX;

			cycles.count += cycles.clock[BRK_CYCLES-1];
			pc = map.Peek16( FetchIRQISRVector() );
		}

		NST_NO_INLINE void Cpu::Jam()
		{
			// roll back and keep jamin'

			pc = (pc - 1) & 0xFFFF;
			cycles.count += cycles.clock[1];

			if (!jammed)
			{
				jammed = true;
				interrupt.Reset();
				NST_DEBUG_MSG("6502 JAM");
				Api::User::eventCallback( Api::User::EVENT_CPU_JAM );
			}
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		uint Cpu::FetchIRQISRVector()
		{
			if (cycles.count >= cycles.frame)
				map.Peek8( 0x3000 );

			if (interrupt.nmiClock != CYCLE_MAX)
			{
				NST_DEBUG_MSG("IRQ -> NMI collision!");

				if (interrupt.nmiClock + cycles.clock[1] <= cycles.count)
				{
					interrupt.nmiClock = CYCLE_MAX;
					return NMI_VECTOR;
				}

				interrupt.nmiClock = cycles.count + 1;
			}

			return IRQ_VECTOR;
		}

		void Cpu::DoISR(const uint vector)
		{
			NST_ASSERT( interrupt.irqClock == CYCLE_MAX );

			if (!jammed)
			{
				Push16( pc );
				Push8( flags.Pack() );
				flags.i = Flags::I;

				cycles.count += cycles.clock[INT_CYCLES-1];
				pc = map.Peek16( vector == NMI_VECTOR ? NMI_VECTOR : FetchIRQISRVector() );

				apu.Clock();
			}
		}

		void Cpu::DoIRQ(const IrqLine line,const Cycle cycle)
		{
			interrupt.low |= line;

			if (!flags.i && interrupt.irqClock == CYCLE_MAX)
			{
				interrupt.irqClock = cycle + cycles.InterruptEdge();
				cycles.NextRound( interrupt.irqClock );
			}
		}

		void Cpu::DoNMI(const Cycle cycle)
		{
			if (interrupt.nmiClock == CYCLE_MAX)
			{
				interrupt.nmiClock = cycle + cycles.InterruptEdge();
				cycles.NextRound( interrupt.nmiClock );
			}
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// main
		////////////////////////////////////////////////////////////////////////////////////////

		void Cpu::ExecuteFrame(Sound::Output* sound)
		{
			NST_VERIFY( cycles.count < cycles.frame );

			apu.BeginFrame( sound );

			Clock();

			switch (hooks.Size())
			{
				case 0:  Run0(); break;
				case 1:  Run1(); break;
				default: Run2(); break;
			}
		}

		void Cpu::EndFrame()
		{
			apu.EndFrame();

			for (const Hook *hook = hooks.Ptr(), *const end = hook+hooks.Size(); hook != end; ++hook)
				hook->Execute();

			NST_ASSERT( cycles.count >= cycles.frame && interrupt.nmiClock >= cycles.frame );

			cycles.count -= cycles.frame;
			ticks += cycles.frame;

			if (interrupt.nmiClock != CYCLE_MAX)
				interrupt.nmiClock -= cycles.frame;

			if (interrupt.irqClock != CYCLE_MAX)
				interrupt.irqClock = (interrupt.irqClock > cycles.frame ? interrupt.irqClock - cycles.frame : 0);
		}

		void Cpu::Clock()
		{
			Cycle clock = apu.Clock();

			if (clock > cycles.frame)
				clock = cycles.frame;

			if (cycles.count < interrupt.nmiClock)
			{
				if (clock > interrupt.nmiClock)
					clock = interrupt.nmiClock;

				if (cycles.count < interrupt.irqClock)
				{
					if (clock > interrupt.irqClock)
						clock = interrupt.irqClock;
				}
				else
				{
					interrupt.irqClock = CYCLE_MAX;

					DoISR( IRQ_VECTOR );
				}
			}
			else
			{
				NST_VERIFY( interrupt.irqClock == CYCLE_MAX );

				interrupt.nmiClock = CYCLE_MAX;
				interrupt.irqClock = CYCLE_MAX;

				DoISR( NMI_VECTOR );
			}

			cycles.round = clock;
		}

		inline void Cpu::ExecuteOp()
		{
			cycles.offset = cycles.count;
			(*this.*opcodes[opcode=FetchPc8()])();
		}

		void Cpu::Run0()
		{
			do
			{
				do
				{
					ExecuteOp();
				}
				while (cycles.count < cycles.round);

				Clock();
			}
			while (cycles.count < cycles.frame);
		}

		void Cpu::Run1()
		{
			const Hook hook( *hooks.Ptr() );

			do
			{
				do
				{
					ExecuteOp();
					hook.Execute();
				}
				while (cycles.count < cycles.round);

				Clock();
			}
			while (cycles.count < cycles.frame);
		}

		void Cpu::Run2()
		{
			const Hook* const first = hooks.Ptr();
			const Hook* const last = first + (hooks.Size() - 1);

			do
			{
				do
				{
					ExecuteOp();

					const Hook* NST_RESTRICT hook = first;

					hook->Execute();

					do
					{
						(++hook)->Execute();
					}
					while (hook != last);
				}
				while (cycles.count < cycles.round);

				Clock();
			}
			while (cycles.count < cycles.frame);
		}

		uint Cpu::Peek(const uint address) const
		{
			return map.Peek8( address );
		}

		void Cpu::Poke(const uint address,const uint data) const
		{
			return map.Poke8( address, data );
		}

		////////////////////////////////////////////////////////////////////////////////////////
		// opcodes
		////////////////////////////////////////////////////////////////////////////////////////

		#define StoreZpgX(a_,d_) StoreZpg(a_,d_)
		#define StoreZpgY(a_,d_) StoreZpg(a_,d_)
		#define StoreAbs(a_,d_)  StoreMem(a_,d_)
		#define StoreAbsX(a_,d_) StoreMem(a_,d_)
		#define StoreAbsY(a_,d_) StoreMem(a_,d_)
		#define StoreIndX(a_,d_) StoreMem(a_,d_)
		#define StoreIndY(a_,d_) StoreMem(a_,d_)

		#define NES_I____(instr_,hex_)                \
                                                      \
		void Cpu::op##hex_()                          \
		{                                             \
			instr_();                                 \
		}

		#define NES____C_(nop_,ticks_,hex_)           \
                                                      \
		void Cpu::op##hex_()                          \
		{                                             \
			cycles.count += cycles.clock[ticks_ - 1]; \
		}

		#define NES_IR___(instr_,addr_,hex_)          \
                                                      \
		void Cpu::op##hex_()                          \
		{                                             \
			instr_( addr_##_R() );                    \
		}

		#define NES_I_W__(instr_,addr_,hex_)          \
                                                      \
		void Cpu::op##hex_()                          \
		{                                             \
			const uint dst = addr_##_W();             \
			Store##addr_( dst, instr_() );            \
		}

		#define NES_IRW__(instr_,addr_,hex_)          \
                                                      \
		void Cpu::op##hex_()                          \
		{                                             \
			uint data;                                \
			const uint dst = addr_##_RW( data );      \
			Store##addr_( dst, instr_(data) );        \
		}

		#define NES_IRA__(instr_,hex_)                \
                                                      \
		void Cpu::op##hex_()                          \
		{                                             \
			cycles.count += cycles.clock[1];          \
			a = instr_( a );                          \
		}

		#define NES_I_W_A(instr_,addr_,hex_)          \
                                                      \
		void Cpu::op##hex_()                          \
		{                                             \
			const uint dst = addr_##_W();             \
			Store##addr_( dst, instr_(dst) );         \
		}

		// Unofficial Opcodes SHX/SHY are edge cases
		#define NES_I_W_U(instr_,hex_)                \
                                                      \
		void Cpu::op##hex_()                          \
		{                                             \
			const uint dst = FetchPc16();             \
			cycles.count += cycles.clock[3];          \
			instr_(dst);                              \
		}

		#define NES_IP_C_(instr_,ops_,ticks_,hex_)    \
                                                      \
		void Cpu::op##hex_()                          \
		{                                             \
			pc += ops_;                               \
			cycles.count += cycles.clock[ticks_ - 1]; \
			instr_();                                 \
		}

		// param 1 = instruction
		// param 2 = addressing mode
		// param 3 = cycles
		// param 4 = opcode

		NES_IR___( Adc, Imm,      0x69 )
		NES_IR___( Adc, Zpg,      0x65 )
		NES_IR___( Adc, ZpgX,     0x75 )
		NES_IR___( Adc, Abs,      0x6D )
		NES_IR___( Adc, AbsX,     0x7D )
		NES_IR___( Adc, AbsY,     0x79 )
		NES_IR___( Adc, IndX,     0x61 )
		NES_IR___( Adc, IndY,     0x71 )
		NES_IR___( And, Imm,      0x29 )
		NES_IR___( And, Zpg,      0x25 )
		NES_IR___( And, ZpgX,     0x35 )
		NES_IR___( And, Abs,      0x2D )
		NES_IR___( And, AbsX,     0x3D )
		NES_IR___( And, AbsY,     0x39 )
		NES_IR___( And, IndX,     0x21 )
		NES_IR___( And, IndY,     0x31 )
		NES_IRA__( Asl,           0x0A )
		NES_IRW__( Asl, Zpg,      0x06 )
		NES_IRW__( Asl, ZpgX,     0x16 )
		NES_IRW__( Asl, Abs,      0x0E )
		NES_IRW__( Asl, AbsX,     0x1E )
		NES_I____( Bcc,           0x90 )
		NES_I____( Bcs,           0xB0 )
		NES_I____( Beq,           0xF0 )
		NES_IR___( Bit, Zpg,      0x24 )
		NES_IR___( Bit, Abs,      0x2C )
		NES_I____( Bmi,           0x30 )
		NES_I____( Bne,           0xD0 )
		NES_I____( Bpl,           0x10 )
		NES_I____( Bvc,           0x50 )
		NES_I____( Bvs,           0x70 )
		NES_I____( Clc,           0x18 )
		NES_I____( Cld,           0xD8 )
		NES_I____( Cli,           0x58 )
		NES_I____( Clv,           0xB8 )
		NES_IR___( Cmp, Imm,      0xC9 )
		NES_IR___( Cmp, Zpg,      0xC5 )
		NES_IR___( Cmp, ZpgX,     0xD5 )
		NES_IR___( Cmp, Abs,      0xCD )
		NES_IR___( Cmp, AbsX,     0xDD )
		NES_IR___( Cmp, AbsY,     0xD9 )
		NES_IR___( Cmp, IndX,     0xC1 )
		NES_IR___( Cmp, IndY,     0xD1 )
		NES_IR___( Cpx, Imm,      0xE0 )
		NES_IR___( Cpx, Zpg,      0xE4 )
		NES_IR___( Cpx, Abs,      0xEC )
		NES_IR___( Cpy, Imm,      0xC0 )
		NES_IR___( Cpy, Zpg,      0xC4 )
		NES_IR___( Cpy, Abs,      0xCC )
		NES_IRW__( Dec, Zpg,      0xC6 )
		NES_IRW__( Dec, ZpgX,     0xD6 )
		NES_IRW__( Dec, Abs,      0xCE )
		NES_IRW__( Dec, AbsX,     0xDE )
		NES_I____( Dex,           0xCA )
		NES_I____( Dey,           0x88 )
		NES_IR___( Eor, Imm,      0x49 )
		NES_IR___( Eor, Zpg,      0x45 )
		NES_IR___( Eor, ZpgX,     0x55 )
		NES_IR___( Eor, Abs,      0x4D )
		NES_IR___( Eor, AbsX,     0x5D )
		NES_IR___( Eor, AbsY,     0x59 )
		NES_IR___( Eor, IndX,     0x41 )
		NES_IR___( Eor, IndY,     0x51 )
		NES_IRW__( Inc, Zpg,      0xE6 )
		NES_IRW__( Inc, ZpgX,     0xF6 )
		NES_IRW__( Inc, Abs,      0xEE )
		NES_IRW__( Inc, AbsX,     0xFE )
		NES_I____( Inx,           0xE8 )
		NES_I____( Iny,           0xC8 )
		NES_I____( JmpAbs,        0x4C )
		NES_I____( JmpInd,        0x6C )
		NES_I____( Jsr,           0x20 )
		NES_IR___( Lda, Imm,      0xA9 )
		NES_IR___( Lda, Zpg,      0xA5 )
		NES_IR___( Lda, ZpgX,     0xB5 )
		NES_IR___( Lda, Abs,      0xAD )
		NES_IR___( Lda, AbsX,     0xBD )
		NES_IR___( Lda, AbsY,     0xB9 )
		NES_IR___( Lda, IndX,     0xA1 )
		NES_IR___( Lda, IndY,     0xB1 )
		NES_IR___( Ldx, Imm,      0xA2 )
		NES_IR___( Ldx, Zpg,      0xA6 )
		NES_IR___( Ldx, ZpgY,     0xB6 )
		NES_IR___( Ldx, Abs,      0xAE )
		NES_IR___( Ldx, AbsY,     0xBE )
		NES_IR___( Ldy, Imm,      0xA0 )
		NES_IR___( Ldy, Zpg,      0xA4 )
		NES_IR___( Ldy, ZpgX,     0xB4 )
		NES_IR___( Ldy, Abs,      0xAC )
		NES_IR___( Ldy, AbsX,     0xBC )
		NES_IRA__( Lsr,           0x4A )
		NES_IRW__( Lsr, Zpg,      0x46 )
		NES_IRW__( Lsr, ZpgX,     0x56 )
		NES_IRW__( Lsr, Abs,      0x4E )
		NES_IRW__( Lsr, AbsX,     0x5E )
		NES____C_( Nop,        2, 0x1A )
		NES____C_( Nop,        2, 0x3A )
		NES____C_( Nop,        2, 0x5A )
		NES____C_( Nop,        2, 0x7A )
		NES____C_( Nop,        2, 0xDA )
		NES____C_( Nop,        2, 0xEA )
		NES____C_( Nop,        2, 0xFA )
		NES_IR___( Ora, Imm,      0x09 )
		NES_IR___( Ora, Zpg,      0x05 )
		NES_IR___( Ora, ZpgX,     0x15 )
		NES_IR___( Ora, Abs,      0x0D )
		NES_IR___( Ora, AbsX,     0x1D )
		NES_IR___( Ora, AbsY,     0x19 )
		NES_IR___( Ora, IndX,     0x01 )
		NES_IR___( Ora, IndY,     0x11 )
		NES_I____( Pha,           0x48 )
		NES_I____( Php,           0x08 )
		NES_I____( Pla,           0x68 )
		NES_I____( Plp,           0x28 )
		NES_IRA__( Rol,           0x2A )
		NES_IRW__( Rol, Zpg,      0x26 )
		NES_IRW__( Rol, ZpgX,     0x36 )
		NES_IRW__( Rol, Abs,      0x2E )
		NES_IRW__( Rol, AbsX,     0x3E )
		NES_IRA__( Ror,           0x6A )
		NES_IRW__( Ror, Zpg,      0x66 )
		NES_IRW__( Ror, ZpgX,     0x76 )
		NES_IRW__( Ror, Abs,      0x6E )
		NES_IRW__( Ror, AbsX,     0x7E )
		NES_I____( Rti,           0x40 )
		NES_I____( Rts,           0x60 )
		NES_IR___( Sbc, Imm,      0xE9 )
		NES_IR___( Sbc, Imm,      0xEB )
		NES_IR___( Sbc, Zpg,      0xE5 )
		NES_IR___( Sbc, ZpgX,     0xF5 )
		NES_IR___( Sbc, Abs,      0xED )
		NES_IR___( Sbc, AbsX,     0xFD )
		NES_IR___( Sbc, AbsY,     0xF9 )
		NES_IR___( Sbc, IndX,     0xE1 )
		NES_IR___( Sbc, IndY,     0xF1 )
		NES_I____( Sec,           0x38 )
		NES_I____( Sed,           0xF8 )
		NES_I____( Sei,           0x78 )
		NES_I_W__( Sta, Zpg,      0x85 )
		NES_I_W__( Sta, ZpgX,     0x95 )
		NES_I_W__( Sta, Abs,      0x8D )
		NES_I_W__( Sta, AbsX,     0x9D )
		NES_I_W__( Sta, AbsY,     0x99 )
		NES_I_W__( Sta, IndX,     0x81 )
		NES_I_W__( Sta, IndY,     0x91 )
		NES_I_W__( Stx, Zpg,      0x86 )
		NES_I_W__( Stx, ZpgY,     0x96 )
		NES_I_W__( Stx, Abs,      0x8E )
		NES_I_W__( Sty, Zpg,      0x84 )
		NES_I_W__( Sty, ZpgX,     0x94 )
		NES_I_W__( Sty, Abs,      0x8C )
		NES_I____( Tax,           0xAA )
		NES_I____( Tay,           0xA8 )
		NES_I____( Tsx,           0xBA )
		NES_I____( Txa,           0x8A )
		NES_I____( Txs,           0x9A )
		NES_I____( Tya,           0x98 )

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		// rarely executed

		NES_I____( Brk,           0x00 )
		NES_I____( Jam,           0x02 )
		NES_I____( Jam,           0x12 )
		NES_I____( Jam,           0x22 )
		NES_I____( Jam,           0x32 )
		NES_I____( Jam,           0x42 )
		NES_I____( Jam,           0x52 )
		NES_I____( Jam,           0x62 )
		NES_I____( Jam,           0x72 )
		NES_I____( Jam,           0x92 )
		NES_I____( Jam,           0xB2 )
		NES_I____( Jam,           0xD2 )
		NES_I____( Jam,           0xF2 )

		// unofficial ops

		NES_IR___( Anc, Imm,      0x0B )
		NES_IR___( Anc, Imm,      0x2B )
		NES_IR___( Ane, Imm,      0x8B )
		NES_IR___( Arr, Imm,      0x6B )
		NES_IR___( Asr, Imm,      0x4B )
		NES_IRW__( Dcp, Zpg,      0xC7 )
		NES_IRW__( Dcp, ZpgX,     0xD7 )
		NES_IRW__( Dcp, IndX,     0xC3 )
		NES_IRW__( Dcp, IndY,     0xD3 )
		NES_IRW__( Dcp, Abs,      0xCF )
		NES_IRW__( Dcp, AbsX,     0xDF )
		NES_IRW__( Dcp, AbsY,     0xDB )
		NES_IP_C_( Dop, 1,     2, 0x80 )
		NES_IP_C_( Dop, 1,     2, 0x82 )
		NES_IP_C_( Dop, 1,     2, 0x89 )
		NES_IP_C_( Dop, 1,     2, 0xC2 )
		NES_IP_C_( Dop, 1,     2, 0xE2 )
		NES_IP_C_( Dop, 1,     3, 0x04 )
		NES_IP_C_( Dop, 1,     3, 0x44 )
		NES_IP_C_( Dop, 1,     3, 0x64 )
		NES_IP_C_( Dop, 1,     4, 0x14 )
		NES_IP_C_( Dop, 1,     4, 0x34 )
		NES_IP_C_( Dop, 1,     4, 0x54 )
		NES_IP_C_( Dop, 1,     4, 0x74 )
		NES_IP_C_( Dop, 1,     4, 0xD4 )
		NES_IP_C_( Dop, 1,     4, 0xF4 )
		NES_IRW__( Isb, Zpg,      0xE7 )
		NES_IRW__( Isb, ZpgX,     0xF7 )
		NES_IRW__( Isb, Abs,      0xEF )
		NES_IRW__( Isb, AbsX,     0xFF )
		NES_IRW__( Isb, AbsY,     0xFB )
		NES_IRW__( Isb, IndX,     0xE3 )
		NES_IRW__( Isb, IndY,     0xF3 )
		NES_IR___( Las, AbsY,     0xBB )
		NES_IR___( Lax, Zpg,      0xA7 )
		NES_IR___( Lax, ZpgY,     0xB7 )
		NES_IR___( Lax, Abs,      0xAF )
		NES_IR___( Lax, AbsY,     0xBF )
		NES_IR___( Lax, IndX,     0xA3 )
		NES_IR___( Lax, IndY,     0xB3 )
		NES_IR___( Lxa, Imm,      0xAB )
		NES_IRW__( Rla, Zpg,      0x27 )
		NES_IRW__( Rla, ZpgX,     0x37 )
		NES_IRW__( Rla, Abs,      0x2F )
		NES_IRW__( Rla, AbsX,     0x3F )
		NES_IRW__( Rla, AbsY,     0x3B )
		NES_IRW__( Rla, IndX,     0x23 )
		NES_IRW__( Rla, IndY,     0x33 )
		NES_IRW__( Rra, Zpg,      0x67 )
		NES_IRW__( Rra, ZpgX,     0x77 )
		NES_IRW__( Rra, Abs,      0x6F )
		NES_IRW__( Rra, AbsX,     0x7F )
		NES_IRW__( Rra, AbsY,     0x7B )
		NES_IRW__( Rra, IndX,     0x63 )
		NES_IRW__( Rra, IndY,     0x73 )
		NES_I_W__( Sax, Zpg,      0x87 )
		NES_I_W__( Sax, ZpgY,     0x97 )
		NES_I_W__( Sax, Abs,      0x8F )
		NES_I_W__( Sax, IndX,     0x83 )
		NES_IR___( Sbx, Imm,      0xCB )
		NES_I_W_A( Sha, AbsY,     0x9F )
		NES_I_W_A( Sha, IndY,     0x93 )
		NES_I_W_A( Shs, AbsY,     0x9B )
		NES_I_W_U( Shx,           0x9E ) // Edge case: AbsY done internally
		NES_I_W_U( Shy,           0x9C ) // Edge case: AbsX done internally
		NES_IRW__( Slo, Zpg,      0x07 )
		NES_IRW__( Slo, ZpgX,     0x17 )
		NES_IRW__( Slo, Abs,      0x0F )
		NES_IRW__( Slo, AbsX,     0x1F )
		NES_IRW__( Slo, AbsY,     0x1B )
		NES_IRW__( Slo, IndX,     0x03 )
		NES_IRW__( Slo, IndY,     0x13 )
		NES_IRW__( Sre, Zpg,      0x47 )
		NES_IRW__( Sre, ZpgX,     0x57 )
		NES_IRW__( Sre, Abs,      0x4F )
		NES_IRW__( Sre, AbsX,     0x5F )
		NES_IRW__( Sre, AbsY,     0x5B )
		NES_IRW__( Sre, IndX,     0x43 )
		NES_IRW__( Sre, IndY,     0x53 )
		NES_IP_C_( Top, 2,     4, 0x0C )
		NES_IR___( Top, AbsX,     0x1C )
		NES_IR___( Top, AbsX,     0x3C )
		NES_IR___( Top, AbsX,     0x5C )
		NES_IR___( Top, AbsX,     0x7C )
		NES_IR___( Top, AbsX,     0xDC )
		NES_IR___( Top, AbsX,     0xFC )

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		#undef StoreZpgX
		#undef StoreZpgY
		#undef StoreAbs
		#undef StoreAbsX
		#undef StoreAbsY
		#undef StoreIndX
		#undef StoreIndY

		#undef NES_I____
		#undef NES____C_
		#undef NES_IR___
		#undef NES_I_W__
		#undef NES_IRW__
		#undef NES_IRA__
		#undef NES_I_W_A
		#undef NES_IP_C_
	}
}
