'SdFatSoftSpi' does not name a type

const uint8_t SOFT_MOSI_PIN = SOFTSD_MOSI_PIN;
const uint8_t SOFT_MISO_PIN = SOFTSD_MISO_PIN;
const uint8_t SOFT_SCK_PIN = SOFTSD_SCK_PIN;
SdFatSoftSpi<SOFT_MISO_PIN, SOFT_MOSI_PIN, SOFT_SCK_PIN>  SD;

I'm unsure about how to declare sdfatsoftspi?

Cheers

Show all the code, not snippets you think are relevant.
Use code tags.
Read instructions at the top of the forum.
Same goes for any errors/warnings....use copy for forum.

do you have all the code

Here is the code

//*                                                                              *
//*   NESCAT by Nathalis                                                         *
//*   prototype version: 0.5 (c)2020                                             *
//*   Enhanced version Nintendo Emulator for ESP32                               *
//*                                                                              *
//*   Requirements: ESP32 WEMOS D1 MINI, SD7789 LCD, MICROSD CARD slot,          *
//*   PCM5102 I2S AUDIO MODULE, OTHER PARTS...                                   *
//*                                                                              *
//*   Only for personal & educational use!                                       *
//*   (GPL-3.0 License)                                                          *
//*                                                                              *
//********************************************************************************
//*                                                                              *
//*   features:                                                                  *
//*   - MicroSD card support                                                     *
//*   - 240*240 1.3" LCD SD7789 display                                          *
//*   - Composite TV OUT Video PAL                                               *
//*   - I2S AUDIO support PCM5102 module                                         *
//*   - PS2 (USB) KEYBOARD support (wireless not work)                           *
//*   - huge NES ROMs up to 512kB (read from FLASH)                              *
//*   - PS4 DualShock BlueTooth Gamepad (experimental)                           *
//*                                                                              *
//*   to do:                                                                     *
//*   - ESP32-WROVER PSRAM module support                                        *
//*   - battery install                                                          *
//*   - ESP32 MP3-Player support                                                 *
//*                                                                              *
//*                                                                              *
//********************************************************************************

#define NES_FOLDER ("/NES/")

#define BLUETOOTH_ENABLED
#define KEYBOARD_ENABLED
#define LCD_ENABLED true
#define SOUND_ENABLED true
#define COMPOSITE_VIDEO_ENABLED

#define DEBUG true //Serial debugging enable.
#define DEBUGEXTRA false //Extra Serial debugging enable.

//================================================================================

///!!! do not forget 1KOHM resistors
#define KEYBOARD_DATA 4  /// ---[ 1K ]--- // -D
#define KEYBOARD_CLK 0  /// ---[ 1K ]--- // +D
//COMPOSITE_VIDEO: - //DAC_GPIO25_CHANNEL or DAC_GPIO26_CHANNEL
#define VIDEO_OUT (DAC_GPIO26_CHANNEL)

//micro_SD_Card:
#define SOFTSD_MOSI_PIN 12
#define SOFTSD_MISO_PIN 13
#define SOFTSD_SCK_PIN 14
#define SD_CS_PIN 22

#define PIN_UP     2    //IO2
#define PIN_DOWN   15   //IO2
#define PIN_LEFT   39   //VN
#define PIN_RIGHT  34   //IO34
#define PIN_A      21   //IO21
#define PIN_B      26   //IO26
#define PIN_START  35   //IO36
#define PIN_SELECT 36   //VN

//LCD_ST7789:
#define TFT_CS   -1  // define chip select pin
#define TFT_DC    5  // define data/command pin
#define TFT_RST  19
#define TFT_MOSI 23  // Data out (SDA) //better not change
#define TFT_SCLK 18  // Clock out (SCL) //better not change

//AUDIO_i2S:
#define I2S_BCK_IO 27 //BCK
#define I2S_WS_IO  32 //LCK
#define I2S_DO_IO  25 //DIN
#define I2S_DI_IO  -1

//================================================================================

//********************************************************************************
//MAIN LIBRARIES:

#include <esp_task_wdt.h>
#include "esp_types.h"
#include "esp_heap_caps.h"
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "esp_err.h"
#include "soc/gpio_reg.h"
#include "soc/rtc.h"
#include "soc/soc.h"
#include "soc/i2s_struct.h"
#include "soc/i2s_reg.h"
#include "soc/ledc_struct.h"
#include "soc/rtc_io_reg.h"
#include "soc/io_mux_reg.h"
#include "rom/gpio.h"
#include "rom/lldesc.h"
#include "driver/periph_ctrl.h"
#include "driver/dac.h"
#include "driver/gpio.h"
//********************************************************************************

//--------------------------------------------------------------------------------
//SPI FLASH MEMORY ACCESS:
#include "esp_spi_flash.h"

#define SPI_FLASH_ADDRESS 0x00300000 //0x00300000 working (empty flash area)
#define SPI_FLASH_SECTOR_SIZE 0x1000 //4kB = better not change

uint32_t FILE_ROM_SIZE = 0;
uint32_t FLASH_ROM_SIZE = 0;

//constant data pointer for direct access
const void *ROM;

spi_flash_mmap_handle_t handle1;

uint8_t flashdata[4096] = {0}; //4kB buffer

//--------------------------------------------------------------------------------
//DRAM MEMORY ACCESS:
//--------------------------------------------------------------------------------

char* ROMFILENAME; //NES load File name
unsigned char *rom = 0; //Actual ROM pointer

//********************************************************************************
//Allocated MEMORY variables:

uint8_t* SCREENMEMORY[256];  //256*256 bytes
uint16_t SCREENBUFFER[256];  //512 bytes

//********************************************************************************
//********************************************************************************
//PALETTES:

const uint32_t nes_32bit[64] = { //4colors Green-Yellow-Red-Blue
  0x2D253469, 0x000000D9, 0x000000A1, 0x0F111CA1, 0x001F3F70, 0x0024491C, 0x06264900, 0x061F3B00,
  0x12162300, 0x2D0B0000, 0x27090000, 0x21080000, 0x1806004D, 0x14141414, 0x14141414, 0x14141414,
  0x453950A1, 0x2D0B00D9, 0x210800D9, 0x181C2DD9, 0x002F5EAF, 0x0031624D, 0x153B6C00, 0x2139620E,
  0x2D304900, 0x45110000, 0x3F0F0000, 0x3F0F0038, 0x330C0077, 0x14141414, 0x14141414, 0x14141414,
  0x5D4D6CD9, 0x451D18D9, 0x33232DD9, 0x2D2C42D9, 0x2D416CD9, 0x213E6C85, 0x2D416C4D, 0x3C456C38,
  0x45476C00, 0x5D3F5015, 0x51272646, 0x5D2A2685, 0x571500BD, 0x2D253469, 0x14141414, 0x14141414,
  0x5D4D6CD9, 0x543846D9, 0x453950D9, 0x45405ED9, 0x45476CD9, 0x3C456CA8, 0x4E48699A, 0x544B6C93,
  0x514A6C69, 0x5D465E69, 0x5D3F50A1, 0x5D3F50BD, 0x5D1700D9, 0x514A6CD9, 0x14141414, 0x14141414,
};

const uint16_t nes_16bit[64] = {
  0x7BEF, 0x001F, 0x0017, 0x4157, 0x9010, 0xA804, 0xA880, 0x88A0,
  0x5180, 0x03C0, 0x0340, 0x02C0, 0x020B, 0x0000, 0x0000, 0x0000,
  0xBDF7, 0x03DF, 0x02DF, 0x6A3F, 0xD819, 0xE00B, 0xF9C0, 0xE2E2,
  0xABE0, 0x05C0, 0x0540, 0x0548, 0x0451, 0x0000, 0x0000, 0x0000,
  0xFFDF, 0x3DFF, 0x6C5F, 0x9BDF, 0xFBDF, 0xFAD3, 0xFBCB, 0xFD08,
  0xFDC0, 0xBFC3, 0x5ECA, 0x5FD3, 0x075B, 0x7BCF, 0x0000, 0x0000,
  0xFFFF, 0xA73F, 0xBDDF, 0xDDDF, 0xFDDF, 0xFD38, 0xF696, 0xFF15,
  0xFECF, 0xDFCF, 0xBFD7, 0xBFDB, 0x07FF, 0xFEDF, 0x0000, 0x0000,
};
//********************************************************************************

//********************************************************************************

//#ifdef BLUETOOTH_ENABLED
//Bluetooth_PS4_DualShock_support_for_ESP32:          
//#include "P:\1a Youtube videos tercero/ESP32 Nintendo/NesCat-main/src/hid_server/hid_server.h"
//#endif

//COMPOSITE_VIDEO_OUT
#ifdef COMPOSITE_VIDEO_ENABLED
#include "C:\Users\touwh\OneDrive\Documents\Year 3\Projects\NesCat-main\src\compositevideo\video.h"
#endif

//********************************************************************************

//********************************************************************************
//LIBRARIES:

//LCD_ST7789:
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>

//micro_SD_Card:
#include <SdFat.h>
//#define ENABLE_SOFTWARE_SPI_CLASS 1  // Must be set in SdFat/SdFatConfig.h !!!

//AUDIO_i2S:
#ifdef SOUND_ENABLED
#include "driver/i2s.h"
#endif
//********************************************************************************
//CONFIGS:
//--------------------------------------------------------------------------------

//LCD_ST7789
#define ST7789_DRIVER     // Configure all registers
#define TFT_WIDTH  240
#define TFT_HEIGHT 240
#define LOAD_GLCD   // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH
#define LOAD_FONT2  // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters
#define LOAD_FONT4  // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

//micro_SD_Card
const uint8_t SOFT_MOSI_PIN = SOFTSD_MOSI_PIN;
const uint8_t SOFT_MISO_PIN = SOFTSD_MISO_PIN;
const uint8_t SOFT_SCK_PIN = SOFTSD_SCK_PIN;
SdFatSoftSpi<SOFT_MISO_PIN, SOFT_MOSI_PIN, SOFT_SCK_PIN>  SD;

File fp;
SdFile dirFile; //for root directory file listing
SdFile file;
//********************************************************************************
//********************************************************************************
//********************************************************************************

//--------------------------------------------------------------------------------
///TYPEDEFS:

// Define this if running on little-endian (x86) systems
#define  HOST_LITTLE_ENDIAN //!!! important for Arduino
#define  ZERO_LENGTH 0

// quell stupid compiler warnings
#define  UNUSED(x)   ((x) = (x))

// 16Bit Color set
#define BLACK           0x0000
#define RED             0xF800
#define GREEN           0x07E0
#define BLUE            0x102E
#define CYAN            0x07FF
#define MAGENTA         0xF81F
#define YELLOW          0xFFE0
#define WHITE           0xFFFF
//--------------------------------------------------------------------------------
//================================================================================
//AUDIO_SETUP
#define DEFAULT_SAMPLERATE 24000
#define DEFAULT_FRAGSIZE 60 //max.256, default 240

//VIDEO_SETUP
// Visible (NTSC) screen height
#ifndef NES_VISIBLE_HEIGHT
#define  NES_VISIBLE_HEIGHT   240
#endif // !NES_VISIBLE_HEIGHT 
#define  NES_SCREEN_WIDTH     256
#define  NES_SCREEN_HEIGHT    240

#define PAL

// NTSC = 60Hz, PAL = 50Hz
#ifdef PAL
#define  NES_REFRESH_RATE     50
#else
#define  NES_REFRESH_RATE     60
#endif

#define  MAX_MEM_HANDLERS     32

#define DEFAULT_WIDTH NES_SCREEN_WIDTH
#define DEFAULT_HEIGHT NES_VISIBLE_HEIGHT

#define  NES_CLOCK_DIVIDER    12  //default: 12
//#define  NES_MASTER_CLOCK     21477272.727272727272
#define  NES_MASTER_CLOCK     (236250000 / 11)
#define  NES_SCANLINE_CYCLES  (1364.0 / NES_CLOCK_DIVIDER)
#define  NES_FIQ_PERIOD       (NES_MASTER_CLOCK / NES_CLOCK_DIVIDER / 60)

#define  NES_RAMSIZE          0x800

#define  NES6502_NUMBANKS  16
#define  NES6502_BANKSHIFT 12
#define  NES6502_BANKSIZE  (0x10000 / NES6502_NUMBANKS)
#define  NES6502_BANKMASK  (NES6502_BANKSIZE - 1)


//********************************************************************************
//********************************************************************************
uint8_t NES_POWER = 1;
//********************************************************************************
QueueHandle_t vidQueue;
//********************************************************************************

//********************************************************************************
uint8_t JOY_UP = 0;
uint8_t JOY_DOWN = 0;
uint8_t JOY_LEFT = 0;
uint8_t JOY_RIGHT = 0;
uint8_t JOY_CROSS = 0;
uint8_t JOY_SQUARE = 0;
uint8_t JOY_CIRCLE = 0;
uint8_t JOY_TRIANGLE = 0;
uint8_t JOY_SHARE = 0; //(START)
uint8_t JOY_OPTIONS = 0; //(SELECT)
//********************************************************************************

//********************************************************************************
uint8_t scancode = 0;
boolean keyup = false;
byte keymap[256];

IRAM_ATTR void USB_KEYBOARD() {
#ifdef KEYBOARD_ENABLED
  if (keymap[0x75] == 0) {
    JOY_UP = 1;
  }
  else JOY_UP = 0;
  if (keymap[0x72] == 0) {
    JOY_DOWN = 1;
  }
  else JOY_DOWN = 0;
  if (keymap[0x6B] == 0) {
    JOY_LEFT = 1;
  }
  else JOY_LEFT = 0;
  if (keymap[0x74] == 0) {
    JOY_RIGHT = 1;
  }
  else JOY_RIGHT = 0;

  if (keymap[0x11] == 0 || keymap[0x1A] == 0) JOY_CROSS = 1; //ALT or X
  else JOY_CROSS = 0;
  if (keymap[0x14] == 0 || keymap[0x22] == 0) JOY_SQUARE = 1; //CTRL or Z
  else JOY_SQUARE = 0;
  if (keymap[0x5A] == 0) JOY_SHARE = 1;
  else JOY_SHARE = 0;
  if (keymap[0x66] == 0) JOY_OPTIONS = 1;
  else JOY_OPTIONS = 0;

  if (keymap[0x76] == 0) {
    JOY_SHARE = 1;
    JOY_OPTIONS = 1;
  } else if (JOY_SHARE == 1 && JOY_OPTIONS == 1 ) {
    JOY_SHARE = 0;
    JOY_OPTIONS = 0;
  }

  if (DEBUGEXTRA) {
    if (JOY_UP) Serial.print("UP.");
    if (JOY_DOWN) Serial.print("DOWN.");
    if (JOY_LEFT) Serial.print("LEFT.");
    if (JOY_RIGHT) Serial.print("RIGHT.");
    if (JOY_SHARE) Serial.print("START.");
    if (JOY_OPTIONS) Serial.print("SELECT.");
    if (JOY_CROSS) Serial.print("A.");
    if (JOY_SQUARE) Serial.print("B.");
    Serial.println();
  }
#endif
}
//********************************************************************************

void IRAM_ATTR kb_interruptHandler(void) {
#ifdef KEYBOARD_ENABLED
  static uint8_t bitcount = 0;
  uint8_t val;

  int clock = digitalRead(KEYBOARD_CLK);
  if (clock == 0) {
    return;
  }

  val = digitalRead(KEYBOARD_DATA);
  if (DEBUGEXTRA) Serial.print(".");
  if (DEBUGEXTRA) Serial.print(val);

  bitcount++;

  if (bitcount > 1 && bitcount < 10) { //8bits
    scancode |= ((val & 1) << (bitcount - 2));
  }

  if (bitcount > 10) {
    if (keyup == true) {
      keymap[scancode] = 1;

      if (DEBUGEXTRA) {
        Serial.print(" {");
        Serial.print(scancode, HEX);
        Serial.print("} ");
      }
      keyup = false;
    } else {
      keymap[scancode] = 0;
    }

    if (scancode  == 0xF0  ) {
      keyup = true;
    } else {
      keyup = false;
    }

    if (DEBUGEXTRA) {
      Serial.print("[");
      Serial.print(scancode, HEX);
      Serial.println("]");
    }

    USB_KEYBOARD();

    bitcount = 0;
    scancode = 0;
  }
#endif
}

void kb_begin() {
#ifdef KEYBOARD_ENABLED
  pinMode(KEYBOARD_DATA, INPUT_PULLUP);
  pinMode(KEYBOARD_CLK, INPUT_PULLUP);
  digitalWrite(KEYBOARD_DATA, true);
  digitalWrite(KEYBOARD_CLK, true);
  attachInterrupt(digitalPinToInterrupt(KEYBOARD_CLK), kb_interruptHandler, RISING);
  memset(keymap, 1, sizeof(keymap));
#endif
}
//********************************************************************************

//********************************************************************************
uint8_t buf[64] = {0}; //JoyRead buffer...
int num_get = 0;
IRAM_ATTR static void PS4_JOY() {
#ifdef BLUETOOTH_ENABLED

  // update the bluetooth edr/hid stack
  ///hid_update();

  //read data from JoyStick
  //num_get = hid_get(buf, sizeof(buf) - 1); // called from emulation loop
//********************************************************************************

  if ((num_get != -1) && buf[0] == 161) { // If connected then read JoyStick

    uint8_t JOYBUTTON = buf[6];
    uint8_t JOYBUTTON2 = buf[7];
    ///Serial.println(JOYBUTTON);

    if ( JOYBUTTON % 8 == 0) {
      JOY_UP = 1;
    }
    else if ( JOYBUTTON % 8 == 1) {
      JOY_UP = 1;
      JOY_RIGHT = 1;
      JOY_DOWN = 0;
      JOY_LEFT = 0;
    }
    else if ( JOYBUTTON % 8 == 2) {
      JOY_RIGHT = 1;
      JOY_DOWN = 0;
      JOY_LEFT = 0;
      JOY_UP = 0;
    }
    else if ( JOYBUTTON % 8 == 3) {
      JOY_RIGHT = 1;
      JOY_DOWN = 1;
      JOY_LEFT = 0;
      JOY_UP = 0;
    }
    else if ( JOYBUTTON % 8 == 4) {
      JOY_DOWN = 1;
      JOY_LEFT = 0;
      JOY_RIGHT = 0;
      JOY_UP = 0;
    }
    else if ( JOYBUTTON % 8 == 5) {
      JOY_DOWN = 1;
      JOY_LEFT = 1;
      JOY_RIGHT = 0;
      JOY_UP = 0;
    }
    else if ( JOYBUTTON % 8 == 6) {
      JOY_LEFT = 1;
      JOY_UP = 0;
      JOY_DOWN = 0;
      JOY_RIGHT = 0;
    }
    else if ( JOYBUTTON % 8 == 7) {
      JOY_LEFT = 1;
      JOY_UP = 1;
      JOY_DOWN = 0;
      JOY_RIGHT = 0;
    }
    if (((JOYBUTTON >> 3) & 1) != 0) {
      JOY_UP = 0;
      JOY_DOWN = 0;
      JOY_LEFT = 0;
      JOY_RIGHT = 0;
    }

    if ( JOYBUTTON > 8 && ((JOYBUTTON >> 4) & 1) != 0) {
      JOY_SQUARE = 1;
    }
    else if (((JOYBUTTON >> 4) & 1) == 0) {
      JOY_SQUARE = 0;
    }
    if ( JOYBUTTON > 8 && ((JOYBUTTON >> 4) & 2) != 0) {
      JOY_CROSS = 1;
    }
    else if (((JOYBUTTON >> 4) & 2) == 0) {
      JOY_CROSS = 0;
    }
    if ( JOYBUTTON > 8 && ((JOYBUTTON >> 4) & 4) != 0) {
      JOY_CIRCLE = 1;
    }
    else if (((JOYBUTTON >> 4) & 4) == 0) {
      JOY_CIRCLE = 0;
    }
    if ( JOYBUTTON > 8 && ((JOYBUTTON >> 4) & 8) != 0) {
      JOY_TRIANGLE = 1;
    }
    else if (((JOYBUTTON >> 8) & 1) == 0) {
      JOY_TRIANGLE = 0;
    }

    if (JOYBUTTON2 == 0) {
      JOY_SHARE = 0;
      JOY_OPTIONS = 0;
    }
    if ( JOYBUTTON2 > 8 && ((JOYBUTTON2 >> 4) & 1) != 0) {
      JOY_SHARE = 1;
    }
    if ( JOYBUTTON2 > 8 && ((JOYBUTTON2 >> 4) & 2) != 0) {
      JOY_OPTIONS = 1;
    }
  }
  if (DEBUGEXTRA) {
    if (JOY_UP) Serial.print("UP.");
    if (JOY_DOWN) Serial.print("DOWN.");
    if (JOY_LEFT) Serial.print("LEFT.");
    if (JOY_RIGHT) Serial.print("RIGHT.");
    if (JOY_SHARE) Serial.print("START.");
    if (JOY_OPTIONS) Serial.print("SELECT.");
    if (JOY_CROSS) Serial.print("A.");
    if (JOY_SQUARE) Serial.print("B.");
    Serial.println();
  }

#endif
}
//********************************************************************************
/* read counters */
int pad0_readcount, pad1_readcount, ppad_readcount, ark_readcount;

void input_strobe(void) {
  pad0_readcount = 0;
  pad1_readcount = 0;
  ppad_readcount = 0;
  ark_readcount = 0;
}

uint8_t get_pad0(void) {
  uint8_t value;
  ///  value = (uint8_t) retrieve_type(INP_JOYPAD0);
  value = 0;

  //  12, 33, 9, 10pin not usable

  if (digitalRead(PIN_A) == 1) value |= 1; //A
  if (digitalRead(PIN_B) == 1) value |= 2; //B
  if (digitalRead(PIN_SELECT) == 1) value |= 4; //SELECT
  if (digitalRead(PIN_START) == 1) value |= 8; //START
  if (digitalRead(PIN_UP) == 1) value |= 16; //UP
  if (digitalRead(PIN_DOWN) == 1) value |= 32; //DOWN
  if (digitalRead(PIN_LEFT) == 1) value |= 64; //LEFT
  if (digitalRead(PIN_RIGHT) == 1) value |= 128; //RIGHT

  if (digitalRead(PIN_SELECT) == 1 && digitalRead(PIN_START) == 1) NES_POWER = 0; //EXIT

  //EJECT EMULATOR 
  if (JOY_SHARE == 1 && JOY_OPTIONS == 1) {
    JOY_SHARE = 0;
    JOY_OPTIONS = 0;
    NES_POWER = 0;
  }

  if (JOY_SHARE == 1) value |= 8; //START
  if (JOY_OPTIONS == 1) value |= 4; //SELECT

  if (JOY_UP == 1) value |= 16; //UP
  if (JOY_DOWN == 1) value |= 32; //DOWN
  if (JOY_LEFT == 1) value |= 64; //LEFT
  if (JOY_RIGHT == 1) value |= 128; //RIGHT
  if (JOY_CROSS == 1) value |= 1; //A
  if (JOY_SQUARE == 1) value |= 2; //B
  ///  if (JOY_CIRCLE==1) Serial.println("JOY_CIRCLE");
  ///  if (JOY_TRIANGLE==1) Serial.println("JOY_TRIANGLE");

  // mask out left/right simultaneous keypresses
  if ((value & JOY_UP) && (value & JOY_DOWN)) value &= ~(JOY_UP | JOY_DOWN);
  if ((value & JOY_LEFT) && (value & JOY_RIGHT)) value &= ~(JOY_LEFT | JOY_RIGHT);
  // return (0x40 | value) due to bus conflicts
  ///return (0x40 | ((value >> pad0_readcount++) & 1));
  return (((value >> pad0_readcount++) & 1));
}
//********************************************************************************

//********************************************************************************
//BUFFER TEXT DRAW FUNCTIONS
#include "C:\Users\touwh\OneDrive\Documents\Year 3\Projects\NesCat-main\Libraries\Fonts\Retro8x16.c" //used font...

void screenmemory_fillscreen(uint8_t COLOR) {
  for (uint16_t Ypos = 0; Ypos < DEFAULT_HEIGHT; Ypos++)
    for (uint16_t Xpos = 0; Xpos < DEFAULT_WIDTH; Xpos++) {
      SCREENMEMORY[Ypos][Xpos] = COLOR;
    }

  if (LCD_ENABLED) xQueueSend(vidQueue, &SCREENMEMORY, 0); //refresh LCD
}
void screenmemory_drawpixel(uint16_t X, uint16_t Y, uint8_t COLOR) {
  if (Y < DEFAULT_HEIGHT && X < DEFAULT_WIDTH) SCREENMEMORY[Y][X] = COLOR;
}

void screenmemory_drawfillrectangle(uint16_t X, uint16_t Y, uint16_t Width, uint16_t Height, uint8_t COLOR) {
  for (uint16_t Ypos = Y; Ypos < Y + Height; Ypos++)
    for (uint16_t Xpos = X; Xpos < X + Width; Xpos++) {
      screenmemory_drawpixel(Xpos, Ypos, COLOR);
    }
  if (LCD_ENABLED) xQueueSend(vidQueue, &SCREENMEMORY, 0); //refresh LCD
}

uint8_t draw_char_xy(uint16_t Main_x, uint16_t Main_y, char Main_char, const char* font) {
  uint8_t XcharSize = font[0]; //x char size
  uint8_t YcharSize = font[1]; //y char size
  uint8_t CHAROFFSET = font[2]; //char start offset


  font[3]; //char count
  if (Main_char != '\n' && Main_char != '\r') for (uint16_t Ypos = 0; Ypos < YcharSize; Ypos++)
      for (uint16_t Xpos = 0; Xpos < XcharSize; Xpos += 8) {
        uint8_t CHARLINE = font[(Main_char - CHAROFFSET) * (YcharSize * (XcharSize / 8)) +   (Ypos) * (XcharSize / 8) + Xpos / 8  + 4];

        if ((Xpos + 0 < XcharSize) && (CHARLINE & 0b10000000) != 0) screenmemory_drawpixel(Main_x + Xpos + 0, Main_y + Ypos, 48);
        else screenmemory_drawpixel(Main_x + Xpos + 0, Main_y + Ypos, 63);
        if ((Xpos + 1 < XcharSize) && (CHARLINE & 0b01000000) != 0) screenmemory_drawpixel(Main_x + Xpos + 1, Main_y + Ypos, 48);
        else screenmemory_drawpixel(Main_x + Xpos + 1, Main_y + Ypos, 63);
        if ((Xpos + 2 < XcharSize) && (CHARLINE & 0b00100000) != 0) screenmemory_drawpixel(Main_x + Xpos + 2, Main_y + Ypos, 48);
        else screenmemory_drawpixel(Main_x + Xpos + 2, Main_y + Ypos, 63);
        if ((Xpos + 3 < XcharSize) && (CHARLINE & 0b00010000) != 0) screenmemory_drawpixel(Main_x + Xpos + 3, Main_y + Ypos, 48);
        else screenmemory_drawpixel(Main_x + Xpos + 3, Main_y + Ypos, 63);
        if ((Xpos + 4 < XcharSize) && (CHARLINE & 0b00001000) != 0) screenmemory_drawpixel(Main_x + Xpos + 4, Main_y + Ypos, 48);
        else screenmemory_drawpixel(Main_x + Xpos + 4, Main_y + Ypos, 63);
        if ((Xpos + 5 < XcharSize) && (CHARLINE & 0b00000100) != 0) screenmemory_drawpixel(Main_x + Xpos + 5, Main_y + Ypos, 48);
        else screenmemory_drawpixel(Main_x + Xpos + 5, Main_y + Ypos, 63);
        if ((Xpos + 6 < XcharSize) && (CHARLINE & 0b00000010) != 0) screenmemory_drawpixel(Main_x + Xpos + 6, Main_y + Ypos, 48);
        else screenmemory_drawpixel(Main_x + Xpos + 6, Main_y + Ypos, 63);
        if ((Xpos + 7 < XcharSize) && (CHARLINE & 0b00000001) != 0) screenmemory_drawpixel(Main_x + Xpos + 7, Main_y + Ypos, 48);
        else screenmemory_drawpixel(Main_x + Xpos + 7, Main_y + Ypos, 63);

      }
  return XcharSize;
}
//*******************************************************************************//
uint16_t draw_string_xy(uint16_t x, uint16_t y, char *c, const char* font)
{
  uint8_t width;
  uint8_t XcharSize = font[0]; //x char size
  uint8_t YcharSize = font[1]; //y char size
  uint16_t textwidth = 0;

  while (*c) {
    width = draw_char_xy(x, y, *c, font);
    textwidth += (width);
    x += (width);
    c++;
  }

  if (LCD_ENABLED) xQueueSend(vidQueue, &SCREENMEMORY, 0);

  return textwidth;
}
//*******************************************************************************
const char* DisplayFontSet = NULL;
uint16_t XPOS_CHAR = 0;
uint16_t YPOS_CHAR = 0;

void set_font(const char* font) {
  DisplayFontSet = font;
}

void set_font_XY(uint16_t x, uint16_t y) {
  XPOS_CHAR = x;
  YPOS_CHAR = y;
}
void draw_string(char *c) {
  if (c[strlen(c) - 1] == '\n') {
    draw_string_xy(XPOS_CHAR, YPOS_CHAR, c, DisplayFontSet);
    YPOS_CHAR += (uint8_t)(DisplayFontSet[1]);
    XPOS_CHAR = 0;
  } else {
    XPOS_CHAR += draw_string_xy(XPOS_CHAR, YPOS_CHAR, c, DisplayFontSet);

  }

}
//================================================================================

void (*audio_callback)(void *buffer, int length) = NULL;

//********************************************************************************
//********************************************************************************
//********************************************************************************
TimerHandle_t timer_1;

//Seemingly, this will be called only once. Should call func with a freq of frequency,
IRAM_ATTR int osd_installtimer_1(int frequency, void *func, int funcsize, void *counter, int countersize)
{
  if (DEBUG) Serial.print("Timer install, freq=");
  if (DEBUG) Serial.println(frequency);
  delay(1000);
  timer_1 = xTimerCreate("nes_hid", configTICK_RATE_HZ / frequency, pdTRUE, NULL, (TimerCallbackFunction_t)func);
  xTimerStart(timer_1, 0);
  return 0;
}
//********************************************************************************
//--------------------------------------------------------------------------------
// HID timer ISR
volatile int time_ticks = 0;
IRAM_ATTR static void timer_isr(void)
{
  /*#ifdef BLUETOOTH_ENABLED
    /// hid_update();
    ///PS4_JOY();
    #endif*/ //this causes crashing

  if (audio_callback && NES_POWER == 1) do_audio_frame();
  time_ticks++;
}
IRAM_ATTR static void timer_isr_end(void) {}
IRAM_ATTR int install_timer(int hertz) {
  return osd_installtimer_1(hertz, (void *) timer_isr, (int) timer_isr_end - (int) timer_isr, (void *) &time_ticks, sizeof(time_ticks));
}
//--------------------------------------------------------------------------------
IRAM_ATTR void MEMORY_STATUS() {
  if (DEBUG) {
    Serial.println();
    Serial.println("--------------------------------");
    Serial.print("TOTAL HEAP: ");
    Serial.println(ESP.getHeapSize());
    Serial.print("FREE HEAP: ");
    Serial.println(ESP.getFreeHeap());
    Serial.print("heap_caps_get_free_size: ");
    Serial.println(heap_caps_get_free_size(MALLOC_CAP_8BIT));
    Serial.println("--------------------------------");
  }
}
//********************************************************************************

//................................................................................
//AUDIO SETUP
typedef struct sndinfo_s
{
  int sample_rate;
  int bps;
} sndinfo_t;


#ifdef SOUND_ENABLED


intr_handle_t i2s_interrupt;

i2s_pin_config_t pin_config = {
  .bck_io_num = I2S_BCK_IO,
  .ws_io_num = I2S_WS_IO,
  .data_out_num = I2S_DO_IO,
  .data_in_num = I2S_DI_IO                                               //Not used
};
i2s_config_t audio_cfg = {
  .mode = static_cast<i2s_mode_t>(I2S_MODE_MASTER | I2S_MODE_TX /*| I2S_MODE_DAC_BUILT_IN*/),
  .sample_rate = DEFAULT_SAMPLERATE,
  .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
  ///.channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,
  .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
  .communication_format = static_cast<i2s_comm_format_t>(I2S_COMM_FORMAT_PCM | I2S_COMM_FORMAT_I2S_MSB),
  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1  /*| ESP_INTR_FLAG_IRAM /* | ESP_INTR_FLAG_SHARED*/, ///(DO NOT USE: ESP_INTR_FLAG_IRAM = BLUETOOTH PROBLEM)
  .dma_buf_count = 6,
  // .dma_buf_len = 2 * DEFAULT_FRAGSIZE,
  .dma_buf_len = 4 * DEFAULT_FRAGSIZE,
  .use_apll = false
};
#endif
int init_sound(void)
{
#ifdef SOUND_ENABLED
  i2s_driver_install(I2S_NUM_1, &audio_cfg, 0, NULL);
  i2s_set_pin(I2S_NUM_1, &pin_config);
#endif
  audio_callback = NULL;
  return 0;
}

void osd_setsound(void (*playfunc)(void *buffer, int length))
{
  //Indicates we should call playfunc() to get more data.
  audio_callback = playfunc;
}

void osd_stopsound(void)
{
  audio_callback = NULL;
}

void osd_getsoundinfo(sndinfo_t *info);
void osd_getsoundinfo(sndinfo_t *info)
{
  info->sample_rate = DEFAULT_SAMPLERATE;
  info->bps = 16;
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
#if SOUND_ENABLED
QueueHandle_t queue;
//  uint16_t audio_frame[DEFAULT_FRAGSIZE];
uint16_t audio_frame[2 * DEFAULT_FRAGSIZE];
#endif

void do_audio_frame()
{
  ///printf("do_audio_frame\n");

#if SOUND_ENABLED
  int left = DEFAULT_SAMPLERATE / NES_REFRESH_RATE;
  while (left)
  {
    int n = DEFAULT_FRAGSIZE;
    if (n > left)
      n = left;
    audio_callback(audio_frame, n); //get more data


    //16 bit mono -> 32-bit (16 bit r+l)
    for (int i = n - 1; i >= 0; i--)
    {
      //      audio_frame[i] = audio_frame[i] + 0x8000;
      ///      uint16_t a = (audio_frame[i] >> 3); //VEEERY BAD!
      uint16_t a = (audio_frame[i] >> 0);
      audio_frame[i * 2 + 1] = 0x8000 + a;
      audio_frame[i * 2] = 0x8000 - a;

      ///Serial.print(audio_frame[i]);
    }
    size_t i2s_bytes_write;
    // i2s_write(I2S_NUM_0, (const char *)audio_frame, 2 * n, &i2s_bytes_write, portMAX_DELAY);
    // left -= i2s_bytes_write / 2;
    i2s_write(I2S_NUM_1, (const char *)audio_frame, 4 * n, &i2s_bytes_write, portMAX_DELAY);
    left -= i2s_bytes_write / 4;
  }

#endif
}
//--------------------------------------------------------------------------------

//================================================================================
static void lcd_write_frame(const uint16_t x, const uint16_t y, const uint16_t width, const uint16_t height)
{
  uint8_t n, l;
  uint16_t i, len;
  uint16_t w = TFT_WIDTH;
  uint16_t h = TFT_HEIGHT;
  uint16_t X_ = 0;
  for (i = 0; i < height; i++) {
    uint16_t horizontal_offset = 8;
    for (X_ = 0; X_ < 256; X_++) {
      SCREENBUFFER[X_] = nes_16bit[(0x3F & ((uint8_t *)SCREENMEMORY[i])[X_])];
    }
    ///PAL optimalisation in this case:
    tft.drawRGBBitmap(0, i, (uint16_t *)(SCREENBUFFER), 48, 1);
    tft.drawRGBBitmap(48, i, (uint16_t *)(SCREENBUFFER + 48), 48, 1);
    tft.drawRGBBitmap(96, i, (uint16_t *)(SCREENBUFFER + 96), 48, 1);
    tft.drawRGBBitmap(144, i, (uint16_t *)(SCREENBUFFER + 144), 48, 1);
    tft.drawRGBBitmap(192, i, (uint16_t *)(SCREENBUFFER + 192), 48, 1);
  }

}
//================================================================================
//--------------------------------------------------------------------------------
///QueueHandle_t vidQueue;
static void videoTask(void *arg)
{
  int x = (320 - DEFAULT_WIDTH) / 2;
  int y = ((240 - DEFAULT_HEIGHT) / 2);
  while (1)
  {
    xQueueReceive(vidQueue, &SCREENMEMORY, portMAX_DELAY);
    lcd_write_frame(x, y, DEFAULT_WIDTH, DEFAULT_HEIGHT);
    ///delay(1); //1ms (prevent crash???)
  }
}
//--------------------------------------------------------------------------------
int audiovideo_init() ///osd_init()
{
  // disable Core 0 WDT
  TaskHandle_t idle_0 = xTaskGetIdleTaskHandleForCPU(0);
  esp_task_wdt_delete(idle_0);

  init_sound(); //START AUDIO

  vidQueue = xQueueCreate( 1, sizeof( unsigned int*  ));

  ///lcd_write_frame(0, 0, 320, 240);

  xTaskCreatePinnedToCore(&videoTask, "videoTask", 3072, NULL, 0, NULL, 0);

  if (DEBUG) Serial.println("videoTask Pinned To Core 0...");
  return 0;
}
//--------------------------------------------------------------------------------
//--------------------------------------------------------------------------------
#define MAXFILES 256
#define MAXFILENAME_LENGTH 255
void sortStrings(char arr[][MAXFILENAME_LENGTH], int n)
{
  char temp[MAXFILENAME_LENGTH];

  // Sorting strings using bubble sort
  for (int j = 0; j < n - 1; j++)
  {
    for (int i = j + 1; i < n; i++)
    {
      if (strcmp(arr[j], arr[i]) > 0)
      {
        strcpy(temp, arr[j]);
        strcpy(arr[j], arr[i]);
        strcpy(arr[i], temp);
      }
    }
  }
}
//================================================================================

// Max of ten files since files are selected with a single digit.
const uint16_t nMaxfiles = MAXFILES; //max. 256 NES roms

char filename[nMaxfiles][MAXFILENAME_LENGTH] = {0};
char fileext[nMaxfiles][4] = {0};

char* NESMENU() {
  uint8_t num = 0;
  uint8_t loadedFileNames = 0;

  screenmemory_fillscreen(63); //black color
  set_font_XY(0, 10);

  //--------------------------------------------------------------------------------
  //LOAD FILENAMES INTO MEMORY...

  num = 0;
  //Load List files in root directory.
  ///if (!dirFile.open("/", O_READ)) {
  if (!dirFile.open(NES_FOLDER, O_READ)) {

    SD.errorHalt("open root failed");
    while (1) {};
  }
  while (num < nMaxfiles && file.openNext(&dirFile, O_READ)) {

    // Skip directories and hidden files.
    if (!file.isSubDir() && !file.isHidden()) {

      for (uint8_t i = sizeof(filename[num]); i > 3; i--) filename[num][i] = 0;

      file.getName(filename[num], MAXFILENAME_LENGTH);

      for (uint8_t i = sizeof(filename[num]); i > 3; i--) {
        if (filename[num][i] != 0) {
          fileext[num][3] = '\0';
          fileext[num][2] = filename[num][i];
          fileext[num][1] = filename[num][i - 1];
          fileext[num][0] = filename[num][i - 2];
          break;
        }
      }
      //check NES File extension, then increase index
      if ((fileext[num][0] == 'N' || fileext[num][0] == 'n')
          && (fileext[num][1] == 'E' || fileext[num][1] == 'e')
          && (fileext[num][2] == 'S' || fileext[num][2] == 's')) {
        num++;

      }
    }
    loadedFileNames = num;
    file.close();
  }
  if (DEBUG) {
    Serial.print("Count of loaded File Names:");
    Serial.println(loadedFileNames);
  }
  //--------------------------------------------------------------------------------
  ///int n = sizeof(filename)/sizeof(filename[0]);

  sortStrings(filename, loadedFileNames);
  //--------------------------------------------------------------------------------

  //DRAW FILENAMES INTO BUFFER
  uint8_t CURSOR = 0;
  uint8_t PAGE = 0;

#define FILESPERPAGE 8

  bool NamesDisplayed = false;

  while (1) {
    //hid_update();
    PS4_JOY();

    PAGE = CURSOR / FILESPERPAGE;

    if (!NamesDisplayed) {
      screenmemory_fillscreen(63); //black color
      for (num = PAGE * FILESPERPAGE; num < ((PAGE + 1)*FILESPERPAGE) && num < loadedFileNames; num++) {
        set_font_XY(40, 48 + 20 * (num % FILESPERPAGE));
        draw_string(filename[num]);
        delay(1);
        ///}
      }
      NamesDisplayed = true;
    }
    //--------------------------------------------------------------------------------
    //PROCESS CURSOR SELECTION

    if (digitalRead(PIN_A) == 1) {
      JOY_CROSS = 1;  //A
      delay(200);
    }
    if (digitalRead(PIN_B) == 1) {
      JOY_SQUARE = 1;   //B
      delay(200);
    }
    if (digitalRead(PIN_SELECT) == 1) {
      JOY_OPTIONS = 1;   //SELECT
      delay(200);
    }
    if (digitalRead(PIN_START) == 1) {
      JOY_SHARE = 1;   //START
      delay(200);
    }
    if (digitalRead(PIN_UP) == 1) {
      JOY_UP = 1;
      delay(200);
    }
    if (digitalRead(PIN_DOWN) == 1) {
      JOY_DOWN = 1;   //DOWN
      delay(200);
    }
    if (digitalRead(PIN_LEFT) == 1) {
      JOY_LEFT = 1;   //LEFT
      delay(200);
    }
    if (digitalRead(PIN_RIGHT) == 1) {
      JOY_RIGHT = 1;   //RIGHT
      delay(200);
    }



    //Empty Cursor
    set_font_XY(16, 48 + (20 * (CURSOR % FILESPERPAGE)));
    draw_string("  ");
    if (JOY_UP == 1 ) {
      if (CURSOR % FILESPERPAGE == 0) NamesDisplayed = false; //changed page
      if (CURSOR == 0 && loadedFileNames > 0) CURSOR = loadedFileNames - 1;
      else if (CURSOR > 0 && loadedFileNames > 0) CURSOR--;
      JOY_UP = 0;
    }
    if (JOY_DOWN == 1 ) {
      if (CURSOR % FILESPERPAGE == FILESPERPAGE - 1 || CURSOR == loadedFileNames - 1) NamesDisplayed = false; //changed page
      if (CURSOR == loadedFileNames - 1 && loadedFileNames > 0) CURSOR = 0;
      else if (CURSOR < loadedFileNames - 1 && loadedFileNames > 0) CURSOR++;
      JOY_DOWN = 0;
    }
    if (JOY_LEFT == 1) {
      if (CURSOR > FILESPERPAGE - 1) CURSOR -= FILESPERPAGE;
      NamesDisplayed = false;
      JOY_LEFT = 0;
    }
    if (JOY_RIGHT == 1) {
      if (CURSOR / FILESPERPAGE < loadedFileNames / FILESPERPAGE) CURSOR += FILESPERPAGE;
      if (CURSOR > loadedFileNames - 1) CURSOR = loadedFileNames - 1;
      NamesDisplayed = false;
      JOY_RIGHT = 0;
    }
    if ((JOY_CROSS == 1 || JOY_SHARE == 1) && JOY_OPTIONS == 0) {
      dirFile.close();
      JOY_CROSS = 0;
      JOY_SHARE = 0;
      JOY_OPTIONS = 0;
      return filename[CURSOR] ; //START //A
    }
    //Draw Cursor
    set_font_XY(16, 48 + (20 * (CURSOR % FILESPERPAGE)));
    draw_string("->");
    delay(25);
  };

}
//================================================================================

//================================================================================
char loadmessage[64];
unsigned char *getromdata(char* ROMFILENAME_)
{

  char namebuffer[256];
  strncpy(namebuffer, NES_FOLDER, sizeof(namebuffer));
  strncat(namebuffer, ROMFILENAME_, sizeof(namebuffer));

  fp = SD.open(namebuffer);

  if (DEBUG) Serial.print("FILE SIZE: ");
  if (DEBUG) Serial.println(fp.size());

  FILE_ROM_SIZE = fp.size();

  uint16_t BLOCKCOUNT = (FILE_ROM_SIZE + SPI_FLASH_SECTOR_SIZE) / SPI_FLASH_SECTOR_SIZE;
  uint16_t BLOCKSIZEPX = DEFAULT_WIDTH / BLOCKCOUNT;
  Serial.print("BLOCKCOUNT: ");
  Serial.print(BLOCKCOUNT);
  Serial.print(" BLOCKSIZEPX: ");
  Serial.print(BLOCKSIZEPX);
  Serial.println();
  BLOCKSIZEPX++;

  // Read NES rom to SPI FLASH!
  uint32_t i = 0;
  for (i = 0; i < fp.size() + SPI_FLASH_SECTOR_SIZE; i++) {
    if (DEBUGEXTRA && i % SPI_FLASH_SECTOR_SIZE == 0) {
      Serial.print("ROM loaded 4kB:");
      Serial.println(i / 0x1000);
    }
    if (i > 0 && i % SPI_FLASH_SECTOR_SIZE == 0) {
      ///spi_flash_erase_sector(SPI_FLASH_ADDRESS/SPI_FLASH_SECTOR_SIZE + (i/SPI_FLASH_SECTOR_SIZE)-SPI_FLASH_SECTOR_SIZE);
      delayMicroseconds(300);
      spi_flash_erase_range(SPI_FLASH_ADDRESS + i - SPI_FLASH_SECTOR_SIZE, sizeof(flashdata));
      delayMicroseconds(300);
      spi_flash_write(SPI_FLASH_ADDRESS + i - SPI_FLASH_SECTOR_SIZE, flashdata, sizeof(flashdata));
      delayMicroseconds(300);

      sprintf(loadmessage, " %d / %d", i, FILE_ROM_SIZE);
      set_font_XY(8, 8);
      draw_string("Loaded:");
      draw_string(loadmessage);
      screenmemory_drawfillrectangle(((i / SPI_FLASH_SECTOR_SIZE) - 1)* BLOCKSIZEPX, 24, BLOCKSIZEPX, 16, 57);
    }
    delayMicroseconds(50);
    if (fp.available()) flashdata[i % SPI_FLASH_SECTOR_SIZE] = fp.read();
    delayMicroseconds(50);
  }
  fp.close();

  FLASH_ROM_SIZE = i; //Size of File and Offset Align

  if (DEBUG) Serial.print("FLASH SIZE: ");
  if (DEBUG) Serial.println(FLASH_ROM_SIZE);

  ROM = 0;

  ///if (handle1) spi_flash_munmap(handle1);

  printf("Mapping %x (+%x)\n", SPI_FLASH_ADDRESS, FLASH_ROM_SIZE);

  ESP_ERROR_CHECK( spi_flash_mmap(SPI_FLASH_ADDRESS, FLASH_ROM_SIZE, SPI_FLASH_MMAP_DATA, &ROM, &handle1) );

  printf("mmap_res: handle=%d ptr=%p\n", handle1, ROM);

  Serial.println("[NES ROM MAPPED ON FLASH!]");

  return (unsigned char *)ROM;
}
//================================================================================



//--------------------------------------------------------------------------------
//
//   EMULATOR CORE HEADERS:
//
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
//CPU.H:
// Define this to enable decimal mode in ADC / SBC (not needed in NES)
//#define  NES6502_DECIMAL

// P (flag) register bitmasks
#define  N_FLAG         0x80
#define  V_FLAG         0x40
#define  R_FLAG         0x20  // Reserved, always 1 
#define  B_FLAG         0x10
#define  D_FLAG         0x08
#define  I_FLAG         0x04
#define  Z_FLAG         0x02
#define  C_FLAG         0x01

// Vector addresses
#define  NMI_VECTOR     0xFFFA
#define  RESET_VECTOR   0xFFFC
#define  IRQ_VECTOR     0xFFFE

// cycle counts for interrupts
#define  INT_CYCLES     7
#define  RESET_CYCLES   6

#define  NMI_MASK       0x01
#define  IRQ_MASK       0x02

// Stack is located on 6502 page 1
#define  STACK_OFFSET   0x0100

typedef struct {
  uint32_t min_range, max_range;
  uint8_t (*read_func)(uint32_t address);
} nes6502_memread;

typedef struct {
  uint32_t min_range, max_range;
  void (*write_func)(uint32_t address, uint8_t value);
} nes6502_memwrite;

typedef struct {
  uint8_t *mem_page[NES6502_NUMBANKS];  // memory page pointers
  nes6502_memread *read_handler;
  nes6502_memwrite *write_handler;
  uint32_t pc_reg;
  uint8_t a_reg, p_reg;
  uint8_t x_reg, y_reg;
  uint8_t s_reg;
  uint8_t jammed;  // is processor jammed?
  uint8_t int_pending, int_latency;
  int32_t total_cycles, burn_cycles;
} nes6502_context;

//--------------------------------------------------------------------------------
//PPU.H:
// PPU register defines
#define  PPU_CTRL0            0x2000
#define  PPU_CTRL1            0x2001
#define  PPU_STAT             0x2002
#define  PPU_OAMADDR          0x2003
#define  PPU_OAMDATA          0x2004
#define  PPU_SCROLL           0x2005
#define  PPU_VADDR            0x2006
#define  PPU_VDATA            0x2007

#define  PPU_OAMDMA           0x4014
#define  PPU_JOY0             0x4016
#define  PPU_JOY1             0x4017

// $2000
#define  PPU_CTRL0F_NMI       0x80
#define  PPU_CTRL0F_OBJ16     0x20
#define  PPU_CTRL0F_BGADDR    0x10
#define  PPU_CTRL0F_OBJADDR   0x08
#define  PPU_CTRL0F_ADDRINC   0x04
#define  PPU_CTRL0F_NAMETAB   0x03

// $2001
#define  PPU_CTRL1F_OBJON     0x10
#define  PPU_CTRL1F_BGON      0x08
#define  PPU_CTRL1F_OBJMASK   0x04
#define  PPU_CTRL1F_BGMASK    0x02

// $2002
#define  PPU_STATF_VBLANK     0x80
#define  PPU_STATF_STRIKE     0x40
#define  PPU_STATF_MAXSPRITE  0x20

// Sprite attribute byte bitmasks
#define  OAMF_VFLIP           0x80
#define  OAMF_HFLIP           0x40
#define  OAMF_BEHIND          0x20

// Maximum number of sprites per horizontal scanline
#define  PPU_MAXSPRITE        8

// some mappers do *dumb* things
typedef void (*ppulatchfunc_t)(uint32_t address, uint8_t value);
typedef void (*ppuvromswitch_t)(uint8_t value);

typedef struct ppu_s {
  // big nasty memory chunks
  uint8_t nametab[0x1000];
  uint8_t oam[256];
  uint8_t palette[32];
  uint8_t *page[16];

  // hardware registers
  uint8_t ctrl0, ctrl1, stat, oam_addr;
  uint32_t vaddr, vaddr_latch;
  int tile_xofs, flipflop;
  int vaddr_inc;
  uint32_t tile_nametab;

  uint8_t obj_height;
  uint32_t obj_base, bg_base;

  bool bg_on, obj_on;
  bool obj_mask, bg_mask;

  uint8_t latch, vdata_latch;
  uint8_t strobe;

  bool strikeflag;
  uint32_t strike_cycle;

  // callbacks for naughty mappers
  ppulatchfunc_t latchfunc;
  ppuvromswitch_t vromswitch;

  bool vram_accessible;

  bool vram_present;
  bool drawsprites;
} ppu_t;
//--------------------------------------------------------------------------------
//APU.H:
// define this for realtime generated noise
#define  REALTIME_NOISE

#define  APU_WRA0       0x4000
#define  APU_WRA1       0x4001
#define  APU_WRA2       0x4002
#define  APU_WRA3       0x4003
#define  APU_WRB0       0x4004
#define  APU_WRB1       0x4005
#define  APU_WRB2       0x4006
#define  APU_WRB3       0x4007
#define  APU_WRC0       0x4008
#define  APU_WRC2       0x400A
#define  APU_WRC3       0x400B
#define  APU_WRD0       0x400C
#define  APU_WRD2       0x400E
#define  APU_WRD3       0x400F
#define  APU_WRE0       0x4010
#define  APU_WRE1       0x4011
#define  APU_WRE2       0x4012
#define  APU_WRE3       0x4013

#define  APU_SMASK      0x4015

// length of generated noise
#define  APU_NOISE_32K  0x7FFF
#define  APU_NOISE_93   93

#define  APU_BASEFREQ   1789772.7272727272727272

// channel structures:  As much data as possible is precalculated, to keep the sample processing as lean as possible

typedef struct rectangle_s {
  uint8_t regs[4];

  bool enabled;

  float accum;
  int32_t freq;
  int32_t output_vol;
  bool fixed_envelope;
  bool holdnote;
  uint8_t volume;

  int32_t sweep_phase;
  int32_t sweep_delay;
  bool sweep_on;
  uint8_t sweep_shifts;
  uint8_t sweep_length;
  bool sweep_inc;

  // this may not be necessary in the future
  int32_t freq_limit;
  int32_t env_phase;
  int32_t env_delay;
  uint8_t env_vol;

  int vbl_length;
  uint8_t adder;
  int duty_flip;
} rectangle_t;

typedef struct triangle_s {
  uint8_t regs[3];

  bool enabled;

  float accum;
  int32_t freq;
  int32_t output_vol;

  uint8_t adder;

  bool holdnote;
  bool counter_started;
  // quasi-hack
  int write_latency;

  int vbl_length;
  int linear_length;
} triangle_t;

typedef struct noise_s {
  uint8_t regs[3];

  bool enabled;

  float accum;
  int32_t freq;
  int32_t output_vol;

  int32_t env_phase;
  int32_t env_delay;
  uint8_t env_vol;
  bool fixed_envelope;
  bool holdnote;

  uint8_t volume;

  int vbl_length;

#ifdef REALTIME_NOISE
  uint8_t xor_tap;
#else
  bool short_sample;
  int cur_pos;
#endif // REALTIME_NOISE 
} noise_t;

typedef struct dmc_s
{
  uint8_t regs[4];

  // bodge for timestamp queue
  bool enabled;

  float accum;
  int32_t freq;
  int32_t output_vol;

  uint32_t address;
  uint32_t cached_addr;
  int dma_length;
  int cached_dmalength;
  uint8_t cur_byte;

  bool looping;
  bool irq_gen;
  bool irq_occurred;

} dmc_t;

enum
{
  APU_FILTER_NONE,
  APU_FILTER_LOWPASS,
  APU_FILTER_WEIGHTED
};

typedef struct
{
  uint32_t min_range, max_range;
  uint8_t (*read_func)(uint32_t address);
} apu_memread;

typedef struct
{
  uint32_t min_range, max_range;
  void (*write_func)(uint32_t address, uint8_t value);
} apu_memwrite;

// external sound chip stuff
typedef struct apuext_s
{
  int   (*init)(void);
  void  (*shutdown)(void);
  void  (*reset)(void);
  int32_t (*process)(void);
  apu_memread *mem_read;
  apu_memwrite *mem_write;
} apuext_t;


typedef struct apu_s
{
  rectangle_t rectangle[2];
  triangle_t triangle;
  noise_t noise;
  dmc_t dmc;
  uint8_t enable_reg;

  void *buffer; // pointer to output buffer
  int num_samples;

  uint8_t mix_enable;
  int filter_type;

  double base_freq;
  float cycle_rate;

  int sample_rate;
  int sample_bits;
  int refresh_rate;

  void (*process)(void *buffer, int num_samples);
  void (*irq_callback)(void);
  uint8_t (*irqclear_callback)(void);

  // external sound chip
  apuext_t *ext;
} apu_t;
//--------------------------------------------------------------------------------
//ROM.H:
typedef enum {
  MIRROR_HORIZ   = 0,
  MIRROR_VERT    = 1
} mirror_t;

#define  ROM_FLAG_BATTERY     0x01
#define  ROM_FLAG_TRAINER     0x02
#define  ROM_FLAG_FOURSCREEN  0x04
#define  ROM_FLAG_VERSUS      0x08

typedef struct rominfo_s
{
  // pointers to ROM and VROM
  uint8_t *rom, *vrom;

  // pointers to SRAM and VRAM
  uint8_t *sram, *vram;

  // number of banks
  int rom_banks, vrom_banks;
  int sram_banks, vram_banks;

  int mapper_number;
  mirror_t mirror;

  uint8_t flags;
} rominfo_t;

typedef struct inesheader_s
{
  uint8_t ines_magic[4];
  uint8_t rom_banks;
  uint8_t vrom_banks;
  uint8_t rom_type;
  uint8_t mapper_hinybble;
  uint8_t reserved[8];
} inesheader_t;
//--------------------------------------------------------------------------------
//LIBSNS.H:
#define TAG_LENGTH 4

struct mapper1Data {
  unsigned char registers[4];
  unsigned char latch;
  unsigned char numberOfBits;
};

struct mapper4Data {
  unsigned char irqCounter;
  unsigned char irqLatchCounter;
  unsigned char irqCounterEnabled;
  unsigned char last8000Write;
};

struct mapper5Data {
  unsigned char dummy; // needed for some compilers; remove if any members are added
};

struct mapper6Data {
  unsigned char irqCounter;
  unsigned char irqLatchCounter;
  unsigned char irqCounterEnabled;
  unsigned char last43FEWrite;
  unsigned char last4500Write;
};

struct mapper9Data {
  unsigned char latch[2];
  unsigned char lastB000Write;
  unsigned char lastC000Write;
  unsigned char lastD000Write;
  unsigned char lastE000Write;
};

struct mapper10Data {
  unsigned char latch[2];
  unsigned char lastB000Write;
  unsigned char lastC000Write;
  unsigned char lastD000Write;
  unsigned char lastE000Write;
};

struct mapper16Data {
  unsigned char irqCounterLowByte;
  unsigned char irqCounterHighByte;
  unsigned char irqCounterEnabled;
};

struct mapper17Data {
  unsigned char irqCounterLowByte;
  unsigned char irqCounterHighByte;
  unsigned char irqCounterEnabled;
};

struct mapper18Data {
  unsigned char irqCounterLowByte;
  unsigned char irqCounterHighByte;
  unsigned char irqCounterEnabled;
};

struct mapper19Data {
  unsigned char irqCounterLowByte;
  unsigned char irqCounterHighByte;
  unsigned char irqCounterEnabled;
};

struct mapper21Data {
  unsigned char irqCounter;
  unsigned char irqCounterEnabled;
};

struct mapper24Data {
  unsigned char irqCounter;
  unsigned char irqCounterEnabled;
};

struct mapper40Data {
  unsigned char irqCounter;
  unsigned char irqCounterEnabled;
};

struct mapper69Data {
  unsigned char irqCounterLowByte;
  unsigned char irqCounterHighByte;
  unsigned char irqCounterEnabled;
};

struct mapper90Data {
  unsigned char irqCounter;
  unsigned char irqLatchCounter;
  unsigned char irqCounterEnabled;
};

struct mapper224Data {
  unsigned char chrRamWriteable;
};

struct mapper225Data {
  unsigned char prgSize;
  unsigned char registers[4];
};

struct mapper226Data {
  unsigned char chrRamWriteable;
};

struct mapper228Data {
  unsigned char prgChipSelected;
};

struct mapper230Data {
  unsigned char numberOfResets;
};

typedef struct _SnssFileHeader {
  char tag[TAG_LENGTH + 1];
  unsigned int numberOfBlocks;
} SnssFileHeader;

// this block appears before every block in the SNSS file
typedef struct _SnssBlockHeader {
  char tag[TAG_LENGTH + 1];
  unsigned int blockVersion;
  unsigned int blockLength;
} SnssBlockHeader;

#define MAPPER_BLOCK_LENGTH 0x98
typedef struct _SnssMapperBlock
{
  unsigned short prgPages[4];
  unsigned short chrPages[8];

  union _extraData
  {
    unsigned char mapperData[128];
    struct mapper1Data mapper1;
    struct mapper4Data mapper4;
    struct mapper5Data mapper5;
    struct mapper6Data mapper6;
    struct mapper9Data mapper9;
    struct mapper10Data mapper10;
    struct mapper16Data mapper16;
    struct mapper17Data mapper17;
    struct mapper18Data mapper18;
    struct mapper19Data mapper19;
    struct mapper21Data mapper21;
    struct mapper24Data mapper24;
    struct mapper40Data mapper40;
    struct mapper69Data mapper69;
    struct mapper90Data mapper90;
    struct mapper224Data mapper224;
    struct mapper225Data mapper225;
    struct mapper226Data mapper226;
    struct mapper228Data mapper228;
    struct mapper230Data mapper230;
  } extraData;
} SnssMapperBlock;
//--------------------------------------------------------------------------------
//MMC.H:
#define  MMC_LASTBANK      -1

typedef struct {
  uint32_t min_range, max_range;
  uint8_t (*read_func)(uint32_t address);
} map_memread;

typedef struct {
  uint32_t min_range, max_range;
  void (*write_func)(uint32_t address, uint8_t value);
} map_memwrite;

typedef struct mapintf_s {
  int number;
  char *name;
  void (*init)(void);
  void (*vblank)(void);
  void (*hblank)(int vblank);
  void (*get_state)(SnssMapperBlock *state);
  void (*set_state)(SnssMapperBlock *state);
  map_memread *mem_read;
  map_memwrite *mem_write;
  apuext_t *sound_ext;
} mapintf_t;

typedef struct mmc_s {
  mapintf_t *intf;
  rominfo_t *cart;  // link it back to the cart
} mmc_t;
//--------------------------------------------------------------------------------
//NES.H

typedef struct nes_s {
  // hardware things
  nes6502_context *cpu;
  nes6502_memread readhandler[MAX_MEM_HANDLERS];
  nes6502_memwrite writehandler[MAX_MEM_HANDLERS];

  ppu_t *ppu;
  apu_t *apu;
  mmc_t *mmc;
  rominfo_t *rominfo;

  bool fiq_occurred;
  uint8_t fiq_state;
  int fiq_cycles;

  int scanline;

  // Timing stuff
  float scanline_cycles;
} nes_t;
//--------------------------------------------------------------------------------
ppu_t ppu;
apu_t apu;
mmc_t mmc;

nes_t nes;
nes_t *NESmachine;

//--------------------------------------------------------------------------------
//
//   EMULATOR CORE CODES:
//
//--------------------------------------------------------------------------------
//ROM.C
// Max length for displayed filename
#define ROM_DISP_MAXLEN 20

#define ROM_FOURSCREEN 0x08
#define ROM_TRAINER 0x04
#define ROM_BATTERY 0x02
#define ROM_MIRRORTYPE 0x01
#define ROM_INES_MAGIC "NES\x1A"

#define TRAINER_OFFSET 0x1000
#define TRAINER_LENGTH 0x200
#define VRAM_LENGTH 0x2000
#define ROM_BANK_LENGTH 0x4000
#define VROM_BANK_LENGTH 0x2000
#define SRAM_BANK_LENGTH 0x0400
#define VRAM_BANK_LENGTH 0x2000

// Allocate space for SRAM
int rom_allocsram(rominfo_t *rominfo);
int rom_allocsram(rominfo_t *rominfo) {
  // Load up SRAM
  if (NULL == rominfo->sram) rominfo->sram = (uint8_t*)malloc(SRAM_BANK_LENGTH * rominfo->sram_banks);
  if (NULL == rominfo->sram) {
    ///gui_sendmsg(GUI_RED, "Could not allocate space for battery RAM");
    return -1;
  }

  // make damn sure SRAM is clear
  memset(rominfo->sram, 0, SRAM_BANK_LENGTH * rominfo->sram_banks);
  return 0;
}

// If there's a trainer, load it in at $7000
void rom_loadtrainer(unsigned char **rom, rominfo_t *rominfo);
void rom_loadtrainer(unsigned char **rom, rominfo_t *rominfo) {
  if (rominfo->flags & ROM_FLAG_TRAINER)
  {
    //      fread(rominfo->sram + TRAINER_OFFSET, TRAINER_LENGTH, 1, fp);
    memcpy(rominfo->sram + TRAINER_OFFSET, *rom, TRAINER_LENGTH);
    rom += TRAINER_LENGTH;
    ///nes_log_printf("Read in trainer at $7000\n");
  }
}

int rom_loadrom(unsigned char **rom, rominfo_t *rominfo);
int rom_loadrom(unsigned char **rom, rominfo_t *rominfo) {
  rominfo->rom = *rom;
  *rom += ROM_BANK_LENGTH * rominfo->rom_banks;

  // If there's VROM, allocate and stuff it in
  if (rominfo->vrom_banks)
  {
    rominfo->vrom = *rom;
    *rom += VROM_BANK_LENGTH * rominfo->vrom_banks;
  } else  {
    if (NULL == rominfo->vram) rominfo->vram = (uint8_t*)malloc(VRAM_LENGTH);
    if (NULL == rominfo->vram)
    {
      ///gui_sendmsg(GUI_RED, "Could not allocate space for VRAM");
      return -1;
    }
    memset(rominfo->vram, 0, VRAM_LENGTH);
  }

  return 0;
}

#define RESERVED_LENGTH 8

int rom_getheader(unsigned char **rom, rominfo_t *rominfo);
int rom_getheader(unsigned char **rom, rominfo_t *rominfo) {
  inesheader_t head;
  uint8_t reserved[RESERVED_LENGTH];
  bool header_dirty;

  // Read in the header
  if (DEBUG) {
    Serial.print("Head:  ");
    Serial.print((*rom)[0]);
    Serial.print("  ");
    Serial.print((*rom)[1]);
    Serial.print("  ");
    Serial.print((*rom)[2]);
    Serial.print("  ");
    Serial.println((*rom)[3]);
  }

  memcpy(&head, *rom, sizeof(head));
  *rom += sizeof(head);

  if (memcmp(head.ines_magic, ROM_INES_MAGIC, 4)) {
    if (DEBUG) Serial.println("ROM is not a valid NES image");
    if (DEBUG) tft.println("ROM is not a valid NES image");
    return -1;
  }

  rominfo->rom_banks = head.rom_banks;
  rominfo->vrom_banks = head.vrom_banks;
  // iNES assumptions
  rominfo->sram_banks = 8; //1kB banks, so 8KB
  rominfo->vram_banks = 1; // 8kB banks, so 8KB
  rominfo->mirror = (head.rom_type & ROM_MIRRORTYPE) ? MIRROR_VERT : MIRROR_HORIZ;
  rominfo->flags = 0;
  if (head.rom_type & ROM_BATTERY) rominfo->flags |= ROM_FLAG_BATTERY;
  if (head.rom_type & ROM_TRAINER) rominfo->flags |= ROM_FLAG_TRAINER;
  if (head.rom_type & ROM_FOURSCREEN) rominfo->flags |= ROM_FLAG_FOURSCREEN;
  // TODO: fourscreen a mirroring type?
  rominfo->mapper_number = head.rom_type >> 4;

  // Do a compare - see if we've got a clean extended header
  memset(reserved, 0, RESERVED_LENGTH);
  if (0 == memcmp(head.reserved, reserved, RESERVED_LENGTH)) {
    // We were clean
    header_dirty = false;
    rominfo->mapper_number |= (head.mapper_hinybble & 0xF0);
  } else {
    header_dirty = true;

    // @!?#@! DiskDude.
    if (('D' == head.mapper_hinybble) && (0 == memcmp(head.reserved, "iskDude!", 8))) {
      ///nes_log_printf("`DiskDude!' found in ROM header, ignoring high mapper nybble\n");
    } else {
      ///nes_log_printf("ROM header dirty, possible problem\n");
      rominfo->mapper_number |= (head.mapper_hinybble & 0xF0);
    }
    ///rom_adddirty(rominfo->filename);
  }
  // Check for VS unisystem mapper
  if (99 == rominfo->mapper_number) rominfo->flags |= ROM_FLAG_VERSUS;
  return 0;
}

/* Free a ROM */
void rom_free(rominfo_t **rominfo);
void rom_free(rominfo_t **rominfo)
{
  if (NULL == *rominfo)
  {
    if (DEBUG) Serial.println("ROM not loaded");
    return;
  }

  /*  if ((*rominfo)->sram)
      free((*rominfo)->sram);
    Serial.println("sram");    */
  /*  if ((*rominfo)->rom)
      free((*rominfo)->rom);
    Serial.println("rom");
    if ((*rominfo)->vrom)
      free((*rominfo)->vrom);
    Serial.println("vrom");
    if ((*rominfo)->vram)
      free((*rominfo)->vram);
    Serial.println("vram"); */

  free(*rominfo);

}
//================================================================================

//--------------------------------------------------------------------------------
///CPU.C:

// internal CPU context
static nes6502_context cpu;
static int remaining_cycles = 0; // so we can release timeslice
// memory region pointers
static uint8_t *ram = NULL, *stack = NULL;
static uint8_t null_page[NES6502_BANKSIZE];

//#define  NES6502_DISASM
//#define  NES6502_JUMPTABLE

#define  ADD_CYCLES(x) { remaining_cycles -= (x); cpu.total_cycles += (x); }
#define PAGE_CROSS_CHECK(addr, reg) { if ((reg) > (uint8_t) (addr)) ADD_CYCLES(1); }
#define EMPTY_READ(value)  // empty 
#define IMMEDIATE_BYTE(value) { value = bank_readbyte(PC++); }
#define ABSOLUTE_ADDR(address) { address = bank_readword(PC); PC += 2; }
#define ABSOLUTE(address, value) { ABSOLUTE_ADDR(address); value = mem_readbyte(address); }
#define ABSOLUTE_BYTE(value) { ABSOLUTE(temp, value); }
#define ABS_IND_X_ADDR(address) { ABSOLUTE_ADDR(address); address = (address + X) & 0xFFFF; }
#define ABS_IND_X(address, value) { ABS_IND_X_ADDR(address); value = mem_readbyte(address); }
#define ABS_IND_X_BYTE(value) { ABS_IND_X(temp, value); }
#define ABS_IND_X_BYTE_READ(value) { ABS_IND_X_ADDR(temp); PAGE_CROSS_CHECK(temp, X); value = mem_readbyte(temp); }
#define ABS_IND_Y_ADDR(address) { ABSOLUTE_ADDR(address); address = (address + Y) & 0xFFFF; }
#define ABS_IND_Y(address, value) { ABS_IND_Y_ADDR(address); value = mem_readbyte(address); }
#define ABS_IND_Y_BYTE(value) { ABS_IND_Y(temp, value); }
#define ABS_IND_Y_BYTE_READ(value) { ABS_IND_Y_ADDR(temp); PAGE_CROSS_CHECK(temp, Y); value = mem_readbyte(temp); }
#define ZERO_PAGE_ADDR(address) { IMMEDIATE_BYTE(address); }
#define ZERO_PAGE(address, value) { ZERO_PAGE_ADDR(address); value = ZP_READBYTE(address); }
#define ZERO_PAGE_BYTE(value) { ZERO_PAGE(btemp, value); }
#define ZP_IND_X_ADDR(address) { ZERO_PAGE_ADDR(address); address += X; }
#define ZP_IND_X(address, value) { ZP_IND_X_ADDR(address); value = ZP_READBYTE(address); }
#define ZP_IND_X_BYTE(value) { ZP_IND_X(btemp, value); }
#define ZP_IND_Y_ADDR(address) { ZERO_PAGE_ADDR(address); address += Y; }
#define ZP_IND_Y_BYTE(value) { ZP_IND_Y_ADDR(btemp); value = ZP_READBYTE(btemp); }
#define INDIR_X_ADDR(address) { ZERO_PAGE_ADDR(btemp); btemp += X; address = zp_readword(btemp); }
#define INDIR_X(address, value) { INDIR_X_ADDR(address); value = mem_readbyte(address); }
#define INDIR_X_BYTE(value) { INDIR_X(temp, value); }
#define INDIR_Y_ADDR(address) { ZERO_PAGE_ADDR(btemp); address = (zp_readword(btemp) + Y) & 0xFFFF; }
#define INDIR_Y(address, value) { INDIR_Y_ADDR(address); value = mem_readbyte(address); }
#define INDIR_Y_BYTE(value) { INDIR_Y(temp, value); }
#define INDIR_Y_BYTE_READ(value) { INDIR_Y_ADDR(temp); PAGE_CROSS_CHECK(temp, Y); value = mem_readbyte(temp); }
#define  PUSH(value)             stack[S--] = (uint8_t) (value)
#define  PULL()                  stack[++S]
#define  SCATTER_FLAGS(value) { n_flag = (value) & N_FLAG; v_flag = (value) & V_FLAG; b_flag = (value) & B_FLAG; \
    d_flag = (value) & D_FLAG; i_flag = (value) & I_FLAG; z_flag = (0 == ((value) & Z_FLAG)); c_flag = (value) & C_FLAG; }
#define  COMBINE_FLAGS() ( (n_flag & N_FLAG) | (v_flag ? V_FLAG : 0) | R_FLAG | (b_flag ? B_FLAG : 0) | (d_flag ? D_FLAG : 0) \
                           | (i_flag ? I_FLAG : 0) | (z_flag ? 0 : Z_FLAG) | c_flag )
#define  SET_NZ_FLAGS(value)     n_flag = z_flag = (value);
#define RELATIVE_BRANCH(condition) { if (condition) { IMMEDIATE_BYTE(btemp); if (((int8_t) btemp + (PC & 0x00FF)) & 0x100) \
        ADD_CYCLES(1); ADD_CYCLES(3); PC += (int8_t) btemp; } else { PC++; ADD_CYCLES(2); } }
#define JUMP(address) { PC = bank_readword((address)); }
#define NMI_PROC() { PUSH(PC >> 8); PUSH(PC & 0xFF); b_flag = 0; PUSH(COMBINE_FLAGS()); i_flag = 1; JUMP(NMI_VECTOR); }
#define IRQ_PROC() { PUSH(PC >> 8); PUSH(PC & 0xFF); b_flag = 0; PUSH(COMBINE_FLAGS()); i_flag = 1; JUMP(IRQ_VECTOR); }

// Warning! NES CPU has no decimal mode, so by default this does no BCD!
#ifdef NES6502_DECIMAL
#define ADC(cycles, read_func) { read_func(data); if (d_flag) { temp = (A & 0x0F) + (data & 0x0F) + c_flag; if (temp >= 10) \
        temp = (temp - 10) | 0x10; temp += (A & 0xF0) + (data & 0xF0); z_flag = (A + data + c_flag) & 0xFF;  n_flag = temp; \
      v_flag = ((~(A ^ data)) & (A ^ temp) & 0x80); if (temp > 0x90) { temp += 0x60; c_flag = 1; } else { c_flag = 0; } \
      A = (uint8_t) temp; } else { temp = A + data + c_flag; c_flag = (temp >> 8) & 1; v_flag = ((~(A ^ data)) & (A ^ temp) & 0x80); \
      A = (uint8_t) temp; SET_NZ_FLAGS(A); } ADD_CYCLES(cycles); }
#else // NES6502_DECIMAL 
#define ADC(cycles, read_func) { read_func(data); temp = A + data + c_flag; c_flag = (temp >> 8) & 1; \
    v_flag = ((~(A ^ data)) & (A ^ temp) & 0x80); A = (uint8_t) temp; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#endif // NES6502_DECIMAL 

#define ANC(cycles, read_func) { read_func(data); A &= data; c_flag = (n_flag & N_FLAG) >> 7; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define AND(cycles, read_func) { read_func(data); A &= data; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define ANE(cycles, read_func) { read_func(data); A = (A | 0xEE) & X & data; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }

#ifdef NES6502_DECIMAL
#define ARR(cycles, read_func) { read_func(data); data &= A; if (d_flag) { temp = (data >> 1) | (c_flag << 7);  SET_NZ_FLAGS(temp); \
      v_flag = (temp ^ data) & 0x40; if (((data & 0x0F) + (data & 0x01)) > 5) temp = (temp & 0xF0) | ((temp + 0x6) & 0x0F); \
      if (((data & 0xF0) + (data & 0x10)) > 0x50) { temp = (temp & 0x0F) | ((temp + 0x60) & 0xF0); c_flag = 1; } else { \
        c_flag = 0; } A = (uint8_t) temp; } else { A = (data >> 1) | (c_flag << 7); SET_NZ_FLAGS(A); c_flag = (A & 0x40) >> 6; \
      v_flag = ((A >> 6) ^ (A >> 5)) & 1; } ADD_CYCLES(cycles); }
#else // NES6502_DECIMAL 
#define ARR(cycles, read_func) { read_func(data); data &= A; A = (data >> 1) | (c_flag << 7); \
    SET_NZ_FLAGS(A); c_flag = (A & 0x40) >> 6; v_flag = ((A >> 6) ^ (A >> 5)) & 1; ADD_CYCLES(cycles); }
#endif // NES6502_DECIMAL 

#define ASL(cycles, read_func, write_func, addr) { read_func(addr, data); c_flag = data >> 7; data <<= 1; write_func(addr, data); \
    SET_NZ_FLAGS(data); ADD_CYCLES(cycles); }
#define ASL_A() { c_flag = A >> 7; A <<= 1; SET_NZ_FLAGS(A); ADD_CYCLES(2); }
#define ASR(cycles, read_func) { read_func(data); data &= A; c_flag = data & 1; A = data >> 1; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define BCC() { RELATIVE_BRANCH(0 == c_flag); }
#define BCS() { RELATIVE_BRANCH(0 != c_flag); }
#define BEQ() { RELATIVE_BRANCH(0 == z_flag); }
#define BIT(cycles, read_func) { read_func(data); n_flag = data; v_flag = data & V_FLAG; z_flag = data & A; ADD_CYCLES(cycles); }
#define BMI() { RELATIVE_BRANCH(n_flag & N_FLAG); }
#define BNE() { RELATIVE_BRANCH(0 != z_flag); }
#define BPL() { RELATIVE_BRANCH(0 == (n_flag & N_FLAG)); }
#define BRK() { PC++; PUSH(PC >> 8); PUSH(PC & 0xFF); b_flag = 1; PUSH(COMBINE_FLAGS()); i_flag = 1; JUMP(IRQ_VECTOR); ADD_CYCLES(7); }
#define BVC() { RELATIVE_BRANCH(0 == v_flag); }
#define BVS() { RELATIVE_BRANCH(0 != v_flag); }
#define CLC() { c_flag = 0; ADD_CYCLES(2); }
#define CLD() { d_flag = 0; ADD_CYCLES(2); }
#define CLI() { i_flag = 0; ADD_CYCLES(2); if (cpu.int_pending && remaining_cycles > 0) { cpu.int_pending = 0; \
      IRQ_PROC(); ADD_CYCLES(INT_CYCLES); } }
#define CLV() { v_flag = 0; ADD_CYCLES(2); }
#define _COMPARE(reg, value) { temp = (reg) - (value); c_flag = ((temp & 0x100) >> 8) ^ 1; SET_NZ_FLAGS((uint8_t) temp); }
#define CMP(cycles, read_func) { read_func(data); _COMPARE(A, data); ADD_CYCLES(cycles); }
#define CPX(cycles, read_func) { read_func(data); _COMPARE(X, data); ADD_CYCLES(cycles); }
#define CPY(cycles, read_func) { read_func(data); _COMPARE(Y, data); ADD_CYCLES(cycles);  }
#define DCP(cycles, read_func, write_func, addr) { read_func(addr, data); data--; write_func(addr, data); CMP(cycles, EMPTY_READ); }
#define _DEC(cycles, read_func, write_func, addr) { read_func(addr, data); data--; write_func(addr, data); SET_NZ_FLAGS(data); \
    ADD_CYCLES(cycles); }
#define DEX() { X--; SET_NZ_FLAGS(X); ADD_CYCLES(2); }
#define DEY() { Y--; SET_NZ_FLAGS(Y); ADD_CYCLES(2); }
#define DOP(cycles) { PC++; ADD_CYCLES(cycles); }
#define EOR(cycles, read_func) { read_func(data); A ^= data; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define INC(cycles, read_func, write_func, addr) { read_func(addr, data); data++; write_func(addr, data); \
    SET_NZ_FLAGS(data); ADD_CYCLES(cycles); }
#define INX() { X++; SET_NZ_FLAGS(X); ADD_CYCLES(2); }
#define INY() { Y++; SET_NZ_FLAGS(Y); ADD_CYCLES(2); }
#define ISB(cycles, read_func, write_func, addr) { read_func(addr, data); data++; write_func(addr, data); SBC(cycles, EMPTY_READ); }

#ifdef NES6502_TESTOPS
#define JAM() { cpu_Jam(); }
#else // !NES6502_TESTOPS 
#define JAM() { PC--; cpu.jammed = true; cpu.int_pending = 0; ADD_CYCLES(2); }
#endif // !NES6502_TESTOPS 

#define JMP_INDIRECT() { temp = bank_readword(PC); if (0xFF == (temp & 0xFF)) \
      PC = (bank_readbyte(temp & 0xFF00) << 8) | bank_readbyte(temp); else JUMP(temp); ADD_CYCLES(5); }
#define JMP_ABSOLUTE() { JUMP(PC); ADD_CYCLES(3); }
#define JSR() { PC++; PUSH(PC >> 8); PUSH(PC & 0xFF); JUMP(PC - 1); ADD_CYCLES(6); }
#define LAS(cycles, read_func) { read_func(data); A = X = S = (S & data); SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define LAX(cycles, read_func) { read_func(A); X = A; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define LDA(cycles, read_func) { read_func(A); SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define LDX(cycles, read_func) { read_func(X); SET_NZ_FLAGS(X); ADD_CYCLES(cycles); }
#define LDY(cycles, read_func) { read_func(Y); SET_NZ_FLAGS(Y); ADD_CYCLES(cycles); }
#define LSR(cycles, read_func, write_func, addr) { read_func(addr, data); c_flag = data & 1; data >>= 1; \
    write_func(addr, data); SET_NZ_FLAGS(data); ADD_CYCLES(cycles); }
#define LSR_A() { c_flag = A & 1; A >>= 1; SET_NZ_FLAGS(A); ADD_CYCLES(2); }
#define LXA(cycles, read_func) { read_func(data); A = X = ((A | 0xEE) & data); SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define NOP() { ADD_CYCLES(2); }
#define ORA(cycles, read_func) { read_func(data); A |= data; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define PHA() { PUSH(A); ADD_CYCLES(3); }
#define PHP() { PUSH(COMBINE_FLAGS() | B_FLAG); ADD_CYCLES(3); }
#define PLA() { A = PULL(); SET_NZ_FLAGS(A); ADD_CYCLES(4); }
#define PLP() { btemp = PULL(); SCATTER_FLAGS(btemp); ADD_CYCLES(4); }
#define RLA(cycles, read_func, write_func, addr) { read_func(addr, data); btemp = c_flag; c_flag = data >> 7; \
    data = (data << 1) | btemp; write_func(addr, data); A &= data; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define ROL(cycles, read_func, write_func, addr) { read_func(addr, data); btemp = c_flag; c_flag = data >> 7; \
    data = (data << 1) | btemp; write_func(addr, data); SET_NZ_FLAGS(data); ADD_CYCLES(cycles); }
#define ROL_A() { btemp = c_flag; c_flag = A >> 7; A = (A << 1) | btemp; SET_NZ_FLAGS(A); ADD_CYCLES(2); }
#define ROR(cycles, read_func, write_func, addr) { read_func(addr, data); btemp = c_flag << 7; c_flag = data & 1; \
    data = (data >> 1) | btemp; write_func(addr, data); SET_NZ_FLAGS(data); ADD_CYCLES(cycles); }
#define ROR_A() { btemp = c_flag << 7; c_flag = A & 1; A = (A >> 1) | btemp; SET_NZ_FLAGS(A); ADD_CYCLES(2); }
#define RRA(cycles, read_func, write_func, addr) { read_func(addr, data); btemp = c_flag << 7; c_flag = data & 1; \
    data = (data >> 1) | btemp; write_func(addr, data); ADC(cycles, EMPTY_READ); }
#define RTI() { btemp = PULL(); SCATTER_FLAGS(btemp); PC = PULL(); PC |= PULL() << 8; ADD_CYCLES(6); \
    if (0 == i_flag && cpu.int_pending && remaining_cycles > 0) { cpu.int_pending = 0; IRQ_PROC(); ADD_CYCLES(INT_CYCLES); } }
#define RTS() { PC = PULL(); PC = (PC | (PULL() << 8)) + 1; ADD_CYCLES(6); }
#define SAX(cycles, read_func, write_func, addr) { read_func(addr); data = A & X; write_func(addr, data); ADD_CYCLES(cycles); }

#ifdef NES6502_DECIMAL
#define SBC(cycles, read_func) { read_func(data); temp = A - data - (c_flag ^ 1); if (d_flag) { uint8_t al, ah; \
      al = (A & 0x0F) - (data & 0x0F) - (c_flag ^ 1); ah = (A >> 4) - (data >> 4); if (al & 0x10) { al -= 6; ah--; \
      } if (ah & 0x10) { ah -= 6; c_flag = 0; } else { c_flag = 1; } v_flag = (A ^ temp) & (A ^ data) & 0x80; \
      SET_NZ_FLAGS(temp & 0xFF); A = (ah << 4) | (al & 0x0F); } else { v_flag = (A ^ temp) & (A ^ data) & 0x80; \
      c_flag = ((temp & 0x100) >> 8) ^ 1; A = (uint8_t) temp; SET_NZ_FLAGS(A & 0xFF); } ADD_CYCLES(cycles); }
#else
#define SBC(cycles, read_func) { read_func(data); temp = A - data - (c_flag ^ 1); v_flag = (A ^ data) & (A ^ temp) & 0x80; \
    c_flag = ((temp >> 8) & 1) ^ 1; A = (uint8_t) temp; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#endif // NES6502_DECIMAL 
#define SBX(cycles, read_func) { read_func(data); temp = (A & X) - data; c_flag = ((temp >> 8) & 1) ^ 1; X = temp & 0xFF; \
    SET_NZ_FLAGS(X); ADD_CYCLES(cycles); }
#define SEC() { c_flag = 1; ADD_CYCLES(2); }
#define SED() { d_flag = 1; ADD_CYCLES(2); }
#define SEI() { i_flag = 1; ADD_CYCLES(2); }
#define SHA(cycles, read_func, write_func, addr) { read_func(addr); data = A & X & ((uint8_t) ((addr >> 8) + 1)); \
    write_func(addr, data); ADD_CYCLES(cycles); }
#define SHS(cycles, read_func, write_func, addr) { read_func(addr); S = A & X; data = S & ((uint8_t) ((addr >> 8) + 1)); \
    write_func(addr, data); ADD_CYCLES(cycles); }
#define SHX(cycles, read_func, write_func, addr) { read_func(addr); data = X & ((uint8_t) ((addr >> 8) + 1)); \
    write_func(addr, data); ADD_CYCLES(cycles); }
#define SHY(cycles, read_func, write_func, addr) { read_func(addr); data = Y & ((uint8_t) ((addr >> 8 ) + 1)); \
    write_func(addr, data); ADD_CYCLES(cycles); }
#define SLO(cycles, read_func, write_func, addr) { read_func(addr, data); c_flag = data >> 7; data <<= 1; \
    write_func(addr, data); A |= data; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define SRE(cycles, read_func, write_func, addr) { read_func(addr, data); c_flag = data & 1; data >>= 1; \
    write_func(addr, data); A ^= data; SET_NZ_FLAGS(A); ADD_CYCLES(cycles); }
#define STA(cycles, read_func, write_func, addr) { read_func(addr); write_func(addr, A); ADD_CYCLES(cycles); }
#define STX(cycles, read_func, write_func, addr) { read_func(addr); write_func(addr, X); ADD_CYCLES(cycles); }
#define STY(cycles, read_func, write_func, addr) { read_func(addr); write_func(addr, Y); ADD_CYCLES(cycles); }
#define TAX() { X = A; SET_NZ_FLAGS(X); ADD_CYCLES(2); }
#define TAY() { Y = A; SET_NZ_FLAGS(Y); ADD_CYCLES(2); }
#define TOP() { PC += 2; ADD_CYCLES(4); }
#define TSX() { X = S; SET_NZ_FLAGS(X); ADD_CYCLES(2); }
#define TXA() { A = X; SET_NZ_FLAGS(A); ADD_CYCLES(2); }
#define TXS() { S = X; ADD_CYCLES(2); }
#define TYA() { A = Y; SET_NZ_FLAGS(A); ADD_CYCLES(2); }

#define  ZP_READBYTE(addr)          ram[(addr)]
#define  ZP_WRITEBYTE(addr, value)  ram[(addr)] = (uint8_t) (value)

#ifdef HOST_LITTLE_ENDIAN
static inline uint32_t zp_readword(register uint8_t address) {
  return (uint32_t) (*(uint16_t *)(ram + address));
}
static inline uint32_t bank_readword(register uint32_t address) {
  return (uint32_t) (*(uint16_t *)(cpu.mem_page[address >> NES6502_BANKSHIFT] + (address & NES6502_BANKMASK)));
}
#else // !HOST_LITTLE_ENDIAN 
static inline uint32_t zp_readword(register uint8_t address) {
  uint32_t x = (uint32_t) * (uint16_t *)(ram + address);
  return (x << 8) | (x >> 8);

}
static inline uint32_t bank_readword(register uint32_t address) {
  uint32_t x = (uint32_t) * (uint16_t *)(cpu.mem_page[address >> NES6502_BANKSHIFT] + (address & NES6502_BANKMASK));
  return (x << 8) | (x >> 8);
}
#endif // !HOST_LITTLE_ENDIAN 

static inline uint8_t bank_readbyte(register uint32_t address) {
  return cpu.mem_page[address >> NES6502_BANKSHIFT][address & NES6502_BANKMASK];
}
static inline void bank_writebyte(register uint32_t address, register uint8_t value) {
  cpu.mem_page[address >> NES6502_BANKSHIFT][address & NES6502_BANKMASK] = value;
}
static uint8_t mem_readbyte(uint32_t address) {
  nes6502_memread *mr;

  if (address < 0x800) { // RAM
    return ram[address];
  } else if (address >= 0x8000) {
    return bank_readbyte(address);
  } else {
    for (mr = cpu.read_handler; mr->min_range != 0xFFFFFFFF; mr++) {
      if (address >= mr->min_range && address <= mr->max_range)
        return mr->read_func(address);
    }
  }
  return bank_readbyte(address);
}

static void mem_writebyte(uint32_t address, uint8_t value) {
  nes6502_memwrite *mw;
  if (address < 0x800) {
    ram[address] = value;
    return;
  } else {
    for (mw = cpu.write_handler; mw->min_range != 0xFFFFFFFF; mw++) {
      if (address >= mw->min_range && address <= mw->max_range) {
        mw->write_func(address, value);
        return;
      }
    }
  }
  bank_writebyte(address, value);
}

void nes6502_setcontext(nes6502_context *context);
void nes6502_setcontext(nes6502_context *context) {
  int loop;
  cpu = *context;
  for (loop = 0; loop < NES6502_NUMBANKS; loop++) {
    if (NULL == cpu.mem_page[loop]) cpu.mem_page[loop] = null_page;
  }
  ram = cpu.mem_page[0];  // quick zero-page/RAM references
  stack = ram + STACK_OFFSET;
}

void nes6502_getcontext(nes6502_context *context);
void nes6502_getcontext(nes6502_context *context) {
  int loop;
  *context = cpu;
  for (loop = 0; loop < NES6502_NUMBANKS; loop++) {
    if (null_page == context->mem_page[loop]) context->mem_page[loop] = NULL;
  }
}

uint8_t nes6502_getbyte(uint32_t address) {
  return bank_readbyte(address);
}

uint32_t nes6502_getcycles(bool reset_flag) {
  uint32_t cycles = cpu.total_cycles;
  if (reset_flag) cpu.total_cycles = 0;
  return cycles;
}

#define  GET_GLOBAL_REGS() { PC = cpu.pc_reg; A = cpu.a_reg; X = cpu.x_reg; Y = cpu.y_reg; SCATTER_FLAGS(cpu.p_reg); S = cpu.s_reg; }
#define  STORE_LOCAL_REGS() { cpu.pc_reg = PC; cpu.a_reg = A; cpu.x_reg = X; cpu.y_reg = Y; cpu.p_reg = COMBINE_FLAGS(); cpu.s_reg = S; }

#define  MIN(a,b)    (((a) < (b)) ? (a) : (b))

#ifdef NES6502_JUMPTABLE
#define  OPCODE_BEGIN(xx)  op##xx:
#define  OPCODE_END if (remaining_cycles <= 0) goto end_execute; goto *opcode_table[bank_readbyte(PC++)];
#else // !NES6502_JUMPTABLE 
#define  OPCODE_BEGIN(xx)  case 0x##xx:
#define  OPCODE_END        break;
#endif // !NES6502_JUMPTABLE 


int nes6502_execute(int timeslice_cycles) {
  int old_cycles = cpu.total_cycles;

  uint32_t temp, addr; // for macros
  uint8_t btemp, baddr; // for macros
  uint8_t data;

  uint8_t n_flag, v_flag, b_flag;
  uint8_t d_flag, i_flag, z_flag, c_flag;

  uint32_t PC;
  uint8_t A, X, Y, S;

#ifdef NES6502_JUMPTABLE
  static const void *opcode_table[256] =
  {
    &&op00, &&op01, &&op02, &&op03, &&op04, &&op05, &&op06, &&op07,
    &&op08, &&op09, &&op0A, &&op0B, &&op0C, &&op0D, &&op0E, &&op0F,
    &&op10, &&op11, &&op12, &&op13, &&op14, &&op15, &&op16, &&op17,
    &&op18, &&op19, &&op1A, &&op1B, &&op1C, &&op1D, &&op1E, &&op1F,
    &&op20, &&op21, &&op22, &&op23, &&op24, &&op25, &&op26, &&op27,
    &&op28, &&op29, &&op2A, &&op2B, &&op2C, &&op2D, &&op2E, &&op2F,
    &&op30, &&op31, &&op32, &&op33, &&op34, &&op35, &&op36, &&op37,
    &&op38, &&op39, &&op3A, &&op3B, &&op3C, &&op3D, &&op3E, &&op3F,
    &&op40, &&op41, &&op42, &&op43, &&op44, &&op45, &&op46, &&op47,
    &&op48, &&op49, &&op4A, &&op4B, &&op4C, &&op4D, &&op4E, &&op4F,
    &&op50, &&op51, &&op52, &&op53, &&op54, &&op55, &&op56, &&op57,
    &&op58, &&op59, &&op5A, &&op5B, &&op5C, &&op5D, &&op5E, &&op5F,
    &&op60, &&op61, &&op62, &&op63, &&op64, &&op65, &&op66, &&op67,
    &&op68, &&op69, &&op6A, &&op6B, &&op6C, &&op6D, &&op6E, &&op6F,
    &&op70, &&op71, &&op72, &&op73, &&op74, &&op75, &&op76, &&op77,
    &&op78, &&op79, &&op7A, &&op7B, &&op7C, &&op7D, &&op7E, &&op7F,
    &&op80, &&op81, &&op82, &&op83, &&op84, &&op85, &&op86, &&op87,
    &&op88, &&op89, &&op8A, &&op8B, &&op8C, &&op8D, &&op8E, &&op8F,
    &&op90, &&op91, &&op92, &&op93, &&op94, &&op95, &&op96, &&op97,
    &&op98, &&op99, &&op9A, &&op9B, &&op9C, &&op9D, &&op9E, &&op9F,
    &&opA0, &&opA1, &&opA2, &&opA3, &&opA4, &&opA5, &&opA6, &&opA7,
    &&opA8, &&opA9, &&opAA, &&opAB, &&opAC, &&opAD, &&opAE, &&opAF,
    &&opB0, &&opB1, &&opB2, &&opB3, &&opB4, &&opB5, &&opB6, &&opB7,
    &&opB8, &&opB9, &&opBA, &&opBB, &&opBC, &&opBD, &&opBE, &&opBF,
    &&opC0, &&opC1, &&opC2, &&opC3, &&opC4, &&opC5, &&opC6, &&opC7,
    &&opC8, &&opC9, &&opCA, &&opCB, &&opCC, &&opCD, &&opCE, &&opCF,
    &&opD0, &&opD1, &&opD2, &&opD3, &&opD4, &&opD5, &&opD6, &&opD7,
    &&opD8, &&opD9, &&opDA, &&opDB, &&opDC, &&opDD, &&opDE, &&opDF,
    &&opE0, &&opE1, &&opE2, &&opE3, &&opE4, &&opE5, &&opE6, &&opE7,
    &&opE8, &&opE9, &&opEA, &&opEB, &&opEC, &&opED, &&opEE, &&opEF,
    &&opF0, &&opF1, &&opF2, &&opF3, &&opF4, &&opF5, &&opF6, &&opF7,
    &&opF8, &&opF9, &&opFA, &&opFB, &&opFC, &&opFD, &&opFE, &&opFF
  };
#endif // NES6502_JUMPTABLE 

  remaining_cycles = timeslice_cycles;
  GET_GLOBAL_REGS();

  // check for DMA cycle burning
  if (cpu.burn_cycles && remaining_cycles > 0) {
    int burn_for;
    burn_for = MIN(remaining_cycles, cpu.burn_cycles);
    ADD_CYCLES(burn_for);
    cpu.burn_cycles -= burn_for;
  }
  if (0 == i_flag && cpu.int_pending && remaining_cycles > 0) {
    cpu.int_pending = 0;
    IRQ_PROC();
    ADD_CYCLES(INT_CYCLES);
  }
#ifdef NES6502_JUMPTABLE
  OPCODE_END
#else // !NES6502_JUMPTABLE 

  // Continue until we run out of cycles
  while (remaining_cycles > 0) {
    switch (bank_readbyte(PC++)) {
#endif // !NES6502_JUMPTABLE 

  OPCODE_BEGIN(00)  // BRK
  BRK();
  OPCODE_END

  OPCODE_BEGIN(01)  // ORA ($nn,X)
  ORA(6, INDIR_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(02)  // JAM
  OPCODE_BEGIN(12)  // JAM
  OPCODE_BEGIN(22)  // JAM
  OPCODE_BEGIN(32)  // JAM
  OPCODE_BEGIN(42)  // JAM
  OPCODE_BEGIN(52)  // JAM
  OPCODE_BEGIN(62)  // JAM
  OPCODE_BEGIN(72)  // JAM
  OPCODE_BEGIN(92)  // JAM
  OPCODE_BEGIN(B2)  // JAM
  OPCODE_BEGIN(D2)  // JAM
  OPCODE_BEGIN(F2)  // JAM
  JAM();
  // kill the CPU
  remaining_cycles = 0;
  OPCODE_END

  OPCODE_BEGIN(03)
  SLO(8, INDIR_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(04)  // NOP $nn
  OPCODE_BEGIN(44)  // NOP $nn
  OPCODE_BEGIN(64)  // NOP $nn
  DOP(3);
  OPCODE_END

  OPCODE_BEGIN(05)  // ORA $nn
  ORA(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(06)  // ASL $nn
  ASL(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(07)  // SLO $nn
  SLO(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(08)  // PHP
  PHP();
  OPCODE_END

  OPCODE_BEGIN(09)  // ORA #$nn
  ORA(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(0A)  // ASL A
  ASL_A();
  OPCODE_END

  OPCODE_BEGIN(0B)  // ANC #$nn
  ANC(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(0C)  // NOP $nnnn
  TOP();
  OPCODE_END

  OPCODE_BEGIN(0D)  // ORA $nnnn
  ORA(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(0E)  // ASL $nnnn
  ASL(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(0F)  // SLO $nnnn
  SLO(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(10)  // BPL $nnnn
  BPL();
  OPCODE_END

  OPCODE_BEGIN(11)  // ORA ($nn),Y
  ORA(5, INDIR_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(13)  // SLO ($nn),Y
  SLO(8, INDIR_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(14)  // NOP $nn,X
  OPCODE_BEGIN(34)  // NOP
  OPCODE_BEGIN(54)  // NOP $nn,X
  OPCODE_BEGIN(74)  // NOP $nn,X
  OPCODE_BEGIN(D4)  // NOP $nn,X
  OPCODE_BEGIN(F4)  // NOP ($nn,X)
  DOP(4);
  OPCODE_END

  OPCODE_BEGIN(15)  // ORA $nn,X
  ORA(4, ZP_IND_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(16)  // ASL $nn,X
  ASL(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(17)  // SLO $nn,X
  SLO(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(18)  // CLC
  CLC();
  OPCODE_END

  OPCODE_BEGIN(19)  // ORA $nnnn,Y
  ORA(4, ABS_IND_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(1A)  // NOP
  OPCODE_BEGIN(3A)  // NOP
  OPCODE_BEGIN(5A)  // NOP
  OPCODE_BEGIN(7A)  // NOP
  OPCODE_BEGIN(DA)  // NOP
  OPCODE_BEGIN(FA)  // NOP
  NOP();
  OPCODE_END

  OPCODE_BEGIN(1B)  // SLO $nnnn,Y
  SLO(7, ABS_IND_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(1C)  // NOP $nnnn,X
  OPCODE_BEGIN(3C)  // NOP $nnnn,X
  OPCODE_BEGIN(5C)  // NOP $nnnn,X
  OPCODE_BEGIN(7C)  // NOP $nnnn,X
  OPCODE_BEGIN(DC)  // NOP $nnnn,X
  OPCODE_BEGIN(FC)  // NOP $nnnn,X
  TOP();
  OPCODE_END

  OPCODE_BEGIN(1D)  // ORA $nnnn,X
  ORA(4, ABS_IND_X_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(1E)  // ASL $nnnn,X
  ASL(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(1F)  // SLO $nnnn,X
  SLO(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(20)  // JSR $nnnn
  JSR();
  OPCODE_END

  OPCODE_BEGIN(21)  // AND ($nn,X)
  AND(6, INDIR_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(23)  // RLA ($nn,X)
  RLA(8, INDIR_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(24)  // BIT $nn
  BIT(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(25)  // AND $nn
  AND(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(26)  // ROL $nn
  ROL(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(27)  // RLA $nn
  RLA(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(28)  // PLP
  PLP();
  OPCODE_END

  OPCODE_BEGIN(29)  // AND #$nn
  AND(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(2A)  // ROL A
  ROL_A();
  OPCODE_END

  OPCODE_BEGIN(2B)  // ANC #$nn
  ANC(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(2C)  // BIT $nnnn
  BIT(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(2D)  // AND $nnnn
  AND(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(2E)  // ROL $nnnn
  ROL(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(2F)  // RLA $nnnn
  RLA(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(30)  // BMI $nnnn
  BMI();
  OPCODE_END

  OPCODE_BEGIN(31)  // AND ($nn),Y
  AND(5, INDIR_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(33)  // RLA ($nn),Y
  RLA(8, INDIR_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(35)  // AND $nn,X
  AND(4, ZP_IND_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(36)  // ROL $nn,X
  ROL(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(37)  // RLA $nn,X
  RLA(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(38)  // SEC
  SEC();
  OPCODE_END

  OPCODE_BEGIN(39)  // AND $nnnn,Y
  AND(4, ABS_IND_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(3B)  // RLA $nnnn,Y
  RLA(7, ABS_IND_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(3D)  // AND $nnnn,X
  AND(4, ABS_IND_X_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(3E)  // ROL $nnnn,X
  ROL(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(3F)  // RLA $nnnn,X
  RLA(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(40)  // RTI
  RTI();
  OPCODE_END

  OPCODE_BEGIN(41)  // EOR ($nn,X)
  EOR(6, INDIR_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(43)  // SRE ($nn,X)
  SRE(8, INDIR_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(45)  // EOR $nn
  EOR(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(46)  // LSR $nn
  LSR(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(47)  // SRE $nn
  SRE(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(48)  // PHA
  PHA();
  OPCODE_END

  OPCODE_BEGIN(49)  // EOR #$nn
  EOR(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(4A)  // LSR A
  LSR_A();
  OPCODE_END

  OPCODE_BEGIN(4B)  // ASR #$nn
  ASR(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(4C)  // JMP $nnnn
  JMP_ABSOLUTE();
  OPCODE_END

  OPCODE_BEGIN(4D)  // EOR $nnnn
  EOR(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(4E)  // LSR $nnnn
  LSR(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(4F)  // SRE $nnnn
  SRE(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(50)  // BVC $nnnn
  BVC();
  OPCODE_END

  OPCODE_BEGIN(51)  // EOR ($nn),Y
  EOR(5, INDIR_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(53)  // SRE ($nn),Y
  SRE(8, INDIR_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(55)  // EOR $nn,X
  EOR(4, ZP_IND_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(56)  // LSR $nn,X
  LSR(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(57)  // SRE $nn,X
  SRE(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(58)  // CLI
  CLI();
  OPCODE_END

  OPCODE_BEGIN(59)  // EOR $nnnn,Y
  EOR(4, ABS_IND_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(5B)  // SRE $nnnn,Y
  SRE(7, ABS_IND_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(5D)  // EOR $nnnn,X
  EOR(4, ABS_IND_X_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(5E)  // LSR $nnnn,X
  LSR(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(5F)  // SRE $nnnn,X
  SRE(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(60)  // RTS
  RTS();
  OPCODE_END

  OPCODE_BEGIN(61)  // ADC ($nn,X)
  ADC(6, INDIR_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(63)  // RRA ($nn,X)
  RRA(8, INDIR_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(65)  // ADC $nn
  ADC(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(66)  // ROR $nn
  ROR(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(67)  // RRA $nn
  RRA(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(68)  // PLA
  PLA();
  OPCODE_END

  OPCODE_BEGIN(69)  // ADC #$nn
  ADC(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(6A)  // ROR A
  ROR_A();
  OPCODE_END

  OPCODE_BEGIN(6B)  // ARR #$nn
  ARR(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(6C)  // JMP ($nnnn)
  JMP_INDIRECT();
  OPCODE_END

  OPCODE_BEGIN(6D)  // ADC $nnnn
  ADC(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(6E)  // ROR $nnnn
  ROR(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(6F)  // RRA $nnnn
  RRA(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(70)  // BVS $nnnn
  BVS();
  OPCODE_END

  OPCODE_BEGIN(71)  // ADC ($nn),Y
  ADC(5, INDIR_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(73)  // RRA ($nn),Y
  RRA(8, INDIR_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(75)  // ADC $nn,X
  ADC(4, ZP_IND_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(76)  // ROR $nn,X
  ROR(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(77)  // RRA $nn,X
  RRA(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(78)  // SEI
  SEI();
  OPCODE_END

  OPCODE_BEGIN(79)  // ADC $nnnn,Y
  ADC(4, ABS_IND_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(7B)  // RRA $nnnn,Y
  RRA(7, ABS_IND_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(7D)  // ADC $nnnn,X
  ADC(4, ABS_IND_X_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(7E)  // ROR $nnnn,X
  ROR(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(7F)  // RRA $nnnn,X
  RRA(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(80)  // NOP #$nn
  OPCODE_BEGIN(82)  // NOP #$nn
  OPCODE_BEGIN(89)  // NOP #$nn
  OPCODE_BEGIN(C2)  // NOP #$nn
  OPCODE_BEGIN(E2)  // NOP #$nn
  DOP(2);
  OPCODE_END

  OPCODE_BEGIN(81)  // STA ($nn,X)
  STA(6, INDIR_X_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(83)  // SAX ($nn,X)
  SAX(6, INDIR_X_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(84)  // STY $nn
  STY(3, ZERO_PAGE_ADDR, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(85)  // STA $nn
  STA(3, ZERO_PAGE_ADDR, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(86)  // STX $nn
  STX(3, ZERO_PAGE_ADDR, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(87)  // SAX $nn
  SAX(3, ZERO_PAGE_ADDR, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(88)  // DEY
  DEY();
  OPCODE_END

  OPCODE_BEGIN(8A)  // TXA
  TXA();
  OPCODE_END

  OPCODE_BEGIN(8B)  // ANE #$nn
  ANE(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(8C)  // STY $nnnn
  STY(4, ABSOLUTE_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(8D)  // STA $nnnn
  STA(4, ABSOLUTE_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(8E)  // STX $nnnn
  STX(4, ABSOLUTE_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(8F)  // SAX $nnnn
  SAX(4, ABSOLUTE_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(90)  // BCC $nnnn
  BCC();
  OPCODE_END

  OPCODE_BEGIN(91)  // STA ($nn),Y
  STA(6, INDIR_Y_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(93)  // SHA ($nn),Y
  SHA(6, INDIR_Y_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(94)  // STY $nn,X
  STY(4, ZP_IND_X_ADDR, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(95)  // STA $nn,X
  STA(4, ZP_IND_X_ADDR, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(96)  // STX $nn,Y
  STX(4, ZP_IND_Y_ADDR, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(97)  // SAX $nn,Y
  SAX(4, ZP_IND_Y_ADDR, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(98)  // TYA
  TYA();
  OPCODE_END

  OPCODE_BEGIN(99)  // STA $nnnn,Y
  STA(5, ABS_IND_Y_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(9A)  // TXS
  TXS();
  OPCODE_END

  OPCODE_BEGIN(9B)  // SHS $nnnn,Y
  SHS(5, ABS_IND_Y_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(9C)  // SHY $nnnn,X
  SHY(5, ABS_IND_X_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(9D)  // STA $nnnn,X
  STA(5, ABS_IND_X_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(9E)  // SHX $nnnn,Y
  SHX(5, ABS_IND_Y_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(9F)  // SHA $nnnn,Y
  SHA(5, ABS_IND_Y_ADDR, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(A0)  // LDY #$nn
  LDY(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(A1)  // LDA ($nn,X)
  LDA(6, INDIR_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(A2)  // LDX #$nn
  LDX(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(A3)  // LAX ($nn,X)
  LAX(6, INDIR_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(A4)  // LDY $nn
  LDY(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(A5)  // LDA $nn
  LDA(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(A6)  // LDX $nn
  LDX(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(A7)  // LAX $nn
  LAX(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(A8)  // TAY
  TAY();
  OPCODE_END

  OPCODE_BEGIN(A9)  // LDA #$nn
  LDA(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(AA)  // TAX
  TAX();
  OPCODE_END

  OPCODE_BEGIN(AB)  // LXA #$nn
  LXA(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(AC)  // LDY $nnnn
  LDY(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(AD)  // LDA $nnnn
  LDA(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(AE)  // LDX $nnnn
  LDX(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(AF)  // LAX $nnnn
  LAX(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(B0)  // BCS $nnnn
  BCS();
  OPCODE_END

  OPCODE_BEGIN(B1)  // LDA ($nn),Y
  LDA(5, INDIR_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(B3)  // LAX ($nn),Y
  LAX(5, INDIR_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(B4)  // LDY $nn,X
  LDY(4, ZP_IND_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(B5)  // LDA $nn,X
  LDA(4, ZP_IND_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(B6)  // LDX $nn,Y
  LDX(4, ZP_IND_Y_BYTE);
  OPCODE_END

  OPCODE_BEGIN(B7)  // LAX $nn,Y
  LAX(4, ZP_IND_Y_BYTE);
  OPCODE_END

  OPCODE_BEGIN(B8)  // CLV
  CLV();
  OPCODE_END

  OPCODE_BEGIN(B9)  // LDA $nnnn,Y
  LDA(4, ABS_IND_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(BA)  // TSX
  TSX();
  OPCODE_END

  OPCODE_BEGIN(BB)  // LAS $nnnn,Y
  LAS(4, ABS_IND_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(BC)  // LDY $nnnn,X
  LDY(4, ABS_IND_X_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(BD)  // LDA $nnnn,X
  LDA(4, ABS_IND_X_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(BE)  // LDX $nnnn,Y
  LDX(4, ABS_IND_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(BF)  // LAX $nnnn,Y
  LAX(4, ABS_IND_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(C0)  // CPY #$nn
  CPY(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(C1)  // CMP ($nn,X)
  CMP(6, INDIR_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(C3)  // DCP ($nn,X)
  DCP(8, INDIR_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(C4)  // CPY $nn
  CPY(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(C5)  // CMP $nn
  CMP(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(C6)  // DEC $nn
  _DEC(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(C7)  // DCP $nn
  DCP(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(C8)  // INY
  INY();
  OPCODE_END

  OPCODE_BEGIN(C9)  // CMP #$nn
  CMP(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(CA)  // DEX
  DEX();
  OPCODE_END

  OPCODE_BEGIN(CB)  // SBX #$nn
  SBX(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(CC)  // CPY $nnnn
  CPY(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(CD)  // CMP $nnnn
  CMP(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(CE)  // DEC $nnnn
  _DEC(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(CF)  // DCP $nnnn
  DCP(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(D0)  // BNE $nnnn
  BNE();
  OPCODE_END

  OPCODE_BEGIN(D1)  // CMP ($nn),Y
  CMP(5, INDIR_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(D3)  // DCP ($nn),Y
  DCP(8, INDIR_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(D5)  // CMP $nn,X
  CMP(4, ZP_IND_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(D6)  // DEC $nn,X
  _DEC(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(D7)  // DCP $nn,X
  DCP(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(D8)  // CLD
  CLD();
  OPCODE_END

  OPCODE_BEGIN(D9)  // CMP $nnnn,Y
  CMP(4, ABS_IND_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(DB)  // DCP $nnnn,Y
  DCP(7, ABS_IND_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(DD)  // CMP $nnnn,X
  CMP(4, ABS_IND_X_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(DE)  // DEC $nnnn,X
  _DEC(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(DF)  // DCP $nnnn,X
  DCP(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(E0)  // CPX #$nn
  CPX(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(E1)  // SBC ($nn,X)
  SBC(6, INDIR_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(E3)  // ISB ($nn,X)
  ISB(8, INDIR_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(E4)  // CPX $nn
  CPX(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(E5)  // SBC $nn
  SBC(3, ZERO_PAGE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(E6)  // INC $nn
  INC(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(E7)  // ISB $nn
  ISB(5, ZERO_PAGE, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(E8)  // INX
  INX();
  OPCODE_END

  OPCODE_BEGIN(E9)  // SBC #$nn
  OPCODE_BEGIN(EB)  // USBC #$nn
  SBC(2, IMMEDIATE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(EA)  // NOP
  NOP();
  OPCODE_END

  OPCODE_BEGIN(EC)  // CPX $nnnn
  CPX(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(ED)  // SBC $nnnn
  SBC(4, ABSOLUTE_BYTE);
  OPCODE_END

  OPCODE_BEGIN(EE)  // INC $nnnn
  INC(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(EF)  // ISB $nnnn
  ISB(6, ABSOLUTE, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(F0)  // BEQ $nnnn
  BEQ();
  OPCODE_END

  OPCODE_BEGIN(F1)  // SBC ($nn),Y
  SBC(5, INDIR_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(F3)  // ISB ($nn),Y
  ISB(8, INDIR_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(F5)  // SBC $nn,X
  SBC(4, ZP_IND_X_BYTE);
  OPCODE_END

  OPCODE_BEGIN(F6)  // INC $nn,X
  INC(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(F7)  // ISB $nn,X
  ISB(6, ZP_IND_X, ZP_WRITEBYTE, baddr);
  OPCODE_END

  OPCODE_BEGIN(F8)  // SED
  SED();
  OPCODE_END

  OPCODE_BEGIN(F9)  // SBC $nnnn,Y
  SBC(4, ABS_IND_Y_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(FB)  // ISB $nnnn,Y
  ISB(7, ABS_IND_Y, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(FD)  // SBC $nnnn,X
  SBC(4, ABS_IND_X_BYTE_READ);
  OPCODE_END

  OPCODE_BEGIN(FE)  // INC $nnnn,X
  INC(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END

  OPCODE_BEGIN(FF)  // ISB $nnnn,X
  ISB(7, ABS_IND_X, mem_writebyte, addr);
  OPCODE_END
#ifdef NES6502_JUMPTABLE
end_execute:
#else // !NES6502_JUMPTABLE 
}
}
#endif // !NES6502_JUMPTABLE 
  STORE_LOCAL_REGS();
  return (cpu.total_cycles - old_cycles);
}

void nes6502_reset(void) // Issue a CPU Reset
{
  cpu.p_reg = Z_FLAG | R_FLAG | I_FLAG;     // Reserved bit always 1
  cpu.int_pending = 0;                      // No pending interrupts
  cpu.int_latency = 0;                      // No latent interrupts
  cpu.pc_reg = bank_readword(RESET_VECTOR); // Fetch reset vector
  cpu.burn_cycles = RESET_CYCLES;
  cpu.jammed = false;
}

#define  DECLARE_LOCAL_REGS uint32_t PC; uint8_t A, X, Y, S; uint8_t n_flag, v_flag, b_flag; uint8_t d_flag, i_flag, z_flag, c_flag;

void nes6502_nmi(void) { // Non-maskable interrupt
  DECLARE_LOCAL_REGS

  if (false == cpu.jammed)
  {
    GET_GLOBAL_REGS();
    NMI_PROC();
    cpu.burn_cycles += INT_CYCLES;
    STORE_LOCAL_REGS();
  }
}

void nes6502_irq(void) { // Interrupt request
  DECLARE_LOCAL_REGS
  if (false == cpu.jammed) {
    GET_GLOBAL_REGS();
    if (0 == i_flag) {
      IRQ_PROC();
      cpu.burn_cycles += INT_CYCLES;
    } else {
      cpu.int_pending = 1;
    }
    STORE_LOCAL_REGS();
  }
}

void nes6502_burn(int cycles) { // Set dead cycle period
  cpu.burn_cycles += cycles;
}

void nes6502_release(void) { // Release our timeslice
  remaining_cycles = 0;
}
//--------------------------------------------------------------------------------
//NES.C part
void nes_setfiq(uint8_t value) {
  nes.fiq_state = value;
  nes.fiq_cycles = (int) NES_FIQ_PERIOD;
}
void nes_nmi(void) {
  nes6502_nmi();
}
//--------------------------------------------------------------------------------
//PPU.C:

// PPU access
#define  PPU_MEM(x)           ppu.page[(x) >> 10][(x)]

// Background (color 0) and solid sprite pixel flags
#define  BG_TRANS             0x80
#define  SP_PIXEL             0x40
#define  BG_CLEAR(V)          ((V) & BG_TRANS)
#define  BG_SOLID(V)          (0 == BG_CLEAR(V))
#define  SP_CLEAR(V)          (0 == ((V) & SP_PIXEL))

// Full BG color
#define  FULLBG               (ppu.palette[0] | BG_TRANS)

void ppu_displaysprites(bool display) {
  ppu.drawsprites = display;
}

void ppu_getcontext(ppu_t *dest_ppu);
void ppu_getcontext(ppu_t *dest_ppu) {
  int nametab[4];

  *dest_ppu = ppu;

  // we can't just copy contexts here, because more than likely,
  // the top 8 pages of the ppu are pointing to internal PPU memory,
  // which means we need to recalculate the page pointers.
  // TODO: we can either get rid of the page pointing in the code,
  // or add more robust checks to make sure that pages 8-15 are
  // definitely pointing to internal PPU RAM, not just something
  // that some crazy mapper paged in.

  nametab[0] = (ppu.page[8] - ppu.nametab + 0x2000) >> 10;
  nametab[1] = (ppu.page[9] - ppu.nametab + 0x2400) >> 10;
  nametab[2] = (ppu.page[10] - ppu.nametab + 0x2800) >> 10;
  nametab[3] = (ppu.page[11] - ppu.nametab + 0x2C00) >> 10;

  dest_ppu->page[8] = dest_ppu->nametab + (nametab[0] << 10) - 0x2000;
  dest_ppu->page[9] = dest_ppu->nametab + (nametab[1] << 10) - 0x2400;
  dest_ppu->page[10] = dest_ppu->nametab + (nametab[2] << 10) - 0x2800;
  dest_ppu->page[11] = dest_ppu->nametab + (nametab[3] << 10) - 0x2C00;
  dest_ppu->page[12] = dest_ppu->page[8] - 0x1000;
  dest_ppu->page[13] = dest_ppu->page[9] - 0x1000;
  dest_ppu->page[14] = dest_ppu->page[10] - 0x1000;
  dest_ppu->page[15] = dest_ppu->page[11] - 0x1000;
}

void ppu_setcontext(ppu_t *src_ppu);
void ppu_setcontext(ppu_t *src_ppu) {
  int nametab[4];

  ppu = *src_ppu;

  // we can't just copy contexts here, because more than likely,
  // the top 8 pages of the ppu are pointing to internal PPU memory,
  // which means we need to recalculate the page pointers.
  // TODO: we can either get rid of the page pointing in the code,
  // or add more robust checks to make sure that pages 8-15 are
  // definitely pointing to internal PPU RAM, not just something
  // that some crazy mapper paged in.

  nametab[0] = (src_ppu->page[8] - src_ppu->nametab + 0x2000) >> 10;
  nametab[1] = (src_ppu->page[9] - src_ppu->nametab + 0x2400) >> 10;
  nametab[2] = (src_ppu->page[10] - src_ppu->nametab + 0x2800) >> 10;
  nametab[3] = (src_ppu->page[11] - src_ppu->nametab + 0x2C00) >> 10;

  ppu.page[8] = ppu.nametab + (nametab[0] << 10) - 0x2000;
  ppu.page[9] = ppu.nametab + (nametab[1] << 10) - 0x2400;
  ppu.page[10] = ppu.nametab + (nametab[2] << 10) - 0x2800;
  ppu.page[11] = ppu.nametab + (nametab[3] << 10) - 0x2C00;
  ppu.page[12] = ppu.page[8] - 0x1000;
  ppu.page[13] = ppu.page[9] - 0x1000;
  ppu.page[14] = ppu.page[10] - 0x1000;
  ppu.page[15] = ppu.page[11] - 0x1000;
}

ppu_t *ppu_create(void);
ppu_t *ppu_create(void) {
  static bool pal_generated = false;
  ppu_t *temp;

  if (NULL == temp) temp = (ppu_t*) malloc(sizeof(ppu_t));
  if (NULL == temp)
    return NULL;

  memset(temp, 0, sizeof(ppu_t));

  temp->latchfunc = NULL;
  temp->vromswitch = NULL;
  temp->vram_present = false;
  temp->drawsprites = true;

  // TODO: probably a better way to do this...
  if (false == pal_generated)
  {
    ///pal_generate();
    pal_generated = true;
  }

  ///ppu_setdefaultpal(temp);

  return temp;
}

void ppu_setpage(int size, int page_num, uint8_t *location)
{
  // deliberately fall through
  switch (size)
  {
    case 8:
      ppu.page[page_num++] = location;
      ppu.page[page_num++] = location;
      ppu.page[page_num++] = location;
      ppu.page[page_num++] = location;
    case 4:
      ppu.page[page_num++] = location;
      ppu.page[page_num++] = location;
    case 2:
      ppu.page[page_num++] = location;
    case 1:
      ppu.page[page_num++] = location;
      break;
  }
}

// make sure $3000-$3F00 mirrors $2000-$2F00
void ppu_mirrorhipages(void)
{
  ppu.page[12] = ppu.page[8] - 0x1000;
  ppu.page[13] = ppu.page[9] - 0x1000;
  ppu.page[14] = ppu.page[10] - 0x1000;
  ppu.page[15] = ppu.page[11] - 0x1000;
}

void ppu_mirror(int nt1, int nt2, int nt3, int nt4)
{
  ppu.page[8] = ppu.nametab + (nt1 << 10) - 0x2000;
  ppu.page[9] = ppu.nametab + (nt2 << 10) - 0x2400;
  ppu.page[10] = ppu.nametab + (nt3 << 10) - 0x2800;
  ppu.page[11] = ppu.nametab + (nt4 << 10) - 0x2C00;
  ppu.page[12] = ppu.page[8] - 0x1000;
  ppu.page[13] = ppu.page[9] - 0x1000;
  ppu.page[14] = ppu.page[10] - 0x1000;
  ppu.page[15] = ppu.page[11] - 0x1000;
}

// bleh, for snss
uint8_t *ppu_getpage(int page)
{
  return ppu.page[page];
}

void mem_trash(uint8_t *buffer, int length)
{
  int i;

  for (i = 0; i < length; i++)
    buffer[i] = (uint8_t) rand();
}

// reset state of ppu
void ppu_reset()
{
  ///if (HARD_RESET == reset_type)
  mem_trash(ppu.oam, 256);
  ///memset(ppu.oam, 0, 256);

  ppu.ctrl0 = 0;
  ppu.ctrl1 = PPU_CTRL1F_OBJON | PPU_CTRL1F_BGON;
  ppu.stat = 0;
  ppu.flipflop = 0;
  ppu.vaddr_latch = 0x2000;
  ppu.vaddr = ppu.vaddr_latch = 0x2000;

  ppu.oam_addr = 0;
  ppu.tile_xofs = 0;

  ppu.latch = 0;
  ppu.vram_accessible = true;
}

// we render a scanline of graphics first so we know exactly
// where the sprite 0 strike is going to occur (in terms of
// cpu cycles), using the relation that 3 pixels == 1 cpu cycle
void ppu_setstrike(int x_loc)
{
  if (false == ppu.strikeflag)
  {
    ppu.strikeflag = true;

    // 3 pixels per cpu cycle
    ppu.strike_cycle = nes6502_getcycles(false) + (x_loc / 3);
  }
}

void ppu_oamdma(uint8_t value)
{
  uint32_t cpu_address;
  uint8_t oam_loc;

  cpu_address = (uint32_t) (value << 8);

  // Sprite DMA starts at the current SPRRAM address
  oam_loc = ppu.oam_addr;
  do
  {
    ppu.oam[oam_loc++] = nes6502_getbyte(cpu_address++);
  }
  while (oam_loc != ppu.oam_addr);

  // TODO: enough with houdini
  cpu_address -= 256;
  // Odd address in $2003
  if ((ppu.oam_addr >> 2) & 1) {
    for (oam_loc = 4; oam_loc < 8; oam_loc++) ppu.oam[oam_loc] = nes6502_getbyte(cpu_address++);
    cpu_address += 248;
    for (oam_loc = 0; oam_loc < 4; oam_loc++) ppu.oam[oam_loc] = nes6502_getbyte(cpu_address++);
  } else { // Even address in $2003
    for (oam_loc = 0; oam_loc < 8; oam_loc++)
      ppu.oam[oam_loc] = nes6502_getbyte(cpu_address++);
  }

  // make the CPU spin for DMA cycles
  nes6502_burn(513);
  nes6502_release();
}

void ppu_writehigh(uint32_t address, uint8_t value)
{
  switch (address)
  {
    case PPU_OAMDMA:
      ppu_oamdma(value);
      break;

    case PPU_JOY0:
      // VS system VROM switching - bleh!
      if (ppu.vromswitch) ppu.vromswitch(value);

      // see if we need to strobe them joypads
      value &= 1;

      if (0 == value && ppu.strobe) input_strobe();

      ppu.strobe = value;

      //NES Controller strobe
      ///  digitalWrite(NESCTRL_Latch, HIGH);
      ///  digitalWrite(NESCTRL_Latch, LOW);
      //----------------------

      break;

    case PPU_JOY1: // frame IRQ control
      nes_setfiq(value);
      break;

    default:
      break;
  }
}

uint8_t ppu_readhigh(uint32_t address) {
  uint8_t value;
  switch (address)
  {
    case PPU_JOY0:
      value = get_pad0();

      ///      if (digitalRead(NESCTRL_Data) == LOW) value=255;
      ///      digitalWrite(NESCTRL_Clock, HIGH);
      ///      digitalWrite(NESCTRL_Clock, LOW);

      ///pad0_readcount++;
      break;

    case PPU_JOY1:
      value = 0;
      break;

    default:
      value = 0xFF;
      break;
  }

  return value;
}

// Read from $2000-$2007
uint8_t ppu_read(uint32_t address) {
  uint8_t value;
  // handle mirrored reads up to $3FFF
  switch (address & 0x2007)
  {
    case PPU_STAT:
      value = (ppu.stat & 0xE0) | (ppu.latch & 0x1F);

      if (ppu.strikeflag) {
        if (nes6502_getcycles(false) >= ppu.strike_cycle)
          value |= PPU_STATF_STRIKE;
      }

      // clear both vblank flag and vram address flipflop
      ppu.stat &= ~PPU_STATF_VBLANK;
      ppu.flipflop = 0;
      break;

    case PPU_VDATA:
      // buffered VRAM reads
      value = ppu.latch = ppu.vdata_latch;

      // VRAM only accessible during VBL
      if ((ppu.bg_on || ppu.obj_on) && !ppu.vram_accessible) {
        ppu.vdata_latch = 0xFF;
        ///nes_log_printf("VRAM read at $%04X, scanline %d\n", ppu.vaddr, nes_getcontextptr()->scanline);
      } else {
        uint32_t addr = ppu.vaddr;
        if (addr >= 0x3000) addr -= 0x1000;
        ppu.vdata_latch = PPU_MEM(addr);
      }
      ppu.vaddr += ppu.vaddr_inc;
      ppu.vaddr &= 0x3FFF;
      break;

    case PPU_OAMDATA:
    case PPU_CTRL0:
    case PPU_CTRL1:
    case PPU_OAMADDR:
    case PPU_SCROLL:
    case PPU_VADDR:
    default:
      value = ppu.latch;
      break;
  }

  return value;
}

// Write to $2000-$2007
void ppu_write(uint32_t address, uint8_t value) {
  // write goes into ppu latch...
  ppu.latch = value;

  switch (address & 0x2007)
  {
    case PPU_CTRL0:
      ppu.ctrl0 = value;
      ppu.obj_height = (value & PPU_CTRL0F_OBJ16) ? 16 : 8;
      ppu.bg_base = (value & PPU_CTRL0F_BGADDR) ? 0x1000 : 0;
      ppu.obj_base = (value & PPU_CTRL0F_OBJADDR) ? 0x1000 : 0;
      ppu.vaddr_inc = (value & PPU_CTRL0F_ADDRINC) ? 32 : 1;
      ppu.tile_nametab = value & PPU_CTRL0F_NAMETAB;

      // Mask out bits 10 & 11 in the ppu latch
      ppu.vaddr_latch &= ~0x0C00;
      ppu.vaddr_latch |= ((value & 3) << 10);
      break;
    case PPU_CTRL1:
      ppu.ctrl1 = value;
      ppu.obj_on = (value & PPU_CTRL1F_OBJON) ? true : false;
      ppu.bg_on = (value & PPU_CTRL1F_BGON) ? true : false;
      ppu.obj_mask = (value & PPU_CTRL1F_OBJMASK) ? false : true;
      ppu.bg_mask = (value & PPU_CTRL1F_BGMASK) ? false : true;
      break;
    case PPU_OAMADDR:
      ppu.oam_addr = value;
      break;
    case PPU_OAMDATA:
      ppu.oam[ppu.oam_addr++] = value;
      break;
    case PPU_SCROLL:
      if (0 == ppu.flipflop) {
        // Mask out bits 4 - 0 in the ppu latch
        ppu.vaddr_latch &= ~0x001F;
        ppu.vaddr_latch |= (value >> 3);    // Tile number
        ppu.tile_xofs = (value & 7);  // Tile offset (0-7 pix)
      } else {
        // Mask out bits 14-12 and 9-5 in the ppu latch
        ppu.vaddr_latch &= ~0x73E0;
        ppu.vaddr_latch |= ((value & 0xF8) << 2);   // Tile number
        ppu.vaddr_latch |= ((value & 7) << 12);     // Tile offset (0-7 pix)
      }
      ppu.flipflop ^= 1;
      break;
    case PPU_VADDR:
      if (0 == ppu.flipflop) {
        // Mask out bits 15-8 in ppu latch
        ppu.vaddr_latch &= ~0xFF00;
        ppu.vaddr_latch |= ((value & 0x3F) << 8);
      } else {
        // Mask out bits 7-0 in ppu latch
        ppu.vaddr_latch &= ~0x00FF;
        ppu.vaddr_latch |= value;
        ppu.vaddr = ppu.vaddr_latch;
      }
      ppu.flipflop ^= 1;
      break;
    case PPU_VDATA:
      if (ppu.vaddr < 0x3F00) {
        // VRAM only accessible during scanlines 241-260
        if ((ppu.bg_on || ppu.obj_on) && !ppu.vram_accessible) {
          ///nes_log_printf("VRAM write to $%04X, scanline %d\n", ppu.vaddr, nes_getcontextptr()->scanline);
          PPU_MEM(ppu.vaddr) = 0xFF; // corrupt
        } else {
          uint32_t addr = ppu.vaddr;
          if (false == ppu.vram_present && addr >= 0x3000) ppu.vaddr -= 0x1000;
          PPU_MEM(addr) = value;
        }
      } else {
        if (0 == (ppu.vaddr & 0x0F)) {
          int i;
          for (i = 0; i < 8; i ++) ppu.palette[i << 2] = (value & 0x3F) | BG_TRANS;
        } else if (ppu.vaddr & 3) {
          ppu.palette[ppu.vaddr & 0x1F] = value & 0x3F;
        }
      }
      ppu.vaddr += ppu.vaddr_inc;
      ppu.vaddr &= 0x3FFF;
      break;
    default:
      break;
  }
}

Did you follow the instruction in the last line above, to enable software spi?

No I did not, should I edit the header file?

That instruction seems very clear to me, and failure to follow it could easily explain the problem you are having.

Not sure what I should edit? I've uncommented the ENABLE_SOFTWARE_SPI_CLASS 1 line in both files but I am still getting the same error. Here is the header file SdFatConfig.h

/**
 * Copyright (c) 20011-2017 Bill Greiman
 * This file is part of the SdFat library for SD memory cards.
 *
 * MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
/**
 * \file
 * \brief configuration definitions
 */
#ifndef SdFatConfig_h
#define SdFatConfig_h
#include <Arduino.h>
#include <stdint.h>
#ifdef __AVR__
#include <avr/io.h>
#endif  // __AVR__
//------------------------------------------------------------------------------
/**
 * Set USE_LONG_FILE_NAMES nonzero to use long file names (LFN).
 * Long File Name are limited to a maximum length of 255 characters.
 *
 * This implementation allows 7-bit characters in the range
 * 0X20 to 0X7E except the following characters are not allowed:
 *
 *  < (less than)
 *  > (greater than)
 *  : (colon)
 *  " (double quote)
 *  / (forward slash)
 *  \ (backslash)
 *  | (vertical bar or pipe)
 *  ? (question mark)
 *  * (asterisk)
 *
 */
#define USE_LONG_FILE_NAMES 1
//------------------------------------------------------------------------------
/**
 * If the symbol ENABLE_EXTENDED_TRANSFER_CLASS is nonzero, the class SdFatEX
 * will be defined. If the symbol ENABLE_SOFTWARE_SPI_CLASS is also nonzero,
 * the class SdFatSoftSpiEX will be defined.
 *
 * These classes used extended multi-block SD I/O for better performance.
 * the SPI bus may not be shared with other devices in this mode.
 */
#define ENABLE_EXTENDED_TRANSFER_CLASS 0
//-----------------------------------------------------------------------------
/**
 * If the symbol USE_STANDARD_SPI_LIBRARY is nonzero, the classes SdFat and
 * SdFatEX use the standard Arduino SPI.h library. If USE_STANDARD_SPI_LIBRARY
 * is zero, an optimized custom SPI driver is used if it exists.
 */
#define USE_STANDARD_SPI_LIBRARY 0
//-----------------------------------------------------------------------------
/**
 * If the symbol ENABLE_SOFTWARE_SPI_CLASS is nonzero, the class SdFatSoftSpi
 * will be defined. If ENABLE_EXTENDED_TRANSFER_CLASS is also nonzero,
 * the class SdFatSoftSpiEX will be defined.
 */
#define ENABLE_SOFTWARE_SPI_CLASS 1
//------------------------------------------------------------------------------
/**
 * If CHECK_FLASH_PROGRAMMING is zero, overlap of single sector flash 
 * programming and other operations will be allowed for faster write
 * performance.
 *
 * Some cards will not sleep in low power mode unless CHECK_FLASH_PROGRAMMING
 * is non-zero.
 */
#define CHECK_FLASH_PROGRAMMING 1
//------------------------------------------------------------------------------
/**
 * Set MAINTAIN_FREE_CLUSTER_COUNT nonzero to keep the count of free clusters
 * updated.  This will increase the speed of the freeClusterCount() call
 * after the first call.  Extra flash will be required.
 */
#define MAINTAIN_FREE_CLUSTER_COUNT 0
//------------------------------------------------------------------------------
/**
 * To enable SD card CRC checking set USE_SD_CRC nonzero.
 *
 * Set USE_SD_CRC to 1 to use a smaller CRC-CCITT function.  This function
 * is slower for AVR but may be fast for ARM and other processors.
 *
 * Set USE_SD_CRC to 2 to used a larger table driven CRC-CCITT function.  This
 * function is faster for AVR but may be slower for ARM and other processors.
 */
#define USE_SD_CRC 0
//------------------------------------------------------------------------------
/**
 * Handle Watchdog Timer for WiFi modules.
 *
 * Yield will be called before accessing the SPI bus if it has been more
 * than WDT_YIELD_TIME_MICROS microseconds since the last yield call by SdFat.
 */
#if defined(PLATFORM_ID) || defined(ESP8266)
// If Particle device or ESP8266 call yield.
#define WDT_YIELD_TIME_MICROS 100000
#else
#define WDT_YIELD_TIME_MICROS 0
#endif
//------------------------------------------------------------------------------
/**
 * Set FAT12_SUPPORT nonzero to enable use if FAT12 volumes.
 * FAT12 has not been well tested and requires additional flash.
 */
#define FAT12_SUPPORT 0
//------------------------------------------------------------------------------
/**
 * Set DESTRUCTOR_CLOSES_FILE nonzero to close a file in its destructor.
 *
 * Causes use of lots of heap in ARM.
 */
#define DESTRUCTOR_CLOSES_FILE 0
//------------------------------------------------------------------------------
/**
 * Call flush for endl if ENDL_CALLS_FLUSH is nonzero
 *
 * The standard for iostreams is to call flush.  This is very costly for
 * SdFat.  Each call to flush causes 2048 bytes of I/O to the SD.
 *
 * SdFat has a single 512 byte buffer for SD I/O so it must write the current
 * data block to the SD, read the directory block from the SD, update the
 * directory entry, write the directory block to the SD and read the data
 * block back into the buffer.
 *
 * The SD flash memory controller is not designed for this many rewrites
 * so performance may be reduced by more than a factor of 100.
 *
 * If ENDL_CALLS_FLUSH is zero, you must call flush and/or close to force
 * all data to be written to the SD.
 */
#define ENDL_CALLS_FLUSH 0
//------------------------------------------------------------------------------
/**
 * Set USE_SEPARATE_FAT_CACHE nonzero to use a second 512 byte cache
 * for FAT table entries.  This improves performance for large writes
 * that are not a multiple of 512 bytes.
 */
#ifdef __arm__
#define USE_SEPARATE_FAT_CACHE 1
#else  // __arm__
#define USE_SEPARATE_FAT_CACHE 0
#endif  // __arm__
//------------------------------------------------------------------------------
/**
 * Set USE_MULTI_BLOCK_IO nonzero to use multi-block SD read/write.
 *
 * Don't use mult-block read/write on small AVR boards.
 */
#if defined(RAMEND) && RAMEND < 3000
#define USE_MULTI_BLOCK_IO 0
#else  // RAMEND
#define USE_MULTI_BLOCK_IO 1
#endif  // RAMEND
//-----------------------------------------------------------------------------
/** Enable SDIO driver if available. */
#if defined(__MK64FX512__) || defined(__MK66FX1M0__)
#define ENABLE_SDIO_CLASS 1
#define ENABLE_SDIOEX_CLASS 1
#else  // ENABLE_SDIO_CLASS
#define ENABLE_SDIO_CLASS 0
#endif  // ENABLE_SDIO_CLASS
//------------------------------------------------------------------------------
/**
 * Determine the default SPI configuration.
 */
#if defined(__STM32F1__) || defined(__STM32F4__)
// has multiple SPI ports
#define SD_HAS_CUSTOM_SPI 2
#elif defined(__AVR__)\
  || defined(__SAM3X8E__) || defined(__SAM3X8H__)\
  || (defined(__arm__) && defined(CORE_TEENSY))\
  || defined(ESP8266)
#define SD_HAS_CUSTOM_SPI 1
#else  // SD_HAS_CUSTOM_SPI
// Use standard SPI library.
#define SD_HAS_CUSTOM_SPI 0
#endif  // SD_HAS_CUSTOM_SPI
//------------------------------------------------------------------------------
/**
 * Check if API to select HW SPI port is needed.
 */
#if (USE_STANDARD_SPI_LIBRARY || SD_HAS_CUSTOM_SPI < 2)
#define IMPLEMENT_SPI_PORT_SELECTION 0
#else  // USE_STANDARD_SPI_LIBRARY
#define IMPLEMENT_SPI_PORT_SELECTION 1
#endif  // USE_STANDARD_SPI_LIBRARY
#endif  // SdFatConfig_h

I looked at my installed Arduino libraries. I had 2 SdFat libraries installed, the newest version and the version i required. So i deleted the newest version and it seems to have worked.

Thanks for the help

Good job!

bro, I'm working on the same Project, can u tell me which version of SdFat did u used?

Make sure you use electricnoobs library's. He has them on his YouTube page. I think he's edited some of the code so uninstall the library's you've downloaded from Arduino and use his ones. They're all in a file

Perhaps a providing a link would be nice?
I was looking, but he has a lot of videos.

1 Like

Hi mate! did you managed to complete this project? Im working on it and stuck at compiling the code.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.