//******************************************
// SUPER NINTENDO
//******************************************

/******************************************
  Defines
 *****************************************/
// SNES Hi and LoRom, SA is HI with different Sram dumping
#define EX 4
#define SA 3
#define HI 1
#define LO 0

/******************************************
   Variables
 *****************************************/
// Define SNES Cart Reader Variables
int romSpeed = 0;      // 0 = SlowROM, 3 = FastROM
int romChips = 0;      // 0 = ROM only, 1 = ROM & RAM, 2 = ROM & Save RAM,  3 = ROM & DSP1, 4 = ROM & RAM & DSP1, 5 = ROM & Save RAM & DSP1, 19 = ROM & SFX
// 227 = ROM & RAM & GameBoy data, 246 = ROM & DSP2
byte romSizeExp = 0;   // ROM-Size Exponent
int cartCountry = 255;
boolean NP = false;

/******************************************
  Menu
*****************************************/
// SNES menu items
const char SnesMenuItem1[] PROGMEM = "Read Rom";
const char SnesMenuItem2[] PROGMEM = "Read Save";
const char SnesMenuItem3[] PROGMEM = "Write Save";
const char SnesMenuItem4[] PROGMEM = "Test SRAM";
const char SnesMenuItem5[] PROGMEM = "Cycle cart";
const char SnesMenuItem6[] PROGMEM = "Reset";
const char* const menuOptionsSNES[] PROGMEM = {SnesMenuItem1, SnesMenuItem2, SnesMenuItem3, SnesMenuItem4, SnesMenuItem5, SnesMenuItem6};

// Manual config menu items
const char confMenuItem1[] PROGMEM = "Use header info";
const char confMenuItem2[] PROGMEM = "4MB LoRom 256K Sram";
const char confMenuItem3[] PROGMEM = "4MB HiRom 64K Sram";
const char confMenuItem4[] PROGMEM = "6MB ExRom 256K Sram";
const char confMenuItem5[] PROGMEM = "Reset";
const char* const menuOptionsConf[] PROGMEM = {confMenuItem1, confMenuItem2, confMenuItem3, confMenuItem4, confMenuItem5};

// SNES Menu
void snesMenu() {
  // create menu with title and 7 options to choose from
  unsigned char mainMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsSNES, 6);
  mainMenu = question_box("SNES Cart Reader", menuOptions, 6, 0);

  // wait for user choice to come back from the question box menu
  switch (mainMenu)
  {
    case 0:
      display_Clear();
      // Change working dir to root
      sd.chdir("/");
      readROM_SNES();
      compare_checksum();
      break;

    case 1:
      if (sramSize > 0) {
        display_Clear();
        // Change working dir to root
        sd.chdir("/");
        readSRAM();
      }
      else {
        display_Clear();
        print_Error(F("Does not have SRAM"), false);
      }
      break;

    case 2:
      if (sramSize > 0) {
        display_Clear();
        // Change working dir to root
        sd.chdir("/");
        writeSRAM(1);
        unsigned long wrErrors;
        wrErrors = verifySRAM();
        if (wrErrors == 0) {
          println_Msg(F("Verified OK"));
          display_Update();
        }
        else {
          print_Msg(F("Error: "));
          print_Msg(wrErrors);
          println_Msg(F(" bytes "));
          print_Error(F("did not verify."), false);
        }
      }
      else {
        display_Clear();
        print_Error(F("Does not have SRAM"), false);
      }
      break;

    case 3:
      if (sramSize > 0) {
        display_Clear();
        println_Msg(F("Warning:"));
        println_Msg(F("This can erase"));
        println_Msg(F("your save games"));
        println_Msg(F(""));
        println_Msg(F(""));
        println_Msg(F("Press any button to"));
        println_Msg(F("start sram testing"));
        display_Update();
        wait();
        display_Clear();
        // Change working dir to root
        sd.chdir("/");
        readSRAM();
        eraseSRAM(0x00);
        eraseSRAM(0xFF);
        writeSRAM(0);
        unsigned long wrErrors = verifySRAM();
        if (wrErrors == 0) {
          println_Msg(F("Restored OK"));
          display_Update();
        }
        else {
          print_Msg(F("Error: "));
          print_Msg(wrErrors);
          println_Msg(F(" bytes "));
          print_Error(F("did not verify."), false);
        }
      }
      else {
        display_Clear();
        print_Error(F("Does not have SRAM"), false);
      }
      break;

    case 4:
      // For arcademaster1 (Markfrizb) multi-game carts
      // Set reset pin to output (PH0)
      DDRH |= (1 << 0);
      // Switch RST(PH0) to LOW
      PORTH &= ~(1 << 0);
      display_Clear();
      print_Msg("Resetting...");
      display_Update();
      delay(3000);  // wait 3 secs to switch to next game
      asm volatile ("  jmp 0");
      break;

    case 5:
      asm volatile ("  jmp 0");
      break;
  }
  println_Msg(F(""));
  println_Msg(F("Press Button..."));
  display_Update();
  wait();
}

// Menu for manual configuration
void confMenu() {
  // create menu with title and 5 options to choose from
  unsigned char subMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsConf, 5);
  subMenu = question_box("Choose mapping", menuOptions, 5, 0);

  // wait for user choice to come back from the question box menu
  switch (subMenu)
  {
    case 0:
      break;

    case 1:
      romType = LO;
      numBanks = 128;
      sramSize = 256;
      strcpy(romName, "LOROM");
      break;

    case 2:
      romType = HI;
      numBanks = 64;
      sramSize = 64;
      strcpy(romName, "HIROM");
      break;

    case 3:
      romType = EX;
      numBanks = 96;
      sramSize = 256;
      strcpy(romName, "EXROM");
      break;

    case 4:
      // Reset
      asm volatile ("  jmp 0");
      break;
  }
}

/******************************************
   Setup
 *****************************************/
void setup_Snes() {
  // Set cicrstPin(PG1) to Output
  DDRG |= (1 << 1);
  // Output a high signal until we're ready to start
  PORTG |= (1 << 1);
  // Set cichstPin(PG0) to Input
  DDRG &= ~(1 << 0);

  // Adafruit Clock Generator
  //clockgen.set_correction(-29000);
  clockgen.set_correction(0);
  clockgen.init(SI5351_CRYSTAL_LOAD_8PF, 0);
  clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLA);
  clockgen.set_pll(SI5351_PLL_FIXED, SI5351_PLLB);
  clockgen.set_freq(2147727200ULL, SI5351_PLL_FIXED, SI5351_CLK0);
  clockgen.set_freq(307200000ULL, SI5351_PLL_FIXED, SI5351_CLK2);
  clockgen.output_enable(SI5351_CLK0, 1);
  clockgen.output_enable(SI5351_CLK1, 0);
  clockgen.output_enable(SI5351_CLK2, 1);

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

  // Set Control Pins to Output RST(PH0) CS(PH3) WR(PH5) RD(PH6)
  DDRH |= (1 << 0) | (1 << 3) | (1 << 5) | (1 << 6);
  // Switch RST(PH0) and WR(PH5) to HIGH
  PORTH |= (1 << 0) | (1 << 5);
  // Switch CS(PH3) and RD(PH6) to LOW
  PORTH &= ~((1 << 3) | (1 << 6));

  // Set Refresh(PE5) to Output
  DDRE |= (1 << 5);
  // Switch Refresh(PE5) to LOW (needed for SA-1)
  PORTE &= ~(1 << 5);

  // Set CPU Clock(PH1) to Output
  DDRH |= (1 << 1);
  //PORTH &= ~(1 << 1);

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

  // Set expand(PG5) to Imput
  DDRG &= ~(1 << 5);
  // Activate Internal Pullup Resistors
  //PORTG |= (1 << 5);

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

  // Unused pins
  // Set wram(PE4) to Output
  DDRE |= (1 << 4);
  //PORTE &= ~(1 << 4);
  // Set pawr(PJ1) to Output
  DDRJ |= (1 << 1);
  //PORTJ &= ~(1 << 1);
  // Set pard(PJ0) to Output
  DDRJ |= (1 << 0);
  //PORTJ &= ~(1 << 0);

  // Start CIC by outputting a low signal to cicrstPin(PG1)
  PORTG  &= ~(1 << 1);

  // Wait for CIC reset
  delay(1000);

  // Print all the info
  getCartInfo_SNES();
}

/******************************************
   I/O Functions
 *****************************************/
// Switch control pins to write
void controlOut_SNES() {
  // 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_SNES() {
  // Switch WR(PH5) to HIGH
  PORTH |= (1 << 5);
  // Switch CS(PH3) and RD(PH6) to LOW
  PORTH &= ~((1 << 3) | (1 << 6));
}

/******************************************
   Low level functions
 *****************************************/
// Write one byte of data to a location specified by bank and address, 00:0000
void writeBank_SNES(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""nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");

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

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

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

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

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

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

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

/******************************************
  SNES ROM Functions
******************************************/
void getCartInfo_SNES() {
  boolean manualConfig = 0;

  // Print start page
  if (checkcart_SNES() == 0) {
    // Checksum either corrupt or 0000
    manualConfig = 1;
    errorLvl = 1;
    rgb.setColor(255, 0, 0);

    display_Clear();
    println_Msg(F("ERROR"));
    println_Msg(F("Rom header corrupt"));
    println_Msg(F("or missing"));
    println_Msg(F(""));
    println_Msg(F(""));
    println_Msg(F("Press button for"));
    println_Msg(F("manual configuration"));
    println_Msg(F("or powercycle if SA1"));
    display_Update();
    wait();
    // Wait() clears errors but in this case we still have an error
    errorLvl = 1;
  }

  display_Clear();
  print_Msg(F("Rom Name: "));
  println_Msg(romName);

  print_Msg(F("Type: "));
  if (romType == HI)
    print_Msg(F("HiROM"));
  else if (romType == LO)
    print_Msg(F("LoROM"));
  else if (romType == EX)
    print_Msg(F("ExHiRom"));
  else
    print_Msg(romType);
  print_Msg(F(" "));
  if (romSpeed == 0)
    println_Msg(F("SlowROM"));
  else if (romSpeed == 2)
    println_Msg(F("SlowROM"));
  else if (romSpeed == 3)
    println_Msg(F("FastROM"));
  else
    println_Msg(romSpeed);

  print_Msg(F("ICs: ROM "));
  if (romChips == 0)
    println_Msg(F("ONLY"));
  else if (romChips == 1)
    println_Msg(F("RAM"));
  else if (romChips == 2)
    println_Msg(F("SAVE"));
  else if (romChips == 3)
    println_Msg(F("DSP1"));
  else if (romChips == 4)
    println_Msg(F("DSP1 RAM"));
  else if (romChips == 5)
    println_Msg(F("DSP1 SAVE"));
  else if ((romChips == 19) || (romChips == 20) || (romChips == 21) || (romChips == 26))
    println_Msg(F("SuperFX"));
  else if (romChips == 52) {
    println_Msg(F("SA1 RAM"));
    romType = SA;
  }
  else if (romChips == 53) {
    println_Msg(F("SA1 RAM BATT"));
    romType = SA;
  }
  else if (romChips == 69) {
    println_Msg(F("SDD1 BATT"));
  }
  else if (romChips == 227)
    println_Msg(F("RAM GBoy"));
  else if (romChips == 246)
    println_Msg(F("DSP2"));
  else
    println_Msg(F(""));

  print_Msg(F("Rom Size: "));
  print_Msg(romSize);
  println_Msg(F("Mbit"));

  print_Msg(F("Banks: "));
  println_Msg(numBanks);

  print_Msg(F("Sram Size: "));
  print_Msg(sramSize);
  println_Msg(F("Kbit"));

  print_Msg(F("ROM Version: 1."));
  println_Msg(romVersion);

  print_Msg(F("Checksum: "));
  println_Msg(checksumStr);
  display_Update();

  // Wait for user input
  if (enable_OLED) {
    println_Msg(F(" "));
    println_Msg(F(" "));
    println_Msg(F("Press Button..."));
    display_Update();
    wait();
  }

  else if (enable_Serial) {
    println_Msg(F(" "));
  }

  // Start manual config
  if (manualConfig == 1) {
    confMenu();
  }
}

void checkAltConf() {
  char tempStr1[2];
  char tempStr2[5];
  char sizeStr[3];
  char bankStr[3];

  if (myFile.open("snes.txt", O_READ)) {
    while (myFile.available()) {
      // Read 4 bytes into String, do it one at a time so byte order doesn't get mixed up
      sprintf(tempStr1, "%c", myFile.read());
      strcpy(tempStr2, tempStr1);
      sprintf(tempStr1, "%c", myFile.read());
      strcat(tempStr2, tempStr1);
      sprintf(tempStr1, "%c", myFile.read());
      strcat(tempStr2, tempStr1);
      sprintf(tempStr1, "%c", myFile.read());
      strcat(tempStr2, tempStr1);

      // Check if string is a match
      if (strcmp(tempStr2, checksumStr) == 0) {

        // Skip the , in the file
        myFile.seekSet(myFile.curPosition() + 1);

        // Read next two bytes into a string
        romSize = myFile.read() - 48;
        romSize = romSize * 10 +  myFile.read() - 48;

        // Skip the , in the file
        myFile.seekSet(myFile.curPosition() + 1);

        // Add next two bytes to the string
        numBanks  = myFile.read() - 48;
        numBanks  =  numBanks  * 10 +  myFile.read() - 48;
      }
      // If no match empty string advance by 8 and try again
      else {
        myFile.seekSet(myFile.curPosition() + 8);
      }
    }
  }
  // Close the file:
  myFile.close();
}

// Read header information
boolean checkcart_SNES() {
  // set control to read
  dataIn();

  // Get Checksum as string
  sprintf(checksumStr, "%02X%02X", readBank_SNES(0, 65503), readBank_SNES(0, 65502));

  // Check if ExHiROM
  if (readBank_SNES(0, 0xFFD5) == 0x35) {
    romType = EX;
  }
  else {
    // Check if LoROM or HiROM
    romType = (readBank_SNES(0, 0xFFD5) & 1);
  }

  // Check RomSpeed
  romSpeed = (readBank_SNES(0, 65493) >> 4);

  // Check RomChips
  romChips = readBank_SNES(0, 65494);

  if (romChips == 69)
  {
    romSize = 48;
    numBanks = 96;
    romType = HI;
  }
  else
  {
    // Check RomSize
    byte romSizeExp = readBank_SNES(0, 65495) - 7;
    romSize = 1;
    while (romSizeExp--)
      romSize *= 2;

    if ((romType == EX) || (romType == SA)) {
      numBanks = long(romSize) * 2;
    }
    else {
      numBanks = (long(romSize) * 1024 * 1024 / 8) / (32768 + (long(romType) * 32768));
    }
  }

  //Check SD card for alt config
  checkAltConf();

  // Dump name into 8.3 compatible format
  byte myByte = 0;
  byte myLength = 0;
  for (unsigned int i = 65472; i < 65492; i++) {
    myByte = readBank_SNES(0, i);
    if (((char(myByte) >= 48 && char(myByte) <= 57) || (char(myByte) >= 65 && char(myByte) <= 122)) && myLength < 8) {
      romName[myLength] = char(myByte);
      myLength++;
    }
  }

  // Read sramSizeExp
  byte sramSizeExp;
  if ((romChips == 19) || (romChips == 20) || (romChips == 21) || (romChips == 26)) {
    // SuperFX
    if (readBank_SNES(0, 0x7FDA) == 0x33) {
      sramSizeExp = readBank_SNES(0, 0x7FBD);
    }
    else {
      if (strncmp(romName, "STARFOX2", 8) == 0) {
        sramSizeExp = 6;
      }
      else {
        sramSizeExp = 5;
      }
    }
  }
  else {
    // No SuperFX
    sramSizeExp = readBank_SNES(0, 0xFFD8);
  }

  // Calculate sramSize
  if (sramSizeExp != 0) {
    sramSizeExp = sramSizeExp + 3;
    sramSize = 1;
    while (sramSizeExp--)
      sramSize *= 2;
  }
  else {
    sramSize = 0;
  }

  // Check Cart Country
  cartCountry = readBank_SNES(0, 65497);

  // ROM Version
  romVersion = readBank_SNES(0, 65499);

  // Test if checksum is equal to reverse checksum
  if (((word(readBank_SNES(0, 65500)) + (word(readBank_SNES(0, 65501)) * 256)) + (word(readBank_SNES(0, 65502)) + (word(readBank_SNES(0, 65503)) * 256))) == 65535 ) {
    if (strcmp("0000", checksumStr) == 0) {
      return 0;
    }
    else {
      return 1;
    }
  }
  // Either rom checksum is wrong or no cart is inserted
  else {
    return 0;
  }
}

unsigned int calc_checksum (char* fileName, char* folder) {
  unsigned int calcChecksum = 0;
  unsigned int calcChecksumChunk = 0;
  int calcFilesize = 0;
  unsigned int c = 0;
  unsigned long i = 0;
  unsigned long j = 0;

  if (strcmp(folder, "root") != 0)
    sd.chdir(folder);

  // If file exists
  if (myFile.open(fileName, O_READ)) {
    calcFilesize = myFile.fileSize() * 8 / 1024 / 1024;

    // Nintendo Power (SF Memory Cassette)
    // Read up to 0x60000 then add FFs to 0x80000
    if (NP == true) {
      for (i = 0; i < (0x60000 / 512); i++) {
        myFile.read(sdBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += sdBuffer[c];
        }
        calcChecksum = calcChecksumChunk;
      }
      calcChecksum += 0xF47C; // FFs from 0x60000-0x80000
    }
    else if (calcFilesize == 12 || calcFilesize == 20) {
      // Divide filesize by 8 to get number of 8Mbit chunks
      for (i = 0; i < (calcFilesize / 8); i++ ) {
        // Add all the bits in the 8Mbit chunks
        for (j = 0; j < (1048576 / 512); j++) {
          myFile.read(sdBuffer, 512);
          for (c = 0; c < 512; c++) {
            calcChecksumChunk += sdBuffer[c];
          }
        }
        calcChecksum = calcChecksumChunk;
      }
      calcChecksumChunk = 0;
      // Add the 4Mbit rest
      for (j = 0; j < (524288 / 512); j++) {
        myFile.read(sdBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += sdBuffer[c];
        }
      }
      calcChecksum +=  2 * calcChecksumChunk;
    }
    else if (calcFilesize == 24 || calcFilesize == 28) {
      /* Momotarou Dentestu Happy Fix 3MB (24Mbit)
        if ((calcFilesize == 24) && (romSizeExp = 0x0C)) {
        for (i = 0; i < (myFile.fileSize() / 512); i++) {
          myFile.read(sdBuffer, 512);
          for (c = 0; c < 512; c++) {
            calcChecksumChunk += sdBuffer[c];
          }
        }
        calcChecksum = 2 * calcChecksumChunk;
        }
        else {*/
      for (i = 0; i < (calcFilesize / 16); i++ ) {
        // Add all the bits in the 16Mbit chunks
        for (j = 0; j < (2097152 / 512); j++) {
          myFile.read(sdBuffer, 512);
          for (c = 0; c < 512; c++) {
            calcChecksumChunk += sdBuffer[c];
          }
        }
        calcChecksum = calcChecksumChunk;
      }
      calcChecksumChunk = 0;

      // Add the 8Mbit rest
      for (j = 0; j < (1048576 / 512); j++) {
        myFile.read(sdBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += sdBuffer[c];
        }
      }
      calcChecksum +=  2 * calcChecksumChunk;
      //}
    }
    else if ((calcFilesize == 40) && (romChips == 85)) {
      // Daikaijuu Monogatari 2 Fix 5MB (40Mbit)
      // Add the 4MB (32Mbit) start
      for (j = 0; j < (4194304 / 512); j++) {
        myFile.read(sdBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += sdBuffer[c];
        }
        calcChecksum = calcChecksumChunk;
      }
      calcChecksumChunk = 0;
      // Add the 1MB (8Mbit) end
      for (j = 0; j < (1048576 / 512); j++) {
        myFile.read(sdBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += sdBuffer[c];
        }
      }
      calcChecksum +=  4 * calcChecksumChunk;
    }
    else if (calcFilesize == 48) {
      // Star Ocean/Tales of Phantasia Fix 6MB (48Mbit)
      // Add the 4MB (32Mbit) start
      for (j = 0; j < (4194304 / 512); j++) {
        myFile.read(sdBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += sdBuffer[c];
        }
        calcChecksum = calcChecksumChunk;
      }
      calcChecksumChunk = 0;
      // Add the 2MB (16Mbit) end
      for (j = 0; j < (2097152 / 512); j++) {
        myFile.read(sdBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += sdBuffer[c];
        }
      }
      calcChecksum +=  2 * calcChecksumChunk;
    }
    else {
      //calcFilesize == 2 || 4 || 8 || 16 || 32 || 40 || etc
      for (i = 0; i < (myFile.fileSize() / 512); i++) {
        myFile.read(sdBuffer, 512);
        for (c = 0; c < 512; c++) {
          calcChecksumChunk += sdBuffer[c];
        }
        calcChecksum = calcChecksumChunk;
      }
    }
    myFile.close();
    sd.chdir();
    return (calcChecksum);
  }
  // Else show error
  else {
    print_Error(F("DUMP ROM 1ST"), false);
    return 0;
  }
}

boolean compare_checksum() {

  println_Msg(F("Calculating Checksum"));
  display_Update();

  strcpy(fileName, romName);
  strcat(fileName, ".sfc");

  // last used rom folder
  EEPROM_readAnything(0, foldern);
  sprintf(folder, "ROM/%s/%d", romName, foldern - 1);

  char calcsumStr[5];
  sprintf(calcsumStr, "%04X", calc_checksum(fileName, folder));

  if (strcmp(calcsumStr, checksumStr) == 0) {
    print_Msg(F("Result: "));
    println_Msg(calcsumStr);
    println_Msg(F("Checksum matches"));
    display_Update();
    return 1;
  }
  else {
    print_Msg(F("Result: "));
    println_Msg(calcsumStr);
    print_Error(F("Checksum Error"), false);
    display_Update();
    return 0;
  }
}

// Read rom to SD card
void readROM_SNES() {
  // Set control
  dataIn();
  controlIn_SNES();

  // Get name, add extension and convert to char array for sd lib
  strcpy(fileName, romName);
  strcat(fileName, ".sfc");

  // create a new folder for the save file
  EEPROM_readAnything(0, foldern);
  sprintf(folder, "ROM/%s/%d", romName, foldern);
  sd.mkdir(folder, true);
  sd.chdir(folder);

  //clear the screen
  display_Clear();
  println_Msg(F("Creating folder: "));
  println_Msg(folder);
  display_Update();

  // write new folder number back to eeprom
  foldern = foldern + 1;
  EEPROM_writeAnything(0, foldern);

  //open file on sd card
  if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
    print_Error(F("Can't create file on SD"), true);
  }

  // Check if LoROM or HiROM...
  if (romType == LO) {
    println_Msg(F("Dumping LoRom..."));
    display_Update();

    // Read up to 96 banks starting at bank 0×00.
    for (int currBank = 0; currBank < numBanks; currBank++) {
      // Dump the bytes to SD 512B at a time
      for (long currByte = 32768; currByte < 65536; currByte += 512) {
        for (int c = 0; c < 512; c++) {
          sdBuffer[c] = readBank_SNES(currBank, currByte + c);
        }
        myFile.write(sdBuffer, 512);
      }
    }
  }
  // Dump High-type ROM
  else if (((romType == HI) || (romType == SA) || (romType == EX)) && (romChips != 69)) {
    println_Msg(F("Dumping HiRom..."));
    display_Update();

    for (int currBank = 192; currBank < (numBanks + 192); currBank++) {
      for (long currByte = 0; currByte < 65536; currByte += 512) {
        for (int c = 0; c < 512; c++) {
          sdBuffer[c] = readBank_SNES(currBank, currByte + c);
        }
        myFile.write(sdBuffer, 512);
      }
    }
  }
  // Dump SDD1 High-type ROM
  else if ((romType == HI) && (romChips == 69)) {
    println_Msg(F("Dumping SDD1 HiRom..."));
    display_Update();

    controlIn_SNES();
    byte initialSOMap = readBank_SNES(0, 18439);

    for (int currMemmap = 0; currMemmap < (numBanks / 16); currMemmap++) {

      dataOut();
      controlOut_SNES();

      writeBank_SNES(0, 18439, currMemmap);

      dataIn();
      controlIn_SNES();

      for (int currBank = 240; currBank < 256; currBank++) {
        for (long currByte = 0; currByte < 65536; currByte += 512) {
          for (int c = 0; c < 512; c++) {
            sdBuffer[c] = readBank_SNES(currBank, currByte + c);
          }
          myFile.write(sdBuffer, 512);
        }
      }
    }

    dataOut();
    controlOut_SNES();

    writeBank_SNES(0, 18439, initialSOMap);

    dataIn();
    controlIn_SNES();
  }
  // Close the file:
  myFile.close();

  // Signal end of process
  print_Msg(F("Saved as "));
  println_Msg(fileName);
}

/******************************************
  SNES SRAM Functions
*****************************************/
// Write file to SRAM
void writeSRAM (boolean browseFile) {
  if (browseFile) {
    filePath[0] = '\0';
    sd.chdir("/");
    fileBrowser("Select srm file");
    // Create filepath
    sprintf(filePath, "%s/%s", filePath, fileName);
    display_Clear();
  }
  else
    sprintf(filePath, "%s", fileName);

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

    // Set pins to output
    dataOut();

    // Set RST RD WR to High and CS to Low
    controlOut_SNES();

    // LoRom
    if (romType == LO) {
      // Sram size
      long lastByte = (long(sramSize) * 128);
      for (long currByte = 0; currByte <  lastByte; currByte++) {
        writeBank_SNES(0x70, currByte, myFile.read());
      }
    }

    // HiRom
    else if (romType == HI) {
      // Writing SRAM on HiRom needs CS(PH3) to be high
      PORTH |=  (1 << 3);
      // Sram size
      long lastByte = (long(sramSize) * 128) + 0x6000;
      for (long currByte = 0x6000; currByte < lastByte; currByte++) {
        writeBank_SNES(0x30, currByte, myFile.read());
      }
    }
    // ExHiRom
    else if (romType == EX) {
      // Writing SRAM on HiRom needs CS(PH3) to be high
      PORTH |=  (1 << 3);
      // Sram size
      long lastByte = (long(sramSize) * 128) + 0x6000;
      for (long currByte = 0x6000; currByte < lastByte; currByte++) {
        writeBank_SNES(0xB0, currByte, myFile.read());
      }
    }
    // SA1
    else if (romType == SA) {
      long lastByte = (long(sramSize) * 0x80);

      // Enable CPU Clock
      clockgen.set_freq(357954500ULL, SI5351_PLL_FIXED, SI5351_CLK1);
      clockgen.output_enable(SI5351_CLK1, 1);

      // Direct writes to BW-RAM (SRAM) in banks 0x40-0x43 don't work
      // Break BW-RAM (SRAM) into 0x2000 blocks
      // Use $2225 to map BW-RAM block to 0x6000-0x7FFF
      // Writes must be to entire address range 0x0000-0x7FFF
      byte lastBlock = 0;
      lastBlock = lastByte / 0x2000;

      // Writing SRAM on SA1 needs CS(PH3) to be high
      PORTH |=  (1 << 3);

      for (byte currBlock = 0; currBlock < lastBlock; currBlock++) {
        // Set 0x2225 (SA-1 BMAP) to map SRAM Block to 0x6000-0x7FFF
        writeBank_SNES(0, 0x2225, currBlock);
        // Set 0x2227 to 0x80  SA-1 SWBE BW-RAM Write Enable
        writeBank_SNES(0, 0x2227, 0x80);
        for (long currByte = 0x0000; currByte < 0x8000; currByte += 512) {
          if (currByte < 0x6000) {
            for (int c = 0; c < 512; c++) {
              // Shift to bypass protected 1st 0x100 bytes
              writeBank_SNES(0, currByte + c, currBlock + lastBlock);
            }
          }
          else {
            myFile.read(sdBuffer, 512);
            for (int c = 0; c < 512; c++) {
              writeBank_SNES(0, currByte + c, sdBuffer[c]);
            }
          }
        }
      }
      // Set 0x2227 to 0x00  SA-1 SWBE BW-RAM Write Disable
      writeBank_SNES(0, 0x2227, 0x00);
      // Disable CPU clock
      clockgen.output_enable(SI5351_CLK1, 0);

      // Reset SA1
      // Set pins to input
      dataIn();
      // Close the file:
      myFile.close();
      println_Msg(F("SRAM writing finished"));
      println_Msg(F("Press Button to reset"));
      display_Update();
      wait();
      // Set reset pin to output (PH0)
      DDRH |= (1 << 0);
      // Switch RST(PH0) to LOW
      PORTH &= ~(1 << 0);
      display_Clear();
      print_Msg("Resetting...");
      display_Update();
      delay(3000);  // wait 3 secs to switch to next game
      asm volatile ("  jmp 0");
    }

    // Set pins to input
    dataIn();

    // Close the file:
    myFile.close();
    println_Msg(F("SRAM writing finished"));
    display_Update();

  }
  else {
    print_Error(F("File doesnt exist"), false);
  }
}

void readSRAM () {
  // set control
  controlIn_SNES();

  // Get name, add extension and convert to char array for sd lib
  strcpy(fileName, romName);
  strcat(fileName, ".srm");

  // create a new folder for the save file
  EEPROM_readAnything(0, foldern);
  sprintf(folder, "SAVE/%s/%d", romName, foldern);
  sd.mkdir(folder, true);
  sd.chdir(folder);

  // Signal end of process
  print_Msg(F("Reading to SAVE/"));
  print_Msg(romName);
  print_Msg(F("/"));
  print_Msg(foldern);
  print_Msg(F("/"));
  print_Msg(fileName);
  print_Msg(F("..."));
  display_Update();

  // write new folder number back to eeprom
  foldern = foldern + 1;
  EEPROM_writeAnything(0, foldern);

  //open file on sd card
  if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
    print_Error(F("SD Error"), true);
  }
  if (romType == LO) {
    // Sram size
    long lastByte = (long(sramSize) * 128);
    for (long currByte = 0; currByte < lastByte; currByte++) {
      myFile.write(readBank_SNES(0x70, currByte));
    }
  }
  else if (romType == HI) {
    // Dumping SRAM on HiRom needs CS(PH3) to be high
    PORTH |=  (1 << 3);
    // Sram size
    long lastByte = (long(sramSize) * 128) + 0x6000;
    for (long currByte = 0x6000; currByte < lastByte; currByte++) {
      myFile.write(readBank_SNES(0x30, currByte));
    }
  }
  else if (romType == EX) {
    // Dumping SRAM on HiRom needs CS(PH3) to be high
    PORTH |=  (1 << 3);
    // Sram size
    long lastByte = (long(sramSize) * 128) + 0x6000;
    for (long currByte = 0x6000; currByte < lastByte; currByte++) {
      myFile.write(readBank_SNES(0xB0, currByte));
    }
  }
  else if (romType == SA) {
    // Dumping SRAM on HiRom needs CS(PH3) to be high
    PORTH |=  (1 << 3);
    // Sram size
    long lastByte = (long(sramSize) * 128);
    for (long currByte = 0x0; currByte < lastByte; currByte++) {
      myFile.write(readBank_SNES(0x40, currByte));
    }
  }

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

  // Signal end of process
  println_Msg(F("Done"));
  display_Update();
}

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

    // Variable for errors
    writeErrors = 0;

    // Set control
    controlIn_SNES();

    if (romType == LO) {
      // Sram size
      long lastByte = (long(sramSize) * 128);
      for (long currByte = 0; currByte < lastByte; currByte += 512) {
        //fill sdBuffer
        myFile.read(sdBuffer, 512);
        for (int c = 0; c < 512; c++) {
          if ((readBank_SNES(0x70, currByte + c)) != sdBuffer[c]) {
            writeErrors++;
          }
        }
      }
    }
    else if (romType == HI) {
      // Dumping SRAM on HiRom needs CS(PH3) to be high
      PORTH |=  (1 << 3);
      // Sram size
      long lastByte = (long(sramSize) * 128) + 0x6000;
      for (long currByte = 0x6000; currByte < lastByte; currByte += 512) {
        //fill sdBuffer
        myFile.read(sdBuffer, 512);
        for (int c = 0; c < 512; c++) {
          if ((readBank_SNES(0x30, currByte + c)) != sdBuffer[c]) {
            writeErrors++;
          }
        }
      }
    }
    else if (romType == EX) {
      // Dumping SRAM on HiRom needs CS(PH3) to be high
      PORTH |=  (1 << 3);
      // Sram size
      long lastByte = (long(sramSize) * 128) + 0x6000;
      for (long currByte = 0x6000; currByte < lastByte; currByte += 512) {
        //fill sdBuffer
        myFile.read(sdBuffer, 512);
        for (int c = 0; c < 512; c++) {
          if ((readBank_SNES(0xB0, currByte + c)) != sdBuffer[c]) {
            writeErrors++;
          }
        }
      }
    }
    else if (romType == SA) {
      // Dumping SRAM on HiRom needs CS(PH3) to be high
      PORTH |=  (1 << 3);
      // Sram size
      long lastByte = (long(sramSize) * 128);
      for (long currByte = 0x0; currByte < lastByte; currByte += 512) {
        //fill sdBuffer
        myFile.read(sdBuffer, 512);
        for (int c = 0; c < 512; c++) {
          if ((readBank_SNES(0x40, currByte + c)) != sdBuffer[c]) {
            writeErrors++;
          }
        }
      }
    }
    // Close the file:
    myFile.close();
    return writeErrors;
  }
  else {
    print_Error(F("Can't open file"), false);
  }
}

// Overwrite the entire SRAM
boolean eraseSRAM (byte b) {
  print_Msg(F("0x"));
  print_Msg(b, HEX);
  print_Msg(F(": "));
  display_Update();

  // Set pins to output
  dataOut();

  // Set control pins
  controlOut_SNES();

  if (romType == LO) {

    // Sram size
    long lastByte = (long(sramSize) * 128);
    for (long currByte = 0; currByte <  lastByte; currByte++) {
      writeBank_SNES(0x70, currByte, b);
    }
  }
  else if (romType == HI) {
    // Writing SRAM on HiRom needs CS(PH3) to be high
    PORTH |=  (1 << 3);
    // Sram size
    long lastByte = (long(sramSize) * 128) + 0x6000;
    for (long currByte = 0x6000; currByte <  lastByte; currByte++) {
      writeBank_SNES(0x30, currByte, b);

    }
  }
  // ExHiRom
  else if (romType == EX) {
    // Writing SRAM on HiRom needs CS(PH3) to be high
    PORTH |=  (1 << 3);
    // Sram size
    long lastByte = (long(sramSize) * 128) + 0x6000;
    for (long currByte = 0x6000; currByte < lastByte; currByte++) {
      writeBank_SNES(0xB0, currByte, b);
    }
  }
  dataIn();

  // Variable for errors
  writeErrors = 0;

  // Set control
  controlIn_SNES();

  if (romType == LO) {
    // Sram size
    long lastByte = (long(sramSize) * 128);
    for (long currByte = 0; currByte < lastByte; currByte += 512) {
      for (int c = 0; c < 512; c++) {
        if ((readBank_SNES(0x70, currByte + c)) != b) {
          writeErrors++;
        }
      }
    }
  }
  else if (romType == HI) {
    // Dumping SRAM on HiRom needs CS(PH3) to be high
    PORTH |=  (1 << 3);
    // Sram size
    long lastByte = (long(sramSize) * 128) + 0x6000;
    for (long currByte = 0x6000; currByte < lastByte; currByte += 512) {
      for (int c = 0; c < 512; c++) {
        if ((readBank_SNES(0x30, currByte + c)) != b) {
          writeErrors++;
        }
      }
    }
  }
  else if (romType == EX) {
    // Dumping SRAM on HiRom needs CS(PH3) to be high
    PORTH |=  (1 << 3);
    // Sram size
    long lastByte = (long(sramSize) * 128) + 0x6000;
    for (long currByte = 0x6000; currByte < lastByte; currByte += 512) {
      for (int c = 0; c < 512; c++) {
        if ((readBank_SNES(0xB0, currByte + c)) != b) {
          writeErrors++;
        }
      }
    }
  }
  if (writeErrors == 0) {
    println_Msg(F("OK"));
    return 1;
  }
  else {
    println_Msg(F("ERROR"));
    return 0;
  }
  display_Update();
}

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