/**********************************************************************************
                     Nintendo Power Writer for Arduino Mega2560

   Author:           sanni
   Date:             2017-11-13
   Version:          V10

   SD  lib:          https://github.com/greiman/SdFat

   Many thanks to MichlK, skaman and nocash
   Please visit:  http://forum.arduino.cc/index.php?topic=158974.0
   And also:      http://forums.nesdev.com/viewtopic.php?f=12&t=11453
 *********************************************************************************/

/******************************************
   Libraries
 *****************************************/
// SD Card (Pin 50 = MISO, Pin 51 = MOSI, Pin 52 = SCK, Pin 53 = SS)
#include <SPI.h>
#include <SdFat.h>
#define chipSelectPin 53
SdFat sd;
SdBaseFile myFile;

/******************************************
  Pinout
 *****************************************/
// Address Pins
#define a0Pin A0
#define a1Pin A1
#define a2Pin A2
#define a3Pin A3
#define a4Pin A4
#define a5Pin A5
#define a6Pin A6
#define a7Pin A7
#define a8Pin A8
#define a9Pin A9
#define a10Pin A10
#define a11Pin A11
#define a12Pin A12
#define a13Pin A13
#define a14Pin A14
#define a15Pin A15
#define ba0Pin 49 //a16 
#define ba1Pin 48 //a17 
#define ba2Pin 47 //a18 
#define ba3Pin 46 //a19 
#define ba4Pin 45 //a20 
#define ba5Pin 44 //a21
#define ba6Pin 43 //a22
#define ba7Pin 42 //a23

// Control Pins
#define csPin 6
#define irqPin 7
#define wrPin 8
#define rdPin 9

// Data Pins
#define d0Pin 37
#define d1Pin 36
#define d2Pin 35
#define d3Pin 34
#define d4Pin 33
#define d5Pin 32
#define d6Pin 31
#define d7Pin 30

// Clock & Reset Pin
#define rstPin 17
#define clkPin 5

/******************************************
   Variables
 *****************************************/
// Buffer for the SD
byte SDBuffer[512];
byte SDBuffer2[512];

// Filename of SD file
char fileName[13];

// Nintendo Power status
byte NPReady = 0;

// Flashrom ID and size
char flashid[5];
unsigned long flashSize = 4194304;
byte numBanks = 128;
boolean romType = 0;
byte sramSize = 64;

// Variable to count errors
unsigned long writeErrors;

// For incoming serial data
int incomingByte;

/******************************************
   Setup
 *****************************************/
void setup() {
  // Clock Pin to Output
  DDRE |=  (1 << 3);
  // Clock Pin to Low
  PORTE &= ~(1 << 3);

  // Set Address Pins to Output
  //A0-A7
  DDRF = 0xFF;
  //A8-A15
  DDRK = 0xFF;
  //BA0-BA7
  DDRL = 0xFF;

  // Set Control Pins to Output RST(PH0) CS(PH3) WR(PH5) RD(PH6)
  DDRH |= (1 << 0) | (1 << 3) | (1 << 5) | (1 << 6);
  // Output a high signal on all pins, pins are active low therefore everything is disabled now
  PORTH |= (1 << 0) | (1 << 3) | (1 << 5) | (1 << 6);

  // Set IRQ(PH4) to Input
  DDRH &= ~(1 << 4);
  // Activate Internal Pullup Resistors
  PORTH |= (1 << 4);

  // Set Data Pins (D0-D7) to Input
  DDRC = 0x00;
  // Enable Internal Pullups
  //PORTC = 0xFF;

  // Serial Begin
  Serial.begin(9600);
  Serial.println("--------------------------------------------------------->");
  Serial.println("Nintendo Power Writer V10 2017 sanni");
  Serial.println("--------------------------------------------------------->");

  // Init SD card
  if (!sd.begin(chipSelectPin, SPI_FULL_SPEED)) {
    Serial.println("SD Error.");
    while (1);
  }

  // Print SD Info
  Serial.print("SD Card: ");
  Serial.print(sd.card()->cardSize() * 512E-9);
  Serial.print("GB FAT");
  Serial.println(int(sd.vol()->fatType()));
  Serial.println("");
  Serial.println("FILES---------------------------------------------------->");
  // Print all files in root of SD
  Serial.println("Name - Size");
  sd.vwd()->rewind();
  while (myFile.openNext(sd.vwd(), O_READ)) {
    if (myFile.isHidden()) {
    }
    else {
      if (myFile.isDir()) {
        // Indicate a directory.
        Serial.write('/');
      }
      myFile.printName(&Serial);
      Serial.write(' ');
      myFile.printFileSize(&Serial);
      Serial.println();
    }
    myFile.close();
  }
  Serial.println("");
}

/******************************************
   Helper functions
 *****************************************/
// Prompt a filename from the Serial Monitor
void getfilename() {
  Serial.println("");
  Serial.println("-------> Please enter a filename in 8.3 format: _ <-------");
  Serial.println("");
  while (Serial.available() == 0) {
  }
  String strBuffer;
  strBuffer = Serial.readString();
  strBuffer.toCharArray(fileName, 13);
}

// Send a clock pulse
void pulseClock(int times) {
  for (int i = 0; i < times * 2; i++) {
    // Switch the clock pin to 0 if it's 1 and 0 if it's 1
    PORTE ^= (1 << 3);
    // Wait 62.5ns
    __asm__("nop\n\t");
  }
}

/******************************************
   I/O Functions
 *****************************************/
// Switch control pins to write
void controlOut() {
  // Switch RD(PH6) and WR(PH5) to HIGH
  PORTH |= (1 << 6) | (1 << 5);
  // Switch CS(PH3) to LOW
  PORTH &= ~(1 << 3);
}

// Switch control pins to read
void controlIn() {
  // Switch WR(PH5) to HIGH
  PORTH |= (1 << 5);
  // Switch CS(PH3) and RD(PH6) to LOW
  PORTH &= ~((1 << 3) | (1 << 6));
}

// Switch data pins to write
void dataOut() {
  DDRC = 0xFF;
}

// Switch data pins to read
void dataIn() {
  DDRC = 0x00;
  // Pullups
  //PORTC = 0xFF;
}

// Write one byte of data to a location specified by bank and address, 00:0000
void writeBank(byte myBank, word myAddress, byte myData) {
  PORTL = myBank;
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTC = myData;

  // Arduino running at 16Mhz -> one nop = 62.5ns
  // Wait till output is stable
  __asm__("nop\n\t""nop\n\t""nop\n\t");

  // Switch WE(PH5) to LOW
  PORTH &= ~(1 << 5);

  // Leave WE low for at least 60ns
  __asm__("nop\n\t""nop\n\t""nop\n\t");

  // Switch WR(PH5) to HIGH
  PORTH |= (1 << 5);

  // Leave WE high for at least 50ns
  __asm__("nop\n\t""nop\n\t""nop\n\t");
}

// Write a byte to the data pins and pulse the clock pin, used when sending to the MX15001 chip
void writeBankClock(byte myBank, word myAddress, byte myData) {
  if ( (myAddress & 1) == 1 ) digitalWrite(a0Pin, 1); else digitalWrite(a0Pin, 0);
  if ( (myAddress & 2) == 2 ) digitalWrite(a1Pin, 1); else digitalWrite(a1Pin, 0);
  if ( (myAddress & 4) == 4 ) digitalWrite(a2Pin, 1); else digitalWrite(a2Pin, 0);
  if ( (myAddress & 8) == 8 ) digitalWrite(a3Pin, 1); else digitalWrite(a3Pin, 0);
  if ( (myAddress & 16) == 16 ) digitalWrite(a4Pin, 1); else digitalWrite(a4Pin, 0);
  if ( (myAddress & 32) == 32 ) digitalWrite(a5Pin, 1); else digitalWrite(a5Pin, 0);
  if ( (myAddress & 64) == 64 ) digitalWrite(a6Pin, 1); else digitalWrite(a6Pin, 0);
  if ( (myAddress & 128) == 128 ) digitalWrite(a7Pin, 1); else digitalWrite(a7Pin, 0);
  if ( (myAddress & 256) == 256 ) digitalWrite(a8Pin, 1); else digitalWrite(a8Pin, 0);
  if ( (myAddress & 512) == 512 ) digitalWrite(a9Pin, 1); else digitalWrite(a9Pin, 0);
  if ( (myAddress & 1024) == 1024 ) digitalWrite(a10Pin, 1); else digitalWrite(a10Pin, 0);
  if ( (myAddress & 2048) == 2048 ) digitalWrite(a11Pin, 1); else digitalWrite(a11Pin, 0);
  if ( (myAddress & 4096) == 4096 ) digitalWrite(a12Pin, 1); else digitalWrite(a12Pin, 0);
  if ( (myAddress & 8192) == 8192 ) digitalWrite(a13Pin, 1); else digitalWrite(a13Pin, 0);
  if ( (myAddress & 16384) == 16384 ) digitalWrite(a14Pin, 1); else digitalWrite(a14Pin, 0);
  if ( (myAddress & 32768) == 32768 ) digitalWrite(a15Pin, 1); else digitalWrite(a15Pin, 0);

  if ( (myBank & 1) == 1 ) digitalWrite(ba0Pin, 1); else digitalWrite(ba0Pin, 0);
  if ( (myBank & 2) == 2 ) digitalWrite(ba1Pin, 1); else digitalWrite(ba1Pin, 0);
  if ( (myBank & 4) == 4 ) digitalWrite(ba2Pin, 1); else digitalWrite(ba2Pin, 0);
  if ( (myBank & 8) == 8 ) digitalWrite(ba3Pin, 1); else digitalWrite(ba3Pin, 0);
  if ( (myBank & 16) == 16 ) digitalWrite(ba4Pin, 1); else digitalWrite(ba4Pin, 0);
  if ( (myBank & 32) == 32 ) digitalWrite(ba5Pin, 1); else digitalWrite(ba5Pin, 0);
  if ( (myBank & 64) == 64 ) digitalWrite(ba6Pin, 1); else digitalWrite(ba6Pin, 0);
  if ( (myBank & 128) == 128 ) digitalWrite(ba7Pin, 1); else digitalWrite(ba7Pin, 0);

  if ( (myData & 1) == 1 ) digitalWrite(d0Pin, 1); else digitalWrite(d0Pin, 0);
  if ( (myData & 2) == 2 ) digitalWrite(d1Pin, 1); else digitalWrite(d1Pin, 0);
  if ( (myData & 4) == 4 ) digitalWrite(d2Pin, 1); else digitalWrite(d2Pin, 0);
  if ( (myData & 8) == 8 ) digitalWrite(d3Pin, 1); else digitalWrite(d3Pin, 0);
  if ( (myData & 16) == 16 ) digitalWrite(d4Pin, 1); else digitalWrite(d4Pin, 0);
  if ( (myData & 32) == 32 ) digitalWrite(d5Pin, 1); else digitalWrite(d5Pin, 0);
  if ( (myData & 64) == 64 ) digitalWrite(d6Pin, 1); else digitalWrite(d6Pin, 0);
  if ( (myData & 128) == 128 ) digitalWrite(d7Pin, 1); else digitalWrite(d7Pin, 0);

  // Pull WE low
  digitalWrite(wrPin, LOW);

  // Pulse clock pin 4 times
  pulseClock(8);

  // Pull WE high
  digitalWrite(wrPin, HIGH);

  // Pulse clock pin 4 times
  pulseClock(8);
}

// Read one byte of data from a location specified by bank and address, 00:0000
byte readBank(byte myBank, word myAddress) {
  PORTL = myBank;
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;

  // Arduino running at 16Mhz -> one nop = 62.5ns
  __asm__("nop\n\t""nop\n\t""nop\n\t");

  // Read
  byte tempByte = PINC;
  return tempByte;
}

// Read a byte from the digital pins and pulse the clock, used when sending to the MX15001 chip
byte readBankClock(byte myBank, word myAddress) {
  if ( (myAddress & 1) == 1 ) digitalWrite(a0Pin, 1); else digitalWrite(a0Pin, 0);
  if ( (myAddress & 2) == 2 ) digitalWrite(a1Pin, 1); else digitalWrite(a1Pin, 0);
  if ( (myAddress & 4) == 4 ) digitalWrite(a2Pin, 1); else digitalWrite(a2Pin, 0);
  if ( (myAddress & 8) == 8 ) digitalWrite(a3Pin, 1); else digitalWrite(a3Pin, 0);
  if ( (myAddress & 16) == 16 ) digitalWrite(a4Pin, 1); else digitalWrite(a4Pin, 0);
  if ( (myAddress & 32) == 32 ) digitalWrite(a5Pin, 1); else digitalWrite(a5Pin, 0);
  if ( (myAddress & 64) == 64 ) digitalWrite(a6Pin, 1); else digitalWrite(a6Pin, 0);
  if ( (myAddress & 128) == 128 ) digitalWrite(a7Pin, 1); else digitalWrite(a7Pin, 0);
  if ( (myAddress & 256) == 256 ) digitalWrite(a8Pin, 1); else digitalWrite(a8Pin, 0);
  if ( (myAddress & 512) == 512 ) digitalWrite(a9Pin, 1); else digitalWrite(a9Pin, 0);
  if ( (myAddress & 1024) == 1024 ) digitalWrite(a10Pin, 1); else digitalWrite(a10Pin, 0);
  if ( (myAddress & 2048) == 2048 ) digitalWrite(a11Pin, 1); else digitalWrite(a11Pin, 0);
  if ( (myAddress & 4096) == 4096 ) digitalWrite(a12Pin, 1); else digitalWrite(a12Pin, 0);
  if ( (myAddress & 8192) == 8192 ) digitalWrite(a13Pin, 1); else digitalWrite(a13Pin, 0);
  if ( (myAddress & 16384) == 16384 ) digitalWrite(a14Pin, 1); else digitalWrite(a14Pin, 0);
  if ( (myAddress & 32768) == 32768 ) digitalWrite(a15Pin, 1); else digitalWrite(a15Pin, 0);

  if ( (myBank & 1) == 1 ) digitalWrite(ba0Pin, 1); else digitalWrite(ba0Pin, 0);
  if ( (myBank & 2) == 2 ) digitalWrite(ba1Pin, 1); else digitalWrite(ba1Pin, 0);
  if ( (myBank & 4) == 4 ) digitalWrite(ba2Pin, 1); else digitalWrite(ba2Pin, 0);
  if ( (myBank & 8) == 8 ) digitalWrite(ba3Pin, 1); else digitalWrite(ba3Pin, 0);
  if ( (myBank & 16) == 16 ) digitalWrite(ba4Pin, 1); else digitalWrite(ba4Pin, 0);
  if ( (myBank & 32) == 32 ) digitalWrite(ba5Pin, 1); else digitalWrite(ba5Pin, 0);
  if ( (myBank & 64) == 64 ) digitalWrite(ba6Pin, 1); else digitalWrite(ba6Pin, 0);
  if ( (myBank & 128) == 128 ) digitalWrite(ba7Pin, 1); else digitalWrite(ba7Pin, 0);

  // Pulse clock 4 times
  pulseClock(8);

  // Read
  byte tempByte = 0;
  if (digitalRead(d0Pin)) tempByte = tempByte + 1;
  if (digitalRead(d1Pin)) tempByte = tempByte + 2;
  if (digitalRead(d2Pin)) tempByte = tempByte + 4;
  if (digitalRead(d3Pin)) tempByte = tempByte + 8;
  if (digitalRead(d4Pin)) tempByte = tempByte + 16;
  if (digitalRead(d5Pin)) tempByte = tempByte + 32;
  if (digitalRead(d6Pin)) tempByte = tempByte + 64;
  if (digitalRead(d7Pin)) tempByte = tempByte + 128;

  return tempByte;
}

/******************************************
   29F1601 flashrom functions
 *****************************************/
// Reset the MX29F1601 flashrom, startbank is 0xC0 for first and 0xE0 for second flashrom
void resetFlash(int startBank) {
  // Configure control pins
  controlOut();
  // Set data pins to output
  dataOut();

  // Reset command sequence
  if (romType) {
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x5555 * 2, 0xf0);
  }
  else {
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(1, 0x8000 + 0x1555 * 2, 0xf0);
  }

  // Set data pins to input
  dataIn();
  // Set control pins to input and therefore pull CE low and latch status register content
  controlIn();
}

// Print flashrom manufacturer and device ID
void idFlash(int startBank) {
  // Configure control pins
  controlOut();
  // Set data pins to output
  dataOut();

  if (romType) {
    // ID command sequence
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x5555 * 2, 0x90);

    // Set data pins to input again
    dataIn();
    // Set control pins to input
    controlIn();

    // Read the two id bytes into a string
    sprintf(flashid, "%x%x", readBank(startBank, 0x00), readBank(startBank, 0x02));
  }
  else {
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(1, 0x8000 + 0x1555 * 2, 0x90);

    // Set data pins to input again
    dataIn();
    // Set control pins to input
    controlIn();

    // Read the two id bytes into a string
    sprintf(flashid, "%x%x", readBank(0, 0x8000), readBank(0, 0x8000 + 0x02));
  }

  // Set data pins to input
  dataIn();
  // Set control pins to input and therefore pull CE low and latch status register content
  controlIn();
}

// Write the flashroms by reading a file from the SD card, pos defines where in the file the reading/writing should start
void writeFlash(int startBank, uint32_t pos) {
  Serial.print("Writing ");
  Serial.print(flashSize);
  Serial.print(" Bytes into ");
  Serial.print(numBanks);
  Serial.print(" Banks ");
  if (romType)
    Serial.println("(HiROM).");
  else
    Serial.println("(LoRom).");

  // Open file on sd card
  if (myFile.open(fileName, O_READ)) {

    // Seek to a new position in the file
    if (pos != 0)
      myFile.seekCur(pos);

    // Configure control pins
    controlOut();
    // Set data pins to output
    dataOut();

    if (romType) {
      // Write hirom
      for (int currBank = startBank; currBank < startBank + numBanks; currBank++) {
        // Print Status
        Serial.print(".");

        // Fill SDBuffer with 4 pages
        for (unsigned long currBuffer = 0; currBuffer < 0x10000; currBuffer += 512) {
          myFile.read(SDBuffer, 512);

          // Write the four pages out of the buffer to the rom chip one page at a time
          for (unsigned long currPage = 0; currPage < 512; currPage += 128) {
            // Write command sequence
            writeBank(startBank, 0x5555 * 2, 0xaa);
            writeBank(startBank, 0x2AAA * 2, 0x55);
            writeBank(startBank, 0x5555 * 2, 0xa0);

            for (byte currByte = 0; currByte < 128; currByte++) {
              // Write one byte of data
              writeBank(currBank, currBuffer + currPage + currByte, SDBuffer[currPage + currByte]);

              if (currByte == 127) {
                // Write the last byte twice or else it won't write at all
                writeBank(currBank, currBuffer + currPage + currByte, SDBuffer[currPage + currByte]);
              }
            }
            // Wait until write is finished
            busyCheck(startBank);
          }
        }
      }
    }
    else {
      // Write lorom
      for (int currBank = 0; currBank < numBanks; currBank++) {

        // Print Status
        Serial.print(".");

        for (unsigned long currByte = 0x8000; currByte < 0x10000; currByte += 128) {
          myFile.read(SDBuffer, 128);
          // Write command sequence
          writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
          writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
          writeBank(1, 0x8000 + 0x1555 * 2, 0xa0);

          for (byte c = 0; c < 128; c++) {
            // Write one byte of data
            writeBank(currBank, currByte + c, SDBuffer[c]);

            if (c == 127) {
              // Write the last byte twice or else it won't write at all
              writeBank(currBank, currByte + c, SDBuffer[c]);
            }
          }
          // Wait until write is finished
          busyCheck(startBank);
        }
      }
    }
    // Close the file:
    myFile.close();
    Serial.println("");
  }
  else {
    Serial.println("Can't open file on SD.");
  }
}

// Write 4MB to both flashroms in parallel
void writeNP() {
  unsigned long bankSize = 0x10000;
  int bufferSize = 512;
  unsigned long currOffset = 2097152;
  numBanks = 32;

  // Open file on sd card
  if (myFile.open(fileName, O_READ)) {

    // Configure control pins
    controlOut();
    // Set data pins to output
    dataOut();

    // Write 32 hirom banks at 65536B each per chip, so 4MB total
    for (int currBank = 0; currBank < numBanks; currBank++) {
      Serial.print(".");

      // Fill 2 SDBuffers with 4(if bufferSize=512) pages each, one page has 128B
      for (unsigned long currBuffer = 0; currBuffer < bankSize; currBuffer += bufferSize) {

        // Seek to current position in the file
        myFile.seekSet((currBank * bankSize) + currBuffer);
        myFile.read(SDBuffer, bufferSize);

        myFile.seekSet((currBank * bankSize) + currOffset + currBuffer);
        myFile.read(SDBuffer2, bufferSize);

        // Write 2x 128B at once out of the two SDBuffers to the flashroms until all the buffer's content was written
        for (unsigned long currPage = 0; currPage < bufferSize; currPage += 128) {

          // Write command sequence to 1st flashrom
          writeBank(0xC0, 0x5555 * 2, 0xaa);
          writeBank(0xC0, 0x2AAA * 2, 0x55);
          writeBank(0xC0, 0x5555 * 2, 0xa0);

          for (byte currByte = 0; currByte < 128; currByte++) {

            // Write one byte of data
            writeBank(0xC0 + currBank, currBuffer + currPage + currByte, SDBuffer[currPage + currByte]);

            if (currByte == 127) {
              // Write the last byte twice or else it won't write at all
              writeBank(0xC0 + currBank, currBuffer + currPage + currByte, SDBuffer[currPage + currByte]);
            }
          }

          // Write command sequence to 2nd flashrom
          writeBank(0xE0, 0x5555 * 2, 0xaa);
          writeBank(0xE0, 0x2AAA * 2, 0x55);
          writeBank(0xE0, 0x5555 * 2, 0xa0);

          for (byte currByte = 0; currByte < 128; currByte++) {

            // Write one byte of data
            writeBank(0xE0 + currBank, currBuffer + currPage + currByte, SDBuffer2[currPage + currByte]);

            if (currByte == 127) {
              // Write the last byte twice or else it won't write at all
              writeBank(0xE0 + currBank, currBuffer + currPage + currByte, SDBuffer2[currPage + currByte]);
            }
          }
          // Wait until writes are finished
          busyCheck(0xC0);
          busyCheck(0xE0);
        }
      }
    }
    // Close the file:
    myFile.close();
  }
  else {
    Serial.println("Can't open file on SD.");
  }
}

// Delay between write operations based on status register
void busyCheck(byte startBank) {
  // Set data pins to input
  dataIn();
  // Set control pins to input and therefore pull CE low and latch status register content
  controlIn();

  // Read register
  readBank(startBank, 0x0000);

  // Read D7 while D7 = 0
  //1 = B00000001, 1 << 7 = B10000000, PINC = B1XXXXXXX (X = don't care), & = bitwise and
  while (!(PINC & (1 << 7))) {
    // CE or OE must be toggled with each subsequent status read or the
    // completion of a program or erase operation will not be evident.
    // Switch RD(PH6) to HIGH
    PORTH |= (1 << 6);

    // one nop ~62.5ns
    __asm__("nop\n\t");

    // Switch RD(PH6) to LOW
    PORTH &= ~(1 << 6);

    // one nop ~62.5ns
    __asm__("nop\n\t");
  }

  // Configure control pins
  controlOut();
  // Set data pins to output
  dataOut();
}

// Erase the flashrom to 0xFF
void eraseFlash(int startBank) {
  // Configure control pins
  controlOut();
  // Set data pins to output
  dataOut();

  if (romType) {
    // Erase command sequence
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x5555 * 2, 0x80);
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x5555 * 2, 0x10);
  }
  else {
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(1, 0x8000 + 0x1555 * 2, 0x80);
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(1, 0x8000 + 0x1555 * 2, 0x10);
  }

  // Wait for erase to complete
  busyCheck(startBank);
}

// Check if an erase succeeded, return 1 if blank and 0 if not
byte blankcheck(int startBank) {
  // Set data pins to input again
  dataIn();
  // Set control pins to input
  controlIn();

  byte blank = 1;
  if (romType) {
    for (int currBank = startBank; currBank < startBank + numBanks; currBank++) {
      for (unsigned long currByte = 0; currByte < 0x10000; currByte++) {
        if (readBank(currBank, currByte) != 0xFF) {
          currBank =  startBank + numBanks;
          blank = 0;
        }
      }
    }
  }
  else {
    for (int currBank = 0; currBank < numBanks; currBank++) {
      for (unsigned long currByte = 0x8000; currByte < 0x10000; currByte++) {
        if (readBank(currBank, currByte) != 0xFF) {
          currBank = numBanks;
          blank = 0;
        }
      }
    }
  }
  return blank;
}

// Check if a write succeeded, returns 0 if all is ok and number of errors if not
unsigned long verifyFlash(int startBank, uint32_t pos) {
  unsigned long  verified = 0;

  // Open file on sd card
  if (myFile.open(fileName, O_READ)) {
    // Set file starting position
    myFile.seekCur(pos);

    // Set data pins to input
    dataIn();
    // Set control pins to input
    controlIn();

    if (romType) {
      for (int currBank = startBank; currBank < startBank + numBanks; currBank++) {
        for (unsigned long currByte = 0; currByte < 0x10000; currByte += 512) {
          // Fill SDBuffer
          myFile.read(SDBuffer, 512);
          for (int c = 0; c < 512; c++) {
            if (readBank(currBank, currByte + c) != SDBuffer[c]) {
              verified++;
            }
          }
        }
      }
    }
    else {
      for (int currBank = 0; currBank < numBanks; currBank++) {
        for (unsigned long currByte = 0x8000; currByte < 0x10000; currByte += 512) {
          // Fill SDBuffer
          myFile.read(SDBuffer, 512);
          for (int c = 0; c < 512; c++) {
            if (readBank(currBank, currByte + c) != SDBuffer[c]) {
              verified++;
            }
          }
        }
      }
    }
    // Close the file:
    myFile.close();
  }
  else {
    // SD Error
    verified = 999999;
    Serial.println("Can't open file on SD.");
  }
  // Return 0 if verified ok, or number of errors
  return verified;
}

// Read flashroms and save them to the SD card
void readFlash() {
  // Set data pins to input
  dataIn();
  // Set control pins to input
  controlIn();

  Serial.print("Reading flash into file ");
  Serial.println(fileName);

  // Open file on sd card
  if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
    Serial.println("Can't create file on SD.");
    while (1);
  }
  if (romType) {
    for (int currBank = 0xC0; currBank < 0xC0 + numBanks; currBank++) {

      // Print Status
      Serial.print(".");

      for (unsigned long currByte = 0; currByte < 0x10000; currByte += 512) {
        for (int c = 0; c < 512; c++) {
          SDBuffer[c] = readBank(currBank, currByte + c);
        }
        myFile.write(SDBuffer, 512);
      }
    }
  }
  else {
    for (int currBank = 0; currBank < numBanks; currBank++) {

      // Print Status
      Serial.print(".");

      for (unsigned long currByte = 0x8000; currByte < 0x10000; currByte += 512) {
        for (int c = 0; c < 512; c++) {
          SDBuffer[c] = readBank(currBank, currByte + c);
        }
        myFile.write(SDBuffer, 512);
      }
    }
  }
  // Close the file:
  myFile.close();
  Serial.println("");
}

// Print part of the flash to the serial monitor
void printFlash(boolean hirom, unsigned long startByte, unsigned long startBank, int numBytes) {

  // Set data pins to input
  dataIn();
  // Set control pins to input
  controlIn();

  char buffer[3];
  if (hirom) {
    for (unsigned long currByte = 0; currByte < numBytes; currByte += 16) {
      for (int c = 0; c < 16; c++) {
        itoa (readBank(startBank, currByte + c), buffer, 16);
        for (int i = 0; i < 2 - strlen(buffer); i++) {
          Serial.print('0');
        }
        // Now print the significant bits
        Serial.print(buffer);
        Serial.print(" ");
      }
      Serial.println("");
    }
  }
  else {
    for (unsigned long currByte = 0x8000; currByte < 0x8000 + numBytes; currByte += 16) {
      for (int c = 0; c < 16; c++) {
        itoa (readBank(startBank, currByte + c), buffer, 16);
        for (int i = 0; i < 2 - strlen(buffer); i++) {
          Serial.print('0');
        }
        // Now print the significant bits
        Serial.print(buffer);
        Serial.print(" ");
      }
      Serial.println("");
    }
  }
}

// Print multiple 64 bytes sections out of different parts of the flashroms
void printFlashAll() {
  // Romtype, startByte, numBytes
  Serial.println("0x00/0xC0");
  printFlash(romType, 0, 0xC0, 64);
  Serial.println("0x80000/0xC8");
  printFlash(romType, 0x80000, 0xC8, 64);
  Serial.println("0x100000/0xD0");
  printFlash(romType, 0x100000, 0xD0, 64);
  Serial.println("0x180000/0xD8");
  printFlash(romType, 0x180000, 0xD8, 64);
  Serial.println("0x200000/0xE0");
  printFlash(romType, 0x200000, 0xE0, 64);
  Serial.println("0x280000/0xE8");
  printFlash(romType, 0x280000, 0xE8, 64);
  Serial.println("0x300000/0xF0");
  printFlash(romType, 0x300000, 0xF0, 64);
  Serial.println("0x380000/0xF8");
  printFlash(romType, 0x380000, 0xF8, 64);
}

// Display protected sectors/banks as 0xc2 and unprotected as 0x00
byte readSectorProtection(byte startBank) {
  byte isProtected = 0;

  // Configure control pins
  controlOut();
  // Set data pins to output
  dataOut();

  // Display Sector Protection Status
  if (romType) {
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x5555 * 2, 0x90);

    // Configure control pins
    controlIn();
    // Set data pins to output
    dataIn();

    for (int i = 0; i <= 0x1F; i++) {
      if (readBank(startBank + i, 0x04) == 0xC2) {
        Serial.print("Sector 0x");
        Serial.print(startBank + i, HEX);
        Serial.println(" is protected");
        isProtected = 1;
      }
      else if (readBank(startBank + i, 0x04) == 0x0) {
        /*Serial.print("Sector 0x");
          Serial.print(startBank + i, HEX);
          Serial.println(" is not protected");*/
      }
      else {
        /*Serial.print("Sector 0x");
          Serial.print(startBank + i, HEX);
          Serial.println(" READ ERROR");*/
        isProtected = 2;
      }
    }
  }
  else {
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(1, 0x8000 + 0x1555 * 2, 0x90);

    // Configure control pins
    controlIn();
    // Set data pins to output
    dataIn();

    for (int i = 0; i <= 0x1F; i++) {
      if (readBank(0, 0x8000 + 0x04) == 0xC2) {
        Serial.print("Sector 0x");
        Serial.print(startBank + i, HEX);
        Serial.println(" is protected");
        isProtected = 1;
      }
      else if (readBank(0, 0x8000 + 0x04) == 0x0) {
      }
      else {
        isProtected = 2;
      }
    }
  }
  return isProtected;
}

void unlockSectorProtection(byte startBank) {
  // Configure control pins
  controlOut();
  // Set data pins to output
  dataOut();

  // Remove Sector Protection
  if (romType) {
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x5555 * 2, 0x60);
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x0000 * 2, 0x40);
  }
  else {
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(1, 0x8000 + 0x1555 * 2, 0x60);
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(0, 0x8000 + 0x0000 * 2, 0x40);
  }
  // Wait until write is finished
  busyCheck(startBank);
}

void setSectorProtection(byte startBank) {
  // Configure control pins
  controlOut();
  // Set data pins to output
  dataOut();

  // Set Sector Protection
  if (romType) {
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x5555 * 2, 0x60);
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x0000 * 2, 0x20);
  }
  else {
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(1, 0x8000 + 0x1555 * 2, 0x60);
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(0, 0x8000 + 0x0000 * 2, 0x20);
  }
  // Wait until write is finished
  busyCheck(startBank);
}

/******************************************
   Nintendo Power
 *****************************************/
// Switch to HiRom All and unlock Write Protection
boolean unlockedHirom() {
  romType = 1;

  if (sendNP(0x04) == 0x2A) {
    // Unlock Write Protection
    sendNP(0x02);
    if (readBankClock(0, 0x2401) == 0x4) {
      return 1;
    }
    else {
      Serial.println("Error: Unlocking WP failed.");
      Serial.println("Please power-cycle NP cart.");
      readPorts();
      return 0;
    }
  }
  else {
    Serial.println("Error: Switching to HiRom All failed.");
    Serial.println("Please power-cycle NP cart.");
    readPorts();
    return 0;
  }
}

// Read the ports at 0x2400-0x2407 and print the results
void readPorts() {
  // Set data pins to input
  dataIn();
  // Set control pins to input
  controlIn();

  for (unsigned long myAddr = 0x2400; myAddr < 0x2408; myAddr++) {
    Serial.print(readBankClock(0, myAddr), HEX);
    Serial.print(" ");
  }
  Serial.println(" ");
}

void eraseMapping(byte startBank) {
  // Switch to write
  dataOut();
  controlOut();

  if (romType) {
    // Prepare to erase/write Page Buffer
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x5555 * 2, 0x77);
    // Erase Page Buffer
    writeBank(startBank, 0x5555 * 2, 0xaa);
    writeBank(startBank, 0x2AAA * 2, 0x55);
    writeBank(startBank, 0x5555 * 2, 0xe0);
  }
  else {
    // Prepare to erase/write Page Buffer
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(1, 0x8000 + 0x1555 * 2, 0x77);

    // Erase Page Buffer
    writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
    writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
    writeBank(1, 0x8000 + 0x1555 * 2, 0xe0);
  }

  // Wait until erase is finished
  busyCheck(startBank);

  // Switch to read
  dataIn();
  controlIn();
}

// Read the current mapping from the hidden "page buffer"
void readMapping() {

  Serial.print("Reading mapping into file ");
  Serial.println(fileName);

  // Switch to write
  dataOut();
  controlOut();

  // Reset to defaults
  writeBank(0xC0, 0x0000, 0x38);
  writeBank(0xC0, 0x0000, 0xd0);
  // Read Extended Status Register (GSR and PSR)
  writeBank(0xC0, 0x0000, 0x71);
  // Page Buffer Swap
  writeBank(0xC0, 0x0000, 0x72);
  // Read Page Buffer
  writeBank(0xC0, 0x0000, 0x75);

  // Switch to read
  dataIn();
  controlIn();

  //open file on sd card
  if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
    Serial.println("SD Error");
  }

  // Read the mapping info out of the 1st chip
  for (unsigned long currByte = 0xFF00; currByte <= 0xFFFF; currByte++) {
    myFile.write(readBank(0xC0, currByte));
  }

  // Switch to write
  dataOut();
  controlOut();

  // Reset to defaults
  writeBank(0xE0, 0x0000, 0x38);
  writeBank(0xE0, 0x0000, 0xd0);
  // Read Extended Status Register (GSR and PSR)
  writeBank(0xE0, 0x0000, 0x71);
  // Page Buffer Swap
  writeBank(0xE0, 0x0000, 0x72);
  // Read Page Buffer
  writeBank(0xE0, 0x0000, 0x75);

  // Switch to read
  dataIn();
  controlIn();

  // Read the mapping info out of the 1st chip
  for (unsigned long currByte = 0xFF00; currByte <= 0xFFFF; currByte++) {
    myFile.write(readBank(0xE0, currByte));
  }

  // Close the file:
  myFile.close();

  // Switch to write
  dataOut();
  controlOut();

  // Reset Flash
  writeBank(0xC0, 0x5555 * 2, 0xaa);
  writeBank(0xC0, 0x2AAA * 2, 0x55);
  writeBank(0xC0, 0x5555 * 2, 0xf0);

  // Reset Flash
  writeBank(0xE0, 0x5555 * 2, 0xaa);
  writeBank(0xE0, 0x2AAA * 2, 0x55);
  writeBank(0xE0, 0x5555 * 2, 0xf0);

  // Switch to read
  dataIn();
  controlIn();

  // Signal end of process
  Serial.println("Done.");
}

void writeMapping(byte startBank, uint32_t pos) {
  // Switch to write
  dataOut();
  controlOut();

  // Open file on sd card
  if (myFile.open(fileName, O_READ)) {

    // Seek to a new position in the file
    if (pos != 0)
      myFile.seekCur(pos);


    if (romType) {
      // Write to Page Buffer
      for (unsigned long currByte = 0x00; currByte < 0x100; currByte += 128) {
        // Prepare to erase/write Page Buffer
        writeBank(startBank, 0x5555 * 2, 0xaa);
        writeBank(startBank, 0x2AAA * 2, 0x55);
        writeBank(startBank, 0x5555 * 2, 0x77);
        // Write Page Buffer Command
        writeBank(startBank, 0x5555 * 2, 0xaa);
        writeBank(startBank, 0x2AAA * 2, 0x55);
        writeBank(startBank, 0x5555 * 2, 0x99);

        myFile.read(SDBuffer, 128);

        for (byte c = 0; c < 128; c++) {
          writeBank(startBank, currByte + c, SDBuffer[c]);
          // Write last byte twice
          if (c == 127) {
            writeBank(startBank, currByte + c, SDBuffer[c]);
          }
        }
        busyCheck(startBank);
      }
    }
    else {
      // Write to Page Buffer
      for (unsigned long currByte = 0x0000; currByte < 0x100; currByte += 128) {
        // Prepare to erase/write Page Buffer
        writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
        writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
        writeBank(1, 0x8000 + 0x1555 * 2, 0x77);
        // Write Page Buffer Command
        writeBank(1, 0x8000 + 0x1555 * 2, 0xaa);
        writeBank(0, 0x8000 + 0x2AAA * 2, 0x55);
        writeBank(1, 0x8000 + 0x1555 * 2, 0x99);

        myFile.read(SDBuffer, 128);

        for (byte c = 0; c < 128; c++) {
          writeBank(1, 0x8000 + currByte + c, SDBuffer[c]);
          // Write last byte twice
          if (c == 127) {
            writeBank(1, 0x8000 + currByte + c, SDBuffer[c]);
          }
        }
        busyCheck(startBank);
      }
    }
    // Close the file:
    myFile.close();
  }
  else {
    Serial.println("Can't open file on SD");
  }

  // Switch to read
  dataIn();
  controlIn();
}

// Print the current mapping from the hidden "page buffer"
void printMapping(byte startBank) {
  // Switch to write
  dataOut();
  controlOut();

  if (romType) {
    // Unlock read map
    writeBank(startBank, 0x0000, 0x38);
    writeBank(startBank, 0x0000, 0xd0);
    // Read Extended Status Register (GSR and PSR)
    writeBank(startBank, 0x0000, 0x71);
    // Page Buffer Swap
    writeBank(startBank, 0x0000, 0x72);
    // Read Page Buffer
    writeBank(startBank, 0x0000, 0x75);

    // Switch to read
    dataIn();
    controlIn();

    char buffer[3];
    for (unsigned long currByte = 0xFF00; currByte < 0xFFFF; currByte += 16) {
      Serial.print("0x");
      Serial.print(startBank, HEX);
      Serial.print(currByte, HEX);
      Serial.print(" ");
      for (int c = 0; c < 16; c++) {
        itoa (readBank(startBank, currByte + c), buffer, 16);
        for (int i = 0; i < 2 - strlen(buffer); i++) {
          Serial.print('0');
        }
        // Now print the significant bits
        Serial.print(buffer);
        Serial.print(" ");
      }
      Serial.println("");
    }

  }
  else {
    writeBank(0, 0x8000 + 0x0000, 0x38);
    writeBank(0, 0x8000 + 0x0000, 0xd0);
    // Read Extended Status Register (GSR and PSR)
    writeBank(0, 0x8000 + 0x0000, 0x71);
    // Page Buffer Swap
    writeBank(0, 0x8000 + 0x0000, 0x72);
    // Read Page Buffer
    writeBank(0, 0x8000 + 0x0000, 0x75);

    // Switch to read
    dataIn();
    controlIn();

    char buffer[3];
    for (unsigned long currByte = 0x0000; currByte < 0x100; currByte += 16) {
      Serial.print("0x");
      Serial.print(currByte, HEX);
      Serial.print(" ");
      for (int c = 0; c < 16; c++) {
        itoa (readBank(0, 0x8000 + currByte + c), buffer, 16);
        for (int i = 0; i < 2 - strlen(buffer); i++) {
          Serial.print('0');
        }
        // Now print the significant bits
        Serial.print(buffer);
        Serial.print(" ");
      }
      Serial.println("");
    }
  }

  // Switch to read
  dataIn();
  controlIn();
}

// Send a command to the MX15001 chip
byte sendNP(byte command) {
  // Switch to write
  dataOut();
  controlOut();

  // Write command then pulse clock pin 2 times
  writeBankClock(0, 0x2400, 0x09);
  pulseClock(4);

  // Switch to read
  dataIn();
  controlIn();

  // Read status
  NPReady = readBankClock(0, 0x2400);

  // Switch to write
  dataOut();
  controlOut();

  writeBankClock(0, 0x2401, 0x28);
  pulseClock(4);
  writeBankClock(0, 0x2401, 0x84);
  pulseClock(4);

  // NP_CMD_06h, send this only if above read has returned 7Dh, not if it's already returning 2Ah
  if (NPReady == 0x7D) {
    writeBankClock(0, 0x2400, 0x06);
    pulseClock(4);
    writeBankClock(0, 0x2400, 0x39);
    pulseClock(4);
  }

  // Write the command
  writeBankClock(0, 0x2400, command);

  // Switch to read
  dataIn();
  controlIn();

  // Read status
  NPReady = readBankClock(0, 0x2400);
  return NPReady;
}

// This function will erase and program one of the flashroms(0xC0 or 0xE0) from a file off the SD card
void programFlash(int startBank, uint32_t pos) {
  // Switch NP cart's mapping
  if (unlockedHirom()) {

    // Get ID
    idFlash(startBank);
    if (strcmp(flashid, "c2f3") == 0) {
      Serial.print("Flash ID: ");
      Serial.println(flashid);
      resetFlash(startBank);

      // Erase flash
      Serial.print("Checking if flash is empty...");
      if (blankcheck(startBank))
        Serial.println("Success.");
      else {
        Serial.println("Flash is not blank.");
        Serial.print("Erasing...");
        eraseFlash(startBank);
        resetFlash(startBank);
        Serial.println("Done.");
        Serial.print("Checking if flash is empty...");
        if (blankcheck(startBank))
          Serial.println("Success.");
        else {
          Serial.println("Abort: Could not erase flash.");
          while (1);
        }
      }
      // Write flash
      writeFlash(startBank, pos);

      // Reset flash
      resetFlash(startBank);

      // Checking for errors
      Serial.print("Checking for write errors...");
      writeErrors = verifyFlash(startBank, pos);
      if (writeErrors == 0) {
        Serial.println("Flashrom verified ok");
      }
      else {
        Serial.print("Number of Errors: ");
        Serial.println(writeErrors);
      }
    }
    else {
      Serial.println("Error: Wrong Flash ID");
    }
  }
}

// This function will erase and program the NP cart(both flashroms) from a 4MB file off the SD card
void programNP() {
  // Switch NP cart's mapping
  if (unlockedHirom()) {

    // Get ID
    for (int currFlash = 0; currFlash < 33; currFlash += 32) {
      idFlash(0xC0 + currFlash);
      if (strcmp(flashid, "c2f3") == 0) {
        resetFlash(0xC0 + currFlash);

        // Erase flash
        Serial.print("Checking if flash 0x");
        Serial.print(0xC0 + currFlash, HEX);
        Serial.print(" is empty...");
        if (blankcheck(0xC0 + currFlash))
          Serial.println("Success.");
        else {
          Serial.println("Flash is not blank.");
          Serial.print("Erasing...");
          eraseFlash(0xC0 + currFlash);
          resetFlash(0xC0 + currFlash);
          if (blankcheck(0xC0 + currFlash))
            Serial.println("Done.");
          else {
            Serial.println("Abort: Could not erase flash.");
            while (1);
          }
        }
      }
      else {
        Serial.println("Error: Wrong Flash ID");
        while (1);
      }
    }
    // Write flash
    Serial.print("Writing...");
    writeNP();
    Serial.println("");

    // Reset flash
    resetFlash(0xC0);
    resetFlash(0xE0);

    // Checking for errors
    Serial.println("Checking for write errors...");
    writeErrors = verifyFlash(0xC0, 0);
    if (writeErrors == 0) {
      Serial.println("Flash 0xC0 verified OK.");
    }
    else {
      Serial.print("Number of Errors: ");
      Serial.println(writeErrors);
    }
    // Checking for errors
    writeErrors = verifyFlash(0xE0, 2097152);
    if (writeErrors == 0) {
      Serial.println("Flash 0xE0 verified OK.");
    }
    else {
      Serial.print("Number of Errors: ");
      Serial.println(writeErrors);
    }
  }
}

/******************************************
  SNES SRAM Functions
*****************************************/
// Dump the SRAM to the SD card
void writeSRAM () {
  //open file on sd card
  if (myFile.open(fileName, O_READ)) {

    // Set pins to output
    dataOut();
    // Set RST RD WR to High and CS to Low
    controlOut();

    // Dump HiRom
    if (romType) {
      // Writing SRAM on HiRom needs CS to be high
      digitalWrite(csPin, HIGH);
      // Sram size
      long lastByte = (long(sramSize) * 128) + 24576;
      for (long currByte = 24576; currByte < lastByte; currByte++) { //startAddr = 0x6000
        writeBank(48, currByte, myFile.read()); //startBank = 0x30
      }
    }
    // Dump LoRom
    else {
      // Sram size
      long lastByte = (long(sramSize) * 128);
      for (long currByte = 0; currByte <  lastByte; currByte++) { //startAddr = 0x0000
        writeBank(112, currByte, myFile.read()); //startBank = 0x70
      }
    }

    // set control
    controlIn();
    // Set pins to input
    dataIn();

    // Close the file:
    myFile.close();
    Serial.println("SRAM writing finished");
  }
  else {
    Serial.println("File doesnt exist");
  }
}

void readSRAM () {
  // set control
  controlIn();
  // Set pins to input
  dataIn();

  //open file on sd card
  if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
    Serial.println("SD Error");
  }
  if (romType) {
    // Dumping SRAM on HiRom needs CS to be high
    digitalWrite(csPin, HIGH);
    // Sram size
    long lastByte = (long(sramSize) * 128) + 24576;
    for (long currByte = 24576; currByte < lastByte; currByte++) { //startAddr = 0x6000
      myFile.write(readBank(48, currByte)); //startBank = 0x30
    }
  }
  else {
    // Sram size
    long lastByte = (long(sramSize) * 128);
    for (long currByte = 0; currByte < lastByte; currByte++) { //startAddr = 0x0000
      myFile.write(readBank(112, currByte)); //startBank = 0x70
    }
  }
  // Close the file:
  myFile.close();

  // Signal end of process
  Serial.print("Saved to ");
  Serial.println(fileName);
}

// Check if the SRAM was written without any error
unsigned long verifySRAM() {
  //open file on sd card
  if (myFile.open(fileName, O_READ)) {

    // Variable for errors
    writeErrors = 0;

    // set control
    controlIn();
    // Set pins to input
    dataIn();

    if (romType) {
      // Dumping SRAM on HiRom needs CS to be high
      digitalWrite(csPin, HIGH);
      // Sram size
      long lastByte = (long(sramSize) * 128) + 24576;
      for (long currByte = 24576; currByte < lastByte; currByte += 512) {
        //fill sdBuffer
        myFile.read(SDBuffer, 512);
        for (unsigned long c = 0; c < 512; c++) {
          if ((readBank(48, currByte + c)) != SDBuffer[c]) {
            writeErrors++;
          }
        }
      }
    }
    else {
      // Sram size
      long lastByte = (long(sramSize) * 128);
      for (long currByte = 0; currByte < lastByte; currByte += 512) {
        //fill sdBuffer
        myFile.read(SDBuffer, 512);
        for (unsigned long c = 0; c < 512; c++) {
          if ((readBank(112, currByte + c)) != SDBuffer[c]) {
            writeErrors++;
          }
        }
      }
    }

    // Close the file:
    myFile.close();
    return writeErrors;
  }
  else {
    Serial.println("Can't open file");
  }
}

/******************************************
  Main loop
*****************************************/
void loop() {
  // Print menu to serial monitor
  Serial.println("MENU----------------------------------------------------->");
  Serial.println("Change Mode: ");
  Serial.println("(0)Menu  (2)GAME2 (4)GAME4 (6)GAME6 (8)HIROM ALL");
  Serial.println("(1)GAME1 (3)GAME3 (5)GAME5 (7)GAME7 (9)HIROM MENU");
  Serial.println("");
  Serial.println("Flash Commands: ");
  Serial.println("(A)Read ID (B)Reset (C)Print Flash");
  Serial.println("");
  Serial.println("Read / Write Rom: ");
  Serial.println("(D)Read Rom (E)Erase Flash (F)Write Rom");
  Serial.println("");
  Serial.println("Read / Write Mapping: ");
  Serial.println("(G)Print mapping (H)Save mapping");
  Serial.println("(I)Erase mapping (J)Write mapping");
  Serial.println("");
  Serial.println("Sector Protection: ");
  Serial.println("(K)Read Sector Protection");
  Serial.println("(L)Unlock Sector Protection");
  Serial.println("(M)Set Sector Protection");
  Serial.println("");
  Serial.println("Read / Write SRAM: ");
  Serial.println("(N)Read SRAM");
  Serial.println("(O)Write SRAM");
  Serial.println("");
  Serial.println("Misc: ");
  Serial.println("(P)Print Ports");
  Serial.println("(Q)Unlock WP");

  Serial.println("");

  // Wait for user input
  Serial.println("-------> Please enter a single digit or letter: _  <-------");
  Serial.println("");

  while (Serial.available() == 0) {
  }

  // Read the incoming byte:
  incomingByte = Serial.read();

  // Execute user choice
  switch (incomingByte) {
    case 48:
      Serial.println("(0)Menu 0x80");
      // Reset to Menu
      romType = 0;
      sendNP(0x80);
      readPorts();
      break;

    case 49:
      Serial.println("(1)GAME1 0x81");
      // Reset to GAME1
      romType = 0;
      sendNP(0x81);
      readPorts();
      break;

    case 50:
      Serial.println("(2)GAME2 0x82");
      // Reset to GAME2
      romType = 0;
      sendNP(0x82);
      readPorts();
      break;

    case 51:
      Serial.println("(3)GAME3 0x83");
      // Reset to GAME3
      romType = 0;
      sendNP(0x83);
      readPorts();
      break;

    case 52:
      Serial.println("(4)GAME4 0x84");
      // Reset to GAME4
      romType = 0;
      sendNP(0x84);
      readPorts();
      break;

    case 53:
      Serial.println("(5)GAME5 0x85");
      // Reset to GAME5
      romType = 0;
      sendNP(0x85);
      readPorts();
      break;

    case 54:
      Serial.println("(6)GAME6 0x86");
      // Reset to GAME6
      romType = 0;
      sendNP(0x86);
      readPorts();
      break;

    case 55:
      Serial.println("(7)GAME7 0x87");
      // Reset to GAME7
      romType = 0;
      sendNP(0x87);
      readPorts();
      break;

    case 56:
      Serial.println("(8)HIROM ALL 0x04");
      // Reset to HIROM ALL
      romType = 1;
      if (sendNP(0x04) == 0x2A)
        Serial.println("Success");
      else
        Serial.println("Failed");
      readPorts();
      break;

    case 57:
      Serial.println("(9)HIROM MENU 0x05");
      // Reset to HIROM MENU
      romType = 1;
      sendNP(0x05);
      readPorts();
      break;

    case 97:
      Serial.println("(A)Read Flash ID");
      // Read Flash ID
      idFlash(0xC0);
      Serial.print("Flash ID 1: ");
      Serial.println(flashid);
      resetFlash(0xC0);
      idFlash(0xE0);
      Serial.print("Flash ID 2: ");
      Serial.println(flashid);
      resetFlash(0xE0);
      break;

    case 98:
      Serial.println("(B)Reset flash");
      // Reset flash #1
      resetFlash(0xC0);
      // Reset flash #2
      resetFlash(0xE0);
      // Reset flash #2
      resetFlash(0xE0);
      break;

    case 99:
      Serial.println("(C)Print Flash");
      // Print flash
      Serial.println("Flash: ");
      printFlashAll();
      Serial.println(" ");
      break;

    case 100:
      Serial.println("(D)Dump NP");
      resetFlash(0xC0);
      resetFlash(0xE0);
      romType = 1;
      flashSize = 4194304;
      numBanks = 64;
      Serial.print("Switching Mapping to HiRom All...");
      if (sendNP(0x04) == 0x2A) {
        Serial.println("Success.");
        // Read flash
        getfilename();
        readFlash();
      }
      else {
        Serial.println("Error: Switching to HiRom All failed.");
        Serial.println("Please power - cycle NP cart.");
        readPorts();
      }
      break;

    case 101:
      Serial.println("(E)Erase NP");
      flashSize = 2097152;
      numBanks = 32;
      if (unlockedHirom()) {
        Serial.println("Erasing flashrom #1");
        // Erase Flash #1
        eraseFlash(0xC0);
        resetFlash(0xC0);
        Serial.print("Blankcheck...");
        if (blankcheck(0xC0))
          Serial.println("Success.");
        else
          Serial.println("failed.");
        // Erase Flash #2
        Serial.println("Erasing flashrom #2");
        eraseFlash(0xE0);
        resetFlash(0xE0);
        Serial.print("Blankcheck...");
        if (blankcheck(0xE0))
          Serial.println("Success.");
        else
          Serial.println("failed.");
      }
      break;

    case 102:
      Serial.println("(F)Write NP");
      flashSize = 2097152;
      numBanks = 32;
      // Prompt user input
      getfilename();

      //Serial Programming
      Serial.println("Programming 1st flashrom.");
      // Program 1st flashrom
      programFlash(0xC0, 0);
      Serial.println("");
      Serial.println("Programming 2nd flashrom.");
      // Program 2nd flashrom
      programFlash(0xE0, 2097152);

      //Parallel Programming
      //programNP();
      break;

    case 103:
      Serial.println("(G)Print mapping");
      if (unlockedHirom()) {
        printMapping(0xC0);
        printMapping(0xE0);
      }
      break;

    case 104:
      Serial.println("(H)Save mapping");
      if (unlockedHirom()) {
        getfilename();
        readMapping();
      }
      break;

    case 105:
      Serial.println("(I)Erase mapping");
      if (unlockedHirom()) {
        eraseMapping(0xD0); // 0xC0 not working
        eraseMapping(0xE0);
        printMapping(0xC0);
        printMapping(0xE0);
      }
      break;

    case 106:
      Serial.println("(J)Write mapping");
      if (unlockedHirom()) {
        getfilename();
        writeMapping(0xD0, 0); // 0xC0 not working
        writeMapping(0xE0, 256);
        printMapping(0xC0);
        printMapping(0xE0);
      }
      break;

    case 107:
      Serial.println("(K)Read Sector Protection");
      if (unlockedHirom()) {
        byte protectStatus;
        protectStatus = readSectorProtection(0xC0);
        if (protectStatus == 2)
          Serial.println("Can't access Flash C0");
        else if (protectStatus == 0)
          Serial.println("Flash C0 unprotected");
        protectStatus = readSectorProtection(0xE0);
        if (protectStatus == 2)
          Serial.println("Can't access Flash E0");
        else if (protectStatus == 0)
          Serial.println("Flash E0 unprotected");
      }
      break;

    case 108:
      Serial.println("(L)Unlock Sector Protection");
      if (unlockedHirom()) {
        unlockSectorProtection(0xC1);
        unlockSectorProtection(0xE0);
      }
      break;

    case 109:
      Serial.println("(M)Set Sector Protection");
      if (unlockedHirom()) {
        setSectorProtection(0xC1);
        setSectorProtection(0xE0);
      }
      break;

    case 110:
      Serial.println("(N)Read SRAM");
      if (sendNP(0x04) == 0x2A) {
        romType = 1;
        getfilename();
        readSRAM();
      }
      else
        Serial.println("Switching to Hirom failed.");
      break;

    case 111:
      Serial.println("(O)Write SRAM");
      if (sendNP(0x04) == 0x2A) {
        romType = 1;
        getfilename();
        writeSRAM();
        Serial.print("Verifying...");
        if (verifySRAM() == 0)
          Serial.println("OK.");
        else
          Serial.println("failed.");
      }
      else
        Serial.println("Switching to Hirom failed.");
      break;

    case 112:
      Serial.println("(P)Print Ports");
      readPorts();
      break;

    case 113:
      Serial.println("(Q)Unlock WP");
      sendNP(0x02);
      if (readBankClock(0, 0x2401) == 0x4) {
        Serial.println("Success.");
        readPorts();
      }
      else {
        Serial.println("Error: Unlocking WP failed.");
        Serial.println("Please power - cycle NP cart.");
        readPorts();
      }
      break;
  }
  Serial.println("");
}

//******************************************
// End of File
//******************************************
