//******************************************
// CP System III Module
// Cartridge
// 32/64/128 Mbits SIMM
// Tested with  HW5
// https://github.com/herzmx/CPS3-OSCR-Adapter
//******************************************
#if (defined(ENABLE_CPS3) && defined(ENABLE_FLASH8) && defined(ENABLE_FLASH16)) 
/******************************************
   Variables
 *****************************************/
uint32_t cartRegion = 0;
uint32_t cartCD = 0;
uint32_t cartCDPatch = 0;
uint32_t multiCart = 0;
uint32_t multiCartPatch1 = 0;
uint32_t multiCartPatch2 = 0;
uint32_t cartRegionOffset = 0;
uint32_t cartnocdOffset = 0;
uint16_t flashids[8];

/******************************************
   Menu
 *****************************************/
// CPS3 start menu options
static const char cpsMenuItem1[] PROGMEM = "CPS3 cartridge";
static const char cpsMenuItem2[] PROGMEM = "32/128 Mbits SIMM";
static const char cpsMenuItem3[] PROGMEM = "64 Mbits SIMM";
static const char cpsMenuItem4[] PROGMEM = "32/128 MBytes SIMM";
static const char cpsMenuItem5[] PROGMEM = "64 MBytes SIMM";
static const char* const menuOptionsCPS[] PROGMEM = { cpsMenuItem1, cpsMenuItem2, cpsMenuItem3, cpsMenuItem4, cpsMenuItem5, FSTRING_RESET };

// Cartridge region options
static const char cpsCartRegion0[] PROGMEM = "No Patch";
static const char cpsCartRegion1[] PROGMEM = "JAP";
static const char cpsCartRegion2[] PROGMEM = "ASIA";
static const char cpsCartRegion3[] PROGMEM = "EUR";
static const char cpsCartRegion4[] PROGMEM = "USA";
static const char cpsCartRegion5[] PROGMEM = "HISPANIC";
static const char cpsCartRegion6[] PROGMEM = "BRAZIL";
static const char cpsCartRegion7[] PROGMEM = "OCEANIA";
static const char cpsCartRegion8[] PROGMEM = "ASIA NCD";
static const char* const menuOptionsCartRegion[] PROGMEM = { cpsCartRegion0, cpsCartRegion1, cpsCartRegion2, cpsCartRegion3, cpsCartRegion4, cpsCartRegion5, cpsCartRegion6, cpsCartRegion7, cpsCartRegion8, FSTRING_RESET };

// Cartridge cd options
static const char cpsCDItem1[] PROGMEM = "CD";
static const char cpsCDItem2[] PROGMEM = "NOCD";
static const char* const menuOptionsCartCD[] PROGMEM = { cpsCartRegion0, cpsCDItem1, cpsCDItem2, FSTRING_RESET };

// CPS3 start menu
void cpsMenu() {
  // create menu with title and 6 options to choose from
  unsigned char cpsType;
  convertPgm(menuOptionsCPS, 6);
  cpsType = question_box(FS(FSTRING_SELECT_CART_TYPE), menuOptions, 6, 0);

  // wait for user choice to come back from the question box menu
  switch (cpsType) {
    case 0:
      display_Clear();
      display_Update();
      mode = CORE_CPS3_CART;
      setup_CPS3();
      id_Flash8();
      wait();
      break;
    case 1:
      display_Clear();
      display_Update();
      mode = CORE_CPS3_128SIMM;
      setup_CPS3();
      id_SIMM2x8();
      wait();
      break;
    case 2:
      display_Clear();
      display_Update();
      mode = CORE_CPS3_64SIMM;
      setup_CPS3();
      id_SIMM4x8();
      wait();
      break;
    case 3:
      display_Clear();
      display_Update();
      mode = CORE_CPS3_01SIMM;
      setup_CPS3();
      break;
    case 4:
      display_Clear();
      display_Update();
      mode = CORE_CPS3_512SIMM;
      setup_CPS3();
      break;
    case 5:
      resetArduino();
      break;
    default:
      print_MissingModule();  // does not return
  }
}

// CPS3 Cartridge menu
void flashromCPS_Cartridge() {
  // create menu with title and 7 options to choose from
  unsigned char mainMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsFLASH8, 7);
  mainMenu = question_box(F("CPS3 Cartdrige Writer"), menuOptions, 7, 0);

  // wait for user choice to come back from the question box menu
  switch (mainMenu) {
    case 0:
      display_Clear();
      println_Msg(F("Blankcheck"));
      display_Update();
      time = millis();
      resetFlash8();
      blankcheck_Flash();
      break;

    case 1:
      if (flashromType != 0) {
        display_Clear();
        println_Msg(F("Warning: This will erase"));
        println_Msg(F("your CPS3 Cartdrige BIOS"));
        print_STR(press_button_STR, 1);
        display_Update();
        wait();
        rgbLed(black_color);
        println_Msg(FS(FSTRING_EMPTY));
        println_Msg(F("Please wait..."));
        display_Update();
        time = millis();

        switch (flashromType) {
          case 1: eraseFlash29F032(); break;
          case 2: eraseFlash29F1610(); break;
          case 3: eraseFlash28FXXX(); break;
        }

        println_Msg(F("CPS3 Cartdrige BIOS erased"));
        display_Update();
        resetFlash8();
      } else {
        readOnlyMode();
      }
      break;

    case 2:
      time = millis();
      resetFlash8();
      readCartridge();
      break;

    case 3:
      if (flashromType != 0) {
        // Set cartridge region
        filePath[0] = '\0';
        sd.chdir("/");
        fileBrowser(FS(FSTRING_SELECT_FILE));
        // Calculate CRC32 of BIOS
        display_Clear();
        println_Msg("Looking BIOS patch");
        println_Msg(FS(FSTRING_EMPTY));
        println_Msg(F("Please wait..."));
        display_Update();
        char crcStr[9];
        sprintf(crcStr, "%08lX", calculateCRC(fileName, filePath, 0));
        setCartridgePatchData(crcStr);
        display_Clear();
        time = millis();

        switch (flashromType) {
          case 1:
            break;
          case 2:
            if ((flashid == 0x0458) || (flashid == 0x0158) || (flashid == 0x01AB) || (flashid == 0x0422) || (flashid == 0x0423))
              writeCartridge();
            else if (flashid == 0x0)  // Manual flash config, pick most common type
              writeFlash29LV640();
            break;
          case 3:
            break;
        }

        delay(100);

        // Reset twice just to be sure
        resetFlash8();
        resetFlash8();

        verifyCartridge();
      } else {
        readOnlyMode();
      }
      break;

    case 4:
      time = 0;
      display_Clear();
      resetFlash8();
      println_Msg(F("ID Flashrom"));
      switch (flashromType) {
        case 0: break;
        case 1: idFlash29F032(); break;
        case 2: idFlash29F1610(); break;
        case 3: idFlash28FXXX(); break;
      }
      println_Msg(FS(FSTRING_EMPTY));
      printFlash(40);
      println_Msg(FS(FSTRING_EMPTY));
      display_Update();
      resetFlash8();
      break;

    case 5:
      time = 0;
      display_Clear();
      println_Msg(F("Print first 70Bytes"));
      display_Update();
      resetFlash8();
      printFlash(70);
      break;

    case 6:
      time = 0;
      display_Clear();
      display_Update();
      resetFlash8();
      resetArduino();
      break;
  }
  if (time != 0) {
    print_Msg(F("Operation took : "));
    print_Msg((millis() - time) / 1000, DEC);
    println_Msg(F("s"));
    display_Update();
  }
  // Prints string out of the common strings array either with or without newline
  print_STR(press_button_STR, 0);
  display_Update();
  wait();
}

// CPS3 Cartridge region patch menu
void cpsCartRegionMenu() {
  cartRegion = pageMenu(F("Cartridge Region Patch"), menuOptionsCartRegion, 10);
  if (cartRegion < 9) {
    display_Clear();
    display_Update();
  } else {
    time = 0;
    display_Clear();
    display_Update();
    resetFlash8();
    resetArduino();
  }
}

// CPS3 Cartridge CD patch menu
void cpsCartCDMenu() {
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsCartCD, 4);
  cartCD = question_box(F("Cartridge CD Patch"), menuOptions, 4, 0);

  // wait for user choice to come back from the question box menu
  if (cartCD < 3) {
    display_Clear();
    display_Update();
  } else {
    time = 0;
    display_Clear();
    display_Update();
    resetFlash8();
    resetArduino();
  }
}

// CPS3 32/128 SIMM menu
void flashromCPS_SIMM2x8() {
  // create menu with title and 7 options to choose from
  unsigned char mainMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsFLASH8, 7);
  mainMenu = question_box(F("CPS3 SIMM Writer 2x8bit"), menuOptions, 7, 0);

  // wait for user choice to come back from the question box menu
  switch (mainMenu) {
    case 0:
      display_Clear();
      println_Msg(F("Blankcheck"));
      display_Update();
      time = millis();
      resetSIMM2x8();
      blankcheckSIMM2x8();
      break;

    case 1:
      if (flashromType != 0) {
        display_Clear();
        println_Msg(F("Warning: This will erase"));
        println_Msg(F("your CPS3 SIMM 2x8bit"));
        print_STR(press_button_STR, 1);
        display_Update();
        wait();
        rgbLed(black_color);
        println_Msg(FS(FSTRING_EMPTY));
        println_Msg(F("Please wait..."));
        display_Update();
        time = millis();

        switch (flashromType) {
          case 1: eraseSIMM2x8(); break;
          case 2: break;
          case 3: break;
        }

        println_Msg(F("CPS3 SIMM 2x8bit erased"));
        display_Update();
        resetSIMM2x8();
      } else {
        readOnlyMode();
      }
      break;

    case 2:
      time = millis();
      resetSIMM2x8();
      readSIMM2x8B();
      break;

    case 3:
      if (flashromType != 0) {
        // Set cartridge region
        filePath[0] = '\0';
        sd.chdir("/");
        fileBrowser(FS(FSTRING_SELECT_FILE));
        display_Clear();
        time = millis();

        switch (flashromType) {
          case 1:
            if (flashid == 0x04AD)
              writeSIMM2x8();
            break;
          case 2:
            break;
          case 3:
            break;
        }

        delay(100);

        // Reset twice just to be sure
        resetSIMM2x8();
        resetSIMM2x8();

        verifySIMM2x8();
      } else {
        readOnlyMode();
      }
      break;

    case 4:
      time = 0;
      display_Clear();
      resetFlash8();
      println_Msg(F("ID Flashrom"));
      switch (flashromType) {
        case 0: break;
        case 1: idFlash2x8(0x0); break;
        case 2: break;
        case 3: break;
      }
      println_Msg(FS(FSTRING_EMPTY));
      printFlash16(40);
      println_Msg(FS(FSTRING_EMPTY));
      display_Update();
      resetSIMM2x8();
      break;

    case 5:
      time = 0;
      display_Clear();
      println_Msg(F("Print first 70Bytes"));
      display_Update();
      resetSIMM2x8();
      printFlash16(70);
      break;

    case 6:
      time = 0;
      display_Clear();
      display_Update();
      resetSIMM2x8();
      resetArduino();
      break;
  }
  if (time != 0) {
    print_Msg(F("Operation took : "));
    print_Msg((millis() - time) / 1000, DEC);
    println_Msg(F("s"));
    display_Update();
  }
  // Prints string out of the common strings array either with or without newline
  print_STR(press_button_STR, 0);
  display_Update();
  wait();
}

// CPS3 32/128 SIMM menu
void flashromCPS_SIMM4x8() {
  // create menu with title and 7 options to choose from
  unsigned char mainMenu;
  // Copy menuOptions out of progmem
  convertPgm(menuOptionsFLASH8, 7);
  mainMenu = question_box(F("CPS3 SIMM Writer 4x8bit"), menuOptions, 7, 0);

  // wait for user choice to come back from the question box menu
  switch (mainMenu) {
    case 0:
      display_Clear();
      println_Msg(F("Blankcheck"));
      display_Update();
      time = millis();
      resetSIMM4x8();
      blankcheckSIMM4x8();
      break;

    case 1:
      if (flashromType != 0) {
        display_Clear();
        println_Msg(F("Warning: This will erase"));
        println_Msg(F("your CPS3 SIMM 4x8bit"));
        print_STR(press_button_STR, 1);
        display_Update();
        wait();
        rgbLed(black_color);
        println_Msg(FS(FSTRING_EMPTY));
        println_Msg(F("Please wait..."));
        display_Update();
        time = millis();

        switch (flashromType) {
          case 1: eraseSIMM4x8(); break;
          case 2: break;
          case 3: break;
        }

        println_Msg(F("CPS3 SIMM 4x8bit erased"));
        display_Update();
        resetSIMM4x8();
      } else {
        readOnlyMode();
      }
      break;

    case 2:
      time = millis();
      resetSIMM4x8();
      readSIMM4x8();
      break;

    case 3:
      if (flashromType != 0) {
        // Set cartridge region
        filePath[0] = '\0';
        sd.chdir("/");
        fileBrowser(FS(FSTRING_SELECT_FILE));
        display_Clear();
        time = millis();

        switch (flashromType) {
          case 1:
            if (flashid == 0x04AD)
              writeSIMM4x8();
            break;
          case 2:
            break;
          case 3:
            break;
        }

        delay(100);

        // Reset twice just to be sure
        resetSIMM4x8();
        resetSIMM4x8();

        verifySIMM4x8();
      } else {
        readOnlyMode();
      }
      break;

    case 4:
      time = 0;
      display_Clear();
      resetFlash8();
      println_Msg(F("ID Flashrom"));
      switch (flashromType) {
        case 0: break;
        case 1: 
            enable64MSB();
            idFlash2x8(0x0);
            enable64LSB();
            idFlash2x8(0x1);
          break;
        case 2: break;
        case 3: break;
      }
      println_Msg(FS(FSTRING_EMPTY));
      printSIMM4x8(40);
      println_Msg(FS(FSTRING_EMPTY));
      display_Update();
      resetSIMM4x8();
      break;

    case 5:
      time = 0;
      display_Clear();
      println_Msg(F("Print first 70Bytes"));
      display_Update();
      resetSIMM4x8();
      enable64MSB();
      printSIMM4x8(70);
      break;

    case 6:
      time = 0;
      display_Clear();
      display_Update();
      resetSIMM4x8();
      resetArduino();
      break;
  }
  if (time != 0) {
    print_Msg(F("Operation took : "));
    print_Msg((millis() - time) / 1000, DEC);
    println_Msg(F("s"));
    display_Update();
  }
  // Prints string out of the common strings array either with or without newline
  print_STR(press_button_STR, 0);
  display_Update();
  wait();
}

/******************************************
   Setup
 *****************************************/
void setup_CPS3() {
  // Set Address Pins to Output
  //A0-A7
  DDRF = 0xFF;
  //A8-A15
  DDRK = 0xFF;
  //A16-A23
  DDRL = 0xFF;
  //A24(PJ0), A25(PJ1)
  DDRJ |= (1 << 0) | (1 << 1);

  // Set Control Pins to Output
  // RST(PH0) OE(PH1) BYTE(PH3) WE(PH4) CKIO_CPU(PH5) CE/CER_CST(PH6)
  DDRH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
  // CE64LSB(PE3) CE128(PE4)
  DDRE |= (1 << 3) | (1 << 4);
  // RST_CPUC(PG1) CE64MSB(PG5)
  DDRG |= (1 << 1) | (1 << 5);

  // Set Data Pins (D0-D15) to Input
  DDRC = 0x00;
  DDRA = 0x00;
  // Disable Internal Pullups
  PORTC = 0x00;
  PORTA = 0x00;

  // Setting RST(PH0) OE(PH1) WE(PH4) CKIO_CPU(PH5) HIGH
  PORTH |= (1 << 0) | (1 << 1) | (1 << 4) | (1 << 5);
  // Setting CE64LSB(PE3) CE128(PE4) HIGH
  PORTE |= (1 << 3) | (1 << 4);
  // Setting CE64MSB(PG5) HIGH
  PORTG |= (1 << 5);
  // Setting CE_CART/CER_CST(PH6) HIGH
  PORTH |= (1 << 6);
  // Setting BYTE(PH3) LOW
  PORTH &= ~(1 << 3);
  // Setting RST_CPUC(PG1) LOW
  PORTG &= ~(1 << 1);

  if (mode == CORE_CPS3_128SIMM || mode == CORE_CPS3_01SIMM) {
    // Setting CE128(PE4) LOW
    PORTE &= ~(1 << 4);
  } else if (mode == CORE_CPS3_CART) {
    // Setting CE_CART/CER_CST(PH6) LOW
    PORTH &= ~(1 << 6);
  }

  byteCtrl = 1;
}

/******************************************
   Low level functions
 *****************************************/
void enable64MSB() {
  // Setting CE64LSB(PE3) HIGH
  PORTE |= (1 << 3);
  // Setting CE64MSB(PG5) LOW
  PORTG &= ~(1 << 5);
  // Wait till output is stable
  NOP;
  NOP;
}

void enable64LSB() {
  // Setting CE64MSB(PG5) HIGH
  PORTG |= (1 << 5);
  // Setting CE64LSB(PE3) LOW
  PORTE &= ~(1 << 3);
  // Wait till output is stable
  NOP;
  NOP;
}

/******************************************
  helper functions
*****************************************/
uint32_t char2int(char hex) {
  uint32_t byte = 0;
  // transform hex character to the 4bit equivalent number, using the ascii table indexes
  if (hex >= '0' && hex <= '9')
    byte = hex - 48;
  else if (hex >= 'A' && hex <= 'F')
    byte = hex - 55;
  return byte;
}

/******************************************
  Command functions
*****************************************/
void writeByteCommand_Flash2x8(uint32_t bank, byte command) {
  writeWord_Flash((bank << 21) | 0x555, 0xaaaa);
  writeWord_Flash((bank << 21) | 0x2aa, 0x5555);
  writeWord_Flash((bank << 21) | 0x555, command << 8 | command);
}

/******************************************
  Cartridge functions
*****************************************/
void readCartridge() {
  // Reset to root directory
  sd.chdir("/");

  createFolderAndOpenFile("CPS3", "Cartridge", "29f400", "u2");

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

  for (unsigned long currByte = 0; currByte < flashSize; currByte += 512) {
    for (int c = 0; c < 512; c++) {
      sdBuffer[c] = readByte_Flash(currByte + c);
    }
    myFile.write(sdBuffer, 512);
    // Update progress bar
    processedProgressBar += 512;
    draw_progressbar(processedProgressBar, totalProgressBar);
    // Blink led
    if (currByte % 25600 == 0)
      blinkLED();
  }

  // Close the file:
  myFile.close();
  // Compare dump CRC with db values
  compareCRC("cps3.txt", 0, 1, 0);
  println_Msg(F("Finished reading"));
  display_Update();
}

void writeCartridge() {
  if (openFlashFile()) {
    // Set data pins to output
    dataOut();

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

    // Fill sdBuffer
    for (unsigned long currByte = 0; currByte < fileSize; currByte += 512) {
      myFile.read(sdBuffer, 512);
      // Blink led
      if (currByte % 2048 == 0)
        blinkLED();

      for (int c = 0; c < 512; c++) {
        // Write command sequence
        writeByteCommandShift_Flash(0xa0);

        // Write patch to avoid region menu in multi
        if ((currByte + c) == 0x405F && cartRegion > 0 && multiCart == 1) {
          writeByte_Flash(currByte + c, multiCartPatch1 & 0xFF);
          busyCheck29F032(currByte + c, multiCartPatch1 & 0xFF);
          continue;
        }
        if ((currByte + c) == 0x20746 && cartRegion > 0 && multiCart == 1) {
          writeByte_Flash(currByte + c, multiCartPatch2 & 0xFF);
          busyCheck29F032(currByte + c, multiCartPatch2 & 0xFF);
          continue;
        }
        // Write cartridge region
        if ((currByte + c) == cartRegionOffset && cartRegion > 0) {
          writeByte_Flash(currByte + c, cartRegion & 0xFF);
          busyCheck29F032(currByte + c, cartRegion & 0xFF);
          continue;
        }
        // Write cartridge cd
        if ((currByte + c) == cartnocdOffset && cartCD > 0) {
          writeByte_Flash(currByte + c, cartCDPatch & 0xFF);
          busyCheck29F032(currByte + c, cartCDPatch & 0xFF);
          continue;
        }
        // Write current byte
        writeByte_Flash(currByte + c, sdBuffer[c]);
        busyCheck29F032(currByte + c, sdBuffer[c]);
      }
      // update progress bar
      processedProgressBar += 512;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }

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

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

void verifyCartridge() {
  if (openVerifyFlashFile()) {
    blank = 0;

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

    for (unsigned long currByte = 0; currByte < fileSize; currByte += 512) {
      //fill sdBuffer
      myFile.read(sdBuffer, 512);
      for (int c = 0; c < 512; c++) {
        // Verify patch to avoid region menu
        if ((currByte + c) == 0x405F && cartRegion > 0 && multiCart == 1 && readByte_Flash(currByte + c) == multiCartPatch1 & 0xFF) {
          continue;
        } else if ((currByte + c) == 0x20746 && cartRegion > 0 && multiCart == 1 && readByte_Flash(currByte + c) == multiCartPatch2 & 0xFF) {
          continue;
          // Verify cartridge region
        } else if ((currByte + c) == cartRegionOffset && cartRegion > 0 && readByte_Flash(currByte + c) == cartRegion & 0xFF) {
          continue;
          // Verify cartridge cd
        } else if ((currByte + c) == cartnocdOffset && cartCD > 0 && readByte_Flash(currByte + c) == cartCDPatch & 0xFF) {
          continue;
        } else if (readByte_Flash(currByte + c) != sdBuffer[c]) {
          blank++;
        }
      }
      // Update progress bar
      processedProgressBar += 512;
      draw_progressbar(processedProgressBar, totalProgressBar);
      // Blink led
      if (currByte % 25600 == 0)
        blinkLED();
    }
    if (blank == 0) {
      println_Msg(F("Cart BIOS 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();
  }
}

void setCartridgePatchData(char crcStr[9]) {
  // Init vars
  cartRegionOffset = 0;
  //Search for CRC32 in file
  char crc_search[9];
  char tmpDWord[9];
  //go to root
  sd.chdir("/");
  if (myFile.open("cps3.txt", O_READ)) {
    while (myFile.available()) {
      // Skip first line with name
      skip_line(&myFile);
      // Read the CRC32 from database
      for (byte i = 0; i < 8; i++) {
        crc_search[i] = char(myFile.read());
      }
      crc_search[8] = '\0';
      //if checksum search successful, set patch data
      if (strcmp(crc_search, crcStr) == 0) {
        // Skip the , in the file
        myFile.seekCur(1);
        // Read region offset
        cartRegionOffset = char2int(myFile.read()) << 16 | char2int(myFile.read()) << 12 | char2int(myFile.read()) << 8 | char2int(myFile.read()) << 4 | char2int(myFile.read());
        // Skip the , in the file
        myFile.seekCur(1);
        if (cartRegionOffset != 0) {
          // Ask for cart region change
          cpsCartRegionMenu();
          if (cartRegion != 0) {
            uint8_t skipRegions = 8 - cartRegion;
            cartRegion -= 1;
            myFile.seekCur(cartRegion * 2);
            cartRegion = (char2int(myFile.read())) << 4 | char2int(myFile.read());
            myFile.seekCur(skipRegions * 2);
          } else {
            myFile.seekCur(16);
          }
        } else {
          myFile.seekCur(16);
        }
        // Skip the , in the file
        myFile.seekCur(1);
        cartnocdOffset = char2int(myFile.read()) << 16 | char2int(myFile.read()) << 12 | char2int(myFile.read()) << 8 | char2int(myFile.read()) << 4 | char2int(myFile.read());
        // Skip the , in the file
        myFile.seekCur(1);
        if (cartnocdOffset != 0) {
          // Ask for cart cd change
          cpsCartCDMenu();
          cartCDPatch = char2int(myFile.read()) << 4 | char2int(myFile.read());
          if (cartCD == 1) {
            cartCDPatch = char2int(myFile.read()) << 4 | char2int(myFile.read());
          } else {
            // Skip the byte
            myFile.seekCur(2);
          }
        } else {
          myFile.seekCur(4);
        }
        // Skip the , in the file
        myFile.seekCur(1);
        // Read multi cart dat from database
        multiCart = char2int(myFile.read());
        // Read multi cart patches
        if (multiCart == 1) {
          multiCartPatch1 = char2int(myFile.read()) << 4 | char2int(myFile.read());
          multiCartPatch2 = char2int(myFile.read()) << 4 | char2int(myFile.read());
        }
        break;
      }
      // If no match go to next entry
      else {
        // skip rest of line
        myFile.seekCur(42);
        // skip third empty line
        skip_line(&myFile);
      }
    }
    myFile.close();
  }
}

/******************************************
  2x8bit SIMM functions
*****************************************/
void id_SIMM2x8() {
  idFlash2x8(0x0);
  idFlash2x8(0x1);
  idFlash2x8(0x2);
  idFlash2x8(0x3);
  resetSIMM2x8();
  uint8_t ngFlash = 0;
  uint8_t okFlash = 0;

  if (flashids[6] == flashids[7]) {
    flashid = flashids[7];
    sprintf(flashid_str, "%04X", flashid);
    okFlash = 2;
    for (byte i = 0; i < 6; i++) {
      if (flashid == flashids[i])
        okFlash += 1;
      else
        ngFlash += 1;
    }
  }
  // Print start screen
  display_Clear();
  display_Update();
  println_Msg(F("SIMM Writer 2x8bit"));
  println_Msg("");
  println_Msg("");
  print_Msg(F("Flash ID: "));
  println_Msg(flashid_str);
  if (flashid == 0x04AD && okFlash == 2 && ngFlash == 6) {
    println_Msg(F("32 Mbit Fujitsu SIMM detected"));
    flashSize = 0x400000;
    flashromType = 1;
  } else if (flashid == 0x04AD && okFlash == 8) {
    println_Msg(F("128 Mbit Fujitsu SIMM detected"));
    flashSize = 0x1000000;
    flashromType = 1;
  } else if (flashid == 0x04AD && okFlash > 2) {
    println_Msg(F("?? Mbit Fujitsu SIMM detected"));
    println_Msg(F("With some bad flash"));
    flashSize = 0x1000000;
    flashromType = 0;
  } else {
    // ID not found
    flashSize = 0x1000000;
    flashromType = 0;
    display_Clear();
    println_Msg(F("SIMM Writer 2x8bit"));
    println_Msg("");
    print_Msg(F("ID Type 1: "));
    println_Msg(vendorID);
    print_Msg(F("ID Type 2: "));
    println_Msg(flashid_str);
    println_Msg("");
    println_Msg(F("UNKNOWN FLASHROM"));
    println_Msg("");
    // Prints string out of the common strings array either with or without newline
    print_Error(press_button_STR);
    display_Update();
    wait();
    // print first 40 bytes of flash
    display_Clear();
    println_Msg(F("First 40 bytes:"));
    println_Msg(FS(FSTRING_EMPTY));
    printFlash16(40);
    resetSIMM2x8();
  }
  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();
  resetSIMM2x8();
}

void resetSIMM2x8() {
  resetFlash2x8(0x3);
  resetFlash2x8(0x2);
  resetFlash2x8(0x1);
  resetFlash2x8(0x0);
}

void blankcheckSIMM2x8() {

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

  blank = 1;
  word d = 0;
  unsigned long simmAddress = 0;
  for (unsigned long currBuffer = 0; currBuffer < flashSize / 2; currBuffer += 256) {
    // Fill buffer
    for (int c = 0; c < 256; c++) {
      simmAddress = currBuffer + c;
      word currWord = readWord_Flash(simmAddress);
      // Read byte right
      sdBuffer[d + 1] = ((currWord >> 8) & 0xFF);
      // Read byte left
      sdBuffer[d] = (currWord & 0xFF);
      d += 2;
    }
    // Check if all bytes are 0xFF
    d = 0;
    for (unsigned long currByte = 0; currByte < 256; currByte++) {
      if (sdBuffer[d] != 0xFF || sdBuffer[d + 1] != 0xFF) {
        currByte = 256;
        currBuffer = flashSize / 2;
        blank = 0;
      }
      d += 2;
    }
    d = 0;
    // Update progress bar
    processedProgressBar += 512;
    draw_progressbar(processedProgressBar, totalProgressBar);
    // Blink led
    if (currBuffer % 25600 == 0)
      blinkLED();
  }
  if (blank) {
    println_Msg(F("SIMM is empty"));
    display_Update();
  } else {
    println_Msg(FS(FSTRING_EMPTY));
    print_Error(F("Error: Not blank"));
  }
}

// From readFlash
void readSIMM2x8() {
  // Reset to root directory
  sd.chdir("/");

  createFolderAndOpenFile("CPS3", "SIMM", "128M", "bin");

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

  word d = 0;
  unsigned long simmAddress = 0;
  char tmpWord[5];
  char tmpDWord[9];
  for (unsigned long currByte = 0; currByte < flashSize / 2; currByte += 256) {
    for (word c = 0; c < 256; c++) {
      simmAddress = currByte + c;
      word currWord = readWord_Flash(simmAddress);
      // Split word into two bytes
      // Right
      sdBuffer[d + 1] = ((currWord >> 8) & 0xFF);
      // Left
      sdBuffer[d] = (currWord & 0xFF);
      d += 2;
    }
    myFile.write(sdBuffer, 512);
    d = 0;
    // Update progress bar
    processedProgressBar += 512;
    draw_progressbar(processedProgressBar, totalProgressBar);
    // Blink led
    if (currByte % 25600 == 0)
      blinkLED();
  }

  // Close the file:
  myFile.close();
  println_Msg(F("Finished reading"));
  display_Update();
}

// From readFlash16
void readSIMM2x8B() {
  // Reset to root directory
  sd.chdir("/");

  createFolderAndOpenFile("CPS3", "SIMM", "128M", "bin");
  //Initialize progress bar
  uint32_t processedProgressBar = 0;
  uint32_t totalProgressBar = flashSize;
  draw_progressbar(0, totalProgressBar);

  word d = 0;
  unsigned long simmAddress = 0;
  for (unsigned long currByte = 0; currByte < flashSize / 2; currByte += 256) {
    for (word c = 0; c < 256; c++) {
      simmAddress = currByte + c;

      word currWord = readWord_Flash(simmAddress);
      // Split word into two bytes
      // Right
      sdBuffer[d + 1] = ((currWord >> 8) & 0xFF);
      // Left
      sdBuffer[d] = (currWord & 0xFF);
      d += 2;
    }
    myFile.write(sdBuffer, 512);
    d = 0;
    // Update progress bar
    processedProgressBar += 512;
    draw_progressbar(processedProgressBar, totalProgressBar);
    // Blink led
    if (currByte % 25600 == 0)
      blinkLED();
  }

  // Close the file:
  myFile.close();
  println_Msg(F("Finished reading."));
  display_Update();
}

void eraseSIMM2x8() {
  eraseFlash2x8(0x3);
  eraseFlash2x8(0x2);
  eraseFlash2x8(0x1);
  eraseFlash2x8(0x0);
}

// From writeFlash29F032
void writeSIMM2x8() {
  if (openFlashFile()) {
    // Set data pins to output
    dataOut16();

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

    // Fill sdBuffer LSB
    unsigned long simmAddress = 0;
    for (unsigned long currByte = 0; currByte < fileSize / 2; currByte += 256) {
      myFile.read(sdBuffer, 512);
      // Blink led
      if (currByte % 4096 == 0)
        blinkLED();

      noInterrupts();
      for (int c = 0; c < 256; c++) {
        simmAddress = currByte + c;
        word myWord = ((sdBuffer[(c * 2) + 1] & 0xFF) << 8) | (sdBuffer[c * 2] & 0xFF);
        // Skip if data exist in flash
        dataIn16();
        word wordFlash = readWord_Flash(simmAddress);
        dataOut16();
        if (wordFlash == myWord || myWord == 0xFFFF) {
          continue;
        }
        // Write command sequence
        writeByteCommand_Flash2x8(simmAddress >> 21, 0xa0);
        // Write current word
        writeWord_Flash(simmAddress, myWord);
        busyCheck2x8(simmAddress, myWord);
      }
      interrupts();
      // update progress bar
      processedProgressBar += 512;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }

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

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

// From verifyFlash16
void verifySIMM2x8() {
  if (openVerifyFlashFile()) {
    blank = 0;
    word d = 0;
    unsigned long simmAddress = 0;
    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = fileSize;
    draw_progressbar(0, totalProgressBar);
    for (unsigned long currByte = 0; currByte < fileSize / 2; currByte += 256) {
      //fill sdBuffer
      myFile.read(sdBuffer, 512);
      for (int c = 0; c < 256; c++) {
        // Set address
        simmAddress = currByte + c;
        word currWord = ((sdBuffer[d + 1] << 8) | sdBuffer[d]);
        if (readWord_Flash(simmAddress) != currWord) {
          blank++;
        }
        d += 2;
      }
      d = 0;
      // Update progress bar
      if (currByte % 256 == 0) {
        processedProgressBar += 512;
        draw_progressbar(processedProgressBar, totalProgressBar);
      }
      // Blink led
      if (currByte % 25600 == 0)
        blinkLED();
    }
    if (blank == 0) {
      println_Msg(F("SIMM 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();
  }
}

/******************************************
  4x8bit SIMM functions
*****************************************/
void id_SIMM4x8() {
  enable64MSB();
  idFlash2x8(0x0);
  enable64LSB();
  idFlash2x8(0x1);
  resetSIMM4x8();
  uint8_t ngFlash = 0;
  uint8_t okFlash = 0;
  
  flashid = flashids[7];
  sprintf(flashid_str, "%04X", flashid);
  for (byte i = 4; i < 8; i++) {
    if (flashid == flashids[i])
      okFlash += 1;
    else
      ngFlash += 1;
  }

  // Print start screen
  display_Clear();
  display_Update();
  println_Msg(F("SIMM Writer 4x8bit"));
  println_Msg("");
  println_Msg("");
  print_Msg(F("Flash ID: "));
  println_Msg(flashid_str);
  if (flashid == 0x04AD && okFlash == 4) {
    println_Msg(F("64 Mbit Fujitsu SIMM detected"));
    flashSize = 0x800000;
    flashromType = 1;
  } else if (flashid == 0x04AD && okFlash < 4) {
    println_Msg(F("Fujitsu SIMM detected"));
    println_Msg(F("With some bad flash"));
    flashSize = 0x800000;
    flashromType = 0;
  } else {
    // ID not found
    display_Clear();
    println_Msg(F("SIMM Writer 4x8bit"));
    println_Msg("");
    print_Msg(F("ID Type 1: "));
    println_Msg(vendorID);
    print_Msg(F("ID Type 2: "));
    println_Msg(flashid_str);
    println_Msg("");
    println_Msg(F("UNKNOWN FLASHROM"));
    println_Msg("");
    // Prints string out of the common strings array either with or without newline
    print_Error(press_button_STR);
    display_Update();
    wait();
    // print first 40 bytes of flash
    display_Clear();
    println_Msg(F("First 40 bytes:"));
    println_Msg(FS(FSTRING_EMPTY));
    printFlash16(40);
    resetSIMM4x8();
  }
  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();
  resetSIMM4x8();
}

void resetSIMM4x8() {
  enable64MSB();
  resetFlash2x8(0x0);
  enable64LSB();
  resetFlash2x8(0x0);
}

void blankcheckSIMM4x8() {

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

  blank = 1;
  word d = 0;
  for (unsigned long currBuffer = 0; currBuffer < flashSize / 4; currBuffer += 128) {
    // Fill buffer
    for (int c = 0; c < 128; c++) {
      enable64MSB();
      word currWord = readWord_Flash(currBuffer + c);
      // Read byte right
      sdBuffer[d + 1] = ((currWord >> 8) & 0xFF);
      // Read byte left
      sdBuffer[d] = (currWord & 0xFF);
      enable64LSB();
      currWord = readWord_Flash(currBuffer + c);
      // Read byte right
      sdBuffer[d + 3] = ((currWord >> 8) & 0xFF);
      // Read byte left
      sdBuffer[d + 2] = (currWord & 0xFF);
      d += 4;
    }
    // Check if all bytes are 0xFF
    d = 0;
    for (unsigned long currByte = 0; currByte < 128; currByte++) {
      if (sdBuffer[d] != 0xFF || sdBuffer[d + 1] != 0xFF || sdBuffer[d + 2] != 0xFF || sdBuffer[d + 3] != 0xFF) {
        currByte = 128;
        currBuffer = flashSize / 4;
        blank = 0;
      }
      d += 4;
    }
    d = 0;
    // Update progress bar
    processedProgressBar += 512;
    draw_progressbar(processedProgressBar, totalProgressBar);
    // Blink led
    if (currBuffer % 25600 == 0)
      blinkLED();
  }
  if (blank) {
    println_Msg(F("SIMM is empty"));
    display_Update();
  } else {
    println_Msg(FS(FSTRING_EMPTY));
    print_Error(F("Error: Not blank"));
  }
}

void eraseSIMM4x8() {
  enable64MSB();
  eraseFlash2x8(0x0);
  enable64LSB();
  eraseFlash2x8(0x0);
}

// From readFlash16
void readSIMM4x8() {
  // Reset to root directory
  sd.chdir("/");

  createFolderAndOpenFile("CPS3", "SIMM", "64M", "bin");
  //Initialize progress bar
  uint32_t processedProgressBar = 0;
  uint32_t totalProgressBar = flashSize;
  draw_progressbar(0, totalProgressBar);

  word d = 0;
  for (unsigned long currByte = 0; currByte < flashSize / 4; currByte += 128) {
    for (word c = 0; c < 128; c++) {
      enable64MSB();
      word currWord = readWord_Flash(currByte + c);
      // Split word into two bytes
      // Right
      sdBuffer[d + 1] = ((currWord >> 8) & 0xFF);
      // Left
      sdBuffer[d] = (currWord & 0xFF);

      enable64LSB();
      currWord = readWord_Flash(currByte + c);
      // Split word into two bytes
      // Right
      sdBuffer[d + 3] = ((currWord >> 8) & 0xFF);
      // Left
      sdBuffer[d + 2] = (currWord & 0xFF);
      d += 4;
    }
    myFile.write(sdBuffer, 512);
    d = 0;
    // Update progress bar
    processedProgressBar += 512;
    draw_progressbar(processedProgressBar, totalProgressBar);
    // Blink led
    if (currByte % 12800 == 0)
      blinkLED();
  }

  // Close the file:
  myFile.close();
  println_Msg(F("Finished reading."));
  display_Update();
}

void writeSIMM4x8() {
  if (openFlashFile()) {
    // Set data pins to output
    dataOut16();

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

    // Fill sdBuffer
    for (unsigned long currByte = 0; currByte < fileSize / 4; currByte += 128) {
      myFile.read(sdBuffer, 512);
      // Blink led
      if (currByte % 2048 == 0)
        blinkLED();
      
      noInterrupts();
      for (int c = 0; c < 128; c++) {
        // 0600 0EA0
        enable64MSB();
        // 0006
        word myWord = ((sdBuffer[(c * 4) + 1] & 0xFF) << 8) | (sdBuffer[(c * 4)] & 0xFF);
        // Write command sequence
        writeByteCommand_Flash2x8(0x0, 0xa0);
        // Write current word
        writeWord_Flash(currByte + c, myWord);
        busyCheck2x8(currByte + c, myWord);

        enable64LSB();
        // A00E
        myWord = ((sdBuffer[(c * 4) + 3] & 0xFF) << 8) | (sdBuffer[(c * 4) + 2] & 0xFF);
        // Write command sequence
        writeByteCommand_Flash2x8(0x0, 0xa0);
        // Write current word
        writeWord_Flash(currByte + c, myWord);
        busyCheck2x8(currByte + c, myWord);
      }
      interrupts();
      // update progress bar
      processedProgressBar += 512;
      draw_progressbar(processedProgressBar, totalProgressBar);
    }

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

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

void verifySIMM4x8() {
  if (openVerifyFlashFile()) {
    blank = 0;
    word d = 0;
    //Initialize progress bar
    uint32_t processedProgressBar = 0;
    uint32_t totalProgressBar = fileSize;
    draw_progressbar(0, totalProgressBar);

    for (unsigned long currByte = 0; currByte < fileSize / 4; currByte += 128) {
      //fill sdBuffer
      myFile.read(sdBuffer, 512);
      for (int c = 0; c < 128; c++) {
        enable64MSB();
        word currWord = ((sdBuffer[d + 1] << 8) | sdBuffer[d]);
        if (readWord_Flash(currByte + c) != currWord) {
          blank++;
        }
        enable64LSB();
        currWord = ((sdBuffer[d + 3] << 8) | sdBuffer[d + 2]);
        if (readWord_Flash(currByte + c) != currWord) {
          blank++;
        }
        d += 4;
      }
      d = 0;
      // Update progress bar
      if (currByte % 128 == 0) {
        processedProgressBar += 512;
        draw_progressbar(processedProgressBar, totalProgressBar);
      }
      // Blink led
      if (currByte % 25600 == 0)
        blinkLED();
    }

    if (blank == 0) {
      println_Msg(F("SIMM 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();
  }
}

void printSIMM4x8(int numBytes) {
  /*
    right_byte = short_val & 0xFF;
    left_byte = ( short_val >> 8 ) & 0xFF
    short_val = ( ( left_byte & 0xFF ) << 8 ) | ( right_byte & 0xFF );
  */

  char buf[3];

  for (int currByte = 0; currByte < numBytes / 4; currByte += 4) {
    // 2 dwords per line
    for (int c = 0; c < 2; c++) {
      enable64MSB();
      word currWord = readWord_Flash(currByte + c);

      // Split word into two bytes
      byte left_byte = currWord & 0xFF;
      byte right_byte = (currWord >> 8) & 0xFF;

      sprintf(buf, "%.2X", left_byte);
      // Now print the significant bits
      print_Msg(buf);

      sprintf(buf, "%.2X", right_byte);
      // Now print the significant bits
      print_Msg(buf);
      enable64LSB();
      currWord = readWord_Flash(currByte + c);

      // Split word into two bytes
      left_byte = currWord & 0xFF;
      right_byte = (currWord >> 8) & 0xFF;

      sprintf(buf, "%.2X", left_byte);
      // Now print the significant bits
      print_Msg(buf);

      sprintf(buf, "%.2X", right_byte);
      // Now print the significant bits
      print_Msg(buf);
    }
    println_Msg("");
  }
  display_Update();
}

/******************************************
  SIMM 2x8bit flashrom functions
*****************************************/

void resetFlash2x8(uint32_t bank) {
  // Set data pins to output
  dataOut16();

  // Reset command sequence
  writeByteCommand_Flash2x8(bank, 0xf0);

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

  delay(500);
}

void idFlash2x8(uint32_t bank) {
  // Set data pins to output
  dataOut16();

  // ID command sequence
  writeByteCommand_Flash2x8(bank, 0x90);

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

  // Read the two id bytes into a string
  flashids[(bank * 2)] = (readWord_Flash((bank << 21) | 0) >> 8) << 8;
  flashids[(bank * 2)] |= readWord_Flash((bank << 21) | 1) >> 8;
  //sprintf(flashid_str, "%04X", flashid);

  // Read the two id bytes into a string
  flashids[(bank * 2) + 1] = (readWord_Flash((bank << 21) | 0) & 0xFF) << 8;
  flashids[(bank * 2) + 1] |= readWord_Flash((bank << 21) | 1) & 0xFF;
  //sprintf(flashid_str2, "%04X", flashid2);
}

// From eraseFlash29F032
void eraseFlash2x8(uint32_t bank) {
  // Set data pins to output
  dataOut16();

  // Erase command sequence
  writeByteCommand_Flash2x8(bank, 0x80);
  writeByteCommand_Flash2x8(bank, 0x10);

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

  // Read the status register
  word statusReg = readWord_Flash((bank << 21) | 0);

  // After a completed erase D7 and D15 will output 1
  while ((statusReg & 0x8080) != 0x8080) {
    // Blink led
    blinkLED();
    delay(100);
    // Update Status
    statusReg = readWord_Flash((bank << 21) | 0);
  }
}

// From busyCheck29F032
int busyCheck2x8(uint32_t addr, word c) {
  int ret = 0;
  // Set data pins to input
  dataIn16();

  // Setting OE(PH1) LOW
  PORTH &= ~(1 << 1);
  // Setting WE(PH4) HIGH
  PORTH |= (1 << 4);
  NOP;
  NOP;

  //When the Embedded Program algorithm is complete, the device outputs the datum programmed to D7 and D15
  for (;;) {
    word d = readWord_Flash(addr);
    if ((d & 0x8080) == (c & 0x8080)) {
      break;
    }
    if ((d & 0x2020) == 0x2020) {
      // From the datasheet:
      // DQ 5 will indicate if the program or erase time has exceeded the specified limits (internal pulse count).
      // Under these conditions DQ 5 will produce a “1”.
      // This is a failure condition which indicates that the program or erase cycle was not successfully completed.
      // Note : DQ 7 is rechecked even if DQ 5 = “1” because DQ 7 may change simultaneously with DQ 5 .
      d = readWord_Flash(addr);
      if ((d & 0x8080) == (c & 0x8080)) {
        break;
      } else {
        ret = 1;
        break;
      }
    }
  }

  // Set data pins to output
  dataOut16();

  // Setting OE(PH1) HIGH
  PORTH |= (1 << 1);
  NOP;
  NOP;
  return ret;
}

#endif
