//****************************************** // GB MEMORY MODULE //****************************************** #ifdef enable_GBX /****************************************** Menu *****************************************/ // GBM menu items static const char gbmMenuItem1[] PROGMEM = "Read ID"; static const char gbmMenuItem2[] PROGMEM = "Read Flash"; static const char gbmMenuItem3[] PROGMEM = "Erase Flash"; static const char gbmMenuItem4[] PROGMEM = "Blankcheck"; static const char gbmMenuItem5[] PROGMEM = "Write Flash"; static const char gbmMenuItem6[] PROGMEM = "Read Mapping"; static const char gbmMenuItem7[] PROGMEM = "Write Mapping"; static const char* const menuOptionsGBM[] PROGMEM = { gbmMenuItem1, gbmMenuItem2, gbmMenuItem3, gbmMenuItem4, gbmMenuItem5, gbmMenuItem6, gbmMenuItem7 }; void gbmMenu() { // create menu with title and 7 options to choose from unsigned char mainMenu; // Copy menuOptions out of progmem convertPgm(menuOptionsGBM, 7); mainMenu = question_box(F("GB Memory Menu"), menuOptions, 7, 0); // wait for user choice to come back from the question box menu switch (mainMenu) { // Read Flash ID case 0: // Clear screen display_Clear(); readFlashID_GBM(); break; // Read Flash case 1: // Clear screen display_Clear(); // Print warning println_Msg(F("Attention")); println_Msg(F("Always power cycle")); println_Msg(F("cartreader directly")); println_Msg(F("before reading")); println_Msg(""); println_Msg(F("Press Button...")); display_Update(); wait(); // Clear screen display_Clear(); // Reset to root directory sd.chdir("/"); // Enable access to ports 0120h send_GBM(0x09); // Map entire flashrom send_GBM(0x04); // Disable ports 0x0120... send_GBM(0x08); // Read 1MB rom readROM_GBM(64); break; // Erase Flash case 2: // Clear screen display_Clear(); // Print warning println_Msg(F("Attention")); println_Msg(F("This will erase your")); println_Msg(F("NP Cartridge.")); println_Msg(""); println_Msg(""); println_Msg(F("Press Button...")); display_Update(); wait(); // Clear screen display_Clear(); eraseFlash_GBM(); break; // Blankcheck Flash case 3: // Clear screen display_Clear(); if (blankcheckFlash_GBM()) { println_Msg(F("OK")); display_Update(); } else { println_Msg(F("ERROR")); display_Update(); } break; // Write Flash case 4: // Clear screen display_Clear(); filePath[0] = '\0'; sd.chdir("/"); // Launch file browser fileBrowser(F("Select 1MB file")); display_Clear(); sprintf(filePath, "%s/%s", filePath, fileName); // Write rom writeFlash_GBM(); break; // Read mapping case 5: // Clear screen display_Clear(); // Reset to root directory sd.chdir("/"); // Read mapping readMapping_GBM(); break; // Write mapping case 6: // Clear screen display_Clear(); // Print warning println_Msg(F("Attention")); println_Msg(F("This will erase your")); println_Msg(F("NP Cartridge's")); println_Msg(F("mapping data")); println_Msg(""); println_Msg(F("Press Button...")); display_Update(); wait(); // Reset to root directory sd.chdir("/"); // Clear screen display_Clear(); // Clear filepath filePath[0] = '\0'; // Reset to root directory sd.chdir("/"); // Launch file browser fileBrowser(F("Select MAP file")); display_Clear(); sprintf(filePath, "%s/%s", filePath, fileName); display_Update(); // Clear screen display_Clear(); // Erase mapping eraseMapping_GBM(); if (blankcheckMapping_GBM()) { println_Msg(F("OK")); display_Update(); } else { print_Error(F("Erasing failed"), false); break; } // Write mapping writeMapping_GBM(); break; } println_Msg(F("")); println_Msg(F("Press Button...")); display_Update(); wait(); } /****************************************** Setup *****************************************/ void setup_GBM() { // Set RST(PH0) to Input DDRH &= ~(1 << 0); // Activate Internal Pullup Resistors PORTH |= (1 << 0); // Set Address Pins to Output //A0-A7 DDRF = 0xFF; //A8-A15 DDRK = 0xFF; // Set Control Pins to Output RST(PH0) CS(PH3) WR(PH5) RD(PH6) DDRH |= (1 << 3) | (1 << 5) | (1 << 6); // Output a high signal on all pins, pins are active low therefore everything is disabled now PORTH |= (1 << 3) | (1 << 5) | (1 << 6); // Set Data Pins (D0-D7) to Input DDRC = 0x00; delay(400); // Check for Nintendo Power GB Memory cart byte timeout = 0; // First byte of NP register is always 0x21 while (readByte_GBM(0x120) != 0x21) { // Enable ports 0x120h (F2) send_GBM(0x09); __asm__("nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"); timeout++; if (timeout > 10) { println_Msg(F("Error: Time Out")); print_Error(F("Please power cycle"), true); } } } /********************** LOW LEVEL **********************/ // Read one word out of the cartridge byte readByte_GBM(word myAddress) { // Set data pins to Input DDRC = 0x0; PORTF = myAddress & 0xFF; PORTK = (myAddress >> 8) & 0xFF; __asm__("nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"); // Switch CS(PH3) and RD(PH6) to LOW PORTH &= ~(1 << 3); PORTH &= ~(1 << 6); __asm__("nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"); // Read byte tempByte = PINC; // Switch CS(PH3) and RD(PH6) to HIGH PORTH |= (1 << 6); PORTH |= (1 << 3); return tempByte; } // Write one word to data pins of the cartridge void writeByte_GBM(word myAddress, byte myData) { // Set data pins to Output DDRC = 0xFF; PORTF = myAddress & 0xFF; PORTK = (myAddress >> 8) & 0xFF; PORTC = myData; // Pull CS(PH3) and write(PH5) low PORTH &= ~(1 << 3); PORTH &= ~(1 << 5); __asm__("nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"); // Pull CS(PH3) and write(PH5) high PORTH |= (1 << 5); PORTH |= (1 << 3); __asm__("nop\n\t" "nop\n\t" "nop\n\t" "nop\n\t"); // Set data pins to Input (or read errors??!) DDRC = 0x0; } /********************** HELPER FUNCTIONS **********************/ void printSdBuffer(word startByte, word numBytes) { for (int currByte = 0; currByte < numBytes; currByte += 10) { for (byte c = 0; c < 10; c++) { // Convert to char array so we don't lose leading zeros char currByteStr[2]; sprintf(currByteStr, "%02X", sdBuffer[startByte + currByte + c]); print_Msg(currByteStr); } // Add a new line every 10 bytes println_Msg(""); } display_Update(); } void readROM_GBM(word numBanks) { println_Msg(F("Reading Rom...")); display_Update(); // Get name, add extension and convert to char array for sd lib EEPROM_readAnything(0, foldern); sprintf(fileName, "GBM%d", foldern); strcat(fileName, ".bin"); sd.mkdir("NP", true); sd.chdir("NP"); // write new folder number back to eeprom foldern = foldern + 1; EEPROM_writeAnything(0, foldern); // Open file on sd card if (!myFile.open(fileName, O_RDWR | O_CREAT)) { print_Error(F("Can't create file on SD"), true); } else { // Read rom word currAddress = 0; for (word currBank = 1; currBank < numBanks; currBank++) { // Set rom bank writeByte_GBM(0x2100, currBank); // Switch bank start address if (currBank > 1) { currAddress = 0x4000; } for (; currAddress < 0x7FFF; currAddress += 512) { for (int currByte = 0; currByte < 512; currByte++) { sdBuffer[currByte] = readByte_GBM(currAddress + currByte); } myFile.write(sdBuffer, 512); } } // Close the file: myFile.close(); // Signal end of process print_Msg(F("Saved to NP/")); println_Msg(fileName); display_Update(); } } /********************** GB Memory Functions **********************/ void send_GBM(byte myCommand) { switch (myCommand) { case 0x01: //CMD_01h -> ??? writeByte_GBM(0x0120, 0x01); writeByte_GBM(0x013F, 0xA5); break; case 0x02: //CMD_02h -> Write enable Step 2 writeByte_GBM(0x0120, 0x02); writeByte_GBM(0x013F, 0xA5); break; case 0x03: //CMD_03h -> Undo write Step 2 writeByte_GBM(0x0120, 0x03); writeByte_GBM(0x013F, 0xA5); break; case 0x04: //CMD_04h -> Map entire flashrom (MBC4 mode) writeByte_GBM(0x0120, 0x04); writeByte_GBM(0x013F, 0xA5); break; case 0x05: //CMD_05h -> Map menu (MBC5 mode) writeByte_GBM(0x0120, 0x05); writeByte_GBM(0x013F, 0xA5); break; case 0x08: //CMD_08h -> disable writes/reads to/from special Nintendo Power registers (those at 0120h..013Fh) writeByte_GBM(0x0120, 0x08); writeByte_GBM(0x013F, 0xA5); break; case 0x09: //CMD_09h Wakeup -> re-enable access to ports 0120h..013Fh writeByte_GBM(0x0120, 0x09); writeByte_GBM(0x0121, 0xAA); writeByte_GBM(0x0122, 0x55); writeByte_GBM(0x013F, 0xA5); break; case 0x0A: //CMD_0Ah -> Write enable Step 1 writeByte_GBM(0x0120, 0x0A); writeByte_GBM(0x0125, 0x62); writeByte_GBM(0x0126, 0x04); writeByte_GBM(0x013F, 0xA5); break; case 0x10: //CMD_10h -> disable writes to normal MBC registers (such like 2100h) writeByte_GBM(0x0120, 0x10); writeByte_GBM(0x013F, 0xA5); break; case 0x11: //CMD_11h -> re-enable access to MBC registers like 0x2100 writeByte_GBM(0x0120, 0x11); writeByte_GBM(0x013F, 0xA5); break; default: print_Error(F("Unknown Command"), true); break; } } void send_GBM(byte myCommand, word myAddress, byte myData) { byte myAddrLow = myAddress & 0xFF; byte myAddrHigh = (myAddress >> 8) & 0xFF; switch (myCommand) { case 0x0F: // CMD_0Fh -> Write address/byte to flash writeByte_GBM(0x0120, 0x0F); writeByte_GBM(0x0125, myAddrHigh); writeByte_GBM(0x0126, myAddrLow); writeByte_GBM(0x0127, myData); writeByte_GBM(0x013F, 0xA5); break; default: print_Error(F("Unknown Command"), true); break; } } void switchGame_GBM(byte myData) { // Enable ports 0x0120 (F2) send_GBM(0x09); //CMD_C0h -> map selected game without reset writeByte_GBM(0x0120, 0xC0 & myData); writeByte_GBM(0x013F, 0xA5); } void resetFlash_GBM() { // Enable ports 0x0120 (F2) send_GBM(0x09); // Send reset command writeByte_GBM(0x2100, 0x01); send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0xF0); delay(100); } boolean readFlashID_GBM() { // Enable ports 0x0120 (F2) send_GBM(0x09); writeByte_GBM(0x2100, 0x01); // Read ID command send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x90); // Read the two id bytes into a string sprintf(flashid, "%02X%02X", readByte_GBM(0), readByte_GBM(1)); if (strcmp(flashid, "C289") == 0) { print_Msg(F("Flash ID: ")); println_Msg(flashid); display_Update(); resetFlash_GBM(); return 1; } else { print_Msg(F("Flash ID: ")); println_Msg(flashid); print_Error(F("Unknown Flash ID"), true); resetFlash_GBM(); return 0; } } void eraseFlash_GBM() { println_Msg(F("Erasing...")); display_Update(); //enable access to ports 0120h send_GBM(0x09); // Enable write send_GBM(0x0A); send_GBM(0x2); // Unprotect sector 0 send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x60); send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x40); // Wait for unprotect to complete while ((readByte_GBM(0) & 0x80) != 0x80) {} // Send erase command send_GBM(0x0F, 0x5555, 0xaa); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x80); send_GBM(0x0F, 0x5555, 0xaa); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x10); // Wait for erase to complete while ((readByte_GBM(0) & 0x80) != 0x80) {} // Reset flashrom resetFlash_GBM(); } boolean blankcheckFlash_GBM() { print_Msg(F("Blankcheck...")); display_Update(); //enable access to ports 0120h (F2) send_GBM(0x09); // Map entire flashrom send_GBM(0x04); // Disable ports 0x0120... send_GBM(0x08); // Read rom word currAddress = 0; for (byte currBank = 1; currBank < 64; currBank++) { // Set rom bank writeByte_GBM(0x2100, currBank); // Switch bank start address if (currBank > 1) { currAddress = 0x4000; } for (; currAddress < 0x7FFF; currAddress++) { if (readByte_GBM(currAddress) != 0xFF) { return 0; } } } return 1; } void writeFlash_GBM() { print_Msg(F("Writing...")); display_Update(); // Open file on sd card if (myFile.open(filePath, O_READ)) { // Get rom size from file fileSize = myFile.fileSize(); if ((fileSize / 0x4000) > 64) { print_Error(F("File is too big."), true); } // Enable access to ports 0120h send_GBM(0x09); // Enable write send_GBM(0x0A); send_GBM(0x2); // Map entire flash rom send_GBM(0x4); // Set bank for unprotect command, writes to 0x5555 need odd bank number writeByte_GBM(0x2100, 0x1); // Disable ports 0x2100 and 0x120 or else those addresses will not be writable send_GBM(0x10); send_GBM(0x08); // Unprotect sector 0 writeByte_GBM(0x5555, 0xAA); writeByte_GBM(0x2AAA, 0x55); writeByte_GBM(0x5555, 0x60); writeByte_GBM(0x5555, 0xAA); writeByte_GBM(0x2AAA, 0x55); writeByte_GBM(0x5555, 0x40); // Check if flashrom is ready for writing or busy while ((readByte_GBM(0) & 0x80) != 0x80) {} // first bank: 0x0000-0x7FFF, word currAddress = 0x0; // Write 63 banks for (byte currBank = 0x1; currBank < (fileSize / 0x4000); currBank++) { // Blink led blinkLED(); // all following banks: 0x4000-0x7FFF if (currBank > 1) { currAddress = 0x4000; } // Write single bank in 128 byte steps for (; currAddress < 0x7FFF; currAddress += 128) { // Fill SD buffer myFile.read(sdBuffer, 128); // Enable access to ports 0x120 and 0x2100 send_GBM(0x09); send_GBM(0x11); // Set bank writeByte_GBM(0x2100, 0x1); // Disable ports 0x2100 and 0x120 or else those addresses will not be writable send_GBM(0x10); send_GBM(0x08); // Write flash buffer command writeByte_GBM(0x5555, 0xAA); writeByte_GBM(0x2AAA, 0x55); writeByte_GBM(0x5555, 0xA0); // Wait until flashrom is ready again while ((readByte_GBM(0) & 0x80) != 0x80) {} // Enable access to ports 0x120 and 0x2100 send_GBM(0x09); send_GBM(0x11); // Set bank writeByte_GBM(0x2100, currBank); // Disable ports 0x2100 and 0x120 or else those addresses will not be writable send_GBM(0x10); send_GBM(0x08); // Fill flash buffer for (word currByte = 0; currByte < 128; currByte++) { writeByte_GBM(currAddress + currByte, sdBuffer[currByte]); } // Execute write writeByte_GBM(currAddress + 127, 0xFF); // Wait for write to complete while ((readByte_GBM(currAddress) & 0x80) != 0x80) {} } } // Close the file: myFile.close(); println_Msg(F("Done")); } else { print_Error(F("Can't open file"), false); } } void readMapping_GBM() { // Enable ports 0x0120 send_GBM(0x09); // Set WE and WP send_GBM(0x0A); send_GBM(0x2); // Enable hidden mapping area writeByte_GBM(0x2100, 0x01); send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x77); send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x77); // Read mapping println_Msg(F("Reading Mapping...")); display_Update(); // Get name, add extension and convert to char array for sd lib EEPROM_readAnything(0, foldern); sprintf(fileName, "GBM%d", foldern); strcat(fileName, ".map"); sd.mkdir("NP", true); sd.chdir("NP"); // write new folder number back to eeprom foldern = foldern + 1; EEPROM_writeAnything(0, foldern); // Open file on sd card if (!myFile.open(fileName, O_RDWR | O_CREAT)) { print_Error(F("Can't create file on SD"), true); } else { for (byte currByte = 0; currByte < 128; currByte++) { sdBuffer[currByte] = readByte_GBM(currByte); } myFile.write(sdBuffer, 128); // Close the file: myFile.close(); // Signal end of process printSdBuffer(0, 20); printSdBuffer(102, 20); println_Msg(""); print_Msg(F("Saved to NP/")); println_Msg(fileName); display_Update(); } // Reset flash to leave hidden mapping area resetFlash_GBM(); } void eraseMapping_GBM() { println_Msg(F("Erasing...")); display_Update(); //enable access to ports 0120h send_GBM(0x09); // Enable write send_GBM(0x0A); send_GBM(0x2); // Unprotect sector 0 send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x60); send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x40); // Wait for unprotect to complete while ((readByte_GBM(0) & 0x80) != 0x80) {} // Send erase command send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x60); send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x04); // Wait for erase to complete while ((readByte_GBM(0) & 0x80) != 0x80) {} // Reset flashrom resetFlash_GBM(); } boolean blankcheckMapping_GBM() { print_Msg(F("Blankcheck...")); display_Update(); // Enable ports 0x0120 send_GBM(0x09); // Set WE and WP send_GBM(0x0A); send_GBM(0x2); // Enable hidden mapping area writeByte_GBM(0x2100, 0x01); send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x77); send_GBM(0x0F, 0x5555, 0xAA); send_GBM(0x0F, 0x2AAA, 0x55); send_GBM(0x0F, 0x5555, 0x77); // Disable ports 0x0120... send_GBM(0x08); // Read rom for (byte currByte = 0; currByte < 128; currByte++) { if (readByte_GBM(currByte) != 0xFF) { return 0; } } return 1; } void writeMapping_GBM() { print_Msg(F("Writing...")); display_Update(); // Open file on sd card if (myFile.open(filePath, O_READ)) { // Get map file size and check if it exceeds 128KByte if (myFile.fileSize() > 0x80) { print_Error(F("File is too big."), true); } // Enable access to ports 0120h send_GBM(0x09); // Enable write send_GBM(0x0A); send_GBM(0x2); // Map entire flash rom send_GBM(0x4); // Set bank, writes to 0x5555 need odd bank number writeByte_GBM(0x2100, 0x1); // Disable ports 0x2100 and 0x120 or else those addresses will not be writable send_GBM(0x10); send_GBM(0x08); // Unlock write to map area writeByte_GBM(0x5555, 0xAA); writeByte_GBM(0x2AAA, 0x55); writeByte_GBM(0x5555, 0x60); writeByte_GBM(0x5555, 0xAA); writeByte_GBM(0x2AAA, 0x55); writeByte_GBM(0x5555, 0xE0); // Check if flashrom is ready for writing or busy while ((readByte_GBM(0) & 0x80) != 0x80) {} // Fill SD buffer myFile.read(sdBuffer, 128); // Enable access to ports 0x120 and 0x2100 send_GBM(0x09); send_GBM(0x11); // Set bank writeByte_GBM(0x2100, 0x1); // Disable ports 0x2100 and 0x120 or else those addresses will not be writable send_GBM(0x10); send_GBM(0x08); // Write flash buffer command writeByte_GBM(0x5555, 0xAA); writeByte_GBM(0x2AAA, 0x55); writeByte_GBM(0x5555, 0xA0); // Wait until flashrom is ready again while ((readByte_GBM(0) & 0x80) != 0x80) {} // Enable access to ports 0x120 and 0x2100 send_GBM(0x09); send_GBM(0x11); // Set bank writeByte_GBM(0x2100, 0); // Disable ports 0x2100 and 0x120 or else those addresses will not be writable send_GBM(0x10); send_GBM(0x08); // Fill flash buffer for (word currByte = 0; currByte < 128; currByte++) { // Blink led blinkLED(); writeByte_GBM(currByte, sdBuffer[currByte]); } // Execute write writeByte_GBM(127, 0xFF); // Close the file: myFile.close(); println_Msg(F("Done")); } else { print_Error(F("Can't open file"), false); } } #endif //****************************************** // End of File //******************************************