//******************************************
// SEGA MEGA DRIVE MODULE
//******************************************
// Writes to Sega CD Backup RAM Cart require an extra wire from MRES (B02) to VRES (B27)
#ifdef ENABLE_MD

/******************************************
   Variables
 *****************************************/
unsigned long sramEnd;
word eepSize;
word addrhi;
word addrlo;
word chksum;
boolean is32x = 0;
boolean isSVP = 0;
#if (!defined(ENABLE_FLASH8) && defined(ENABLE_FLASH))
unsigned long flashSize;
unsigned long blank;
#endif

//***********************************************
// EEPROM SAVE TYPES
// 1 = Acclaim Type 1    [24C02]
// 2 = Acclaim Type 2    [24C02/24C16/24C65]
// 3 = Capcom/SEGA       [24C01]
// 4 = EA                [24C01]
// 5 = Codemasters       [24C08/24C16/24C65]
//***********************************************
byte eepType;

//*********************************************************
// SERIAL EEPROM LOOKUP TABLE
// Format = {chksum, eepType | eepSize}
// chksum is located in ROM at 0x18E (0xC7)
// eepType and eepSize are combined to conserve memory
//*********************************************************
const byte MDSize[] PROGMEM = { 1, 2, 4, 8, 12, 16, 20, 24, 32, 40 };

static const word PROGMEM eepid[] = {
  // ACCLAIM TYPE 1
  0x5B9F, 0x101,  // NBA Jam (J)
  0x694F, 0x101,  // NBA Jam (UE) (Rev 0)
  0xBFA9, 0x101,  // NBA Jam (UE) (Rev 1)
  // ACCLAIM TYPE 2
  0x16B2, 0x102,   // Blockbuster World Videogame Championship II (U)   [NO HEADER SAVE DATA]
  0xCC3F, 0x102,   // NBA Jam Tournament Edition (W) (Rev 0)            [NO HEADER SAVE DATA]
  0x8AE1, 0x102,   // NBA Jam Tournament Edition (W) (Rev 1)            [NO HEADER SAVE DATA]
  0xDB97, 0x102,   // NBA Jam Tournament Edition 32X (W)
  0x7651, 0x102,   // NFL Quarterback Club (W)
  0xDFE4, 0x102,   // NFL Quarterback Club 32X (W)
  0x3DE6, 0x802,   // NFL Quarterback Club '96 (UE)
  0xCB78, 0x2002,  // Frank Thomas Big Hurt Baseball (UE)
  0x6DD9, 0x2002,  // College Slam (U)
  // CAPCOM
  0xAD23, 0x83,  // Mega Man:  The Wily Wars (E)
  0xEA80, 0x83,  // Rockman Megaworld (J)
  // SEGA
  0x760F, 0x83,  // Evander "Real Deal" Holyfield Boxing (JU)
  0x95E7, 0x83,  // Greatest Heavyweights of the Ring (E)
  0x0000, 0x83,  // Greatest Heavyweights of the Ring (J)             [BLANK CHECKSUM 0000]
  0x7270, 0x83,  // Greatest Heavyweights of the Ring (U)
  0xBACC, 0x83,  // Honoo no Toukyuuji Dodge Danpei (J)
  0xB939, 0x83,  // MLBPA Sports Talk Baseball (U)                    [BAD HEADER SAVE DATA]
  0x487C, 0x83,  // Ninja Burai Densetsu (J)
  0x740D, 0x83,  // Wonder Boy in Monster World (B)
  0x0278, 0x83,  // Wonder Boy in Monster World (J)
  0x9D79, 0x83,  // Wonder Boy in Monster World (UE)
  // EA
  0x8512, 0x84,  // Bill Walsh College Football (UE)                  [BAD HEADER SAVE DATA]
  0xA107, 0x84,  // John Madden Football '93 (UE)                     [NO HEADER SAVE DATA]
  0x246A, 0x84,  // John Madden Football '93 (U) (EA Sports)          [NO HEADER SAVE DATA]
  0x5807, 0x84,  // John Madden Football '93 Championship Edition (U) [NO HEADER SAVE DATA]
  0x2799, 0x84,  // NHLPA Hockey '93 (UE) (Rev 0)                     [NO HEADER SAVE DATA]
  0xFA57, 0x84,  // NHLPA Hockey '93 (UE) (Rev 1)                     [NO HEADER SAVE DATA]
  0x8B9F, 0x84,  // Rings of Power (UE)                               [NO HEADER SAVE DATA]
  // CODEMASTERS
  0x7E65, 0x405,   // Brian Lara Cricket (E)                            [NO HEADER SAVE DATA]
  0x9A5C, 0x2005,  // Brian Lara Cricket 96 (E) (Rev 1.0)               [NO HEADER SAVE DATA]
  0xC4EE, 0x2005,  // Brian Lara Cricket 96 (E) (Rev 1.1)               [NO HEADER SAVE DATA]
  0x7E50, 0x805,   // Micro Machines 2 (E) (J-Cart)                     [NO HEADER SAVE DATA]
  0x165E, 0x805,   // Micro Machines '96 (E) (J-Cart) (Rev 1.0/1.1)     [NO HEADER SAVE DATA]
  0x168B, 0x405,   // Micro Machines Military (E) (J-Cart)              [NO HEADER SAVE DATA]
  0x12C1, 0x2005,  // Shane Warne Cricket (E)                           [NO HEADER SAVE DATA]
};

byte eepcount = (sizeof(eepid) / sizeof(eepid[0])) / 2;
word eepdata;

// CD BACKUP RAM
unsigned long bramSize = 0;

// REALTEC MAPPER
boolean realtec = 0;

#if defined(ENABLED_CONFIG)

int segaSram16bit = 0;

#else /* !ENABLED_CONFIG */

#ifndef OPTION_MD_DEFAULT_SAVE_TYPE
#define OPTION_MD_DEFAULT_SAVE_TYPE 0
#endif /* !OPTION_MD_DEFAULT_SAVE_TYPE */

int segaSram16bit = OPTION_MD_DEFAULT_SAVE_TYPE;

#endif /* ENABLED_CONFIG */

//*****************************************
// SONIC & KNUCKLES LOCK-ON MODE VARIABLES
// SnKmode :
// 0 = Not Sonic & Knuckles
// 1 = Only Sonic & Knucles
// 2 = Sonic & Knucles + Sonic1
// 3 = Sonic & Knucles + Sonic2
// 4 = Sonic & Knucles + Sonic3
// 5 = Sonic & Knucles + Other game
//*****************************************
static byte SnKmode = 0;
static unsigned long cartSizeLockon;
static unsigned long cartSizeSonic2 = 262144;
static word chksumLockon;
static word chksumSonic2 = 0x0635;

/******************************************
   Configuration
 *****************************************/
#if defined(use_md_conf) && !defined(ENABLE_CONFIG)

#warning "DEPRECATED: use_md_conf is deprecated. You should use ENABLE_CONFIG instead."

void mdLoadConf() {
  if (myFile.open("mdconf.txt", O_READ)) {
    char line[64];
    unsigned int n;
    unsigned int i;
    while ((n = myFile.fgets(line, sizeof(line) - 1)) > 0) {
      // preprocess
      for (i = 0; i < n; i++) {
        if (line[i] == ';') {
          // comments
          line[i] = '\0';
          n = i;
          break;
        } else if (line[i] == '\n' || line[i] == '\r') {
          // EOL
          line[n] = '\0';
          n = i;
          break;
        }
      }
      //print_Msg(F("read line: "));
      //println_Msg(line);
      if (line[0] == '[') {
        char* name;
        char* value;
        i = 1;
        name = line + i;
        for (; i < sizeof(line); i++) {
          if (line[i] == ']') {
            line[i] = '\0';
            i++;
            break;
          }
        }
        if (line[i] != '\0') {
          for (; i < sizeof(line); i++) {
            if (line[i] != ' ') {
              value = line + i;
              i++;
              break;
            }
          }
          for (; i < sizeof(line) && line[i] != '\0'; i++) {
            if (line[i] == ' ') {
              line[i] = '\0';
              break;
            }
          }
        }
        //print_Msg(F("read name: "));
        //println_Msg(name);
        //print_Msg(F("value: "));
        //println_Msg(value);
        if (!strcmp("segaSram16bit", name)) {
          // Retrode compatible setting
          // [segaSram16bit] 1   ; 0=no, 1=yes, 2=y+large
          // 0: Output each byte once. Not supported by most emulators.
          // 1: Duplicate each byte. Usable by Kega Fusion.
          // 2: Duplicate each byte. Pad with 0xFF so that the file size is 64KB.
          segaSram16bit = atoi(value);
          if (segaSram16bit != 0 && segaSram16bit != 1 && segaSram16bit != 2) {
            segaSram16bit = DEFAULT_VALUE_SAVE_TYPE;
          }
          print_Msg(F("segaSram16bit: "));
          println_Msg(segaSram16bit);
        }
      }
    }
    myFile.close();
  }
}
#endif

void pulse_clock(int n) {
  for (int i = 0; i < n; i++)
    PORTH ^= (1 << 1);
}

/******************************************
   Menu
 *****************************************/
// MD menu items
static const char MDMenuItem1[] PROGMEM = "Game Cartridge";
static const char MDMenuItem2[] PROGMEM = "SegaCD RamCart";
static const char MDMenuItem3[] PROGMEM = "Flash Repro";
static const char* const menuOptionsMD[] PROGMEM = { MDMenuItem1, MDMenuItem2, MDMenuItem3, FSTRING_RESET };

// Cart menu items
static const char MDCartMenuItem4[] PROGMEM = "Force ROM size";
static const char* const menuOptionsMDCart[] PROGMEM = { FSTRING_READ_ROM, FSTRING_READ_SAVE, FSTRING_WRITE_SAVE, MDCartMenuItem4, FSTRING_REFRESH_CART, FSTRING_RESET };

// Sega CD Ram Backup Cartridge menu items
static const char SCDMenuItem1[] PROGMEM = "Read Backup RAM";
static const char SCDMenuItem2[] PROGMEM = "Write Backup RAM";
static const char* const menuOptionsSCD[] PROGMEM = { SCDMenuItem1, SCDMenuItem2, FSTRING_RESET };

// Sega start menu
void mdMenu() {
  // create menu with title and 4 options to choose from
  unsigned char mdDev;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsMD, 4);
  mdDev = question_box(F("Select MD device"), menuOptions, 4, 0);

  // wait for user choice to come back from the question box menu
  switch (mdDev) {
    case 0:
      display_Clear();
      display_Update();
      setup_MD();
      mode = CORE_MD_CART;
      break;

    case 1:
      display_Clear();
      display_Update();
      setup_MD();
      mode = CORE_SEGA_CD;
      break;

#ifdef ENABLE_FLASH
    case 2:
      display_Clear();
      display_Update();
      setup_MD();
      mode = CORE_MD_CART;
      // Change working dir to root
      filePath[0] = '\0';
      sd.chdir("/");
      fileBrowser(FS(FSTRING_SELECT_FILE));
      display_Clear();
      // Setting CS(PH3) LOW
      PORTH &= ~(1 << 3);

      // ID flash
      resetFlash_MD();
      idFlash_MD();
      resetFlash_MD();
      if (flashid == 0xC2F1) {
        println_Msg(F("MX29F1610 detected"));
        flashSize = 2097152;
      } else if (flashid == 0x017E) {
        println_Msg(F("S29GL064N detected"));
        flashSize = 4194304;
      } else {
        print_Msg(F("Flash ID: "));
        println_Msg(flashid_str);
        print_FatalError(F("Error: Unknown flashrom"));
      }
      println_Msg("Erasing...");
      display_Update();
      eraseFlash_MD();
      resetFlash_MD();
      blankcheck_MD();
      if (flashid == 0xC2F1)
        write29F1610_MD();
      else if (flashid == 0x017E)
        write29GL_MD();
      resetFlash_MD();
      delay(1000);
      resetFlash_MD();
      delay(1000);
      println_Msg("Verifying...");
      verifyFlash_MD();
      // Set CS(PH3) HIGH
      PORTH |= (1 << 3);
      // Prints string out of the common strings array either with or without newline
      print_STR(press_button_STR, 1);
      display_Update();
      wait();
      break;
#endif

    case 3:
      resetArduino();
      break;

    default:
      print_MissingModule();  // does not return
  }
}

void mdCartMenu() {
  // create menu with title and 6 options to choose from
  unsigned char mainMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsMDCart, 6);
  mainMenu = question_box(F("MEGA DRIVE Reader"), menuOptions, 6, 0);

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

      // common ROM read fail state: no cart inserted - tends to report impossibly large cartSize
      // largest known game so far is supposedly "Paprium" at 10MB, so cap sanity check at 16MB
      if (cartSize != 0 && cartSize <= 16777216) {
        // Change working dir to root
        sd.chdir("/");
        if (realtec) {
          readRealtec_MD();
        } else {
          readROM_MD();
          // Calculate and compare CRC32 with database
          if (is32x)
            //database, crcString, renamerom, offset
            compareCRC("32x.txt", 0, 1, 0);
          else
            compareCRC("md.txt", 0, 1, 0);
        }
      } else {
        print_Error(F("Cart has no ROM"));
      }
#ifdef ENABLE_GLOBAL_LOG
      save_log();
#endif
      break;

    case 1:
      display_Clear();
      // Does cartridge have SRAM
      if ((saveType == 1) || (saveType == 2) || (saveType == 3)) {
        // Change working dir to root
        sd.chdir("/");
        println_Msg(F("Reading Sram..."));
        display_Update();
        enableSram_MD(1);
        readSram_MD();
        enableSram_MD(0);
      } else if (saveType == 4) {
        readEEP_MD();
      } else {
        print_Error(F("Cart has no Save"));
      }
      break;

    case 2:
      display_Clear();
      // Does cartridge have SRAM
      if ((saveType == 1) || (saveType == 2) || (saveType == 3)) {
        // Change working dir to root
        sd.chdir("/");
        // Launch file browser
        fileBrowser(F("Select srm file"));
        display_Clear();
        enableSram_MD(1);
        writeSram_MD();
        writeErrors = verifySram_MD();
        enableSram_MD(0);
        if (writeErrors == 0) {
          println_Msg(F("Sram verified OK"));
          display_Update();
        } else {
          print_STR(error_STR, 0);
          print_Msg(writeErrors);
          print_STR(_bytes_STR, 1);
          print_Error(did_not_verify_STR);
        }
      } else if (saveType == 4) {
        // Launch file browser
        fileBrowser(F("Select eep file"));
        display_Clear();
        writeEEP_MD();
      } else {
        print_Error(F("Cart has no Save"));
      }
      break;

    case 3:
      display_Clear();
      force_cartSize_MD();
      break;

    case 4:
      // For multi-game carts
      // Set reset pin to output (PH0)
      DDRH |= (1 << 0);
      // Switch RST(PH0) to LOW
      PORTH &= ~(1 << 0);

      display_Clear();
      print_Msg(F("Resetting..."));
      display_Update();
      delay(3000);  // wait 3 secs to switch to next game
      resetArduino();
      break;

    case 5:
      // Reset
      resetArduino();
      break;
  }
  // Prints string out of the common strings array either with or without newline
  print_STR(press_button_STR, 1);
  display_Update();
  wait();
}

void segaCDMenu() {
  // create menu with title and 3 options to choose from
  unsigned char scdMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsSCD, 3);
  scdMenu = question_box(F("SEGA CD RAM"), menuOptions, 3, 0);

  // wait for user choice to come back from the question box menu
  switch (scdMenu) {
    case 0:
      display_Clear();
      if (bramSize > 0)
        readBram_MD();
      else {
        print_Error(F("Not CD Backup RAM Cart"));
      }
      break;

    case 1:
      display_Clear();
      if (bramSize > 0) {
        // Launch file browser
        fileBrowser(F("Select brm file"));
        display_Clear();
        writeBram_MD();
      } else {
        print_Error(F("Not CD Backup RAM Cart"));
      }
      break;

    case 2:
      // Reset
      asm volatile("  jmp 0");
      break;
  }
  println_Msg(FS(FSTRING_EMPTY));
  // Prints string out of the common strings array either with or without newline
  print_STR(press_button_STR, 1);
  display_Update();
  wait();
}

/******************************************
   Setup
 *****************************************/
void setup_MD() {
  // Request 5V
  setVoltage(VOLTS_SET_5V);

#if defined(ENABLE_CONFIG)
  segaSram16bit = configGetLong(F("md.saveType"));
#elif defined(use_md_conf)
  mdLoadConf();
#endif /*ENABLE_CONFIG*/

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

  // Set Control Pins to Output RST(PH0) CLK(PH1) CS(PH3) WRH(PH4) WRL(PH5) OE(PH6)
  DDRH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);

  // Set TIME(PJ0), AS(PJ1) to Output
  DDRJ |= (1 << 0) | (1 << 1);

  //set ASEL(PG5) to Output
  DDRG |= (1 << 5);

  // Set Data Pins (D0-D15) to Input
  DDRC = 0x00;
  DDRA = 0x00;

  // Setting RST(PH0) CS(PH3) WRH(PH4) WRL(PH5) OE(PH6) HIGH
  PORTH |= (1 << 0) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);

  // Setting TIME(PJ0) AS(PJ1) HIGH
  PORTJ |= (1 << 0) | (1 << 1);

  // setting ASEL(PG5) HIGH
  PORTG |= (1 << 5);

  delay(200);

  // Print all the info
  getCartInfo_MD();
}

/******************************************
   I/O Functions
 *****************************************/

/******************************************
  Low level functions
*****************************************/
void writeWord_MD(unsigned long myAddress, word myData) {
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;
  PORTC = myData;
  PORTA = (myData >> 8) & 0xFF;

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

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

  // Leave WR low for at least 200ns
  __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");

  // Setting CS(PH3) HIGH
  PORTH |= (1 << 3);
  // 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");
}

word readWord_MD(unsigned long myAddress) {
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;

  // Arduino running at 16Mhz -> one nop = 62.5ns
  NOP;

  // Setting CS(PH3) LOW
  PORTH &= ~(1 << 3);
  // Setting OE(PH6) LOW
  PORTH &= ~(1 << 6);
  // Setting AS(PJ1) LOW
  PORTJ &= ~(1 << 1);
  // Setting ASEL(PG5) LOW
  PORTG &= ~(1 << 5);
  // Pulse CLK(PH1), needed for SVP enhanced carts
  pulse_clock(10);

  // most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
  NOP;
  NOP;
  NOP;
  NOP;
  NOP;
  NOP;

  // Read
  word tempWord = ((PINA & 0xFF) << 8) | (PINC & 0xFF);

  // Setting CS(PH3) HIGH
  PORTH |= (1 << 3);
  // Setting OE(PH6) HIGH
  PORTH |= (1 << 6);
  // Setting AS(PJ1) HIGH
  PORTJ |= (1 << 1);
  // Setting ASEL(PG5) HIGH
  PORTG |= (1 << 5);
  // Pulse CLK(PH1), needed for SVP enhanced carts
  pulse_clock(10);

  // these 6x nop delays have been here before, unknown what they mean
  NOP;
  NOP;
  NOP;
  NOP;
  NOP;
  NOP;

  return tempWord;
}

void writeFlash_MD(unsigned long myAddress, word myData) {
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;
  PORTC = myData;
  PORTA = (myData >> 8) & 0xFF;

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

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

  // Leave WE 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");
}

word readFlash_MD(unsigned long myAddress) {
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;

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

  // Setting OE(PH6) LOW
  PORTH &= ~(1 << 6);

  __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");

  // Read
  word tempWord = ((PINA & 0xFF) << 8) | (PINC & 0xFF);

  __asm__("nop\n\t");

  // Setting OE(PH6) HIGH
  PORTH |= (1 << 6);
  __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");

  return tempWord;
}

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

// Switch data pins to read
void dataIn_MD() {
  DDRC = 0x00;
  DDRA = 0x00;
  // Enable Internal Pullups (needed for games like Batman Forever that are open bus with random bytes on the last 1MB, so we get a clean 0xFF padding)
  PORTC = 0xFF;
  PORTA = 0xFF;
}

/******************************************
  MEGA DRIVE functions
*****************************************/
byte copyToRomName_MD(char* output, const byte* input, byte length) {
  byte myLength = 0;

  for (byte i = 0; i < 48; i++) {
    if (((input[i] >= '0' && input[i] <= '9') || (input[i] >= 'A' && input[i] <= 'z')) && myLength < length) {
      output[myLength++] = input[i];
    }
  }

  return myLength;
}

void getCartInfo_MD() {
  // Set control
  dataIn_MD();

  // Get cart size
  cartSize = ((long(readWord_MD(0xD2)) << 16) | readWord_MD(0xD3)) + 1;

  // Check for 32x compatibility
  if ((readWord_MD(0x104 / 2) == 0x2033) && (readWord_MD(0x106 / 2) == 0x3258))
    is32x = 1;
  else
    is32x = 0;

  // Get cart checksum
  chksum = readWord_MD(0xC7);

  // Get cart ID
  char id[15];
  memset(id, 0, 15);
  for (byte c = 0; c < 14; c += 2) {
    // split word
    word myWord = readWord_MD((0x180 + c) / 2);
    byte loByte = myWord & 0xFF;
    byte hiByte = myWord >> 8;

    // write to buffer
    id[c] = hiByte;
    id[c + 1] = loByte;
  }

  // Get cart name
  for (byte c = 0; c < 48; c += 2) {
    // split word
    word myWord = readWord_MD((0x150 + c) / 2);
    byte loByte = myWord & 0xFF;
    byte hiByte = myWord >> 8;

    // write to buffer
    sdBuffer[c] = hiByte;
    sdBuffer[c + 1] = loByte;
  }
  romName[copyToRomName_MD(romName, sdBuffer, sizeof(romName) - 1)] = 0;

  // Identify games using SVP chip
  if (!strncmp("GM MK-1229 ", id, 11) || !strncmp("GM G-7001  ", id, 11))  // Virtua Racing (E/U/J)
    isSVP = 1;
  else
    isSVP = 0;

  // Fix cartridge sizes and checksums according to no-intro database
  if (cartSize == 0x400000) {
    switch (chksum) {
      case 0xCE25:  // Super Street Fighter 2 (J) 40Mbit
      case 0xE41D:  // Super Street Fighter 2 (E) 40Mbit
      case 0xE017:  // Super Street Fighter 2 (U) 40Mbit
        cartSize = 0x500000;
        break;
      case 0x0000:  // Demons of Asteborg v1.0 (W) 120Mbit
        cartSize = 0xEAF2F4;
        break;
      case 0xBCBF:  // Demons of Asteborg v1.1 (W) 120Mbit
      case 0x6E1E:  // Demons of Asteborg v1.11 (W) 120Mbit
        cartSize = 0xEA0000;
        break;
    }
  }
  if (cartSize == 0x300000) {
    switch (chksum) {
      case 0xBC5F:  // Batman Forever (World)
      case 0x3CDD:  // Donald in Maui Mallard (Brazil) (En)
      case 0x44AD:  // Donald in Maui Mallard (Europe) (Rev A)
      case 0x2D9A:  // Foreman for Real (World)
      case 0x5648:  // Justice League Task Force (World)
      case 0x0A29:  // Mega 6 Vol. 3 (Europe)
      case 0x7651:  // NFL Quarterback Club (World)
      case 0x74CA:  // WWF RAW (World)
        cartSize = 0x400000;
        break;
    }
  }
  if (cartSize == 0x200000) {
    switch (chksum) {
      case 0x2078:  // Dynamite Headdy (USA, Europe)
        chksum = 0x9877;
        break;
      case 0xAE95:  // Winter Olympic Games (USA)
        chksum = 0x56A0;
        break;
    }
  }
  if (cartSize == 0x180000) {
    switch (chksum) {
      case 0xFFE2:  // Cannon Fodder (Europe)
      case 0xF418:  // Chaos Engine, The (Europe)
      case 0xF71D:  // Fatal Fury (Europe, Korea) (En)
      case 0xA884:  // Flashback (Europe) (En,Fr)
      case 0x7D68:  // Flashback - The Quest for Identity (USA) (En,Fr)
      case 0x030D:  // Shining Force (Europe)
      case 0xE975:  // Shining Force (USA)
        cartSize = 0x200000;
        break;
    }
  }
  if (cartSize == 0x100000) {
    switch (chksum) {
      case 0xCDF5:  // Life on Mars (Aftermarket)
        cartSize = 0x400000;
        chksum = 0x603A;
        break;
      case 0xF85F:  // Metal Dragon (Aftermarket)
        cartSize = 0x200000;
        chksum = 0x6965;
        break;
    }
  }
  if (cartSize == 0xC0000) {
    switch (chksum) {
      case 0x9D79:  // Wonder Boy in Monster World (USA, Europe)
        cartSize = 0x100000;
        break;
    }
  }
  if (cartSize == 0x80000) {
    switch (chksum) {
      case 0x06C1:  // Madden NFL 98 (USA)
        cartSize = 0x200000;
        chksum = 0x8473;
        break;
      case 0x5B3A:  // NHL 98 (USA)
        cartSize = 0x200000;
        chksum = 0x5613;
        break;
      case 0xD07D:  // Zero Wing (Japan)
        cartSize = 0x100000;
        chksum = 0xF204;
        break;
      case 0x95C9:  // Zero Wing (Europe)
      case 0x9144:  // Zoop (Europe)
      case 0xB8D4:  // Zoop (USA)
        cartSize = 0x100000;
        break;
      case 0xC422:  // Jeopardy! (USA)
        chksum = 0xC751;
        break;
      case 0x0C6A:  // Monopoly (USA)
        chksum = 0xE1AA;
        break;
      case 0xA760:  // Gain Ground (USA)
        chksum = 0x97CD;
        break;
      case 0x1404:  // Wonder Boy III - Monster Lair (Japan, Europe) (En)
        chksum = 0x53B9;
        break;
    }
  }
  if (cartSize == 0x40000) {
    switch (chksum) {
      case 0x8BC6:  // Pac-Attack (USA)
      case 0xB344:  // Pac-Panic (Europe)
        cartSize = 0x100000;
        break;
    }
  }
  if (cartSize == 0x20000) {
    switch (chksum) {
      case 0x7E50:  // Micro Machines 2 - Turbo Tournament (Europe)
        cartSize = 0x100000;
        chksum = 0xD074;
        break;
      case 0x168B:  // Micro Machines - Military (Europe)
        cartSize = 0x100000;
        chksum = 0xCEE0;
        break;
    }
  }

  // Fatman (Japan)
  if (!strncmp("GM T-44013 ", id, 11) && (chksum == 0xFFFF)) {
    chksum = 0xC560;
    cartSize = 0xA0000;
  }
  
  // Slaughter Sport (USA)
  if (!strncmp("GMT5604600jJ", romName, 12) && (chksum == 0xFFFF)) {
    strcpy(romName, "SLAUGHTERSPORT");
    chksum = 0x6BAE;
  }

  // Fixes aftermarket cartridges
  // Beggar Prince (Rev 1)
  if (!strncmp("SF-001", id, 6) && (chksum == 0x3E08)) {
    cartSize = 0x400000;
  }
  // Legend of Wukong
  if (!strncmp("SF-002", id, 6) && (chksum == 0x12B0)) {
    chksum = 0x45C6;
  }
  // YM2612 Instrument Editor
  if (!strncmp("GM 10101010", id, 11) && (chksum == 0xC439)) {
    chksum = 0x21B0;
    cartSize = 0x100000;
  }
  // Technoptimistic
  if (!strncmp("MU REMUTE01", id, 11) && (chksum == 0x0000)) {
    chksum = 0xB55C;
    cartSize = 0x400000;
  }
  // Decoder
  if (!strncmp("GM REMUTE02", id, 11) && (chksum == 0x0000)) {
    chksum = 0x5426;
    cartSize = 0x400000;
  }
  // Handy Harvy
  if (!strncmp("GM HHARVYSG", id, 11) && (chksum == 0x0000)) {
    chksum = 0xD9D2;
    cartSize = 0x100000;
  }
  // Jim Power - The Lost Dimension in 3D
  if (!strncmp("GM T-107036", id, 11) && (chksum == 0x0000)) {
    chksum = 0xAA28;
  }
  // mikeyeldey95
  if (!strncmp("GM 00000000-43", id, 14) && (chksum == 0x0000)) {
    chksum = 0x921B;
    cartSize = 0x400000;
  }
  // Enryuu Seiken Xiao-Mei
  if (!strncmp("GM 00000000-00", id, 14) && (chksum == 0x1E0C)) {
    chksum = 0xE7E5;
    cartSize = 0x400000;
  }
  // Life on Earth - Reimagined
  if (!strncmp("GM 00000000-00", id, 14) && (chksum == 0x6BD5)) {
    chksum = 0x1FEA;
    cartSize = 0x400000;
  }
  // Sasha Darko's Sacred Line I
  if (!strncmp("GM 00000005-00", id, 14) && (chksum == 0x9F34)) {
    chksum = 0xA094;
    cartSize = 0x400000;
  }
  // Sasha Darko's Sacred Line II
  if (!strncmp("GM 00000005-00", id, 14) && (chksum == 0x0E9B)) {
    chksum = 0x6B4B;
    cartSize = 0x400000;
  }
  // Sasha Darko's Sacred Line (Watermelon Release)
  if (!strncmp("GM T-574323-00", id, 14) && (chksum == 0xAEDD)) {
    cartSize = 0x400000;
  }
  // Kromasphere
  if (!strncmp("GM MK-0000 -00", id, 14) && (chksum == 0xC536)) {
    chksum = 0xFAB1;
    cartSize = 0x200000;
  }
  // YM2017
  if (!strncmp("GM CSET0001-02", id, 14) && (chksum == 0x0000)) {
    chksum = 0xE3A9;
  }
  // The Curse of Illmore Bay
  if (!strncmp("1774          ", id, 14) && (chksum == 0x0000)) {
    chksum = 0x6E34;
    cartSize = 0x400000;
  }
  // Coffee Crisis
  if (!strncmp("JN-20160131-03", id, 14) && (chksum == 0x0000)) {
    chksum = 0x8040;
    cartSize = 0x400000;
  }
  // Romeow & Julicat
  if (!strncmp("ROMEOWJULICAT", romName, 13) && (chksum == 0x0000)) {
    chksum = 0xB094;
    cartSize = 0x200000;
  }

  // Sonic & Knuckles checks
  SnKmode = 0;
  if (!strcmp("GM MK-1563 -00", id) && (chksum == 0xDFB3)) {
    char labelLockon[17];
    memset(labelLockon, 0, 17);

    // Get labelLockon
    for (byte c = 0; c < 16; c += 2) {
      // split word
      word myWord = readWord_MD((0x200100 + c) / 2);
      byte loByte = myWord & 0xFF;
      byte hiByte = myWord >> 8;

      // write to buffer
      labelLockon[c] = hiByte;
      labelLockon[c + 1] = loByte;
    }

    // check Lock-on game presence
    if (!(strcmp("SEGA MEGA DRIVE ", labelLockon) & strcmp("SEGA GENESIS    ", labelLockon))) {
      char idLockon[15];
      memset(idLockon, 0, 15);

      // Lock-on cart checksum
      chksumLockon = readWord_MD(0x1000C7);
      // Lock-on cart size
      cartSizeLockon = ((long(readWord_MD(0x1000D2)) << 16) | readWord_MD(0x1000D3)) + 1;

      // Get IdLockon
      for (byte c = 0; c < 14; c += 2) {
        // split word
        word myWord = readWord_MD((0x200180 + c) / 2);
        byte loByte = myWord & 0xFF;
        byte hiByte = myWord >> 8;

        // write to buffer
        idLockon[c] = hiByte;
        idLockon[c + 1] = loByte;
      }

      if (!strncmp("GM 00001009-0", idLockon, 13) || !strncmp("GM 00004049-0", idLockon, 13)) { // Sonic1 ID:GM 00001009-0? or GM 00004049-0?
        SnKmode = 2;
      } else if (!strcmp("GM 00001051-00", idLockon) || !strcmp("GM 00001051-01", idLockon) || !strcmp("GM 00001051-02", idLockon)) { // Sonic2 ID:GM 00001051-00 or GM 00001051-01 or GM 00001051-02
        SnKmode = 3;
        // Prepare Sonic2 Banks
        writeSSF2Map(0x509878, 1);  // 0xA130F1
      } else if (!strcmp("GM MK-1079 -00", idLockon)) { // Sonic3 ID:GM MK-1079 -00
        SnKmode = 4;
      } else { // Other game
        SnKmode = 5;
      }

    } else {
      SnKmode = 1;
    }
  }

  // Serial EEPROM Check
  for (int i = 0; i < eepcount; i++) {
    int index = i * 2;
    word eepcheck = pgm_read_word(eepid + index);
    if (eepcheck == chksum) {
      eepdata = pgm_read_word(eepid + index + 1);
      eepType = eepdata & 0x7;
      eepSize = eepdata & 0xFFF8;
      saveType = 4;  // SERIAL EEPROM
      break;
    }
  }

  // Greatest Heavyweights of the Ring (J) has blank chksum 0x0000
  // Other non-save carts might have the same blank chksum
  // Check header for Serial EEPROM Data
  if (chksum == 0x0000) {
    if (readWord_MD(0xD9) != 0xE840) {  // NOT SERIAL EEPROM
      eepType = 0;
      eepSize = 0;
      saveType = 0;
    }
  }

  // Codemasters EEPROM Check
  // Codemasters used the same incorrect header across multiple carts
  // Carts with checksum 0x165E or 0x168B could be EEPROM or non-EEPROM
  // Check the first DWORD in ROM (0x444E4C44) to identify the EEPROM carts
  if ((chksum == 0x165E) || (chksum == 0x168B)) {
    if (readWord_MD(0x00) != 0x444E) {  // NOT SERIAL EEPROM
      eepType = 0;
      eepSize = 0;
      saveType = 0;
    }
  }

  // CD Backup RAM Cart Check
  // 4 = 128KB (2045 Blocks) Sega CD Backup RAM Cart
  // 6 = 512KB (8189 Blocks) Ultra CD Backup RAM Cart (Aftermarket)
  word bramCheck = readWord_MD(0x00);
  if ((((bramCheck & 0xFF) == 0x04) && ((chksum & 0xFF) == 0x04)) || (((bramCheck & 0xFF) == 0x06) && ((chksum & 0xFF) == 0x06))) {
    unsigned long p = 1 << (bramCheck & 0xFF);
    bramSize = p * 0x2000L;
  }
  if (saveType != 4) {  // NOT SERIAL EEPROM
    // Check if cart has sram
    saveType = 0;
    sramSize = 0;

    // FIXED CODE FOR SRAM/FRAM/PARALLEL EEPROM
    // 0x5241F820 SRAM (ODD BYTES/EVEN BYTES)
    // 0x5241F840 PARALLEL EEPROM - READ AS SRAM
    // 0x5241E020 SRAM (BOTH BYTES)
    if (readWord_MD(0xD8) == 0x5241) {
      word sramType = readWord_MD(0xD9);
      if ((sramType == 0xF820) || (sramType == 0xF840)) {  // SRAM/FRAM ODD/EVEN BYTES
        // Get sram start and end
        sramBase = ((long(readWord_MD(0xDA)) << 16) | readWord_MD(0xDB));
        sramEnd = ((long(readWord_MD(0xDC)) << 16) | readWord_MD(0xDD));
        if (sramBase == 0x20000020 && sramEnd == 0x00010020) {  // Fix for Psy-o-blade
          sramBase = 0x200001;
          sramEnd = 0x203fff;
        }
        // Check alignment of sram
        // 0x300001 for HARDBALL '95 (U)
        // 0x3C0001 for Legend of Wukong (Aftermarket)
        if ((sramBase == 0x200001) || (sramBase == 0x300001) || (sramBase == 0x3C0001)) {
          // low byte
          saveType = 1;  // ODD
          sramSize = (sramEnd - sramBase + 2) / 2;
          // Right shift sram base address so [A21] is set to high 0x200000 = 0b001[0]00000000000000000000
          sramBase = sramBase >> 1;
        } else if (sramBase == 0x200000) {
          // high byte
          saveType = 2;  // EVEN
          sramSize = (sramEnd - sramBase + 1) / 2;
          // Right shift sram base address so [A21] is set to high 0x200000 = 0b001[0]00000000000000000000
          sramBase = sramBase / 2;
        } else {
          print_Msg(("sramType: "));
          print_Msg_PaddedHex16(sramType);
          println_Msg(FS(FSTRING_EMPTY));
          print_Msg(("sramBase: "));
          print_Msg_PaddedHex32(sramBase);
          println_Msg(FS(FSTRING_EMPTY));
          print_Msg(("sramEnd: "));
          print_Msg_PaddedHex32(sramEnd);
          println_Msg(FS(FSTRING_EMPTY));
          print_FatalError(F("Unknown Sram Base"));
        }
      } else if (sramType == 0xE020) {  // SRAM BOTH BYTES
        // Get sram start and end
        sramBase = ((long(readWord_MD(0xDA)) << 16) | readWord_MD(0xDB));
        sramEnd = ((long(readWord_MD(0xDC)) << 16) | readWord_MD(0xDD));

        if (sramBase == 0x200001) {
          saveType = 3;  // BOTH
          sramSize = sramEnd - sramBase + 2;
          sramBase = sramBase >> 1;
        } else if (sramBase == 0x200000) {
          saveType = 3;  // BOTH
          sramSize = sramEnd - sramBase + 1;
          sramBase = sramBase >> 1;
        } else if (sramBase == 0x3FFC00) {
          // Used for some aftermarket carts without sram
          saveType = 0;
        } else {
          print_Msg(("sramType: "));
          print_Msg_PaddedHex16(sramType);
          println_Msg(FS(FSTRING_EMPTY));
          print_Msg(("sramBase: "));
          print_Msg_PaddedHex32(sramBase);
          println_Msg(FS(FSTRING_EMPTY));
          print_Msg(("sramEnd: "));
          print_Msg_PaddedHex32(sramEnd);
          println_Msg(FS(FSTRING_EMPTY));
          print_FatalError(F("Unknown Sram Base"));
        }
      }
    } else {
      // SRAM CARTS WITH BAD/MISSING HEADER SAVE DATA
      switch (chksum) {
        case 0xC2DB:     // Winter Challenge (UE)
          saveType = 1;  // ODD
          sramBase = 0x200001;
          sramEnd = 0x200FFF;
          break;

        case 0xD7B6:     // Buck Rogers: Countdown to Doomsday (UE)
        case 0xFE3E:     // NBA Live '98 (U)
        case 0xFDAD:     // NFL '94 starring Joe Montana (U)
        case 0x632E:     // PGA Tour Golf (UE) (Rev 1)
        case 0xD2BA:     // PGA Tour Golf (UE) (Rev 2)
        case 0x44FE:     // Super Hydlide (J)
          saveType = 1;  // ODD
          sramBase = 0x200001;
          sramEnd = 0x203FFF;
          break;

        case 0xDB5E:     // Might & Magic: Gates to Another World (UE) (Rev 1)
        case 0x3428:     // Starflight (UE) (Rev 0)
        case 0x43EE:     // Starflight (UE) (Rev 1)
          saveType = 3;  // BOTH
          sramBase = 0x200001;
          sramEnd = 0x207FFF;
          break;

        case 0xBF72:     // College Football USA '96 (U)
        case 0x72EF:     // FIFA Soccer '97 (UE)
        case 0xD723:     // Hardball III (U)
        case 0x06C1:     // Madden NFL '98 (U)
        case 0xDB17:     // NHL '96 (UE)
        case 0x5B3A:     // NHL '98 (U)
        case 0x2CF2:     // NFL Sports Talk Football '93 starring Joe Montana (UE)
        case 0xE9B1:     // Summer Challenge (U)
        case 0xEEE8:     // Test Drive II: The Duel (U)
          saveType = 1;  // ODD
          sramBase = 0x200001;
          sramEnd = 0x20FFFF;
          break;
      }
      if (saveType == 1) {
        sramSize = (sramEnd - sramBase + 2) / 2;
        sramBase = sramBase >> 1;
      } else if (saveType == 3) {
        sramSize = sramEnd - sramBase + 2;
        sramBase = sramBase >> 1;
      }
    }
  }

  //Get Lock-on cart name
  if (SnKmode >= 2) {
    char romNameLockon[12];

    //Change romName
    strcpy(romName, "SnK_");

    for (byte c = 0; c < 48; c += 2) {
      // split word
      word myWord = readWord_MD((0x200150 + c) / 2);
      byte loByte = myWord & 0xFF;
      byte hiByte = myWord >> 8;

      // write to buffer
      sdBuffer[c] = hiByte;
      sdBuffer[c + 1] = loByte;
    }
    romNameLockon[copyToRomName_MD(romNameLockon, sdBuffer, sizeof(romNameLockon) - 1)] = 0;

    switch (SnKmode) {
      case 2: strcat(romName, "SONIC1"); break;
      case 3: strcat(romName, "SONIC2"); break;
      case 4: strcat(romName, "SONIC3"); break;
      case 5: strcat(romName, romNameLockon); break;
    }
  }

  // Realtec Mapper Check
  word realtecCheck1 = readWord_MD(0x3F080);  // 0x7E100 "SEGA" (BootROM starts at 0x7E000)
  word realtecCheck2 = readWord_MD(0x3F081);
  if ((realtecCheck1 == 0x5345) && (realtecCheck2 == 0x4741)) {
    realtec = 1;
    strcpy(romName, "Realtec");
    cartSize = 0x80000;
  }

  // Some games are missing the ROM size in the header, in this case calculate ROM size by looking for mirror of the first line of the ROM
  // This does not work for cartridges that have SRAM mapped directly after the maskrom like Striker (Europe)
  if ((cartSize < 0x8000) || (cartSize > 0xEAF400)) {
    for (cartSize = 0x20000 / 2; cartSize < 0x400000 / 2; cartSize += 0x20000 / 2) {
      if ((readWord_MD(0x0) == readWord_MD(cartSize)) && (readWord_MD(0x1) == readWord_MD(0x1 + cartSize)) && (readWord_MD(0x2) == readWord_MD(0x2 + cartSize)) && (readWord_MD(0x3) == readWord_MD(0x3 + cartSize)) && (readWord_MD(0x4) == readWord_MD(0x4 + cartSize)) && (readWord_MD(0x5) == readWord_MD(0x5 + cartSize)) && (readWord_MD(0x6) == readWord_MD(0x6 + cartSize)) && (readWord_MD(0x7) == readWord_MD(0x7 + cartSize)) && (readWord_MD(0x8) == readWord_MD(0x8 + cartSize))) {
        break;
      }
    }
    cartSize = cartSize * 2;
  }

  display_Clear();
  println_Msg(F("Cart Info"));
  println_Msg(FS(FSTRING_SPACE));
  print_Msg(FS(FSTRING_NAME));
  println_Msg(romName);
  if (bramCheck != 0x00FF) {
    print_Msg(F("bramCheck: "));
    print_Msg_PaddedHex16(bramCheck);
    println_Msg(FS(FSTRING_EMPTY));
  }
  if (bramSize > 0) {
    print_Msg(F("bramSize(KB): "));
    println_Msg(bramSize >> 10);
  }
  print_Msg(FS(FSTRING_SIZE));
  print_Msg(cartSize * 8 / 1024 / 1024);
  switch (SnKmode) {
    case 2:
    case 4:
    case 5:
      print_Msg(F("+"));
      print_Msg(cartSizeLockon * 8 / 1024 / 1024);
      break;
    case 3:
      print_Msg(F("+"));
      print_Msg(cartSizeLockon * 8 / 1024 / 1024);
      print_Msg(F("+"));
      print_Msg(cartSizeSonic2 * 8 / 1024 / 1024);
      break;
  }
  println_Msg(F(" MBit"));
  print_Msg(F("ChkS: "));
  print_Msg_PaddedHexByte((chksum >> 8));
  print_Msg_PaddedHexByte((chksum & 0x00ff));
  switch (SnKmode) {
    case 2:
    case 4:
    case 5:
      print_Msg(F("+"));
      print_Msg_PaddedHexByte((chksumLockon >> 8));
      print_Msg_PaddedHexByte((chksumLockon & 0x00ff));
      break;
    case 3:
      print_Msg(F("+"));
      print_Msg_PaddedHexByte((chksumLockon >> 8));
      print_Msg_PaddedHexByte((chksumLockon & 0x00ff));
      print_Msg(F("+"));
      print_Msg_PaddedHexByte((chksumSonic2 >> 8));
      print_Msg_PaddedHexByte((chksumSonic2 & 0x00ff));
      break;
  }
  println_Msg(FS(FSTRING_EMPTY));
  if (saveType == 4) {
    print_Msg(F("Serial EEPROM: "));
    print_Msg(eepSize * 8 / 1024);
    println_Msg(F(" KBit"));
  } else {
    print_Msg(F("Sram: "));
    if (sramSize > 0) {
      print_Msg(sramSize * 8 / 1024);
      println_Msg(F(" KBit"));
    } else
      println_Msg(F("None"));
  }
  println_Msg(FS(FSTRING_SPACE));

  // Wait for user input
#if (defined(ENABLE_LCD) || defined(ENABLE_OLED))
  // Prints string out of the common strings array either with or without newline
  print_STR(press_button_STR, 1);
  display_Update();
  wait();
#endif
}

void writeSSF2Map(unsigned long myAddress, word myData) {
  dataOut_MD();

  // Set TIME(PJ0) HIGH
  PORTJ |= (1 << 0);

  // 0x50987E * 2 = 0xA130FD  Bank 6 (0x300000-0x37FFFF)
  // 0x50987F * 2 = 0xA130FF  Bank 7 (0x380000-0x3FFFFF)
  PORTL = (myAddress >> 16) & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTF = myAddress & 0xFF;
  PORTC = myData;
  PORTA = (myData >> 8) & 0xFF;

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

  // Strobe TIME(PJ0) LOW to latch the data
  PORTJ &= ~(1 << 0);
  // Switch WR(PH5) to LOW
  PORTH &= ~(1 << 5);

  // Leave WR low for at least 200ns
  __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");

  // Switch WR(PH5) to HIGH
  PORTH |= (1 << 5);
  // Set TIME(PJ0) HIGH
  PORTJ |= (1 << 0);

  dataIn_MD();
}

// Read rom and save to the SD card
void readROM_MD() {
  // Checksum
  uint16_t calcCKS = 0;
  uint16_t calcCKSLockon = 0;
  uint16_t calcCKSSonic2 = 0;

  // Set control
  dataIn_MD();

  // Get name, add extension and convert to char array for sd lib
  createFolderAndOpenFile("MD", "ROM", romName, "BIN");

  byte buffer[1024] = { 0 };

  // Phantasy Star/Beyond Oasis with 74HC74 and 74HC139 switch ROM/SRAM at address 0x200000
  if (0x200000 < cartSize && cartSize < 0x400000) {
    enableSram_MD(0);
  }

  // Prepare SSF2 Banks
  if (cartSize > 0x400000) {
    writeSSF2Map(0x50987E, 6);  // 0xA130FD
    writeSSF2Map(0x50987F, 7);  // 0xA130FF
  }
  byte offsetSSF2Bank = 0;
  word d = 0;

  //Initialize progress bar
  uint32_t processedProgressBar = 0;
  uint32_t totalProgressBar = (uint32_t)(cartSize);
  if (SnKmode >= 2) totalProgressBar += (uint32_t)cartSizeLockon;
  if (SnKmode == 3) totalProgressBar += (uint32_t)cartSizeSonic2;
  draw_progressbar(0, totalProgressBar);

  for (unsigned long currBuffer = 0; currBuffer < cartSize / 2; currBuffer += 512) {
    // Blink led
    if (currBuffer % 16384 == 0)
      blinkLED();

    if (currBuffer == 0x200000) {
      writeSSF2Map(0x50987E, 8);  // 0xA130FD
      writeSSF2Map(0x50987F, 9);  // 0xA130FF
      offsetSSF2Bank = 1;
    }

    // Demons of Asteborg Additional Banks
    else if (currBuffer == 0x280000) {
      writeSSF2Map(0x50987E, 10);  // 0xA130FD
      writeSSF2Map(0x50987F, 11);  // 0xA130FF
      offsetSSF2Bank = 2;
    } else if (currBuffer == 0x300000) {
      writeSSF2Map(0x50987E, 12);  // 0xA130FD
      writeSSF2Map(0x50987F, 13);  // 0xA130FF
      offsetSSF2Bank = 3;
    } else if (currBuffer == 0x380000) {
      writeSSF2Map(0x50987E, 14);  // 0xA130FD
      writeSSF2Map(0x50987F, 15);  // 0xA130FF
      offsetSSF2Bank = 4;
    } else if (currBuffer == 0x400000) {
      writeSSF2Map(0x50987E, 16);  // 0xA130FD
      writeSSF2Map(0x50987F, 17);  // 0xA130FF
      offsetSSF2Bank = 5;
    } else if (currBuffer == 0x480000) {
      writeSSF2Map(0x50987E, 18);  // 0xA130FD
      writeSSF2Map(0x50987F, 19);  // 0xA130FF
      offsetSSF2Bank = 6;
    } else if (currBuffer == 0x500000) {
      writeSSF2Map(0x50987E, 20);  // 0xA130FD
      writeSSF2Map(0x50987F, 21);  // 0xA130FF
      offsetSSF2Bank = 7;
    } else if (currBuffer == 0x580000) {
      writeSSF2Map(0x50987E, 22);  // 0xA130FD
      writeSSF2Map(0x50987F, 23);  // 0xA130FF
      offsetSSF2Bank = 8;
    } else if (currBuffer == 0x600000) {
      writeSSF2Map(0x50987E, 24);  // 0xA130FD
      writeSSF2Map(0x50987F, 25);  // 0xA130FF
      offsetSSF2Bank = 9;
    } else if (currBuffer == 0x680000) {
      writeSSF2Map(0x50987E, 26);  // 0xA130FD
      writeSSF2Map(0x50987F, 27);  // 0xA130FF
      offsetSSF2Bank = 10;
    } else if (currBuffer == 0x700000) {
      writeSSF2Map(0x50987E, 28);  // 0xA130FD
      writeSSF2Map(0x50987F, 29);  // 0xA130FF
      offsetSSF2Bank = 11;
    }

    d = 0;

    for (int currWord = 0; currWord < 512; currWord++) {
      unsigned long myAddress = currBuffer + currWord - (offsetSSF2Bank * 0x80000);
      PORTF = myAddress & 0xFF;
      PORTK = (myAddress >> 8) & 0xFF;
      PORTL = (myAddress >> 16) & 0xFF;

      // Arduino running at 16Mhz -> one nop = 62.5ns
      NOP;
      // Setting CS(PH3) LOW
      PORTH &= ~(1 << 3);
      // Setting OE(PH6) LOW
      PORTH &= ~(1 << 6);
      // Setting AS(PJ1) LOW
      PORTJ &= ~(1 << 1);
      // Setting ASEL(PG5) LOW
      PORTG &= ~(1 << 5);
      // Pulse CLK(PH1)
      if (isSVP)
        pulse_clock(10);

      // most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
      NOP;
      NOP;
      NOP;
      NOP;
      NOP;
      NOP;

      // Read
      buffer[d] = PINA;
      buffer[d + 1] = PINC;

      // Setting CS(PH3) HIGH
      PORTH |= (1 << 3);
      // Setting OE(PH6) HIGH
      PORTH |= (1 << 6);
      // Setting AS(PJ1) HIGH
      PORTJ |= (1 << 1);
      // Setting ASEL(PG5) HIGH
      PORTG |= (1 << 5);
      // Pulse CLK(PH1)
      if (isSVP)
        pulse_clock(10);

      // Skip first 256 words
      if (((currBuffer == 0) && (currWord >= 256)) || (currBuffer > 0)) {
        calcCKS += ((buffer[d] << 8) | buffer[d + 1]);
      }
      d += 2;
    }
    myFile.write(buffer, 1024);

    // update progress bar
    processedProgressBar += 1024;
    draw_progressbar(processedProgressBar, totalProgressBar);
  }
  if (SnKmode >= 2) {
    for (unsigned long currBuffer = 0; currBuffer < cartSizeLockon / 2; currBuffer += 512) {
      // Blink led
      if (currBuffer % 16384 == 0)
        blinkLED();

      d = 0;

      for (int currWord = 0; currWord < 512; currWord++) {
        unsigned long myAddress = currBuffer + currWord + cartSize / 2;
        PORTF = myAddress & 0xFF;
        PORTK = (myAddress >> 8) & 0xFF;
        PORTL = (myAddress >> 16) & 0xFF;

        // Arduino running at 16Mhz -> one nop = 62.5ns
        NOP;
        // Setting CS(PH3) LOW
        PORTH &= ~(1 << 3);
        // Setting OE(PH6) LOW
        PORTH &= ~(1 << 6);
        // Setting AS(PJ1) LOW
        PORTJ &= ~(1 << 1);
        // Setting ASEL(PG5) LOW
        PORTG &= ~(1 << 5);
        // Pulse CLK(PH1)
        if (isSVP)
          pulse_clock(10);

        // most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
        NOP;
        NOP;
        NOP;
        NOP;
        NOP;
        NOP;

        // Read
        buffer[d] = PINA;
        buffer[d + 1] = PINC;

        // Setting CS(PH3) HIGH
        PORTH |= (1 << 3);
        // Setting OE(PH6) HIGH
        PORTH |= (1 << 6);
        // Setting AS(PJ1) HIGH
        PORTJ |= (1 << 1);
        // Setting ASEL(PG5) HIGH
        PORTG |= (1 << 5);
        // Pulse CLK(PH1)
        if (isSVP)
          pulse_clock(10);

        // Skip first 256 words
        if (((currBuffer == 0) && (currWord >= 256)) || (currBuffer > 0)) {
          calcCKSLockon += ((buffer[d] << 8) | buffer[d + 1]);
        }
        d += 2;
      }
      myFile.write(buffer, 1024);

      // update progress bar
      processedProgressBar += 1024;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }
  }
  if (SnKmode == 3) {
    for (unsigned long currBuffer = 0; currBuffer < cartSizeSonic2 / 2; currBuffer += 512) {
      // Blink led
      if (currBuffer % 16384 == 0)
        blinkLED();

      d = 0;

      for (int currWord = 0; currWord < 512; currWord++) {
        unsigned long myAddress = currBuffer + currWord + (cartSize + cartSizeLockon) / 2;
        PORTF = myAddress & 0xFF;
        PORTK = (myAddress >> 8) & 0xFF;
        PORTL = (myAddress >> 16) & 0xFF;

        // Arduino running at 16Mhz -> one nop = 62.5ns
        NOP;
        // Setting CS(PH3) LOW
        PORTH &= ~(1 << 3);
        // Setting OE(PH6) LOW
        PORTH &= ~(1 << 6);
        // Setting AS(PJ1) LOW
        PORTJ &= ~(1 << 1);
        // Setting ASEL(PG5) LOW
        PORTG &= ~(1 << 5);
        // Pulse CLK(PH1)
        if (isSVP)
          PORTH ^= (1 << 1);

        // most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
        NOP;
        NOP;
        NOP;
        NOP;
        NOP;
        NOP;

        // Read
        buffer[d] = PINA;
        buffer[d + 1] = PINC;

        // Setting CS(PH3) HIGH
        PORTH |= (1 << 3);
        // Setting OE(PH6) HIGH
        PORTH |= (1 << 6);
        // Setting AS(PJ1) HIGH
        PORTJ |= (1 << 1);
        // Setting ASEL(PG5) HIGH
        PORTG |= (1 << 5);
        // Pulse CLK(PH1)
        if (isSVP)
          PORTH ^= (1 << 1);

        calcCKSSonic2 += ((buffer[d] << 8) | buffer[d + 1]);
        d += 2;
      }
      myFile.write(buffer, 1024);

      // update progress bar
      processedProgressBar += 1024;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }
  }
  // Close the file:
  myFile.close();

  // Reset SSF2 Banks
  if (cartSize > 0x400000) {
    writeSSF2Map(0x50987E, 6);  // 0xA130FD
    writeSSF2Map(0x50987F, 7);  // 0xA130FF
  }

  // Calculate internal checksum
  print_Msg(F("Internal checksum..."));
  display_Update();
  if (chksum == calcCKS) {
    println_Msg(FS(FSTRING_OK));
    display_Update();
  } else {
    println_Msg(F("Error"));
    char calcsumStr[5];
    sprintf(calcsumStr, "%04X", calcCKS);
    println_Msg(calcsumStr);
    print_Error(FS(FSTRING_EMPTY));
    display_Update();
  }

  // More checksums
  if (SnKmode >= 2) {
    print_Msg(F("Lock-on checksum..."));
    if (chksumLockon == calcCKSLockon) {
      println_Msg(FS(FSTRING_OK));
      display_Update();
    } else {
      print_Msg(F("Error"));
      char calcsumStr[5];
      sprintf(calcsumStr, "%04X", calcCKSLockon);
      println_Msg(calcsumStr);
      print_Error(FS(FSTRING_EMPTY));
      display_Update();
    }
  }
  if (SnKmode == 3) {
    print_Msg(F("Adittional checksum..."));
    if (chksumSonic2 == calcCKSSonic2) {
      println_Msg(FS(FSTRING_OK));
      display_Update();
    } else {
      print_Msg(F("Error"));
      char calcsumStr[5];
      sprintf(calcsumStr, "%04X", calcCKSSonic2);
      println_Msg(calcsumStr);
      print_Error(FS(FSTRING_EMPTY));
      display_Update();
    }
  }
}

/******************************************
  SRAM functions
*****************************************/
// Sonic 3 sram enable
void enableSram_MD(boolean enableSram) {
  dataOut_MD();

  // Set D0 to either 1(enable SRAM) or 0(enable ROM)
  PORTC = enableSram;

  // Strobe TIME(PJ0) LOW to latch the data
  PORTJ &= ~(1 << 0);
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");
  // Set TIME(PJ0) HIGH
  PORTJ |= (1 << 0);
  __asm__("nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t"
          "nop\n\t");

  dataIn_MD();
}

// Write sram to cartridge
void writeSram_MD() {
  dataOut_MD();

  // Create filepath
  sprintf(filePath, "%s/%s", filePath, fileName);
  println_Msg(F("Writing..."));
  println_Msg(filePath);
  display_Update();

  // Open file on sd card
  if (myFile.open(filePath, O_READ)) {
    // Write to the lower byte
    if (saveType == 1) {
      for (unsigned long currByte = sramBase; currByte < sramBase + sramSize; currByte++) {
        if (segaSram16bit > 0) {
          // skip high byte
          myFile.read();
        }
        word data = myFile.read() & 0xFF;
        writeWord_MD(currByte, data);
      }
    }
    // Write to the upper byte
    else if (saveType == 2) {
      for (unsigned long currByte = sramBase; currByte < sramBase + sramSize; currByte++) {
        word data = (myFile.read() << 8) & 0xFF00;
        writeWord_MD(currByte, data);
        if (segaSram16bit > 0) {
          // skip low byte
          myFile.read();
        }
      }
    }
    // Write to both bytes
    else if (saveType == 3) {
      for (unsigned long currByte = sramBase; currByte < sramBase + sramSize; currByte++) {
        word data = (myFile.read() << 8) & 0xFF00;
        data |= (myFile.read() & 0xFF);
        writeWord_MD(currByte, data);
      }
    } else
      print_Error(F("Unknown save type"));

    // Close the file:
    myFile.close();
    print_STR(done_STR, 1);
    display_Update();
  } else {
    print_FatalError(sd_error_STR);
  }
  dataIn_MD();
}

// Read sram and save to the SD card
void readSram_MD() {
  dataIn_MD();

  // Get name, add extension and convert to char array for sd lib
  createFolder("MD", "SAVE", romName, "srm");

  // 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_FatalError(sd_error_STR);
  }

  for (unsigned long currBuffer = sramBase; currBuffer < sramBase + sramSize; currBuffer += 256) {
    for (int currWord = 0; currWord < 256; currWord++) {
      word myWord = readWord_MD(currBuffer + currWord);

      if (saveType == 2) {
        // Only use the upper byte
        if (segaSram16bit > 0) {
          sdBuffer[(currWord * 2) + 0] = ((myWord >> 8) & 0xFF);
          sdBuffer[(currWord * 2) + 1] = ((myWord >> 8) & 0xFF);
        } else {
          sdBuffer[currWord] = ((myWord >> 8) & 0xFF);
        }
      } else if (saveType == 1) {
        // Only use the lower byte
        if (segaSram16bit > 0) {
          sdBuffer[(currWord * 2) + 0] = (myWord & 0xFF);
          sdBuffer[(currWord * 2) + 1] = (myWord & 0xFF);
        } else {
          sdBuffer[currWord] = (myWord & 0xFF);
        }
      } else if (saveType == 3) {  // BOTH
        sdBuffer[currWord * 2] = ((myWord >> 8) & 0xFF);
        sdBuffer[(currWord * 2) + 1] = (myWord & 0xFF);
      }
    }
    if (saveType == 3 || segaSram16bit > 0)
      myFile.write(sdBuffer, 512);
    else
      myFile.write(sdBuffer, 256);
  }
  if (segaSram16bit == 2) {
    // pad to 64KB
    for (int i = 0; i < 512; i++) {
      sdBuffer[i] = 0xFF;
    }
    unsigned long padsize = (1UL << 16) - (sramSize << 1);
    unsigned long padblockcount = padsize >> 9;  // number of 512 byte blocks
    for (unsigned long i = 0; i < padblockcount; i++) {
      myFile.write(sdBuffer, 512);
    }
  }
  // Close the file:
  myFile.close();
  print_Msg(F("Saved to "));
  print_Msg(folder);
  println_Msg(F("/"));
  display_Update();
}

unsigned long verifySram_MD() {
  dataIn_MD();
  writeErrors = 0;

  // Open file on sd card
  if (myFile.open(filePath, O_READ)) {
    for (unsigned long currBuffer = sramBase; currBuffer < sramBase + sramSize; currBuffer += 256) {
      for (int currWord = 0; currWord < 256; currWord++) {
        word myWord = readWord_MD(currBuffer + currWord);

        if (saveType == 2) {
          // Only use the upper byte
          sdBuffer[currWord * 2] = ((myWord >> 8) & 0xFF);
        } else if (saveType == 1) {
          // Only use the lower byte
          sdBuffer[currWord * 2] = (myWord & 0xFF);
        } else if (saveType == 3) {  // BOTH
          sdBuffer[(currWord * 2) + 0] = ((myWord >> 8) & 0xFF);
          sdBuffer[(currWord * 2) + 1] = (myWord & 0xFF);
        }
      }
      int step = saveType == 3 ? 1 : 2;
      // Check sdBuffer content against file on sd card
      for (int i = 0; i < 512; i += step) {
        if (saveType == 1 && segaSram16bit > 0) {
          // skip high byte
          myFile.read();
        }
        byte b = myFile.read();
        if (saveType == 2 && segaSram16bit > 0) {
          // skip low byte
          myFile.read();
        }
        if (b != sdBuffer[i]) {
          writeErrors++;
        }
      }
    }

    // Close the file:
    myFile.close();
  } else {
    print_FatalError(sd_error_STR);
  }
  // Return 0 if verified ok, or number of errors
  return writeErrors;
}

#ifdef ENABLE_FLASH
//******************************************
// Flashrom Functions
//******************************************
void resetFlash_MD() {
  // Set data pins to output
  dataOut_MD();

  // Reset command sequence
  writeFlash_MD(0x5555, 0xaa);
  writeFlash_MD(0x2aaa, 0x55);
  writeFlash_MD(0x5555, 0xf0);

  // Set data pins to input again
  dataIn_MD();
}

void write29F1610_MD() {
  // Create filepath
  sprintf(filePath, "%s/%s", filePath, fileName);
  print_STR(flashing_file_STR, 0);
  print_Msg(filePath);
  println_Msg(F("..."));
  display_Update();

  // Open file on sd card
  if (myFile.open(filePath, O_READ)) {
    // Get rom size from file
    fileSize = myFile.fileSize();
    if (fileSize > flashSize) {
      print_FatalError(file_too_big_STR);
    }
    // Set data pins to output
    dataOut_MD();

    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = (uint32_t)fileSize / 2;
    draw_progressbar(0, totalProgressBar);

    // Fill sdBuffer with 1 page at a time then write it repeat until all bytes are written
    int d = 0;
    for (unsigned long currByte = 0; currByte < fileSize / 2; currByte += 64) {
      myFile.read(sdBuffer, 128);

      // Blink led
      if (currByte % 4096 == 0) {
        blinkLED();
      }

      // Write command sequence
      writeFlash_MD(0x5555, 0xaa);
      writeFlash_MD(0x2aaa, 0x55);
      writeFlash_MD(0x5555, 0xa0);

      // Write one full page at a time
      for (byte c = 0; c < 64; c++) {
        word currWord = ((sdBuffer[d] & 0xFF) << 8) | (sdBuffer[d + 1] & 0xFF);
        writeFlash_MD(currByte + c, currWord);
        d += 2;
      }
      d = 0;

      // Check if write is complete
      delayMicroseconds(100);
      busyCheck_MD();

      // update progress bar
      processedProgressBar += 64;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }

    // Set data pins to input again
    dataIn_MD();

    // Close the file:
    myFile.close();
  } else {
    print_STR(open_file_STR, 1);
    display_Update();
  }
}

void write29GL_MD() {
  // Create filepath
  sprintf(filePath, "%s/%s", filePath, fileName);
  print_STR(flashing_file_STR, 0);
  print_Msg(filePath);
  println_Msg(F("..."));
  display_Update();

  // Open file on sd card
  if (myFile.open(filePath, O_READ)) {
    // Get rom size from file
    fileSize = myFile.fileSize();
    if (fileSize > flashSize) {
      print_FatalError(file_too_big_STR);
    }
    // Set data pins to output
    dataOut_MD();

    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = (uint32_t)fileSize;
    draw_progressbar(0, totalProgressBar);

    for (unsigned long currSdBuffer = 0; currSdBuffer < fileSize; currSdBuffer += 512) {
      myFile.read(sdBuffer, 512);

      // Blink led
      if (currSdBuffer % 4096 == 0) {
        blinkLED();
      }

      for (int currWriteBuffer = 0; currWriteBuffer < 512; currWriteBuffer += 32) {
        // Two unlock cycles
        writeFlash_MD(0x555, 0xaa);
        writeFlash_MD(0x2aa, 0x55);
        // Write Buffer Load command to Sector Address
        writeFlash_MD((currSdBuffer + currWriteBuffer) / 2, 0x25);
        // Sector Address, Word count
        writeFlash_MD((currSdBuffer + currWriteBuffer) / 2, 16 - 1);

        // Load buffer
        word currWord;
        for (byte currByte = 0; currByte < 32; currByte += 2) {
          currWord = ((sdBuffer[currWriteBuffer + currByte] & 0xFF) << 8) | (sdBuffer[currWriteBuffer + currByte + 1] & 0xFF);
          writeFlash_MD((currSdBuffer + currWriteBuffer + currByte) / 2, currWord);
        }

        // Write buffer
        writeFlash_MD((currSdBuffer + currWriteBuffer + 32 - 2) / 2, 0x29);

        // Check if write is complete
        // Set data pins to input
        dataIn_MD();

        // Read the status register
        word statusReg = readFlash_MD((currSdBuffer + currWriteBuffer + 32 - 2) / 2);

        while ((statusReg | 0xFF7F) != (currWord | 0xFF7F)) {
          statusReg = readFlash_MD((currSdBuffer + currWriteBuffer + 32 - 2) / 2);
        }

        // Set data pins to output
        dataOut_MD();
      }
      // update progress bar
      processedProgressBar += 512;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }

    // Set data pins to input again
    dataIn_MD();

    // Close the file:
    myFile.close();
  } else {
    print_STR(open_file_STR, 1);
    display_Update();
  }
}

void idFlash_MD() {
  // Set data pins to output
  dataOut_MD();

  // ID command sequence
  writeFlash_MD(0x5555, 0xaa);
  writeFlash_MD(0x2aaa, 0x55);
  writeFlash_MD(0x5555, 0x90);

  // Set data pins to input again
  dataIn_MD();

  // Read the two id bytes into a string
  flashid = (readFlash_MD(0) & 0xFF) << 8;
  flashid |= readFlash_MD(1) & 0xFF;
  sprintf(flashid_str, "%04X", flashid);
}

byte readStatusReg_MD() {
  // Set data pins to output
  dataOut_MD();

  // Status reg command sequence
  writeFlash_MD(0x5555, 0xaa);
  writeFlash_MD(0x2aaa, 0x55);
  writeFlash_MD(0x5555, 0x70);

  // Set data pins to input again
  dataIn_MD();

  // Read the status register
  byte statusReg = readFlash_MD(0);
  return statusReg;
}

void eraseFlash_MD() {
  // Set data pins to output
  dataOut_MD();

  // Erase command sequence
  writeFlash_MD(0x5555, 0xaa);
  writeFlash_MD(0x2aaa, 0x55);
  writeFlash_MD(0x5555, 0x80);
  writeFlash_MD(0x5555, 0xaa);
  writeFlash_MD(0x2aaa, 0x55);
  writeFlash_MD(0x5555, 0x10);

  // Set data pins to input again
  dataIn_MD();

  busyCheck_MD();
}

void blankcheck_MD() {
  blank = 1;
  for (unsigned long currByte = 0; currByte < flashSize / 2; currByte++) {
    if (readFlash_MD(currByte) != 0xFFFF) {
      currByte = flashSize / 2;
      blank = 0;
    }
    if (currByte % 4096 == 0) {
      blinkLED();
    }
  }
  if (!blank) {
    print_Error(F("Error: Not blank"));
  }
}

void verifyFlash_MD() {
  // Open file on sd card
  if (myFile.open(filePath, O_READ)) {
    // Get rom size from file
    fileSize = myFile.fileSize();
    if (fileSize > flashSize) {
      print_FatalError(file_too_big_STR);
    }

    blank = 0;
    word d = 0;
    for (unsigned long currByte = 0; currByte < fileSize / 2; currByte += 256) {
      if (currByte % 4096 == 0) {
        blinkLED();
      }
      //fill sdBuffer
      myFile.read(sdBuffer, 512);
      for (int c = 0; c < 256; c++) {
        word currWord = ((sdBuffer[d] << 8) | sdBuffer[d + 1]);

        if (readFlash_MD(currByte + c) != currWord) {
          blank++;
        }
        d += 2;
      }
      d = 0;
    }
    if (blank == 0) {
      println_Msg(F("Flashrom verified OK"));
      display_Update();
    } else {
      print_STR(error_STR, 0);
      print_Msg(blank);
      print_STR(_bytes_STR, 1);
      print_Error(did_not_verify_STR);
    }
    // Close the file:
    myFile.close();
  } else {
    print_STR(open_file_STR, 1);
    display_Update();
  }
}

// Delay between write operations based on status register
void busyCheck_MD() {
  // Set data pins to input
  dataIn_MD();

  // Read the status register
  word statusReg = readFlash_MD(0);

  while ((statusReg | 0xFF7F) != 0xFFFF) {
    statusReg = readFlash_MD(0);
  }

  // Set data pins to output
  dataOut_MD();
}
#endif

//******************************************
// EEPROM Functions
//******************************************
void EepromInit(byte eepmode) {    // Acclaim Type 2
  PORTF = 0x00;                    // ADDR A0-A7
  PORTK = 0x00;                    // ADDR A8-A15
  PORTL = 0x10;                    // ADDR A16-A23
  PORTA = 0x00;                    // DATA D8-D15
  PORTH |= (1 << 0);               // /RES HIGH
  PORTC = eepmode;                 // EEPROM Switch:  0 = Enable (Read EEPROM), 1 = Disable (Read ROM)
  PORTH &= ~(1 << 3);              // CE LOW
  PORTH &= ~(1 << 4) & ~(1 << 5);  // /UDSW + /LDSW LOW
  PORTH |= (1 << 6);               // OE HIGH
  __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");
  PORTH |= (1 << 4) | (1 << 5);  // /UDSW + /LDSW HIGH
  __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");
}

void writeWord_SDA(unsigned long myAddress, word myData) { /* D0 goes to /SDA when only /LWR is asserted */
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;
  PORTC = myData;
  PORTH &= ~(1 << 3);  // CE LOW
  PORTH &= ~(1 << 5);  // /LDSW LOW
  PORTH |= (1 << 4);   // /UDSW HIGH
  PORTH |= (1 << 6);   // OE HIGH
  if (eepSize > 0x100)
    __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");
  else
    delayMicroseconds(100);
  PORTH |= (1 << 5);  // /LDSW HIGH
  if (eepSize > 0x100)
    __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");
  else
    delayMicroseconds(100);
}

void writeWord_SCL(unsigned long myAddress, word myData) { /* D0 goes to /SCL when only /UWR is asserted */
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;
  PORTC = myData;
  PORTH &= ~(1 << 3);  // CE LOW
  PORTH &= ~(1 << 4);  // /UDSW LOW
  PORTH |= (1 << 5);   // /LDSW HIGH
  PORTH |= (1 << 6);   // OE HIGH
  if (eepSize > 0x100)
    __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");
  else
    delayMicroseconds(100);
  PORTH |= (1 << 4);  // /UDSW HIGH
  if (eepSize > 0x100)
    __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");
  else
    delayMicroseconds(100);
}

void writeWord_CM(unsigned long myAddress, word myData) {  // Codemasters
  PORTF = myAddress & 0xFF;
  PORTK = (myAddress >> 8) & 0xFF;
  PORTL = (myAddress >> 16) & 0xFF;
  PORTC = myData;
  PORTA = (myData >> 8) & 0xFF;

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

  // Switch WR(PH4) to LOW
  PORTH &= ~(1 << 4);
  // Setting CS(PH3) LOW
  PORTH &= ~(1 << 3);
  // Pulse CLK(PH1)
  PORTH ^= (1 << 1);

  // Leave WR low for at least 200ns
  __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");

  // Pulse CLK(PH1)
  PORTH ^= (1 << 1);
  // Setting CS(PH3) HIGH
  PORTH |= (1 << 3);
  // Switch WR(PH4) to HIGH
  PORTH |= (1 << 4);

  // 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");
}

// EEPROM COMMANDS
void EepromStart() {
  if (eepType == 2) {               // Acclaim Type 2
    writeWord_SDA(0x100000, 0x00);  // sda low
    writeWord_SCL(0x100000, 0x00);  // scl low
    writeWord_SDA(0x100000, 0x01);  // sda high
    writeWord_SCL(0x100000, 0x01);  // scl high
    writeWord_SDA(0x100000, 0x00);  // sda low
    writeWord_SCL(0x100000, 0x00);  // scl low
  } else if (eepType == 4) {        // EA
    writeWord_MD(0x100000, 0x00);   // sda low, scl low
    writeWord_MD(0x100000, 0xC0);   // sda, scl high
    writeWord_MD(0x100000, 0x40);   // sda low, scl high
    writeWord_MD(0x100000, 0x00);   // START
  } else if (eepType == 5) {        // Codemasters
    writeWord_CM(0x180000, 0x00);   // sda low, scl low
    writeWord_CM(0x180000, 0x02);   // sda low, scl high
    writeWord_CM(0x180000, 0x03);   // sda, scl high
    writeWord_CM(0x180000, 0x02);   // sda low, scl high
    writeWord_CM(0x180000, 0x00);   // START
  } else {
    writeWord_MD(0x100000, 0x00);  // sda low, scl low
    writeWord_MD(0x100000, 0x03);  // sda, scl high
    writeWord_MD(0x100000, 0x02);  // sda low, scl high
    writeWord_MD(0x100000, 0x00);  // START
  }
}

void EepromSet0() {
  if (eepType == 2) {               // Acclaim Type 2
    writeWord_SDA(0x100000, 0x00);  // sda low
    writeWord_SCL(0x100000, 0x01);  // scl high
    writeWord_SDA(0x100000, 0x00);  // sda low
    writeWord_SCL(0x100000, 0x00);  // scl low
  } else if (eepType == 4) {        // EA
    writeWord_MD(0x100000, 0x00);   // sda low, scl low
    writeWord_MD(0x100000, 0x40);   // sda low, scl high // 0
    writeWord_MD(0x100000, 0x00);   // sda low, scl low
  } else if (eepType == 5) {        // Codemasters
    writeWord_CM(0x180000, 0x00);   // sda low, scl low
    writeWord_CM(0x180000, 0x02);   // sda low, scl high // 0
    writeWord_CM(0x180000, 0x00);   // sda low, scl low
  } else {
    writeWord_MD(0x100000, 0x00);  // sda low, scl low
    writeWord_MD(0x100000, 0x02);  // sda low, scl high // 0
    writeWord_MD(0x100000, 0x00);  // sda low, scl low
  }
}

void EepromSet1() {
  if (eepType == 2) {               // Acclaim Type 2
    writeWord_SDA(0x100000, 0x01);  // sda high
    writeWord_SCL(0x100000, 0x01);  // scl high
    writeWord_SDA(0x100000, 0x01);  // sda high
    writeWord_SCL(0x100000, 0x00);  // scl low
  } else if (eepType == 4) {        // EA
    writeWord_MD(0x100000, 0x80);   // sda high, scl low
    writeWord_MD(0x100000, 0xC0);   // sda high, scl high // 1
    writeWord_MD(0x100000, 0x80);   // sda high, scl low
    writeWord_MD(0x100000, 0x00);   // sda low, scl low
  } else if (eepType == 5) {        // Codemasters
    writeWord_CM(0x180000, 0x01);   // sda high, scl low
    writeWord_CM(0x180000, 0x03);   // sda high, scl high // 1
    writeWord_CM(0x180000, 0x01);   // sda high, scl low
    writeWord_CM(0x180000, 0x00);   // sda low, scl low
  } else {
    writeWord_MD(0x100000, 0x01);  // sda high, scl low
    writeWord_MD(0x100000, 0x03);  // sda high, scl high // 1
    writeWord_MD(0x100000, 0x01);  // sda high, scl low
    writeWord_MD(0x100000, 0x00);  // sda low, scl low
  }
}

void EepromDevice() {  // 24C02+
  EepromSet1();
  EepromSet0();
  EepromSet1();
  EepromSet0();
}

void EepromSetDeviceAddress(word addrhi) {  // 24C02+
  for (int i = 0; i < 3; i++) {
    if ((addrhi >> 2) & 0x1)  // Bit is HIGH
      EepromSet1();
    else  // Bit is LOW
      EepromSet0();
    addrhi <<= 1;  // rotate to the next bit
  }
}

void EepromStatus() {  // ACK
  byte eepStatus = 1;
  if (eepType == 1) {              // Acclaim Type 1
    writeWord_MD(0x100000, 0x01);  // sda high, scl low
    writeWord_MD(0x100000, 0x03);  // sda high, scl high
    do {
      dataIn_MD();
      eepStatus = ((readWord_MD(0x100000) >> 1) & 0x1);
      dataOut_MD();
      delayMicroseconds(4);
    } while (eepStatus == 1);
    writeWord_MD(0x100000, 0x01);   // sda high, scl low
  } else if (eepType == 2) {        // Acclaim Type 2
    writeWord_SDA(0x100000, 0x01);  // sda high
    writeWord_SCL(0x100000, 0x01);  // scl high
    do {
      dataIn_MD();
      eepStatus = (readWord_MD(0x100000) & 0x1);
      dataOut_MD();
      delayMicroseconds(4);
    } while (eepStatus == 1);
    writeWord_SCL(0x100000, 0x00);  // scl low
  } else if (eepType == 3) {        // Capcom/Sega
    writeWord_MD(0x100000, 0x01);   // sda high, scl low
    writeWord_MD(0x100000, 0x03);   // sda high, scl high
    do {
      dataIn_MD();
      eepStatus = (readWord_MD(0x100000) & 0x1);
      dataOut_MD();
      delayMicroseconds(4);
    } while (eepStatus == 1);
    writeWord_MD(0x100000, 0x01);  // sda high, scl low
  } else if (eepType == 4) {       // EA
    writeWord_MD(0x100000, 0x80);  // sda high, scl low
    writeWord_MD(0x100000, 0xC0);  // sda high, scl high
    do {
      dataIn_MD();
      eepStatus = ((readWord_MD(0x100000) >> 7) & 0x1);
      dataOut_MD();
      delayMicroseconds(4);
    } while (eepStatus == 1);
    writeWord_MD(0x100000, 0x80);  // sda high, scl low
  } else if (eepType == 5) {       // Codemasters
    writeWord_CM(0x180000, 0x01);  // sda high, scl low
    writeWord_CM(0x180000, 0x03);  // sda high, scl high
    do {
      dataIn_MD();
      eepStatus = ((readWord_MD(0x1C0000) >> 7) & 0x1);
      dataOut_MD();
      delayMicroseconds(4);
    } while (eepStatus == 1);
    writeWord_CM(0x180000, 0x01);  // sda high, scl low
  }
}

void EepromReadMode() {
  EepromSet1();    // READ
  EepromStatus();  // ACK
}

void EepromWriteMode() {
  EepromSet0();    // WRITE
  EepromStatus();  // ACK
}

void EepromReadData() {
  if (eepType == 1) {  // Acclaim Type 1
    for (int i = 0; i < 8; i++) {
      writeWord_MD(0x100000, 0x03);  // sda high, scl high
      dataIn_MD();
      eepbit[i] = ((readWord_MD(0x100000) >> 1) & 0x1);  // Read 0x100000 with Mask 0x1 (bit 1)
      dataOut_MD();
      writeWord_MD(0x100000, 0x01);  // sda high, scl low
    }
  } else if (eepType == 2) {  // Acclaim Type 2
    for (int i = 0; i < 8; i++) {
      writeWord_SDA(0x100000, 0x01);  // sda high
      writeWord_SCL(0x100000, 0x01);  // scl high
      dataIn_MD();
      eepbit[i] = (readWord_MD(0x100000) & 0x1);  // Read 0x100000 with Mask 0x1 (bit 0)
      dataOut_MD();
      writeWord_SDA(0x100000, 0x01);  // sda high
      writeWord_SCL(0x100000, 0x00);  // scl low
    }
  } else if (eepType == 3) {  // Capcom/Sega
    for (int i = 0; i < 8; i++) {
      writeWord_MD(0x100000, 0x03);  // sda high, scl high
      dataIn_MD();
      eepbit[i] = (readWord_MD(0x100000) & 0x1);  // Read 0x100000 with Mask 0x1 (bit 0)
      dataOut_MD();
      writeWord_MD(0x100000, 0x01);  // sda high, scl low
    }
  } else if (eepType == 4) {  // EA
    for (int i = 0; i < 8; i++) {
      writeWord_MD(0x100000, 0xC0);  // sda high, scl high
      dataIn_MD();
      eepbit[i] = ((readWord_MD(0x100000) >> 7) & 0x1);  // Read 0x100000 with Mask (bit 7)
      dataOut_MD();
      writeWord_MD(0x100000, 0x80);  // sda high, scl low
    }
  } else if (eepType == 5) {  // Codemasters
    for (int i = 0; i < 8; i++) {
      writeWord_CM(0x180000, 0x03);  // sda high, scl high
      dataIn_MD();
      eepbit[i] = ((readWord_MD(0x1C0000) >> 7) & 0x1);  // Read 0x1C0000 with Mask 0x1 (bit 7)
      dataOut_MD();
      writeWord_CM(0x180000, 0x01);  // sda high, scl low
    }
  }
}

void EepromWriteData(byte data) {
  for (int i = 0; i < 8; i++) {
    if ((data >> 7) & 0x1)  // Bit is HIGH
      EepromSet1();
    else  // Bit is LOW
      EepromSet0();
    data <<= 1;  // rotate to the next bit
  }
  EepromStatus();  // ACK
}

void EepromFinish() {
  if (eepType == 2) {               // Acclaim Type 2
    writeWord_SDA(0x100000, 0x00);  // sda low
    writeWord_SCL(0x100000, 0x00);  // scl low
    writeWord_SDA(0x100000, 0x01);  // sda high
    writeWord_SCL(0x100000, 0x00);  // scl low
    writeWord_SDA(0x100000, 0x01);  // sda high
    writeWord_SCL(0x100000, 0x01);  // scl high
    writeWord_SDA(0x100000, 0x01);  // sda high
    writeWord_SCL(0x100000, 0x00);  // scl low
    writeWord_SDA(0x100000, 0x00);  // sda low
    writeWord_SCL(0x100000, 0x00);  // scl low
  } else if (eepType == 4) {        // EA
    writeWord_MD(0x100000, 0x00);   // sda low, scl low
    writeWord_MD(0x100000, 0x80);   // sda high, scl low
    writeWord_MD(0x100000, 0xC0);   // sda high, scl high
    writeWord_MD(0x100000, 0x80);   // sda high, scl low
    writeWord_MD(0x100000, 0x00);   // sda low, scl low
  } else if (eepType == 5) {        // Codemasters
    writeWord_CM(0x180000, 0x00);   // sda low, scl low
    writeWord_CM(0x180000, 0x01);   // sda high, scl low
    writeWord_CM(0x180000, 0x03);   // sda high, scl high
    writeWord_CM(0x180000, 0x01);   // sda high, scl low
    writeWord_CM(0x180000, 0x00);   // sda low, scl low
  } else {
    writeWord_MD(0x100000, 0x00);  // sda low, scl low
    writeWord_MD(0x100000, 0x01);  // sda high, scl low
    writeWord_MD(0x100000, 0x03);  // sda high, scl high
    writeWord_MD(0x100000, 0x01);  // sda high, scl low
    writeWord_MD(0x100000, 0x00);  // sda low, scl low
  }
}

void EepromStop() {
  if (eepType == 2) {               // Acclaim Type 2
    writeWord_SDA(0x100000, 0x00);  // sda low
    writeWord_SCL(0x100000, 0x01);  // scl high
    writeWord_SDA(0x100000, 0x01);  // sda high
    writeWord_SCL(0x100000, 0x01);  // scl high
    writeWord_SDA(0x100000, 0x01);  // sda high
    writeWord_SCL(0x100000, 0x00);  // scl low
    writeWord_SDA(0x100000, 0x00);  // sda low
    writeWord_SCL(0x100000, 0x00);  // scl low // STOP
  } else if (eepType == 4) {        // EA
    writeWord_MD(0x100000, 0x00);   // sda, scl low
    writeWord_MD(0x100000, 0x40);   // sda low, scl high
    writeWord_MD(0x100000, 0xC0);   // sda, scl high
    writeWord_MD(0x100000, 0x80);   // sda high, scl low
    writeWord_MD(0x100000, 0x00);   // STOP
  } else if (eepType == 5) {        // Codemasters
    writeWord_CM(0x180000, 0x00);   // sda low, scl low
    writeWord_CM(0x180000, 0x02);   // sda low, scl high
    writeWord_CM(0x180000, 0x03);   // sda, scl high
    writeWord_CM(0x180000, 0x01);   // sda high, scl low
    writeWord_CM(0x180000, 0x00);   // STOP
  } else {
    writeWord_MD(0x100000, 0x00);  // sda, scl low
    writeWord_MD(0x100000, 0x02);  // sda low, scl high
    writeWord_MD(0x100000, 0x03);  // sda, scl high
    writeWord_MD(0x100000, 0x01);  // sda high, scl low
    writeWord_MD(0x100000, 0x00);  // STOP
  }
}

void EepromSetAddress(word address) {
  if (eepSize > 0x80) {  // 24C02+
    for (int i = 0; i < 8; i++) {
      if ((address >> 7) & 0x1)  // Bit is HIGH
        EepromSet1();
      else  // Bit is LOW
        EepromSet0();
      address <<= 1;  // rotate to the next bit
    }
    EepromStatus();  // ACK
  } else {           // 24C01
    for (int i = 0; i < 7; i++) {
      if ((address >> 6) & 0x1)  // Bit is HIGH
        EepromSet1();
      else  // Bit is LOW
        EepromSet0();
      address <<= 1;  // rotate to the next bit
    }
  }
}

void readEepromByte(word address) {
  addrhi = address >> 8;
  addrlo = address & 0xFF;
  dataOut_MD();
  if (eepType == 2)
    EepromInit(0);  // Enable EEPROM
  EepromStart();    // START
  if (eepSize > 0x80) {
    EepromDevice();         // DEVICE [1010]
    if (eepSize > 0x800) {  // MODE 3 [24C65]
      EepromSetDeviceAddress(0);
      EepromWriteMode();
      EepromSetAddress(addrhi);        // ADDR [A15..A8]
    } else {                           // MODE 2 [24C02/24C08/24C16]
      EepromSetDeviceAddress(addrhi);  // ADDR [A10..A8]
      EepromWriteMode();
    }
  }
  EepromSetAddress(addrlo);
  if (eepSize > 0x80) {
    EepromStart();        // START
    EepromDevice();       // DEVICE [1010]
    if (eepSize > 0x800)  // MODE 3 [24C65]
      EepromSetDeviceAddress(0);
    else                               // MODE 2 [24C02/24C08/24C16]
      EepromSetDeviceAddress(addrhi);  // ADDR [A10..A8]
  }
  EepromReadMode();
  EepromReadData();
  EepromFinish();
  EepromStop();  // STOP
  if (eepType == 2)
    EepromInit(1);  // Disable EEPROM
  // OR 8 bits into byte
  eeptemp = eepbit[0] << 7 | eepbit[1] << 6 | eepbit[2] << 5 | eepbit[3] << 4 | eepbit[4] << 3 | eepbit[5] << 2 | eepbit[6] << 1 | eepbit[7];
  sdBuffer[addrlo] = eeptemp;
}

void writeEepromByte(word address) {
  addrhi = address >> 8;
  addrlo = address & 0xFF;
  eeptemp = sdBuffer[addrlo];
  dataOut_MD();
  if (eepType == 2)
    EepromInit(0);  // Enable EEPROM
  EepromStart();    // START
  if (eepSize > 0x80) {
    EepromDevice();                    // DEVICE [1010]
    if (eepSize > 0x800) {             // MODE 3 [24C65]
      EepromSetDeviceAddress(0);       // [A2-A0] = 000
      EepromWriteMode();               // WRITE
      EepromSetAddress(addrhi);        // ADDR [A15-A8]
    } else {                           // MODE 2 [24C02/24C08/24C16]
      EepromSetDeviceAddress(addrhi);  // ADDR [A10-A8]
      EepromWriteMode();               // WRITE
    }
    EepromSetAddress(addrlo);
  } else {  // 24C01
    EepromSetAddress(addrlo);
    EepromWriteMode();  // WRITE
  }
  EepromWriteData(eeptemp);
  EepromStop();  // STOP
  if (eepType == 2)
    EepromInit(1);  // Disable EEPROM
}

// Read EEPROM and save to the SD card
void readEEP_MD() {
  dataIn_MD();

  // Get name, add extension and convert to char array for sd lib
  createFolder("MD", "SAVE", romName, "eep");

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

  println_Msg(F("Reading..."));
  display_Update();

  // Open file on sd card
  if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
    print_FatalError(sd_error_STR);
  }
  if (eepSize > 0x100) {  // 24C04+
    for (word currByte = 0; currByte < eepSize; currByte += 256) {
      print_Msg(F("*"));
      display_Update();
      for (int i = 0; i < 256; i++) {
        readEepromByte(currByte + i);
      }
      myFile.write(sdBuffer, 256);
    }
  } else {  // 24C01/24C02
    for (word currByte = 0; currByte < eepSize; currByte++) {
      if ((currByte != 0) && ((currByte + 1) % 16 == 0)) {
        print_Msg(F("*"));
        display_Update();
      }
      readEepromByte(currByte);
    }
    myFile.write(sdBuffer, eepSize);
  }
  // Close the file:
  myFile.close();
  println_Msg(FS(FSTRING_EMPTY));
  display_Clear();
  print_Msg(F("Saved to "));
  print_Msg(folder);

  display_Update();
}

void writeEEP_MD() {
  dataOut_MD();

  // Create filepath
  sprintf(filePath, "%s/%s", filePath, fileName);
  println_Msg(F("Writing..."));
  println_Msg(filePath);
  display_Update();

  // Open file on sd card
  if (myFile.open(filePath, O_READ)) {
    if (eepSize > 0x100) {  // 24C04+
      for (word currByte = 0; currByte < eepSize; currByte += 256) {
        myFile.read(sdBuffer, 256);
        for (int i = 0; i < 256; i++) {
          writeEepromByte(currByte + i);
          delay(50);  // DELAY NEEDED
        }
        print_Msg(F("."));
        display_Update();
      }
    } else {  // 24C01/24C02
      myFile.read(sdBuffer, eepSize);
      for (word currByte = 0; currByte < eepSize; currByte++) {
        writeEepromByte(currByte);
        print_Msg(F("."));
        if ((currByte != 0) && ((currByte + 1) % 64 == 0))
          println_Msg(FS(FSTRING_EMPTY));
        display_Update();  // ON SERIAL = delay(100)
      }
    }
    // Close the file:
    myFile.close();
    println_Msg(FS(FSTRING_EMPTY));
    display_Clear();
    print_STR(done_STR, 1);
    display_Update();
  } else {
    print_FatalError(sd_error_STR);
  }
  dataIn_MD();
}

//******************************************
// CD Backup RAM Functions
//******************************************
void readBram_MD() {
  dataIn_MD();

  // Get name, add extension and convert to char array for sd lib
  createFolder("MD", "RAM", "Cart", "brm");

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

  println_Msg(F("Reading..."));
  display_Update();

  // Open file on sd card
  if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
    print_FatalError(sd_error_STR);
  }

  for (unsigned long currByte = 0; currByte < bramSize; currByte += 512) {
    for (int i = 0; i < 512; i++) {
      sdBuffer[i] = readWord_MD(0x300000 + currByte + i);
    }
    myFile.write(sdBuffer, 512);
  }

  // Close the file:
  myFile.close();
  println_Msg(FS(FSTRING_EMPTY));
  display_Clear();
  print_Msg(F("Saved to "));
  print_Msg(folder);

  display_Update();
}

void writeBram_MD() {
  dataOut_MD();

  // Create filepath
  sprintf(filePath, "%s/%s", filePath, fileName);
  println_Msg(F("Writing..."));
  println_Msg(filePath);
  display_Update();

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

    // 0x700000-0x7FFFFF: Writes by /LWR latch D0; 1=RAM write enabled, 0=disabled
    writeWord_MD(0x380000, 1);  // Enable BRAM Writes

    for (unsigned long currByte = 0; currByte < bramSize; currByte += 512) {
      myFile.read(sdBuffer, 512);
      for (int i = 0; i < 512; i++) {
        writeWord_MD(0x300000 + currByte + i, sdBuffer[i]);
      }
    }
    writeWord_MD(0x380000, 0);  // Disable BRAM Writes
    // Close the file:
    myFile.close();
    println_Msg(FS(FSTRING_EMPTY));
    display_Clear();
    print_STR(done_STR, 1);
    display_Update();
  } else {
    print_FatalError(sd_error_STR);
  }
  dataIn_MD();
}

//******************************************
// Realtec Mapper Functions
//******************************************
void writeRealtec(unsigned long address, byte value) {  // Realtec 0x404000 (UPPER)/0x400000 (LOWER)
  dataOut_MD();
  PORTF = address & 0xFF;          // 0x00 ADDR A0-A7
  PORTK = (address >> 8) & 0xFF;   // ADDR A8-A15
  PORTL = (address >> 16) & 0xFF;  //0x20 ADDR A16-A23
  PORTA = 0x00;                    // DATA D8-D15
  PORTH |= (1 << 0);               // /RES HIGH

  PORTH |= (1 << 3);  // CE HIGH
  PORTC = value;
  PORTH &= ~(1 << 4) & ~(1 << 5);  // /UDSW + /LDSW LOW
  PORTH |= (1 << 4) | (1 << 5);    // /UDSW + /LDSW HIGH
  dataIn_MD();
}

void readRealtec_MD() {
  // Set control
  dataIn_MD();

  // Get name, add extension and convert to char array for sd lib
  createFolderAndOpenFile("MD", "ROM", romName, "MD");

  // Realtec Registers
  writeWord_MD(0x201000, 4);  // Number of 128K Blocks 0x402000 (0x201000)
  writeRealtec(0x200000, 1);  // ROM Lower Address 0x400000 (0x200000)
  writeRealtec(0x202000, 0);  // ROM Upper Address 0x404000 (0x202000)

  word d = 0;
  for (unsigned long currBuffer = 0; currBuffer < cartSize / 2; currBuffer += 256) {
    // Blink led
    if (currBuffer % 16384 == 0)
      blinkLED();

    for (int currWord = 0; currWord < 256; currWord++) {
      word myWord = readWord_MD(currBuffer + currWord);
      // Split word into two bytes
      // Left
      sdBuffer[d] = ((myWord >> 8) & 0xFF);
      // Right
      sdBuffer[d + 1] = (myWord & 0xFF);
      d += 2;
    }
    myFile.write(sdBuffer, 512);
    d = 0;
  }
  // Close the file:
  myFile.close();
}

void printRomSize_MD(int index) {
  display_Clear();
  print_Msg(FS(FSTRING_ROM_SIZE));
  print_Msg(pgm_read_byte(&(MDSize[index])));
  println_Msg(F(" Mbit"));
}

void force_cartSize_MD() {
  cartSize = navigateMenu(0, 9, &printRomSize_MD);
  cartSize = pgm_read_byte(&(MDSize[cartSize])) * 131072;
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
  display.setCursor(0, 56);  // Display selection at bottom
#endif
  print_Msg(FS(FSTRING_ROM_SIZE));
  print_Msg(cartSize / 131072);
  println_Msg(F(" Mbit"));
  display_Update();
  delay(1000);
}

#endif

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