cartreader/Cart_Reader/NES.ino
Vincent Pelletier e7ef7fd4bd All: Make flashid an integer
flashid (almost) always contains the hexadecimal representation of two
bytes, which then means it gets compares with strcmp, which in turn need
another string argument. Instead, make it an integer, removing the need to
call strcmp.
Add a separate string representation for printing purposes (maybe this can be
avoided by having the print function format it when needed ?).
The only apparent case where flashid is not an hexadecimal representation
of a pair of bytes is when N64 clears it to "CONF". Set flashid to zero
this case.
This saves about 500 bytes of program space and 200 bytes of ram.
2022-10-28 05:29:20 +00:00

5182 lines
161 KiB
C++

//******************************************
// NES MODULE
//******************************************
// mostly copy&pasted from "Famicom Dumper" 2019-08-31 by skaman
// also based on "CoolArduino" by HardWareMan
// Pinout changes: LED and CIRAM_A10
#ifdef enable_NES
//Line Content
//28 Supported Mappers
//106 Defines
//136 Variables
//197 Menus
//383 Setup
//412 No-Intro SD Database Functions
//1125 Low Level Functions
//1372 CRC Functions
//1426 File Functions
//1527 NES 2.0 Header Functions
//1957 Config Functions
//2760 ROM Functions
//3951 RAM Functions
//4384 Eeprom Functions
//4574 NESmaker Flash Cart Functions
/******************************************
Supported Mappers
*****************************************/
// Supported Mapper Array (iNES Mapper #s)
// Format = {mapper,prglo,prghi,chrlo,chrhi,ramlo,ramhi}
static const byte PROGMEM mapsize[] = {
0, 0, 1, 0, 1, 0, 2, // nrom [sram r/w]
1, 1, 5, 0, 5, 0, 3, // mmc1 [sram r/w]
2, 3, 4, 0, 0, 0, 0, // uxrom
3, 0, 1, 0, 3, 0, 0, // cnrom
4, 1, 5, 0, 6, 0, 1, // mmc3/mmc6 [sram/prgram r/w]
5, 3, 5, 5, 7, 0, 3, // mmc5 [sram r/w]
7, 2, 4, 0, 0, 0, 0, // axrom
9, 3, 3, 5, 5, 0, 0, // mmc2 (punch out)
10, 3, 4, 4, 5, 1, 1, // mmc4 [sram r/w]
11, 1, 3, 1, 5, 0, 0, // Color Dreams [UNLICENSED]
13, 1, 1, 0, 0, 0, 0, // cprom (videomation)
15, 6, 6, 0, 0, 0, 0, // K-1029/K-1030P [UNLICENSED]
16, 3, 4, 5, 6, 0, 1, // bandai x24c02 [eep r/w]
18, 3, 4, 5, 6, 0, 1, // jaleco ss8806 [sram r/w]
19, 3, 4, 5, 6, 0, 1, // namco 106/163 [sram/prgram r/w]
21, 4, 4, 5, 6, 0, 1, // vrc4a/vrc4c [sram r/w]
22, 3, 3, 5, 5, 0, 0, // vrc2a
23, 3, 3, 5, 6, 0, 0, // vrc2b/vrc4e
24, 4, 4, 5, 5, 0, 0, // vrc6a (akumajou densetsu)
25, 3, 4, 5, 6, 0, 1, // vrc2c/vrc4b/vrc4d [sram r/w]
26, 4, 4, 5, 6, 1, 1, // vrc6b [sram r/w]
30, 4, 5, 0, 0, 0, 0, // unrom 512 (NESmaker) [UNLICENSED]
32, 3, 4, 5, 5, 0, 0, // irem g-101
33, 3, 4, 5, 6, 0, 0, // taito tc0190
34, 3, 3, 0, 0, 0, 0, // bnrom [nina-1 NOT SUPPORTED]
37, 4, 4, 6, 6, 0, 0, // (super mario bros + tetris + world cup)
45, 3, 6, 0, 8, 0, 0, // ga23c asic multicart [UNLICENSED]
47, 4, 4, 6, 6, 0, 0, // (super spike vball + world cup)
48, 3, 4, 6, 6, 0, 0, // taito tc0690
58, 1, 6, 1, 6, 0, 0, // BMC-GKB (C)NROM-based multicarts, duplicate of mapper 213 [UNLICENSED]
60, 2, 2, 3, 3, 0, 0, // Reset-based NROM-128 4-in-1 multicarts [UNLICENSED]
62, 7, 7, 8, 8, 0, 0, // K-1017P [UNLICENSED]
64, 2, 3, 4, 5, 0, 0, // tengen rambo-1 [UNLICENSED]
65, 3, 4, 5, 6, 0, 0, // irem h-3001
66, 2, 3, 2, 3, 0, 0, // gxrom/mhrom
67, 3, 3, 5, 5, 0, 0, // sunsoft 3
68, 3, 3, 5, 6, 0, 1, // sunsoft 4 [sram r/w]
69, 3, 4, 5, 6, 0, 1, // sunsoft fme-7/5a/5b [sram r/w]
70, 3, 3, 5, 5, 0, 0, // bandai
71, 2, 4, 0, 0, 0, 0, // camerica/codemasters [UNLICENSED]
72, 3, 3, 5, 5, 0, 0, // jaleco jf-17
73, 3, 3, 0, 0, 0, 0, // vrc3 (salamander)
75, 3, 3, 5, 5, 0, 0, // vrc1
76, 3, 3, 5, 5, 0, 0, // namco 109 variant (megami tensei: digital devil story)
77, 3, 3, 3, 3, 0, 0, // (napoleon senki)
78, 3, 3, 5, 5, 0, 0, // irem 74hc161/32
79, 1, 2, 2, 3, 0, 0, // NINA-03/06 by AVE [UNLICENSED]
80, 3, 3, 5, 6, 0, 1, // taito x1-005 [prgram r/w]
82, 3, 3, 5, 6, 0, 1, // taito x1-017 [prgram r/w]
85, 3, 5, 0, 5, 0, 1, // vrc7 [sram r/w]
86, 3, 3, 4, 4, 0, 0, // jaleco jf-13 (moero pro yakyuu)
87, 0, 1, 2, 3, 0, 0, // Jaleco/Konami CNROM (DIS_74X139X74)
88, 3, 3, 5, 5, 0, 0, // namco (dxrom variant)
89, 3, 3, 5, 5, 0, 0, // sunsoft 2 variant (tenka no goikenban: mito koumon)
92, 4, 4, 5, 5, 0, 0, // jaleco jf-19/jf-21
93, 3, 3, 0, 0, 0, 0, // sunsoft 2
94, 3, 3, 0, 0, 0, 0, // hvc-un1rom (senjou no ookami)
95, 3, 3, 3, 3, 0, 0, // namcot-3425 (dragon buster)
96, 3, 3, 0, 0, 0, 0, // (oeka kids)
97, 4, 4, 0, 0, 0, 0, // irem tam-s1 (kaiketsu yanchamaru)
105, 4, 4, 0, 0, 0, 0, // (nintendo world Championships 1990) [UNTESTED]
118, 3, 4, 5, 5, 0, 1, // txsrom/mmc3 [sram r/w]
119, 3, 3, 4, 4, 0, 0, // tqrom/mmc3
140, 3, 3, 3, 5, 0, 0, // jaleco jf-11/jf-14
146, 1, 2, 2, 3, 0, 0, // Sachen 3015 [UNLICENSED]
152, 2, 3, 5, 5, 0, 0, // BANDAI-74*161/161/32
153, 5, 5, 0, 0, 1, 1, // (famicom jump ii) [sram r/w]
154, 3, 3, 5, 5, 0, 0, // namcot-3453 (devil man)
155, 3, 3, 3, 5, 0, 1, // mmc1 variant [sram r/w]
158, 3, 3, 5, 5, 0, 0, // tengen rambo-1 variant (alien syndrome (u)) [UNLICENSED]
159, 3, 4, 5, 6, 1, 1, // bandai x24c01 [eep r/w]
180, 3, 3, 0, 0, 0, 0, // unrom variant (crazy climber)
184, 1, 1, 2, 3, 0, 0, // sunsoft 1
185, 0, 1, 1, 1, 0, 0, // cnrom lockout
200, 1, 4, 1, 4, 0, 0, // HN-02 multicarts [UNLICENSED]
201, 1, 8, 1, 9, 0, 0, // NROM-256 multicarts [UNLICENSED]
202, 0, 3, 1, 4, 0, 0, // BMC-150IN1 multicarts [UNLICENSED]
203, 1, 4, 1, 4, 0, 0, // various NROM-128 multicarts [UNLICENSED]
206, 1, 3, 2, 4, 0, 0, // dxrom
207, 4, 4, 5, 5, 0, 0, // taito x1-005 variant (fudou myouou den)
210, 3, 5, 5, 6, 0, 0, // namco 175/340
213, 1, 6, 1, 6, 0, 0, // BMC-GKB (C)NROM-based multicarts, duplicate of mapper 58 [UNLICENSED]
225, 7, 7, 8, 8, 0, 0, // ET-4310 (FC) + K-1010 (NES) [UNLICENSED]
229, 5, 5, 6, 6, 0, 0, // BMC 31-IN-1 [UNLICENSED]
232, 4, 4, 0, 0, 0, 0, // Camerica/Codemasters "Quattro" cartridges [UNLICENSED]
235, 6, 8, 0, 0, 0, 0, // "Golden Game" multicarts [UNLICENSED]
240, 1, 5, 1, 5, 0, 3, // C&E Bootleg Board (Sheng Huo Lie Zhuan, Jing Ke Xin Zhuan) [UNLICENSED]
242, 5, 5, 0, 0, 0, 0, // ET-113 [UNLICENSED]
246, 5, 5, 7, 7, 0, 0, // C&E Feng Shen Bang [UNLICENSED]
255, 7, 7, 8, 8, 0, 0, // 110-in-1 multicart (same as 225) [UNLICENSED]
};
/******************************************
Defines
*****************************************/
#define ROMSEL_HI PORTF |= (1 << 1)
#define ROMSEL_LOW PORTF &= ~(1 << 1)
#define PHI2_HI PORTF |= (1 << 0)
#define PHI2_LOW PORTF &= ~(1 << 0)
#define PRG_READ PORTF |= (1 << 7)
#define PRG_WRITE PORTF &= ~(1 << 7)
#define CHR_READ_HI PORTF |= (1 << 5)
#define CHR_READ_LOW PORTF &= ~(1 << 5)
#define CHR_WRITE_HI PORTF |= (1 << 2)
#define CHR_WRITE_LOW PORTF &= ~(1 << 2)
// RGB LED COMMON ANODE
#define LED_RED_OFF setColor_RGB(0, 0, 0)
#define LED_RED_ON setColor_RGB(255, 0, 0)
#define LED_GREEN_OFF setColor_RGB(0, 0, 0)
#define LED_GREEN_ON setColor_RGB(0, 255, 0)
#define LED_BLUE_OFF setColor_RGB(0, 0, 0)
#define LED_BLUE_ON setColor_RGB(0, 0, 255)
#define MODE_READ \
{ \
PORTK = 0xFF; \
DDRK = 0; \
}
#define MODE_WRITE DDRK = 0xFF
#define press 1
#define doubleclick 2
#define hold 3
#define longhold 4
/******************************************
Variables
*****************************************/
// Mapper
byte mapcount = (sizeof(mapsize) / sizeof(mapsize[0])) / 7;
boolean mapfound = false;
byte mapselect;
int PRG[] = { 16, 32, 64, 128, 256, 512, 1024, 2048, 4096 };
byte prglo = 0; // Lowest Entry
byte prghi = 8; // Highest Entry
int CHR[] = { 0, 8, 16, 32, 64, 128, 256, 512, 1024 };
byte chrlo = 0; // Lowest Entry
byte chrhi = 8; // Highest Entry
byte RAM[] = { 0, 8, 16, 32 };
byte ramlo = 0; // Lowest Entry
byte ramhi = 3; // Highest Entry
int banks;
int prg;
int chr;
byte ram;
boolean vrc4e = false;
byte prgchk0;
byte prgchk1;
boolean mmc6 = false;
byte prgchk2;
byte prgchk3;
int eepsize;
byte bytecheck;
byte firstbyte;
boolean flashfound = false; // NESmaker 39SF040 Flash Cart
// Files
char fileCount[3];
#ifndef nointro
FsFile nesFile;
uint32_t prg_crc32;
uint32_t chr_crc32;
char filePRG[] = "PRG.bin";
char fileCHR[] = "CHR.bin";
char fileNES[] = "CART.nes";
char fileBIN[] = "CART.bin";
#endif
// Cartridge Config
byte mapper;
byte newmapper;
byte prgsize;
byte newprgsize;
byte chrsize;
byte newchrsize;
byte ramsize;
byte newramsize;
// Button
int b = 0;
/******************************************
Menus
*****************************************/
// NES start menu
static const char nesMenuItem1[] PROGMEM = "Read iNES Rom";
static const char nesMenuItem2[] PROGMEM = "Read PRG/CHR";
static const char nesMenuItem3[] PROGMEM = "Read Sram";
static const char nesMenuItem4[] PROGMEM = "Write Sram";
static const char nesMenuItem5[] PROGMEM = "Change Mapper";
static const char nesMenuItem6[] PROGMEM = "Flash NESMaker";
//static const char nesMenuItem7[] PROGMEM = "Reset"; (stored in common strings array)
static const char* const menuOptionsNES[] PROGMEM = { nesMenuItem1, nesMenuItem2, nesMenuItem3, nesMenuItem4, nesMenuItem5, nesMenuItem6, string_reset2 };
// NES chips menu
#ifndef nointro
static const char nesChipsMenuItem1[] PROGMEM = "Read PRG & CHR";
#else
static const char nesChipsMenuItem1[] PROGMEM = "Combined PRG+CHR";
#endif
static const char nesChipsMenuItem2[] PROGMEM = "Read only PRG";
static const char nesChipsMenuItem3[] PROGMEM = "Read only CHR";
static const char nesChipsMenuItem4[] PROGMEM = "Back";
static const char* const menuOptionsNESChips[] PROGMEM = { nesChipsMenuItem1, nesChipsMenuItem2, nesChipsMenuItem3, nesChipsMenuItem4 };
// NES start menu
void nesMenu() {
// create menu with title "NES CART READER" and 7 options to choose from
convertPgm(menuOptionsNES, 7);
unsigned char answer = question_box(F("NES CART READER"), menuOptions, 7, 0);
// wait for user choice to come back from the question box menu
switch (answer) {
// Read Rom
case 0:
#ifndef nointro
CartStart();
readPRG(false);
delay(2000);
readCHR(false);
delay(2000);
outputNES();
delay(2000);
readRAM();
delay(2000);
resetROM();
CartFinish();
#else
display_Clear();
// Change working dir to root
sd.chdir("/");
readRom_NES();
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
#ifdef global_log
save_log();
#endif
display_Update();
wait();
#endif
break;
// Read single chip
case 1:
nesChipMenu();
break;
// Read RAM
case 2:
CreateROMFolderInSD();
readRAM();
resetROM();
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
break;
// Write RAM
case 3:
writeRAM();
resetROM();
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
break;
// Change Mapper
case 4:
romName[0] = 'C';
romName[1] = 'A';
romName[2] = 'R';
romName[3] = 'T';
romName[4] = '\0';
setMapper();
checkMapperSize();
setPRGSize();
setCHRSize();
setRAMSize();
checkStatus_NES();
break;
// Write FLASH
case 5:
if (mapper == 30) {
writeFLASH();
resetROM();
} else {
display_Clear();
println_Msg(F("Error:"));
println_Msg(F("Can't write to this cartridge"));
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
}
wait();
break;
// Reset
case 6:
resetArduino();
break;
}
}
void nesChipMenu() {
// create menu with title "Select NES Chip" and 4 options to choose from
convertPgm(menuOptionsNESChips, 4);
unsigned char answer = question_box(F("Select NES Chip"), menuOptions, 4, 0);
// wait for user choice to come back from the question box menu
switch (answer) {
// Read combined PRG/CHR
case 0:
#ifndef nointro
CreateROMFolderInSD();
readPRG(false);
resetROM();
CreateROMFolderInSD();
readCHR(false);
resetROM();
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
#else
display_Clear();
// Change working dir to root
sd.chdir("/");
readRaw_NES();
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
#ifdef global_log
save_log();
#endif
#endif
display_Update();
wait();
break;
// Read PRG
case 1:
CreateROMFolderInSD();
readPRG(false);
resetROM();
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
break;
// Read CHR
case 2:
CreateROMFolderInSD();
readCHR(false);
resetROM();
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
break;
// Return to Main Menu
case 3:
nesMenu();
wait();
break;
}
}
/******************************************
Setup
*****************************************/
void setup_NES() {
// CPU R/W, IRQ, PPU /RD, PPU /A13, CIRAM /CE, PPU /WR, /ROMSEL, PHI2
DDRF = 0b10110111;
// CPU R/W, IRQ, PPU /RD, PPU /A13, CIRAM /CE, PPU /WR, /ROMSEL, PHI2
PORTF = 0b11111111;
// A0-A7 to Output
DDRL = 0xFF;
// A8-A14 to Output
DDRA = 0xFF;
// Set CIRAM A10 to Input
DDRC &= ~(1 << 2);
// Activate Internal Pullup Resistors
PORTC |= (1 << 2);
// Set D0-D7 to Input
PORTK = 0xFF;
DDRK = 0;
set_address(0);
LED_RED_OFF;
LED_GREEN_OFF;
LED_BLUE_OFF;
}
/******************************************
Get Mapping from nointro SD database
*****************************************/
#ifdef nointro
// no clue (taken from fceux)
uint32_t uppow2(uint32_t n) {
int x;
for (x = 31; x >= 0; x--)
if (n & (1u << x)) {
if ((1u << x) != n)
return (1u << (x + 1));
break;
}
return n;
}
void printPRG(unsigned long myOffset) {
display_Clear();
print_Msg(F("Printing PRG at "));
println_Msg(myOffset);
char myBuffer[3];
for (word currLine = 0; currLine < 512; currLine += 16) {
for (byte currByte = 0; currByte < 16; currByte++) {
itoa(read_prg_byte(myOffset + currLine + currByte), myBuffer, 16);
for (word i = 0; i < 2 - strlen(myBuffer); i++) {
print_Msg(F("0"));
}
// Now print the significant bits
print_Msg(myBuffer);
print_Msg(" ");
}
println_Msg("");
}
display_Update();
}
boolean getMapping() {
display_Clear();
println_Msg(F("Searching database"));
display_Update();
// Read first 512 bytes of PRG ROM
for (int x = 0; x < 512; x++) {
sdBuffer[x] = read_prg_byte(0x8000 + x);
}
// Calculate CRC32
uint32_t oldcrc32 = 0xFFFFFFFF;
for (int c = 0; c < 512; c++) {
oldcrc32 = updateCRC(sdBuffer[c], oldcrc32);
}
char crcStr[9];
char tempStr2[2];
char iNES_STR[33];
sprintf(crcStr, "%08lX", ~oldcrc32);
//MMC3 maps the last 8KB block of PRG ROM to 0xE000 while 0x8000 can contain random data after bootup
char crcStrMMC3[9];
// Read first 512 bytes of last block of PRG ROM
for (int x = 0; x < 512; x++) {
sdBuffer[x] = read_prg_byte(0xE000 + x);
}
// Calculate CRC32
oldcrc32 = 0xFFFFFFFF;
for (int c = 0; c < 512; c++) {
oldcrc32 = updateCRC(sdBuffer[c], oldcrc32);
}
sprintf(crcStrMMC3, "%08lX", ~oldcrc32);
print_Msg(F("for "));
print_Msg(crcStr);
if (strcmp(crcStrMMC3, crcStr) != 0) {
print_Msg(F(" or "));
print_Msg(crcStrMMC3);
}
println_Msg(F("..."));
display_Update();
// Filter out all 0xFF checksums at 0x8000 and 0xE000
if ((strcmp(crcStr, "BD7BC39F") == 0) && (strcmp(crcStrMMC3, "BD7BC39F") == 0)) {
delay(200);
println_Msg(F(""));
println_Msg(F("No data found."));
println_Msg(F("Using manual selection"));
display_Update();
delay(500);
romName[0] = 'C';
romName[1] = 'A';
romName[2] = 'R';
romName[3] = 'T';
romName[4] = '\0';
return 0;
} else {
//Search for CRC32 in file
char gamename[100];
char crc_search[9];
//go to root
sd.chdir();
if (myFile.open("nes.txt", O_READ)) {
//Search for same CRC in list
while (myFile.available()) {
// Read game name
get_line(gamename, &myFile, 96);
// Read CRC32 checksum
sprintf(checksumStr, "%c", myFile.read());
for (byte i = 0; i < 7; i++) {
sprintf(tempStr2, "%c", myFile.read());
strcat(checksumStr, tempStr2);
}
// Skip over semicolon
myFile.seekSet(myFile.curPosition() + 1);
// Read CRC32 of first 512 bytes
sprintf(crc_search, "%c", myFile.read());
for (byte i = 0; i < 7; i++) {
sprintf(tempStr2, "%c", myFile.read());
strcat(crc_search, tempStr2);
}
// Skip over semicolon
myFile.seekSet(myFile.curPosition() + 1);
// Read iNES header
get_line(iNES_STR, &myFile, 33);
//Skip every 3rd line
skip_line(&myFile);
//if checksum search was successful set mapper and end search, also filter out 0xFF checksum
if (((strcmp(crc_search, crcStr) == 0) || (strcmp(crc_search, crcStrMMC3) == 0)) && (strcmp(crc_search, "BD7BC39F") != 0)) {
// Rewind to start of entry
for (byte count_newline = 0; count_newline < 4; count_newline++) {
while (1) {
if (myFile.curPosition() == 0) {
break;
} else if (myFile.peek() == '\n') {
myFile.seekSet(myFile.curPosition() - 1);
break;
} else {
myFile.seekSet(myFile.curPosition() - 1);
}
}
}
if (myFile.curPosition() != 0)
myFile.seekSet(myFile.curPosition() + 2);
// Display database
while (myFile.available()) {
display_Clear();
// Read game name
get_line(gamename, &myFile, 96);
// Read CRC32 checksum
sprintf(checksumStr, "%c", myFile.read());
for (byte i = 0; i < 7; i++) {
sprintf(tempStr2, "%c", myFile.read());
strcat(checksumStr, tempStr2);
}
// Skip over semicolon
myFile.seekSet(myFile.curPosition() + 1);
// Read CRC32 of first 512 bytes
sprintf(crc_search, "%c", myFile.read());
for (byte i = 0; i < 7; i++) {
sprintf(tempStr2, "%c", myFile.read());
strcat(crc_search, tempStr2);
}
// Skip over semicolon
myFile.seekSet(myFile.curPosition() + 1);
// Read iNES header
get_line(iNES_STR, &myFile, 33);
// Skip every 3rd line
skip_line(&myFile);
// Convert "4E4553" to (0x4E, 0x45, 0x53)
byte iNES_BUF[2];
for (byte j = 0; j < 16; j++) {
sscanf(iNES_STR + j * 2, "%2X", iNES_BUF);
iNES_HEADER[j] = iNES_BUF[0];
}
// Convert iNES garbage to useful info (thx to fceux)
mapper = (iNES_HEADER[6] >> 4);
mapper |= (iNES_HEADER[7] & 0xF0);
mapper |= ((iNES_HEADER[8] & 0x0F) << 8);
// PRG size
if ((iNES_HEADER[9] & 0x0F) != 0x0F) {
// simple notation
prgsize = (iNES_HEADER[4] | ((iNES_HEADER[9] & 0x0F) << 8)); //*16
} else {
// exponent-multiplier notation
prgsize = (((1 << (iNES_HEADER[4] >> 2)) * ((iNES_HEADER[4] & 0b11) * 2 + 1)) >> 14); //*16
}
if (prgsize != 0)
prgsize = (int(log(prgsize) / log(2)));
// CHR size
if ((iNES_HEADER[9] & 0xF0) != 0xF0) {
// simple notation
chrsize = (uppow2(iNES_HEADER[5] | ((iNES_HEADER[9] & 0xF0) << 4))) * 2; //*4
} else {
chrsize = (((1 << (iNES_HEADER[5] >> 2)) * ((iNES_HEADER[5] & 0b11) * 2 + 1)) >> 13) * 2; //*4
}
if (chrsize != 0)
chrsize = (int(log(chrsize) / log(2)));
// RAM size
ramsize = ((iNES_HEADER[10] & 0xF0) ? (64 << ((iNES_HEADER[10] & 0xF0) >> 4)) : 0) / 4096; //*4
if (ramsize != 0)
ramsize = (int(log(ramsize) / log(2)));
prg = (int_pow(2, prgsize)) * 16;
if (chrsize == 0)
chr = 0; // 0K
else
chr = (int_pow(2, chrsize)) * 4;
if (ramsize == 0)
ram = 0; // 0K
else if (mapper == 82)
ram = 5; // 5K
else
ram = (int_pow(2, ramsize)) * 4;
// Mapper Variants
// Identify variant for use across multiple functions
if (mapper == 4) { // Check for MMC6/MMC3
checkMMC6();
if (mmc6)
ram = 1; // 1K
}
#ifdef global_log
// Disable log to prevent unnecessary logging
println_Log(F("Get Mapping from List"));
dont_log = true;
#endif
println_Msg(gamename);
print_Msg(F("MAPPER: "));
println_Msg(mapper);
print_Msg(F("PRG SIZE: "));
print_Msg(prg);
println_Msg(F("K"));
print_Msg(F("CHR SIZE: "));
print_Msg(chr);
println_Msg(F("K"));
print_Msg(F("RAM SIZE: "));
if (mapper == 0) {
print_Msg(ram / 4);
println_Msg(F("K"));
} else if ((mapper == 16) || (mapper == 80) || (mapper == 159)) {
if (mapper == 16)
print_Msg(ram * 32);
else
print_Msg(ram * 16);
println_Msg(F("B"));
} else if (mapper == 19) {
if (ramsize == 2)
println_Msg(F("128B"));
else {
print_Msg(ram);
println_Msg(F("K"));
}
} else {
print_Msg(ram);
println_Msg(F("K"));
}
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
print_STR(right_to_select_STR, 1);
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#elif defined(SERIAL_MONITOR)
println_Msg(F("U/D to Change"));
println_Msg(F("Space to Select"));
#endif
display_Update();
#ifdef global_log
// Enable log again
dont_log = false;
#endif
int b = 0;
while (1) {
// Check button input
b = checkButton();
// Next
if (b == 1) {
break;
}
// Previous
else if (b == 2) {
for (byte count_newline = 0; count_newline < 7; count_newline++) {
while (1) {
if (myFile.curPosition() == 0) {
break;
} else if (myFile.peek() == '\n') {
myFile.seekSet(myFile.curPosition() - 1);
break;
} else {
myFile.seekSet(myFile.curPosition() - 1);
}
}
}
if (myFile.curPosition() != 0)
myFile.seekSet(myFile.curPosition() + 2);
break;
}
// Selection
else if (b == 3) {
// Get name
byte myLength = 0;
for (unsigned int i = 0; i < 20; i++) {
// Stop at first "(" to remove "(Country)"
if (char(gamename[i]) == 40) {
break;
}
if (((char(gamename[i]) >= 48 && char(gamename[i]) <= 57) || (char(gamename[i]) >= 65 && char(gamename[i]) <= 90) || (char(gamename[i]) >= 97 && char(gamename[i]) <= 122)) && (myLength < 15)) {
romName[myLength] = char(gamename[i]);
myLength++;
}
}
// If name consists out of all japanese characters use CART as name
if (myLength == 0) {
romName[0] = 'C';
romName[1] = 'A';
romName[2] = 'R';
romName[3] = 'T';
romName[4] = '\0';
}
// Save Mapper
EEPROM_writeAnything(7, mapper);
EEPROM_writeAnything(8, prgsize);
EEPROM_writeAnything(9, chrsize);
EEPROM_writeAnything(10, ramsize);
myFile.close();
return 1;
break;
}
}
}
}
}
// File searched until end but nothing found
println_Msg(F(""));
println_Msg(F("CRC not found in database"));
println_Msg(F("Using manual selection"));
display_Update();
delay(1000);
// Print debug
printPRG(0x8000);
printPRG(0xE000);
// Change ROM name to CART
romName[0] = 'C';
romName[1] = 'A';
romName[2] = 'R';
romName[3] = 'T';
romName[4] = '\0';
return 0;
} else {
println_Msg(F("Database file not found"));
return 0;
}
}
}
void selectMapping() {
char gamename[100];
char tempStr2[2];
char iNES_STR[33];
char crc_search[9];
//go to root
sd.chdir();
// Select starting letter
byte myLetter = starting_letter();
if (myLetter == 27) {
// Change Mapper
setMapper();
checkMapperSize();
setPRGSize();
setCHRSize();
setRAMSize();
} else {
// Open database
if (myFile.open("nes.txt", O_READ)) {
#ifdef global_log
// Disable log to prevent unnecessary logging
println_Log(F("Select Mapping from List"));
dont_log = true;
#endif
// Skip ahead to selected starting letter
if ((myLetter > 0) && (myLetter <= 26)) {
while (myFile.available()) {
// Read current name
get_line(gamename, &myFile, 96);
// Compare selected letter with first letter of current name until match
while (gamename[0] != 64 + myLetter) {
skip_line(&myFile);
skip_line(&myFile);
get_line(gamename, &myFile, 96);
}
break;
}
// Rewind one line
for (byte count_newline = 0; count_newline < 2; count_newline++) {
while (1) {
if (myFile.curPosition() == 0) {
break;
} else if (myFile.peek() == '\n') {
myFile.seekSet(myFile.curPosition() - 1);
break;
} else {
myFile.seekSet(myFile.curPosition() - 1);
}
}
}
if (myFile.curPosition() != 0)
myFile.seekSet(myFile.curPosition() + 2);
}
// Display database
while (myFile.available()) {
display_Clear();
// Read game name
get_line(gamename, &myFile, 96);
// Read CRC32 checksum
sprintf(checksumStr, "%c", myFile.read());
for (byte i = 0; i < 7; i++) {
sprintf(tempStr2, "%c", myFile.read());
strcat(checksumStr, tempStr2);
}
// Skip over semicolon
myFile.seekSet(myFile.curPosition() + 1);
// Read CRC32 of first 512 bytes
sprintf(crc_search, "%c", myFile.read());
for (byte i = 0; i < 7; i++) {
sprintf(tempStr2, "%c", myFile.read());
strcat(crc_search, tempStr2);
}
// Skip over semicolon
myFile.seekSet(myFile.curPosition() + 1);
// Read iNES header
get_line(iNES_STR, &myFile, 33);
// Skip every 3rd line
skip_line(&myFile);
// Convert "4E4553" to (0x4E, 0x45, 0x53)
byte iNES_BUF[2];
for (byte j = 0; j < 16; j++) {
sscanf(iNES_STR + j * 2, "%2X", iNES_BUF);
iNES_HEADER[j] = iNES_BUF[0];
}
// Convert iNES garbage to useful info (thx to fceux)
mapper = (iNES_HEADER[6] >> 4);
mapper |= (iNES_HEADER[7] & 0xF0);
mapper |= ((iNES_HEADER[8] & 0x0F) << 8);
// PRG size
if ((iNES_HEADER[9] & 0x0F) != 0x0F) {
// simple notation
prgsize = (iNES_HEADER[4] | ((iNES_HEADER[9] & 0x0F) << 8)); //*16
} else {
// exponent-multiplier notation
prgsize = (((1 << (iNES_HEADER[4] >> 2)) * ((iNES_HEADER[4] & 0b11) * 2 + 1)) >> 14); //*16
}
if (prgsize != 0)
prgsize = (int(log(prgsize) / log(2)));
// CHR size
if ((iNES_HEADER[9] & 0xF0) != 0xF0) {
// simple notation
chrsize = (uppow2(iNES_HEADER[5] | ((iNES_HEADER[9] & 0xF0) << 4))) * 2; //*4
} else {
chrsize = (((1 << (iNES_HEADER[5] >> 2)) * ((iNES_HEADER[5] & 0b11) * 2 + 1)) >> 13) * 2; //*4
}
if (chrsize != 0)
chrsize = (int(log(chrsize) / log(2)));
// RAM size
ramsize = ((iNES_HEADER[10] & 0xF0) ? (64 << ((iNES_HEADER[10] & 0xF0) >> 4)) : 0) / 4096; //*4
if (ramsize != 0)
ramsize = (int(log(ramsize) / log(2)));
prg = (int_pow(2, prgsize)) * 16;
if (chrsize == 0)
chr = 0; // 0K
else
chr = (int_pow(2, chrsize)) * 4;
if (ramsize == 0)
ram = 0; // 0K
else if (mapper == 82)
ram = 5; // 5K
else
ram = (int_pow(2, ramsize)) * 4;
// Mapper Variants
// Identify variant for use across multiple functions
if (mapper == 4) { // Check for MMC6/MMC3
checkMMC6();
if (mmc6)
ram = 1; // 1K
}
println_Msg(gamename);
print_Msg(F("MAPPER: "));
println_Msg(mapper);
print_Msg(F("PRG SIZE: "));
print_Msg(prg);
println_Msg(F("K"));
print_Msg(F("CHR SIZE: "));
print_Msg(chr);
println_Msg(F("K"));
print_Msg(F("RAM SIZE: "));
if (mapper == 0) {
print_Msg(ram / 4);
println_Msg(F("K"));
} else if ((mapper == 16) || (mapper == 80) || (mapper == 159)) {
if (mapper == 16)
print_Msg(ram * 32);
else
print_Msg(ram * 16);
println_Msg(F("B"));
} else if (mapper == 19) {
if (ramsize == 2)
println_Msg(F("128B"));
else {
print_Msg(ram);
println_Msg(F("K"));
}
} else {
print_Msg(ram);
println_Msg(F("K"));
}
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
print_STR(right_to_select_STR, 1);
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#elif defined(SERIAL_MONITOR)
println_Msg(F("U/D to Change"));
println_Msg(F("Space to Select"));
#endif
display_Update();
int b = 0;
while (1) {
// Check button input
b = checkButton();
// Next
if (b == 1) {
break;
}
// Previous
else if (b == 2) {
for (byte count_newline = 0; count_newline < 7; count_newline++) {
while (1) {
if (myFile.curPosition() == 0) {
break;
} else if (myFile.peek() == '\n') {
myFile.seekSet(myFile.curPosition() - 1);
break;
} else {
myFile.seekSet(myFile.curPosition() - 1);
}
}
}
if (myFile.curPosition() != 0)
myFile.seekSet(myFile.curPosition() + 2);
break;
}
// Selection
else if (b == 3) {
// Get name
byte myLength = 0;
for (unsigned int i = 0; i < 20; i++) {
// Stop at first "(" to remove "(Country)"
if (char(gamename[i]) == 40) {
break;
}
if (((char(gamename[i]) >= 48 && char(gamename[i]) <= 57) || (char(gamename[i]) >= 65 && char(gamename[i]) <= 90) || (char(gamename[i]) >= 97 && char(gamename[i]) <= 122)) && (myLength < 15)) {
romName[myLength] = char(gamename[i]);
myLength++;
}
}
// If name consists out of all japanese characters use CART as name
if (myLength == 0) {
romName[0] = 'C';
romName[1] = 'A';
romName[2] = 'R';
romName[3] = 'T';
romName[4] = '\0';
}
// Save Mapper
EEPROM_writeAnything(7, mapper);
EEPROM_writeAnything(8, prgsize);
EEPROM_writeAnything(9, chrsize);
EEPROM_writeAnything(10, ramsize);
myFile.close();
break;
}
}
}
#ifdef global_log
// Enable log again
dont_log = false;
#endif
} else {
print_Error(F("Database file not found"), true);
}
}
}
void readRom_NES() {
// Get name, add extension and convert to char array for sd lib
strcpy(fileName, romName);
strcat(fileName, ".nes");
// create a new folder
EEPROM_readAnything(0, foldern);
sprintf(folder, "NES/ROM/%s/%d", romName, foldern);
sd.mkdir(folder, true);
sd.chdir(folder);
display_Clear();
print_STR(saving_to_STR, 0);
print_Msg(folder);
println_Msg(F("/..."));
display_Update();
// write new folder number back to eeprom
foldern = foldern + 1;
EEPROM_writeAnything(0, foldern);
// Open file on sd card
if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
print_Error(sd_error_STR, true);
}
//Initialize progress bar
uint32_t processedProgressBar = 0;
uint32_t totalProgressBar = (uint32_t)(16 + prgsize * 16 * 1024 + chrsize * 4 * 1024);
draw_progressbar(0, totalProgressBar);
//Write iNES header
myFile.write(iNES_HEADER, 16);
// update progress bar
processedProgressBar += 16;
draw_progressbar(processedProgressBar, totalProgressBar);
//Write PRG
readPRG(true);
// update progress bar
processedProgressBar += prgsize * 16 * 1024;
draw_progressbar(processedProgressBar, totalProgressBar);
//Write CHR
readCHR(true);
// update progress bar
processedProgressBar += chrsize * 4 * 1024;
draw_progressbar(processedProgressBar, totalProgressBar);
// Close the file:
myFile.close();
// Compare CRC32 with database
compareCRC("nes.txt", 0, 1, 16);
}
void readRaw_NES() {
// Get name, add extension and convert to char array for sd lib
strcpy(fileName, romName);
strcat(fileName, ".bin");
// create a new folder
EEPROM_readAnything(0, foldern);
sprintf(folder, "NES/ROM/%s/%d", romName, foldern);
sd.mkdir(folder, true);
sd.chdir(folder);
display_Clear();
print_STR(saving_to_STR, 0);
print_Msg(folder);
println_Msg(F("/..."));
display_Update();
// write new folder number back to eeprom
foldern = foldern + 1;
EEPROM_writeAnything(0, foldern);
// Open file on sd card
if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
print_Error(sd_error_STR, true);
}
//Initialize progress bar
uint32_t processedProgressBar = 0;
uint32_t totalProgressBar = (uint32_t)(prgsize * 16 * 1024 + chrsize * 4 * 1024);
draw_progressbar(0, totalProgressBar);
//Write PRG
readPRG(true);
// update progress bar
processedProgressBar += prgsize * 16 * 1024;
draw_progressbar(processedProgressBar, totalProgressBar);
//Write CHR
readCHR(true);
// update progress bar
processedProgressBar += chrsize * 4 * 1024;
draw_progressbar(processedProgressBar, totalProgressBar);
// Close the file:
myFile.close();
// Compare CRC32 with database
compareCRC("nes.txt", 0, 0, 0);
}
#endif
/******************************************
Low Level Functions
*****************************************/
static void phi2_init() {
int i = 0x80;
unsigned char h = PORTF |= (1 << 0);
unsigned char l = PORTF &= ~(1 << 0);
while (i != 0) {
PORTL = l;
PORTL = h;
i--;
}
}
static void set_address(unsigned int address) {
unsigned char l = address & 0xFF;
unsigned char h = address >> 8;
PORTL = l;
PORTA = h;
// PPU /A13
if ((address >> 13) & 1)
PORTF &= ~(1 << 4);
else
PORTF |= 1 << 4;
}
static void set_romsel(unsigned int address) {
if (address & 0x8000) {
ROMSEL_LOW;
} else {
ROMSEL_HI;
}
}
static unsigned char read_prg_byte(unsigned int address) {
MODE_READ;
PRG_READ;
set_address(address);
PHI2_HI;
set_romsel(address);
_delay_us(1);
return PINK;
}
static unsigned char read_chr_byte(unsigned int address) {
MODE_READ;
PHI2_HI;
ROMSEL_HI;
set_address(address);
CHR_READ_LOW;
_delay_us(1);
uint8_t result = PINK;
CHR_READ_HI;
return result;
}
static void write_prg_byte(unsigned int address, uint8_t data) {
PHI2_LOW;
ROMSEL_HI;
MODE_WRITE;
PRG_WRITE;
PORTK = data;
set_address(address); // PHI2 low, ROMSEL always HIGH
// _delay_us(1);
PHI2_HI;
//_delay_us(10);
set_romsel(address); // ROMSEL is low if need, PHI2 high
_delay_us(1); // WRITING
//_delay_ms(1); // WRITING
// PHI2 low, ROMSEL high
PHI2_LOW;
_delay_us(1);
ROMSEL_HI;
// Back to read mode
// _delay_us(1);
PRG_READ;
MODE_READ;
set_address(0);
// Set phi2 to high state to keep cartridge unreseted
// _delay_us(1);
PHI2_HI;
// _delay_us(1);
}
static void write_chr_byte(unsigned int address, uint8_t data) {
PHI2_LOW;
ROMSEL_HI;
MODE_WRITE;
PORTK = data;
set_address(address); // PHI2 low, ROMSEL always HIGH
//_delay_us(10);
CHR_WRITE_LOW;
_delay_us(1); // WRITING
//_delay_ms(1); // WRITING
CHR_WRITE_HI;
//_delay_us(1);
MODE_READ;
set_address(0);
PHI2_HI;
//_delay_us(1);
}
static void write_prg(unsigned int address, unsigned int len, uint8_t* data) {
LED_RED_ON;
while (len > 0) {
write_prg_byte(address, *data);
address++;
len--;
data++;
}
//_delay_ms(1);
LED_RED_OFF;
}
static void write_chr(unsigned int address, unsigned int len, uint8_t* data) {
LED_RED_ON;
while (len > 0) {
write_chr_byte(address, *data);
address++;
len--;
data++;
}
//_delay_ms(1);
LED_RED_OFF;
}
static void reset_phi2() {
LED_RED_ON;
LED_GREEN_ON;
PHI2_LOW;
ROMSEL_HI;
_delay_ms(100);
PHI2_HI;
LED_RED_OFF;
LED_GREEN_OFF;
}
void resetROM() {
set_address(0);
PHI2_HI;
ROMSEL_HI;
}
void write_mmc1_byte(unsigned int address, uint8_t data) { // write loop for 5 bit register
if (address >= 0xE000) {
for (int i = 0; i < 5; i++) {
write_reg_byte(address, data >> i); // shift 1 bit into temp register [WRITE RAM SAFE]
}
} else {
for (int j = 0; j < 5; j++) {
write_prg_byte(address, data >> j); // shift 1 bit into temp register
}
}
}
// REFERENCE FOR REGISTER WRITE TO 0xE000/0xF000
// PORTF 7 = CPU R/W = 0
// PORTF 6 = /IRQ = 1
// PORTF 5 = PPU /RD = 1
// PORTF 4 = PPU /A13 = 1
// PORTF 3 = CIRAM /CE = 1
// PORTF 2 = PPU /WR = 1
// PORTF 1 = /ROMSEL
// PORTF 0 = PHI2 (M2)
// WRITE RAM SAFE TO REGISTERS 0xE000/0xF000
static void write_reg_byte(unsigned int address, uint8_t data) { // FIX FOR MMC1 RAM CORRUPTION
PHI2_LOW;
ROMSEL_HI; // A15 HI = E000
MODE_WRITE;
PRG_WRITE; // CPU R/W LO
PORTK = data;
set_address(address); // PHI2 low, ROMSEL always HIGH
// DIRECT PIN TO PREVENT RAM CORRUPTION
// DIFFERENCE BETWEEN M2 LO AND ROMSEL HI MUST BE AROUND 33ns
// IF TIME IS GREATER THAN 33ns THEN WRITES TO 0xE000/0xF000 WILL CORRUPT RAM AT 0x6000/0x7000
PORTF = 0b01111101; // ROMSEL LO/M2 HI
PORTF = 0b01111110; // ROMSEL HI/M2 LO
_delay_us(1);
// Back to read mode
PRG_READ;
MODE_READ;
set_address(0);
// Set phi2 to high state to keep cartridge unreseted
PHI2_HI;
}
static void write_ram_byte(unsigned int address, uint8_t data) { // Mapper 19 (Namco 106/163) WRITE RAM SAFE ($E000-$FFFF)
PHI2_LOW;
ROMSEL_HI;
MODE_WRITE;
PRG_WRITE;
PORTK = data;
set_address(address); // PHI2 low, ROMSEL always HIGH
PHI2_HI;
ROMSEL_LOW; // SET /ROMSEL LOW OTHERWISE CORRUPTS RAM
_delay_us(1); // WRITING
// PHI2 low, ROMSEL high
PHI2_LOW;
_delay_us(1);
ROMSEL_HI;
// Back to read mode
PRG_READ;
MODE_READ;
set_address(0);
// Set phi2 to high state to keep cartridge unreseted
PHI2_HI;
}
static void write_wram_byte(unsigned int address, uint8_t data) { // Mapper 5 (MMC5) RAM
PHI2_LOW;
ROMSEL_HI;
set_address(address);
PORTK = data;
_delay_us(1);
MODE_WRITE;
PRG_WRITE;
PHI2_HI;
_delay_us(1); // WRITING
PHI2_LOW;
ROMSEL_HI;
// Back to read mode
PRG_READ;
MODE_READ;
set_address(0);
// Set phi2 to high state to keep cartridge unreseted
PHI2_HI;
}
int int_pow(int base, int exp) { // Power for int
int result = 1;
while (exp) {
if (exp & 1)
result *= base;
exp /= 2;
base *= base;
}
return result;
}
/******************************************
CRC Functions
*****************************************/
FsFile crcFile;
char tempCRC[9];
uint32_t crc32(FsFile& file, uint32_t& charcnt) {
uint32_t oldcrc32 = 0xFFFFFFFF;
charcnt = 0;
while (file.available()) {
crcFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++) {
uint8_t c = sdBuffer[x];
charcnt++;
oldcrc32 = updateCRC(c, oldcrc32);
}
}
return ~oldcrc32;
}
uint32_t crc32EEP(FsFile& file, uint32_t& charcnt) {
uint32_t oldcrc32 = 0xFFFFFFFF;
charcnt = 0;
while (file.available()) {
crcFile.read(sdBuffer, 128);
for (int x = 0; x < 128; x++) {
uint8_t c = sdBuffer[x];
charcnt++;
oldcrc32 = updateCRC(c, oldcrc32);
}
}
return ~oldcrc32;
}
void calcCRC(char* checkFile, unsigned long filesize, uint32_t* crcCopy, unsigned long offset) {
uint32_t crc;
crcFile = sd.open(checkFile);
crcFile.seek(offset);
if (filesize < 1024)
crc = crc32EEP(crcFile, filesize);
else
crc = crc32(crcFile, filesize);
crcFile.close();
sprintf(tempCRC, "%08lX", crc);
if (crcCopy != NULL) {
*crcCopy = crc;
}
print_Msg(F("CRC: "));
println_Msg(tempCRC);
display_Update();
}
/******************************************
File Functions
*****************************************/
void CreateROMFolderInSD() {
sd.chdir();
sprintf(folder, "NES/ROM");
sd.mkdir(folder, true);
sd.chdir(folder);
}
void CreatePRGFileInSD() {
strcpy(fileName, "PRG");
strcat(fileName, ".bin");
for (byte i = 0; i < 100; i++) {
if (!sd.exists(fileName)) {
myFile = sd.open(fileName, O_RDWR | O_CREAT);
break;
}
sprintf(fileCount, "%02d", i);
strcpy(fileName, "PRG.");
strcat(fileName, fileCount);
strcat(fileName, ".bin");
}
if (!myFile) {
LED_RED_ON;
display_Clear();
println_Msg(F("PRG FILE FAILED!"));
display_Update();
print_Error(sd_error_STR, true);
LED_RED_OFF;
}
}
void CreateCHRFileInSD() {
strcpy(fileName, "CHR");
strcat(fileName, ".bin");
for (byte i = 0; i < 100; i++) {
if (!sd.exists(fileName)) {
myFile = sd.open(fileName, O_RDWR | O_CREAT);
break;
}
sprintf(fileCount, "%02d", i);
strcpy(fileName, "CHR.");
strcat(fileName, fileCount);
strcat(fileName, ".bin");
}
if (!myFile) {
LED_RED_ON;
display_Clear();
println_Msg(F("CHR FILE FAILED!"));
display_Update();
print_Error(sd_error_STR, true);
LED_RED_OFF;
}
}
void CreateRAMFileInSD() {
strcpy(fileName, "RAM");
strcat(fileName, ".bin");
for (byte i = 0; i < 100; i++) {
if (!sd.exists(fileName)) {
myFile = sd.open(fileName, O_RDWR | O_CREAT);
break;
}
sprintf(fileCount, "%02d", i);
strcpy(fileName, "RAM.");
strcat(fileName, fileCount);
strcat(fileName, ".bin");
}
if (!myFile) {
LED_RED_ON;
display_Clear();
println_Msg(F("RAM FILE FAILED!"));
display_Update();
print_Error(sd_error_STR, true);
LED_RED_OFF;
}
}
#ifndef nointro
void CartStart() {
sd.chdir();
EEPROM_readAnything(0, foldern); // FOLDER #
sprintf(folder, "NES/CART/%d", foldern);
sd.mkdir(folder, true);
sd.chdir(folder);
}
void CartFinish() {
foldern += 1;
EEPROM_writeAnything(0, foldern); // FOLDER #
sd.chdir();
}
#endif
/******************************************
NES 2.0 Header Functions
*****************************************/
#ifndef nointro
int32_t atoi32_signed(const char* input_string) {
if (input_string == NULL) {
return 0;
}
int int_sign = 1;
int i = 0;
if (input_string[0] == '-') {
int_sign = -1;
i = 1;
}
int32_t return_val = 0;
while (input_string[i] != '\0') {
if (input_string[i] >= '0' && input_string[i] <= '9') {
return_val = (return_val * 10) + (input_string[i] - '0');
} else if (input_string[i] != '\0') {
return 0;
}
i++;
}
return_val = return_val * int_sign;
return return_val;
}
uint32_t atoi32_unsigned(const char* input_string) {
if (input_string == NULL) {
return 0;
}
int i = 0;
uint32_t return_val = 0;
while (input_string[i] != '\0') {
if (input_string[i] >= '0' && input_string[i] <= '9') {
return_val = (return_val * 10) + (input_string[i] - '0');
} else if (input_string[i] != '\0') {
return 0;
}
i++;
}
return return_val;
}
void outputNES() {
display_Clear();
char* outputFile;
unsigned long crcOffset = 0;
uint32_t prg_size_bytes = 1024 * (uint32_t)prg;
uint32_t chr_size_bytes = 1024 * (uint32_t)chr;
int has_header = 0;
unsigned char* nes_header_bytes = getNESHeaderForFileInfo(prg_size_bytes, chr_size_bytes, prg_crc32, chr_crc32);
if (nes_header_bytes != NULL) {
has_header = 1;
}
LED_RED_ON;
LED_GREEN_ON;
LED_BLUE_ON;
if (!myFile.open(filePRG, FILE_READ)) {
LED_GREEN_OFF;
LED_BLUE_OFF;
display_Clear();
println_Msg(F("PRG FILE FAILED!"));
display_Update();
print_Error(sd_error_STR, true);
}
if (has_header) {
outputFile = fileNES;
crcOffset = 16;
} else {
outputFile = fileBIN;
}
if (!sd.exists(outputFile)) {
nesFile = sd.open(outputFile, O_RDWR | O_CREAT);
}
if (!nesFile) {
LED_GREEN_OFF;
LED_BLUE_OFF;
display_Clear();
println_Msg(F("NES FILE FAILED!"));
display_Update();
print_Error(sd_error_STR, true);
}
if (has_header) {
nesFile.write(nes_header_bytes, 16);
free(nes_header_bytes);
display_Clear();
println_Msg(F("SET HEADER"));
display_Update();
}
size_t n;
while ((n = myFile.read(sdBuffer, sizeof(sdBuffer))) > 0) {
nesFile.write(sdBuffer, n);
}
myFile.close();
if (sd.exists(fileCHR)) {
if (!myFile.open(fileCHR, FILE_READ)) {
LED_GREEN_OFF;
LED_BLUE_OFF;
display_Clear();
println_Msg(F("CHR FILE FAILED!"));
display_Update();
print_Error(sd_error_STR, true);
}
while ((n = myFile.read(sdBuffer, sizeof(sdBuffer))) > 0) {
nesFile.write(sdBuffer, n);
}
myFile.close();
}
nesFile.flush();
nesFile.close();
display_Clear();
if (has_header) {
println_Msg(F("NES FILE OUTPUT!"));
} else {
println_Msg(F("BIN FILE OUTPUT!"));
}
println_Msg(F(""));
display_Update();
calcCRC(outputFile, (prg + chr) * 1024, NULL, crcOffset);
LED_RED_OFF;
LED_GREEN_OFF;
LED_BLUE_OFF;
}
unsigned char* getNESHeaderForFileInfo(uint32_t prg_size, uint32_t chr_size, uint32_t prg_crc32, uint32_t chr_crc32) {
if (prg_size == 0) {
return NULL;
}
char* temp_line;
unsigned char* nes20_header;
int i;
if (!myFile.open("/nes20db.txt", FILE_READ)) {
return NULL;
} else {
display_Clear();
println_Msg(F("SEARCHING DB"));
display_Update();
}
temp_line = (char*)malloc(256 * sizeof(char));
while (myFile.available()) {
// We're reading fixed-length lines
// padded with null characters
myFile.read(temp_line, 256);
uint32_t prg_size_db;
uint32_t chr_size_db;
uint32_t prg_crc32_db;
uint32_t chr_crc32_db;
// Match PRG and CHR sizes first, then
// match PRG CRC32 and, if the CHR size
// is greater than zero, the CHR CRC32
// as well.
prg_size_db = getPRGSizeFromDatabaseRow(temp_line);
if (prg_size == prg_size_db) {
chr_size_db = getCHRSizeFromDatabaseRow(temp_line);
if (chr_size == chr_size_db) {
prg_crc32_db = getPRGCRC32FromDatabaseRow(temp_line);
if (prg_crc32 == prg_crc32_db) {
if (chr_size == 0) {
nes20_header = getNES20HeaderBytesFromDatabaseRow(temp_line);
free(temp_line);
myFile.close();
return nes20_header;
} else {
chr_crc32_db = getCHRCRC32FromDatabaseRow(temp_line);
if (chr_crc32 == chr_crc32_db) {
nes20_header = getNES20HeaderBytesFromDatabaseRow(temp_line);
free(temp_line);
myFile.close();
return nes20_header;
}
}
}
}
}
}
free(temp_line);
myFile.close();
return NULL;
}
// IMPORTANT: The string returned from this function MUST
// be passed to free() when ready to be disposed of, in
// order to avoid a memory leak.
char* getDatabaseFieldFromRow(const char* dbstr, uint8_t fieldnum) {
uint8_t field_start_pos = 0;
uint8_t field_end_pos = 1;
uint8_t current_field = 0;
char* return_field;
// Field order, beginning with field 0:
// PRG Size, CHR Size, PRG CRC32, CHR CRC32, Game Title, NES 2.0 Header (as ASCII)
//
// Each entry is on its own line, with a field delimeter of ^^
// I'm assuming that nothing will ever use ^^ in a game title, but it's possible
// that could be wrong, in which case a different field delimeter would need
// to be used, and the logic here updated.
if (dbstr == NULL || fieldnum > 5) {
return NULL;
}
if (dbstr[0] == 0 || dbstr[0] == '\n') {
return NULL;
}
for (; field_end_pos < 255 && current_field < fieldnum; field_end_pos++) {
if (field_start_pos < 254 && dbstr[field_start_pos] == '^' && dbstr[field_start_pos + 1] == '^') {
current_field++;
field_start_pos = field_end_pos;
field_end_pos = field_start_pos + 1;
}
if (current_field < fieldnum && dbstr[field_end_pos - 1] == '^' && dbstr[field_end_pos] == '^' || dbstr[field_end_pos] == 0 || dbstr[field_end_pos] == '\n') {
current_field++;
field_start_pos = field_end_pos + 1;
field_end_pos = field_start_pos + 1;
}
}
field_end_pos = field_start_pos;
while ((dbstr[field_end_pos - 1] != '^' || dbstr[field_end_pos] != '^') && dbstr[field_end_pos] != 0 && dbstr[field_end_pos] != '\n') {
field_end_pos++;
}
if (dbstr[field_end_pos] == '^') {
field_end_pos = field_end_pos - 2;
} else {
field_end_pos = field_end_pos - 1;
}
if ((field_end_pos - field_start_pos + 2) == 0) {
return NULL;
}
return_field = (char*)malloc((field_end_pos - field_start_pos + 2) * sizeof(char));
memcpy(return_field, &dbstr[field_start_pos], field_end_pos - field_start_pos + 1);
return_field[(field_end_pos - field_start_pos) + 1] = 0;
return return_field;
}
unsigned char getNibbleFromChar(char num) {
char ret_char = num & 0x0F;
if (num > '9') {
ret_char += 9;
}
return ret_char;
}
unsigned char getByteFromChars(char msn, char lsn) {
unsigned char return_char;
return_char = (getNibbleFromChar(msn) << 4);
return_char |= getNibbleFromChar(lsn);
return return_char;
}
// IMPORTANT: The byte array returned from this function MUST
// be passed to free() when ready to be disposed of, in
// order to avoid a memory leak.
unsigned char* strToBytes(const char* bytestr) {
uint8_t str_length;
uint8_t byte_length;
uint8_t str_idx;
uint8_t byte_idx = 0;
unsigned char* byte_arr;
if (bytestr == NULL) {
return NULL;
}
str_length = (uint8_t)strlen(bytestr);
if (str_length % 2 != 0) {
return NULL;
}
byte_length = str_length / 2;
byte_arr = (unsigned char*)malloc(byte_length * sizeof(unsigned char));
for (str_idx = 0; str_idx < str_length && bytestr[str_idx] != 0; str_idx = str_idx + 2) {
if (!isxdigit(bytestr[str_idx]) || !isxdigit(bytestr[str_idx + 1])) {
free(byte_arr);
return NULL;
}
byte_arr[byte_idx] = getByteFromChars(bytestr[str_idx], bytestr[str_idx + 1]);
byte_idx++;
}
return byte_arr;
}
uint32_t crc32FromBytes(const unsigned char* bytearr) {
if (bytearr == NULL) {
return 0;
}
return (uint32_t)(((uint32_t)bytearr[0] << 24) | ((uint32_t)bytearr[1] << 16) | ((uint32_t)bytearr[2] << 8) | (uint32_t)bytearr[3]);
}
uint32_t getPRGSizeFromDatabaseRow(const char* crctest) {
char* prg_size_str = getDatabaseFieldFromRow(crctest, 0);
if (prg_size_str == NULL) {
return 0;
}
uint32_t return_size = atoi32_unsigned(prg_size_str);
free(prg_size_str);
return return_size;
}
uint32_t getCHRSizeFromDatabaseRow(const char* crctest) {
char* chr_size_str = getDatabaseFieldFromRow(crctest, 1);
if (chr_size_str == NULL) {
return 0;
}
uint32_t return_size = atoi32_unsigned(chr_size_str);
free(chr_size_str);
return return_size;
}
uint32_t getPRGCRC32FromDatabaseRow(const char* crctest) {
char* prg_crc32_str = getDatabaseFieldFromRow(crctest, 2);
if (prg_crc32_str == NULL) {
return 0;
}
unsigned char* prg_crc32_bytes = strToBytes(prg_crc32_str);
free(prg_crc32_str);
if (prg_crc32_bytes == NULL) {
return 0;
}
uint32_t prg_crc32 = crc32FromBytes(prg_crc32_bytes);
free(prg_crc32_bytes);
return prg_crc32;
}
uint64_t getCHRCRC32FromDatabaseRow(const char* crctest) {
char* chr_crc32_str = getDatabaseFieldFromRow(crctest, 3);
if (chr_crc32_str == NULL) {
return 0;
}
unsigned char* chr_crc32_bytes = strToBytes(chr_crc32_str);
free(chr_crc32_str);
if (chr_crc32_bytes == NULL) {
return 0;
}
uint32_t chr_crc32 = crc32FromBytes(chr_crc32_bytes);
free(chr_crc32_bytes);
return chr_crc32;
}
// IMPORTANT: As with getDatabaseFieldFromRow(), the string
// returned from this function must be passed to free() after
// it's no longer needed in order to avoid a memory leak.
char* getGameTitleFromDatabaseRow(const char* crctest) {
char* game_title_str = getDatabaseFieldFromRow(crctest, 4);
return game_title_str;
}
// IMPORTANT: The byte array returned from this function MUST
// be passed to free() when ready to be disposed of, in
// order to avoid a memory leak.
unsigned char* getNES20HeaderBytesFromDatabaseRow(const char* crctest) {
char* nes_header_str = getDatabaseFieldFromRow(crctest, 5);
if (nes_header_str == NULL) {
return NULL;
}
unsigned char* nes_header_bytes = strToBytes(nes_header_str);
free(nes_header_str);
if (nes_header_bytes == NULL) {
return NULL;
}
return nes_header_bytes;
}
#endif
/******************************************
Config Functions
*****************************************/
void setMapper() {
#ifdef global_log
// Disable log to prevent unnecessary logging
println_Log(F("Set Mapper manually"));
dont_log = true;
#endif
// OLED
#if defined(enable_OLED)
chooseMapper:
// Read stored mapper
EEPROM_readAnything(7, newmapper);
if ((newmapper > 220) || (newmapper < 0))
newmapper = 0;
// Split into digits
byte hundreds = newmapper / 100;
byte tens = newmapper / 10 - hundreds * 10;
byte units = newmapper - hundreds * 100 - tens * 10;
// Cycle through all 3 digits
byte digit = 0;
while (digit < 3) {
display_Clear();
println_Msg(F("Select Mapper:"));
display.setCursor(23, 20);
println_Msg(hundreds);
display.setCursor(43, 20);
println_Msg(tens);
display.setCursor(63, 20);
println_Msg(units);
println_Msg("");
println_Msg("");
println_Msg("");
print_STR(press_to_change_STR, 1);
println_Msg(F("Press right to select"));
if (digit == 0) {
display.setDrawColor(1);
display.drawLine(20, 30, 30, 30);
display.setDrawColor(0);
display.drawLine(40, 30, 50, 30);
display.drawLine(60, 30, 70, 30);
display.setDrawColor(1);
} else if (digit == 1) {
display.setDrawColor(0);
display.drawLine(20, 30, 30, 30);
display.setDrawColor(1);
display.drawLine(40, 30, 50, 30);
display.setDrawColor(0);
display.drawLine(60, 30, 70, 30);
display.setDrawColor(1);
} else if (digit == 2) {
display.setDrawColor(0);
display.drawLine(20, 30, 30, 30);
display.drawLine(40, 30, 50, 30);
display.setDrawColor(1);
display.drawLine(60, 30, 70, 30);
}
display.updateDisplay();
while (1) {
/* Check Button
1 click
2 doubleClick
3 hold
4 longHold */
int b = checkButton();
if (b == 1) {
if (digit == 0) {
if (hundreds < 2)
hundreds++;
else
hundreds = 0;
} else if (digit == 1) {
if (hundreds == 2) {
if (tens < 1)
tens++;
else
tens = 0;
} else {
if (tens < 9)
tens++;
else
tens = 0;
}
} else if (digit == 2) {
if (units < 9)
units++;
else
units = 0;
}
break;
} else if (b == 2) {
if (digit == 0) {
if (hundreds > 0)
hundreds--;
else
hundreds = 2;
} else if (digit == 1) {
if (hundreds == 2) {
if (tens > 0)
tens--;
else
tens = 1;
} else {
if (tens > 0)
tens--;
else
tens = 9;
}
} else if (digit == 2) {
if (units > 0)
units--;
else
units = 9;
}
break;
} else if (b == 3) {
digit++;
break;
}
}
}
display_Clear();
newmapper = hundreds * 100 + tens * 10 + units;
// Check if valid
boolean validMapper = 0;
byte mapcount = (sizeof(mapsize) / sizeof(mapsize[0])) / 7;
for (byte currMaplist = 0; currMaplist < mapcount; currMaplist++) {
if (pgm_read_byte(mapsize + currMaplist * 7) == newmapper)
validMapper = 1;
}
if (!validMapper) {
errorLvl = 1;
display.println(F("Mapper not supported"));
display.updateDisplay();
wait();
goto chooseMapper;
}
// LCD
#elif defined(enable_LCD)
int i = 0;
display_Clear();
mapselect = pgm_read_byte(mapsize + i * 7);
print_Msg(F("Mapper: "));
println_Msg(mapselect);
println_Msg(F(""));
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
display_Update();
while (1) {
int b = checkButton();
if (b == 2) { // Previous Mapper
if (i == 0)
i = mapcount - 1;
else
i--;
display_Clear();
mapselect = pgm_read_byte(mapsize + i * 7);
print_Msg(F("Mapper: "));
println_Msg(mapselect);
println_Msg(F(""));
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
display_Update();
}
else if (b == 1) { // Next Mapper
if (i == (mapcount - 1))
i = 0;
else
i++;
display_Clear();
mapselect = pgm_read_byte(mapsize + i * 7);
print_Msg(F("Mapper: "));
println_Msg(mapselect);
println_Msg(F(""));
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
display_Update();
}
else if (b == 3) { // Long Press - Execute
newmapper = mapselect;
break;
}
}
display.setCursor(0, 56 + 8);
print_Msg(F("MAPPER "));
print_Msg(newmapper);
println_Msg(F(" SELECTED"));
display_Update();
delay(1000);
// Serial Monitor
#elif defined(enable_serial)
setmapper:
String newmap;
mapfound = false;
Serial.println(F("SUPPORTED MAPPERS:"));
for (int i = 0; i < mapcount; i++) {
int index = i * 7;
mapselect = pgm_read_byte(mapsize + index);
Serial.print("[");
Serial.print(mapselect);
Serial.print("]");
if (i < mapcount - 1) {
if ((i != 0) && ((i + 1) % 10 == 0))
Serial.println(F(""));
else
Serial.print(F("\t"));
} else
Serial.println(F(""));
}
Serial.print(F("Enter Mapper: "));
while (Serial.available() == 0) {}
newmap = Serial.readStringUntil('\n');
Serial.println(newmap);
newmapper = newmap.toInt();
for (int i = 0; i < mapcount; i++) {
int index = i * 7;
mapselect = pgm_read_byte(mapsize + index);
if (newmapper == mapselect)
mapfound = true;
}
if (mapfound == false) {
Serial.println(F("MAPPER NOT SUPPORTED!"));
Serial.println(F(""));
newmapper = 0;
goto setmapper;
}
#endif
EEPROM_writeAnything(7, newmapper);
mapper = newmapper;
#ifdef global_log
// Enable log again
dont_log = false;
#endif
}
void checkMapperSize() {
for (int i = 0; i < mapcount; i++) {
int index = i * 7;
byte mapcheck = pgm_read_byte(mapsize + index);
if (mapcheck == mapper) {
prglo = pgm_read_byte(mapsize + index + 1);
prghi = pgm_read_byte(mapsize + index + 2);
chrlo = pgm_read_byte(mapsize + index + 3);
chrhi = pgm_read_byte(mapsize + index + 4);
ramlo = pgm_read_byte(mapsize + index + 5);
ramhi = pgm_read_byte(mapsize + index + 6);
break;
}
}
}
void setPRGSize() {
#ifdef global_log
// Disable log to prevent unnecessary logging
println_Log(F("Set PRG Size"));
dont_log = true;
#endif
#if (defined(enable_LCD) || defined(enable_OLED))
display_Clear();
if (prglo == prghi)
newprgsize = prglo;
else {
b = 0;
int i = prglo;
display_Clear();
print_Msg(F("PRG Size: "));
println_Msg(PRG[i]);
println_Msg(F(""));
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
println_Msg(F("Press right to select"));
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#endif
display_Update();
while (1) {
b = checkButton();
if (b == doubleclick) { // Previous
if (i == prglo)
i = prghi;
else
i--;
display_Clear();
print_Msg(F("PRG Size: "));
println_Msg(PRG[i]);
println_Msg(F(""));
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
println_Msg(F("Press right to select"));
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#endif
display_Update();
}
if (b == press) { // Next
if (i == prghi)
i = prglo;
else
i++;
display_Clear();
print_Msg(F("PRG Size: "));
println_Msg(PRG[i]);
println_Msg(F(""));
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
println_Msg(F("Press right to select"));
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#endif
display_Update();
}
if (b == hold) { // Long Press - Execute
newprgsize = i;
break;
}
}
display.setCursor(0, 56); // Display selection at bottom
}
print_Msg(F("PRG SIZE "));
print_Msg(PRG[newprgsize]);
println_Msg(F("K"));
display_Update();
delay(1000);
#elif defined(enable_serial)
if (prglo == prghi)
newprgsize = prglo;
else {
setprg:
String sizePRG;
for (int i = 0; i < (prghi - prglo + 1); i++) {
Serial.print(F("Select PRG Size: "));
Serial.print(i);
Serial.print(F(" = "));
Serial.print(PRG[i + prglo]);
Serial.println(F("K"));
}
Serial.print(F("Enter PRG Size: "));
while (Serial.available() == 0) {}
sizePRG = Serial.readStringUntil('\n');
Serial.println(sizePRG);
newprgsize = sizePRG.toInt() + prglo;
if (newprgsize > prghi) {
Serial.println(F("SIZE NOT SUPPORTED"));
Serial.println(F(""));
goto setprg;
}
}
Serial.print(F("PRG Size = "));
Serial.print(PRG[newprgsize]);
Serial.println(F("K"));
#endif
EEPROM_writeAnything(8, newprgsize);
prgsize = newprgsize;
#ifdef global_log
// Enable log again
dont_log = false;
#endif
}
void setCHRSize() {
#ifdef global_log
// Disable log to prevent unnecessary logging
println_Log(F("Set CHR Size"));
dont_log = true;
#endif
#if (defined(enable_LCD) || defined(enable_OLED))
display_Clear();
if (chrlo == chrhi)
newchrsize = chrlo;
else {
b = 0;
int i = chrlo;
display_Clear();
print_Msg(F("CHR Size: "));
println_Msg(CHR[i]);
println_Msg(F(""));
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
println_Msg(F("Press right to select"));
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#endif
display_Update();
while (1) {
b = checkButton();
if (b == doubleclick) { // Previous
if (i == chrlo)
i = chrhi;
else
i--;
display_Clear();
print_Msg(F("CHR Size: "));
println_Msg(CHR[i]);
println_Msg(F(""));
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
println_Msg(F("Press right to select"));
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#endif
display_Update();
}
if (b == press) { // Next
if (i == chrhi)
i = chrlo;
else
i++;
display_Clear();
print_Msg(F("CHR Size: "));
println_Msg(CHR[i]);
println_Msg(F(""));
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
println_Msg(F("Press right to select"));
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#endif
display_Update();
}
if (b == hold) { // Long Press - Execute
newchrsize = i;
break;
}
}
display.setCursor(0, 56); // Display selection at bottom
}
print_Msg(F("CHR SIZE "));
print_Msg(CHR[newchrsize]);
println_Msg(F("K"));
display_Update();
delay(1000);
#elif defined(enable_serial)
if (chrlo == chrhi)
newchrsize = chrlo;
else {
setchr:
String sizeCHR;
for (int i = 0; i < (chrhi - chrlo + 1); i++) {
Serial.print(F("Select CHR Size: "));
Serial.print(i);
Serial.print(F(" = "));
Serial.print(CHR[i + chrlo]);
Serial.println(F("K"));
}
Serial.print(F("Enter CHR Size: "));
while (Serial.available() == 0) {}
sizeCHR = Serial.readStringUntil('\n');
Serial.println(sizeCHR);
newchrsize = sizeCHR.toInt() + chrlo;
if (newchrsize > chrhi) {
Serial.println(F("SIZE NOT SUPPORTED"));
Serial.println(F(""));
goto setchr;
}
}
Serial.print(F("CHR Size = "));
Serial.print(CHR[newchrsize]);
Serial.println(F("K"));
#endif
EEPROM_writeAnything(9, newchrsize);
chrsize = newchrsize;
#ifdef global_log
// Enable log again
dont_log = false;
#endif
}
void setRAMSize() {
#ifdef global_log
// Disable log to prevent unnecessary logging
println_Log(F("Set RAM Size"));
dont_log = true;
#endif
#if (defined(enable_LCD) || defined(enable_OLED))
display_Clear();
if (ramlo == ramhi)
newramsize = ramlo;
else {
b = 0;
int i = 0;
display_Clear();
print_Msg(F("RAM Size: "));
if (mapper == 0)
println_Msg(RAM[i] / 4);
else if (mapper == 16)
println_Msg(RAM[i] * 32);
else if (mapper == 19) {
if (i == 2)
println_Msg(F("128"));
else
println_Msg(RAM[i]);
} else if ((mapper == 159) || (mapper == 80))
println_Msg(RAM[i] * 16);
else if (mapper == 82)
println_Msg(i * 5);
else
println_Msg(RAM[i]);
println_Msg(F(""));
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
println_Msg(F("Press right to select"));
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#endif
display_Update();
while (1) {
b = checkButton();
if (b == doubleclick) { // Previous Mapper
if (i == 0)
i = ramhi;
else
i--;
display_Clear();
print_Msg(F("RAM Size: "));
if (mapper == 0)
println_Msg(RAM[i] / 4);
else if (mapper == 16)
println_Msg(RAM[i] * 32);
else if (mapper == 19) {
if (i == 2)
println_Msg(F("128"));
else
println_Msg(RAM[i]);
} else if ((mapper == 159) || (mapper == 80))
println_Msg(RAM[i] * 16);
else if (mapper == 82)
println_Msg(i * 5);
else
println_Msg(RAM[i]);
println_Msg(F(""));
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
println_Msg(F("Press right to select"));
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#endif
display_Update();
}
if (b == press) { // Next
if (i == ramhi)
i = 0;
else
i++;
display_Clear();
print_Msg(F("RAM Size: "));
if (mapper == 0)
println_Msg(RAM[i] / 4);
else if (mapper == 16)
println_Msg(RAM[i] * 32);
else if (mapper == 19) {
if (i == 2)
println_Msg(F("128"));
else
println_Msg(RAM[i]);
} else if ((mapper == 159) || (mapper == 80))
println_Msg(RAM[i] * 16);
else if (mapper == 82)
println_Msg(i * 5);
else
println_Msg(RAM[i]);
println_Msg(F(""));
#if defined(enable_OLED)
print_STR(press_to_change_STR, 1);
println_Msg(F("Press right to select"));
#elif defined(enable_LCD)
print_STR(rotate_to_change_STR, 1);
print_STR(press_to_select_STR, 1);
#endif
display_Update();
}
if (b == hold) { // Long Press - Execute
newramsize = i;
break;
}
}
display.setCursor(0, 56); // Display selection at bottom
}
if ((mapper == 16) || (mapper == 159)) {
int sizeEEP = 0;
print_Msg(F("EEPROM SIZE "));
if (mapper == 16)
sizeEEP = RAM[newramsize] * 32;
else
sizeEEP = RAM[newramsize] * 16;
print_Msg(sizeEEP);
println_Msg(F("B"));
} else if (mapper == 19) {
print_Msg(F("RAM SIZE "));
if (newramsize == 2)
println_Msg(F("128B"));
else {
print_Msg(RAM[newramsize]);
println_Msg(F("K"));
}
} else if (mapper == 80) {
print_Msg(F("RAM SIZE "));
print_Msg(RAM[newramsize] * 16);
println_Msg(F("B"));
} else {
print_Msg(F("RAM SIZE "));
if (mapper == 0)
print_Msg(newramsize * 2);
else if (mapper == 82)
print_Msg(newramsize * 5);
else
print_Msg(RAM[newramsize]);
println_Msg(F("K"));
}
display_Update();
delay(1000);
#elif defined(enable_serial)
if (ramlo == ramhi)
newramsize = ramlo;
else {
setram:
String sizeRAM;
for (int i = 0; i < (ramhi - ramlo + 1); i++) {
Serial.print(F("Select RAM Size: "));
Serial.print(i);
Serial.print(F(" = "));
if (mapper == 0) {
Serial.print(RAM[i] / 4);
Serial.println(F("K"));
} else if ((mapper == 16) || (mapper == 159)) {
if (mapper == 16)
Serial.print(RAM[i + ramlo] * 32);
else
Serial.print(RAM[i + ramlo] * 16);
Serial.println(F("B"));
} else if (mapper == 19) {
if (i == 2)
Serial.println(F("128B"));
else {
Serial.print(RAM[i + ramlo]);
Serial.println(F("K"));
}
} else {
Serial.print(RAM[i + ramlo]);
Serial.println(F("K"));
}
}
Serial.print(F("Enter RAM Size: "));
while (Serial.available() == 0) {}
sizeRAM = Serial.readStringUntil('\n');
Serial.println(sizeRAM);
newramsize = sizeRAM.toInt() + ramlo;
if (newramsize > ramhi) {
Serial.println(F("SIZE NOT SUPPORTED"));
Serial.println(F(""));
goto setram;
}
}
if ((mapper == 16) || (mapper == 159)) {
int sizeEEP = 0;
Serial.print(F("EEPROM Size = "));
if (mapper == 16)
sizeEEP = RAM[newramsize] * 32;
else
sizeEEP = RAM[newramsize] * 16;
Serial.print(sizeEEP);
Serial.println(F("B"));
Serial.println(F(""));
} else if (mapper == 19) {
Serial.print(F("RAM Size = "));
if (newramsize == 2)
Serial.println(F("128B"));
else {
Serial.print(RAM[newramsize]);
Serial.println(F("K"));
}
Serial.println(F(""));
} else if (mapper == 80) {
Serial.print(F("RAM Size = "));
Serial.print(RAM[newramsize] * 16);
Serial.println(F("B"));
Serial.println(F(""));
} else {
Serial.print(F("RAM Size = "));
if (mapper == 0)
Serial.print(newramsize * 2);
else if (mapper == 82)
Serial.print(newramsize * 5);
else
Serial.print(RAM[newramsize]);
Serial.println(F("K"));
Serial.println(F(""));
}
#endif
EEPROM_writeAnything(10, newramsize);
ramsize = newramsize;
#ifdef global_log
// Enable log again
dont_log = false;
#endif
}
// MMC6 Detection
// Mapper 4 includes both MMC3 AND MMC6
// RAM is mapped differently between MMC3 and MMC6
void checkMMC6() { // Detect MMC6 Carts - read PRG 0x3E00A ("STARTROPICS")
write_prg_byte(0x8000, 6); // PRG Bank 0 ($8000-$9FFF)
write_prg_byte(0x8001, 0x1F); // 0x3E000
prgchk0 = read_prg_byte(0x800A);
prgchk1 = read_prg_byte(0x800B);
prgchk2 = read_prg_byte(0x800C);
prgchk3 = read_prg_byte(0x800D);
if ((prgchk0 == 0x53) && (prgchk1 == 0x54) && (prgchk2 == 0x41) && (prgchk3 == 0x52))
mmc6 = true; // MMC6 Cart
}
void checkStatus_NES() {
EEPROM_readAnything(7, mapper);
EEPROM_readAnything(8, prgsize);
EEPROM_readAnything(9, chrsize);
EEPROM_readAnything(10, ramsize);
prg = (int_pow(2, prgsize)) * 16;
if (chrsize == 0)
chr = 0; // 0K
else
chr = (int_pow(2, chrsize)) * 4;
if (ramsize == 0)
ram = 0; // 0K
else if (mapper == 82)
ram = 5; // 5K
else
ram = (int_pow(2, ramsize)) * 4;
// Mapper Variants
// Identify variant for use across multiple functions
if (mapper == 4) { // Check for MMC6/MMC3
checkMMC6();
if (mmc6)
ram = 1; // 1K
} else if (mapper == 30) // Check for Flashable/Non-Flashable
NESmaker_ID(); // Flash ID
display_Clear();
println_Msg(F("NES CART READER"));
println_Msg(F(""));
println_Msg(F("CURRENT SETTINGS"));
println_Msg(F(""));
print_Msg(F("MAPPER: "));
println_Msg(mapper);
print_Msg(F("PRG SIZE: "));
print_Msg(prg);
println_Msg(F("K"));
print_Msg(F("CHR SIZE: "));
print_Msg(chr);
println_Msg(F("K"));
print_Msg(F("RAM SIZE: "));
if (mapper == 0) {
print_Msg(ram / 4);
println_Msg(F("K"));
} else if ((mapper == 16) || (mapper == 80) || (mapper == 159)) {
if (mapper == 16)
print_Msg(ram * 32);
else
print_Msg(ram * 16);
println_Msg(F("B"));
} else if (mapper == 19) {
if (ramsize == 2)
println_Msg(F("128B"));
else {
print_Msg(ram);
println_Msg(F("K"));
}
} else {
print_Msg(ram);
println_Msg(F("K"));
}
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
}
/******************************************
ROM Functions
*****************************************/
void dumpPRG(word base, word address) {
for (int x = 0; x < 512; x++) {
sdBuffer[x] = read_prg_byte(base + address + x);
}
myFile.write(sdBuffer, 512);
}
void dumpCHR(word address) {
for (int x = 0; x < 512; x++) {
sdBuffer[x] = read_chr_byte(address + x);
}
myFile.write(sdBuffer, 512);
}
void dumpCHR_M2(word address) { // MAPPER 45 - PULSE M2 LO/HI
for (int x = 0; x < 512; x++) {
PHI2_LOW;
sdBuffer[x] = read_chr_byte(address + x);
}
myFile.write(sdBuffer, 512);
}
void dumpMMC5RAM(word base, word address) { // MMC5 SRAM DUMP - PULSE M2 LO/HI
for (int x = 0; x < 512; x++) {
PHI2_LOW;
sdBuffer[x] = read_prg_byte(base + address + x);
}
myFile.write(sdBuffer, 512);
}
void writeMMC5RAM(word base, word address) { // MMC5 SRAM WRITE
myFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++) {
do {
write_prg_byte(0x5102, 2); // PRG RAM PROTECT1
write_prg_byte(0x5103, 1); // PRG RAM PROTECT2
write_wram_byte(base + address + x, sdBuffer[x]);
bytecheck = read_prg_byte(base + address + x);
} while (bytecheck != sdBuffer[x]); // CHECK WRITTEN BYTE
}
write_prg_byte(0x5102, 0); // PRG RAM PROTECT1
write_prg_byte(0x5103, 0); // PRG RAM PROTECT2
}
void readPRG(boolean readrom) {
if (!readrom) {
display_Clear();
display_Update();
LED_BLUE_ON;
set_address(0);
_delay_us(1);
CreatePRGFileInSD();
} else {
set_address(0);
_delay_us(1);
}
word base = 0x8000;
if (myFile) {
switch (mapper) {
case 0:
case 3:
case 13:
case 87: // 16K/32K
case 184: // 32K
case 185: // 16K/32K
for (word address = 0; address < ((prgsize * 0x4000) + 0x4000); address += 512) { // 16K or 32K
dumpPRG(base, address);
}
break;
case 1:
case 155: // 32K/64K/128K/256K/512K
banks = int_pow(2, prgsize) - 1;
for (int i = 0; i < banks; i++) { // 16K Banks ($8000-$BFFF)
write_prg_byte(0x8000, 0x80); // Clear Register
write_mmc1_byte(0x8000, 0x0C); // Switch 16K Bank ($8000-$BFFF) + Fixed Last Bank ($C000-$FFFF)
if (prgsize > 4) // 512K
write_mmc1_byte(0xA000, 0x00); // Reset 512K Flag for Lower 256K
if (i > 15) // Switch Upper 256K
write_mmc1_byte(0xA000, 0x10); // Set 512K Flag
write_mmc1_byte(0xE000, i);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
for (word address = 0x4000; address < 0x8000; address += 512) { // Final Bank ($C000-$FFFF)
dumpPRG(base, address);
}
break;
case 2: // 128K/256K
for (int i = 0; i < 8; i++) { // 128K/256K
write_prg_byte(0x8000, i);
for (word address = 0x0; address < (((prgsize - 3) * 0x4000) + 0x4000); address += 512) {
dumpPRG(base, address);
}
}
break;
case 4:
case 47:
case 64:
case 118:
case 119:
case 158:
banks = ((int_pow(2, prgsize) * 2)) - 2; // Set Number of Banks
if (mapper == 47)
write_prg_byte(0xA001, 0x80); // Block Register - PRG RAM Chip Enable, Writable
for (int i = 0; i < banks; i += 2) { // 32K/64K/128K/256K/512K
if (mapper == 47) {
if (i == 0)
write_prg_byte(0x6000, 0); // Switch to Lower Block
else if (i == 16)
write_prg_byte(0x6000, 1); // Switch to Upper Block
}
write_prg_byte(0x8000, 6); // PRG Bank 0 ($8000-$9FFF)
write_prg_byte(0x8001, i);
write_prg_byte(0x8000, 7); // PRG Bank 1 ($A000-$BFFF)
write_prg_byte(0x8001, i + 1);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
if ((mapper == 64) || (mapper == 158)) {
write_prg_byte(0x8000, 15); // PRG Bank 2 ($C000-$DFFF)
write_prg_byte(0x8001, banks);
}
for (word address = 0x4000; address < 0x8000; address += 512) { // Final 2 Banks ($C000-$FFFF)
dumpPRG(base, address);
}
break;
case 5: // 128K/256K/512K
banks = int_pow(2, prgsize) * 2;
write_prg_byte(0x5100, 3); // 8K PRG Banks
for (int i = 0; i < banks; i += 2) { // 128K/256K/512K
write_prg_byte(0x5114, i | 0x80);
write_prg_byte(0x5115, (i + 1) | 0x80);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 7: // 128K/256K
case 34:
case 77:
case 96: // 128K
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i++) { // 32K Banks
write_prg_byte(0x8000, i);
for (word address = 0x0; address < 0x8000; address += 512) { // 32K Banks ($8000-$FFFF)
dumpPRG(base, address);
}
}
break;
case 9: // 128K
for (int i = 0; i < 13; i++) { // 16-3 = 13 = 128K
write_prg_byte(0xA000, i); // $8000-$9FFF
for (word address = 0x0; address < 0x2000; address += 512) { // Switch Bank ($8000-$9FFF)
dumpPRG(base, address);
}
}
for (word address = 0x2000; address < 0x8000; address += 512) { // Final 3 Banks ($A000-$FFFF)
dumpPRG(base, address);
}
break;
case 10: // 128K/256K
for (int i = 0; i < (((prgsize - 3) * 8) + 7); i++) {
write_prg_byte(0xA000, i); // $8000-$BFFF
for (word address = 0x0; address < 0x4000; address += 512) { // Switch Bank ($8000-$BFFF)
dumpPRG(base, address);
}
}
for (word address = 0x4000; address < 0x8000; address += 512) { // Final Bank ($C000-$FFFF)
dumpPRG(base, address);
}
break;
case 11:
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0xFFB0 + i, i);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 15:
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i += 2) {
write_prg_byte(0x8000, i);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 16:
case 159: // 128K/256K
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) {
write_prg_byte(0x6008, i); // Submapper 4
write_prg_byte(0x8008, i); // Submapper 5
for (word address = 0x0; address < 0x4000; address += 512) { // 16K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 18: // 128K/256K
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i += 2) {
write_prg_byte(0x8000, i & 0xF);
write_prg_byte(0x8001, (i >> 4) & 0xF);
write_prg_byte(0x8002, (i + 1) & 0xF);
write_prg_byte(0x8003, ((i + 1) >> 4) & 0xF);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 19: // 128K/256K
for (int j = 0; j < 64; j++) { // Init Register
write_ram_byte(0xE000, 0); // PRG Bank 0 ($8000-$9FFF)
}
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i++) {
write_ram_byte(0xE000, i); // PRG Bank 0 ($8000-$9FFF)
for (word address = 0x0; address < 0x2000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 21: // 256K
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0xA000, i);
for (word address = 0x2000; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 22:
case 23:
case 25:
case 65:
case 75: // 128K/256K
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i += 2) {
write_prg_byte(0x8000, i);
write_prg_byte(0xA000, i + 1);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 24:
case 26: // 256K
case 78: // 128K
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) { // 128K
write_prg_byte(0x8000, i);
for (word address = 0x0; address < 0x4000; address += 512) { // 16K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 30: // 256K/512K
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) { // 256K/512K
if (flashfound)
write_prg_byte(0xC000 + i, i); // Flashable
else
write_prg_byte(0x8000 + i, i); // Non-Flashable
for (word address = 0x0; address < 0x4000; address += 512) { // 16K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 32: // 128K/256K
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i++) { // 128K/256K
write_prg_byte(0x9000, 1); // PRG Mode 0 - Read $A000-$BFFF to avoid difference between Modes 0 and 1
write_prg_byte(0xA000, i); // PRG Bank
for (word address = 0x2000; address < 0x4000; address += 512) { // 8K Banks ($A000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 33:
case 48: // 128K/256K
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i += 2) {
write_prg_byte(0x8000, i); // PRG Bank 0 ($8000-$9FFF)
write_prg_byte(0x8001, i + 1); // PRG Bank 1 ($A000-$BFFF)
for (word address = 0x0; address < 0x4000; address += 512) { // 8K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 37:
banks = ((int_pow(2, prgsize) * 2)) - 2; // Set Number of Banks
write_prg_byte(0xA001, 0x80); // Block Register - PRG RAM Chip Enable, Writable
for (int i = 0; i < banks; i += 2) { // 256K
if (i == 0)
write_prg_byte(0x6000, 0); // Switch to Lower Block ($0000-$FFFF)
else if (i == 8)
write_prg_byte(0x6000, 3); // Switch to 2nd 64K Block ($10000-$1FFFF)
else if (i == 16)
write_prg_byte(0x6000, 4); // Switch to 128K Block ($20000-$3FFFF)
write_prg_byte(0x8000, 6); // PRG Bank 0 ($8000-$9FFF)
write_prg_byte(0x8001, i);
write_prg_byte(0x8000, 7); // PRG Bank 1 ($A000-$BFFF)
write_prg_byte(0x8001, i + 1);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
for (word address = 0x4000; address < 0x8000; address += 512) { // Final 2 Banks ($C000-$FFFF)
dumpPRG(base, address);
}
break;
case 45: // MMC3 Clone with Outer Registers
banks = ((int_pow(2, prgsize) * 2)) - 2; // Set Number of Banks
for (int i = 0; i < banks; i += 2) { // 128K/256K/512K/1024K
// set outer bank registers
write_prg_byte(0x6000, 0x00); // CHR-OR
write_prg_byte(0x6000, (i & 0xC0)); // PRG-OR
write_prg_byte(0x6000, ((i >> 2) & 0xC0)); // CHR-AND,CHR-OR/PRG-OR
write_prg_byte(0x6000, 0x80); // PRG-AND
// set inner bank registers
write_prg_byte(0x8000, 6); // PRG Bank 0 ($8000-$9FFF)
write_prg_byte(0x8001, i);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpPRG(base, address);
}
// set outer bank registers
write_prg_byte(0x6000, 0x00); // CHR-OR
write_prg_byte(0x6000, ((i + 1) & 0xC0)); // PRG-OR
write_prg_byte(0x6000, (((i + 1) >> 2) & 0xC0)); // CHR-AND,CHR-OR/PRG-OR
write_prg_byte(0x6000, 0x80); // PRG-AND
// set inner bank registers
write_prg_byte(0x8000, 7); // PRG Bank 1 ($A000-$BFFF)
write_prg_byte(0x8001, i + 1);
for (word address = 0x2000; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
for (word address = 0x4000; address < 0x8000; address += 512) { // Final 2 Banks ($C000-$FFFF)
dumpPRG(base, address);
}
break;
case 62:
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 + (i * 512) + ((i & 32) << 1), 0x00);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 58:
case 213:
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 + (i & 0x07), 0x00);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 60:
for (word address = 0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
for (int i = 0; i < 3; i++) {
write_prg_byte(0x8D8D, i);
delay(500);
for (word address = 0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 66: // 64K/128K
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i++) { // 64K/128K
write_prg_byte(0x8000, i << 4); // bits 4-5
for (word address = 0x0; address < 0x8000; address += 512) { // 32K Banks ($8000-$FFFF)
dumpPRG(base, address);
}
}
break;
case 67: // 128K
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) { // 128K
write_reg_byte(0xF800, i); // [WRITE RAM SAFE]
for (word address = 0x0; address < 0x4000; address += 512) { // 16K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 68:
case 73: // 128K
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) { // 128K
write_reg_byte(0xF000, i); // [WRITE RAM SAFE]
for (word address = 0x0; address < 0x4000; address += 512) { // 16K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 69: // 128K/256K
banks = int_pow(2, prgsize) * 2;
write_prg_byte(0x8000, 8); // Command Register - PRG Bank 0
write_prg_byte(0xA000, 0); // Parameter Register - PRG RAM Disabled, PRG ROM, Bank 0 to $6000-$7FFF
for (int i = 0; i < banks; i++) { // 128K/256K
write_prg_byte(0x8000, 9); // Command Register - PRG Bank 1
write_prg_byte(0xA000, i); // Parameter Register - ($8000-$9FFF)
for (word address = 0x0000; address < 0x2000; address += 512) { // 8K Banks ($8000-$9FFF)
dumpPRG(base, address);
}
}
break;
case 70:
case 89:
case 152: // 64K/128K
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) { // 128K
write_prg_byte(0x8000, i << 4);
for (word address = 0x0; address < 0x4000; address += 512) { // 16K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 71: // 64K/128K/256K
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) {
write_prg_byte(0xC000, i);
for (word address = 0x0; address < 0x4000; address += 512) { // 16K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 72: // 128K
banks = int_pow(2, prgsize);
write_prg_byte(0x8000, 0); // Reset Register
for (int i = 0; i < banks; i++) { // 128K
write_prg_byte(0x8000, i | 0x80); // PRG Command + Bank
write_prg_byte(0x8000, i); // PRG Bank
for (word address = 0x0; address < 0x4000; address += 512) { // 16K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 76:
case 88:
case 95:
case 154: // 128K
case 206: // 32K/64K/128K
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i += 2) {
write_prg_byte(0x8000, 6); // PRG ROM Command ($8000-$9FFF)
write_prg_byte(0x8001, i); // PRG Bank
write_prg_byte(0x8000, 7); // PRG ROM Command ($A000-$BFFF)
write_prg_byte(0x8001, i + 1); // PRG Bank
for (word address = 0x0; address < 0x4000; address += 512) { // 8K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 79:
case 146:
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x4100, i << 3);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 80: // 128K
case 207: // 256K [CART SOMETIMES NEEDS POWERCYCLE]
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i += 2) {
write_prg_byte(0x7EFA, i); // PRG Bank 0 ($8000-$9FFF)
write_prg_byte(0x7EFC, i + 1); // PRG Bank 1 ($A000-$BFFF)
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 82: // 128K
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i += 2) {
write_prg_byte(0x7EFA, i << 2); // PRG Bank 0 ($8000-$9FFF)
write_prg_byte(0x7EFB, (i + 1) << 2); // PRG Bank 1 ($A000-$BFFF)
for (word address = 0x0; address < 0x4000; address += 512) { // 8K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 85: // 128K/512K
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000, i); // PRG Bank 0 ($8000-$9FFF)
for (word address = 0x0; address < 0x2000; address += 512) { // 8K Banks ($8000-$9FFF)
dumpPRG(base, address);
}
}
break;
case 86:
case 140: // 128K
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i++) { // 128K
write_prg_byte(0x6000, i << 4); // bits 4-5
for (word address = 0x0; address < 0x8000; address += 512) { // 32K Banks ($8000-$FFFF)
dumpPRG(base, address);
}
}
break;
case 92: // 256K
banks = int_pow(2, prgsize);
write_prg_byte(0x8000, 0); // Reset Register
for (int i = 0; i < banks; i++) { // 256K
write_prg_byte(0x8000, i | 0x80); // PRG Command + Bank
write_prg_byte(0x8000, i); // PRG Bank
for (word address = 0x4000; address < 0x8000; address += 512) { // 16K Banks ($C000-$FFFF)
dumpPRG(base, address);
}
}
break;
case 93:
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) {
write_prg_byte(0x6000, i);
write_prg_byte(0x8000, i << 4 | 0x01);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 94:
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) { // 128K
write_prg_byte(0x8000, i << 2);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 97:
case 180:
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) { // 256K
write_prg_byte(0x8000, i); // PRG Bank
for (word address = 0x4000; address < 0x8000; address += 512) { // 16K Banks ($C000-$FFFF)
dumpPRG(base, address);
}
}
break;
case 105: // 256K
write_mmc1_byte(0xA000, 0x00); // Clear PRG Init/IRQ (Bit 4)
write_mmc1_byte(0xA000, 0x10); // Set PRG Init/IRQ (Bit 4) to enable bank swapping
for (int i = 0; i < 4; i++) { // PRG CHIP 1 128K
write_mmc1_byte(0xA000, i << 1);
for (word address = 0x0; address < 0x8000; address += 512) { // 32K Banks ($8000-$FFFF)
dumpPRG(base, address);
}
}
write_mmc1_byte(0x8000, 0x0C); // Switch 16K Bank ($8000-$BFFF) + Fixed Last Bank ($C000-$FFFF)
write_mmc1_byte(0xA000, 0x08); // Select PRG CHIP 2 (Bit 3)
for (int j = 0; j < 8; j++) { // PRG CHIP 2 128K
write_mmc1_byte(0xE000, j);
for (word address = 0x0; address < 0x4000; address += 512) { // 16K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 153: // 512K
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) { // 512K
write_prg_byte(0x8000, i >> 4); // PRG Outer Bank (Documentation says duplicate over $8000-$8003 registers)
write_prg_byte(0x8001, i >> 4); // PRG Outer Bank
write_prg_byte(0x8002, i >> 4); // PRG Outer Bank
write_prg_byte(0x8003, i >> 4); // PRG Outer Bank
write_prg_byte(0x8008, i & 0xF); // PRG Inner Bank
for (word address = 0x0000; address < 0x4000; address += 512) { // 16K Banks ($8000-$BFFF)
dumpPRG(base, address);
}
}
break;
case 200:
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 + (i & 0x07), 0);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 201:
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 + (i & 0xFF), 0);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 202:
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 | (i << 1), 0);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 203:
banks = int_pow(2, prgsize);
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000, (i & 0x1F) << 2);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 210: // 128K/256K
banks = int_pow(2, prgsize) * 2;
for (int i = 0; i < banks; i += 2) {
write_prg_byte(0xE000, i); // PRG Bank 0 ($8000-$9FFF) [WRITE NO RAM]
write_prg_byte(0xE800, i + 1); // PRG Bank 1 ($A000-$BFFF) [WRITE NO RAM]
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 225:
case 255:
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 + (i << 6), i << 6);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 229:
write_prg_byte(0x8000, 0);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
for (int i = 2; i < 32; i++) {
write_prg_byte(0x8000 + i, i);
for (word address = 0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 232:
banks = int_pow(2, prgsize) / 4;
for (int outerbank = 0; outerbank < 4; outerbank++) {
write_prg_byte(0x8000, outerbank << 3);
for (int i = 0; i < banks; i++) {
write_prg_byte(0xC000, i);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
}
break;
case 235:
for (int i = 0; i < 32; i++) {
write_prg_byte(0x8000 + i, 0);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
if (prgsize > 6) {
for (int i = 32; i < 64; i++) {
write_prg_byte(0x80E0 + i, 0);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
if (prgsize > 7) {
for (int i = 64; i < 96; i++) {
write_prg_byte(0x81E0 + i, 0);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
for (int i = 96; i < 128; i++) {
write_prg_byte(0x82E0 + i, 0);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
}
}
break;
case 240:
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x5FFF, (i & 0xF) << 4);
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 242: // total size is 640k THIS IS NORMAL
for (int i = 0; i < 32; i++) { // dump 1st chip of 512k
write_prg_byte(0x8400 + (i * 4), 0);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
for (int i = 0; i < 8; i++) { // dump 2nd chip of 128k
write_prg_byte(0x8000 + (i * 4), 0);
for (word address = 0x0; address < 0x4000; address += 512) {
dumpPRG(base, address);
}
}
break;
case 246:
banks = int_pow(2, prgsize) / 2;
for (int i = 0; i < banks; i += 4) {
write_prg_byte(0x6000, (i | 0));
write_prg_byte(0x6001, (i | 1));
write_prg_byte(0x6002, (i | 2));
write_prg_byte(0x6003, (i | 3));
for (word address = 0x0; address < 0x8000; address += 512) {
dumpPRG(base, address);
}
}
break;
}
if (!readrom) {
myFile.flush();
myFile.close();
println_Msg(F("PRG FILE DUMPED!"));
println_Msg(F(""));
display_Update();
#ifndef nointro
calcCRC(fileName, prg * 1024, &prg_crc32, 0);
#endif
}
}
set_address(0);
PHI2_HI;
ROMSEL_HI;
LED_BLUE_OFF;
}
void readCHR(boolean readrom) {
if (!readrom) {
display_Clear();
display_Update();
}
LED_GREEN_ON;
set_address(0);
_delay_us(1);
if (chrsize == 0) {
println_Msg(F("CHR SIZE 0K"));
display_Update();
} else {
if (!readrom) {
CreateCHRFileInSD();
}
if (myFile) {
switch (mapper) {
case 0: // 8K
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
break;
case 1:
case 155:
banks = int_pow(2, chrsize);
for (int i = 0; i < banks; i += 2) { // 8K/16K/32K/64K/128K (Bank #s are based on 4K Banks)
write_prg_byte(0x8000, 0x80); // Clear Register
write_mmc1_byte(0xA000, i);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 3: // 8K/16K/32K
case 66: // 16K/32K
case 70:
case 152: // 128K
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) { // 8K Banks
write_prg_byte(0x8000, i); // CHR Bank 0
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 4:
case 47:
case 64:
case 118:
case 119:
case 158:
banks = int_pow(2, chrsize) * 4;
if (mapper == 47)
write_prg_byte(0xA001, 0x80); // Block Register - PRG RAM Chip Enable, Writable
for (int i = 0; i < banks; i += 4) { // 8K/16K/32K/64K/128K/256K
if (mapper == 47) {
if (i == 0)
write_prg_byte(0x6000, 0); // Switch to Lower Block
else if (i == 128)
write_prg_byte(0x6000, 1); // Switch to Upper Block
}
write_prg_byte(0x8000, 0); // CHR Bank 0 ($0000-$07FF)
write_prg_byte(0x8001, i);
write_prg_byte(0x8000, 1); // CHR Bank 1 ($0800-$0FFF)
write_prg_byte(0x8001, i + 2);
for (word address = 0x0; address < 0x1000; address += 512) {
dumpCHR(address);
}
}
break;
case 5: // 128K/256K/512K
banks = int_pow(2, chrsize) / 2;
write_prg_byte(0x5101, 0); // 8K CHR Banks
for (int i = 0; i < banks; i++) {
if (i == 0)
write_prg_byte(0x5130, 0); // Set Upper 2 bits
else if (i == 8)
write_prg_byte(0x5130, 1); // Set Upper 2 bits
else if (i == 16)
write_prg_byte(0x5130, 2); // Set Upper 2 bits
else if (i == 24)
write_prg_byte(0x5130, 3); // Set Upper 2 bits
write_prg_byte(0x5127, i);
for (word address = 0x0; address < 0x2000; address += 512) { // ($0000-$1FFF)
dumpCHR(address);
}
}
break;
case 9:
case 10: // Mapper 9: 128K, Mapper 10: 64K/128K
if (mapper == 9)
banks = 32;
else // Mapper 10
banks = int_pow(2, chrsize);
for (int i = 0; i < banks; i++) { // 64K/128K
write_prg_byte(0xB000, i);
write_prg_byte(0xC000, i);
for (word address = 0x0; address < 0x1000; address += 512) {
dumpCHR(address);
}
}
break;
case 11:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0xFFB0 + i, i << 4);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 16:
case 159: // 128K/256K
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x6000, i); // Submapper 4
write_prg_byte(0x8000, i); // Submapper 5
for (word address = 0x0; address < 0x400; address += 512) {
dumpCHR(address);
}
}
break;
case 18: // 128K/256K
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i++) {
write_prg_byte(0xA000, i & 0xF); // CHR Bank Lower 4 bits
write_prg_byte(0xA001, (i >> 4) & 0xF); // CHR Bank Upper 4 bits
for (word address = 0x0; address < 0x400; address += 512) {
dumpCHR(address);
}
}
break;
case 19: // 128K/256K
for (int j = 0; j < 64; j++) { // Init Register
write_ram_byte(0xE800, 0xC0); // CHR RAM High/Low Disable (ROM Enable)
}
banks = int_pow(2, chrsize) * 4;
write_ram_byte(0xE800, 0xC0); // CHR RAM High/Low Disable (ROM Enable)
for (int i = 0; i < banks; i += 8) {
write_prg_byte(0x8000, i); // CHR Bank 0
write_prg_byte(0x8800, i + 1); // CHR Bank 1
write_prg_byte(0x9000, i + 2); // CHR Bank 2
write_prg_byte(0x9800, i + 3); // CHR Bank 3
write_prg_byte(0xA000, i + 4); // CHR Bank 4
write_prg_byte(0xA800, i + 5); // CHR Bank 5
write_prg_byte(0xB000, i + 6); // CHR Bank 6
write_prg_byte(0xB800, i + 7); // CHR Bank 7
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 21: // 128K/256K
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i++) {
write_prg_byte(0xB000, i & 0xF); // CHR Bank Lower 4 bits
if (chrsize == 5) // Check CHR Size to determine VRC4a (128K) or VRC4c (256K)
write_prg_byte(0xB002, (i >> 4) & 0xF); // CHR Bank Upper 4 bits VRC4a (Wai Wai World 2)
else // banks == 256
write_prg_byte(0xB040, (i >> 4) & 0xF); // CHR Bank Upper 4 bits VRC4c (Ganbare Goemon Gaiden 2)
for (word address = 0x0; address < 0x400; address += 512) {
dumpCHR(address);
}
}
break;
case 22: // 128K
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i++) {
write_prg_byte(0xB000, (i << 1) & 0xF); // CHR Bank Lower 4 bits
write_prg_byte(0xB002, (i >> 3) & 0xF); // CHR Bank Upper 4 bits
for (word address = 0x0; address < 0x400; address += 512) {
dumpCHR(address);
}
}
break;
case 23: // 128K
// Detect VRC4e Carts - read PRG 0x1FFF6 (DATE)
// Boku Dracula-kun = 890810, Tiny Toon = 910809
// Crisis Force = 910701, Parodius Da! = 900916
write_prg_byte(0x8000, 15);
prgchk0 = read_prg_byte(0x9FF6);
if (prgchk0 == 0x30) { // Check for "0" in middle of date
vrc4e = true; // VRC4e Cart
}
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i++) {
write_prg_byte(0xB000, i & 0xF); // CHR Bank Lower 4 bits
if (vrc4e == true)
write_prg_byte(0xB004, (i >> 4) & 0xF); // CHR Bank Upper 4 bits VRC4e
else
write_prg_byte(0xB001, (i >> 4) & 0xF); // CHR Bank Upper 4 bits VRC2b/VRC4f
for (word address = 0x0; address < 0x400; address += 512) {
dumpCHR(address);
}
}
break;
case 24: // 128K
banks = int_pow(2, chrsize) * 4;
write_prg_byte(0xB003, 0); // PPU Banking Mode 0
for (int i = 0; i < banks; i += 8) {
write_prg_byte(0xD000, i); // CHR Bank 0
write_prg_byte(0xD001, i + 1); // CHR Bank 1
write_prg_byte(0xD002, i + 2); // CHR Bank 2
write_prg_byte(0xD003, i + 3); // CHR Bank 3
write_prg_byte(0xE000, i + 4); // CHR Bank 4 [WRITE NO RAM]
write_prg_byte(0xE001, i + 5); // CHR Bank 5 [WRITE NO RAM]
write_prg_byte(0xE002, i + 6); // CHR Bank 6 [WRITE NO RAM]
write_prg_byte(0xE003, i + 7); // CHR Bank 7 [WRITE NO RAM]
for (word address = 0x0; address < 0x2000; address += 512) { // 1K Banks
dumpCHR(address);
}
}
break;
case 25: // 128K/256K
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i++) {
write_prg_byte(0xB000, i & 0xF); // CHR Bank Lower 4 bits
if ((ramsize > 0) || (banks == 128)) // VRC2c (Ganbare Goemon Gaiden)/VRC4b (Bio Miracle/Gradius 2/Racer Mini)
write_prg_byte(0xB002, (i >> 4) & 0xF); // CHR Bank Upper 4 bits VRC2c/VRC4b
else
write_prg_byte(0xB008, (i >> 4) & 0xF); // CHR Bank Upper 4 bits VRC4d (Teenage Mutant Ninja Turtles)
for (word address = 0x0; address < 0x400; address += 512) {
dumpCHR(address);
}
}
break;
case 26: // 128K/256K
banks = int_pow(2, chrsize) * 4;
write_prg_byte(0xB003, 0x00);
for (int i = 0; i < banks; i += 4) {
write_prg_byte(0xD000, i + 0); // CHR Bank 0
write_prg_byte(0xD002, i + 1); // CHR Bank 1
write_prg_byte(0xD001, i + 2); // CHR Bank 2
write_prg_byte(0xD003, i + 3); // CHR Bank 3
for (word address = 0x0; address < 0x1000; address += 512) { // 1K Banks
dumpCHR(address);
}
}
break;
case 32: // 128K
case 65: // 128K/256K
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i += 8) {
write_prg_byte(0xB000, i); // CHR Bank 0
write_prg_byte(0xB001, i + 1); // CHR Bank 1
write_prg_byte(0xB002, i + 2); // CHR Bank 2
write_prg_byte(0xB003, i + 3); // CHR Bank 3
write_prg_byte(0xB004, i + 4); // CHR Bank 4
write_prg_byte(0xB005, i + 5); // CHR Bank 5
write_prg_byte(0xB006, i + 6); // CHR Bank 6
write_prg_byte(0xB007, i + 7); // CHR Bank 7
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 33: // 128K/256K
case 48: // 256K
banks = int_pow(2, chrsize) * 2;
for (int i = 0; i < banks; i += 2) { // 2K Banks
write_prg_byte(0x8002, i); // CHR Bank 0
write_prg_byte(0x8003, i + 1); // CHR Bank 1
for (word address = 0x0; address < 0x1000; address += 512) {
dumpCHR(address);
}
}
break;
case 37:
banks = int_pow(2, chrsize) * 4;
write_prg_byte(0xA001, 0x80); // Block Register - PRG RAM Chip Enable, Writable
for (int i = 0; i < banks; i += 4) { // 256K
if (i == 0)
write_prg_byte(0x6000, 0); // Switch to Lower Block ($00000-$1FFFF)
else if (i == 128)
write_prg_byte(0x6000, 4); // Switch to Upper Block ($20000-$3FFFF)
write_prg_byte(0x8000, 0); // CHR Bank 0 ($0000-$07FF)
write_prg_byte(0x8001, i);
write_prg_byte(0x8000, 1); // CHR Bank 1 ($0800-$0FFF)
write_prg_byte(0x8001, i + 2);
for (word address = 0x0; address < 0x1000; address += 512) {
dumpCHR(address);
}
}
break;
case 45: // 128K/256K/512K/1024K
banks = int_pow(2, chrsize) * 4;
write_prg_byte(0xA001, 0x80); // Unlock Write Protection - not used by some carts
for (int i = 0; i < banks; i++) {
// set outer bank registers
write_prg_byte(0x6000, 0x00); // CHR-OR
write_prg_byte(0x6000, 0x00); // PRG-OR
write_prg_byte(0x6000, (((i / 256) << 4) | 0x0F)); // CHR-AND,CHR-OR/PRG-OR
write_prg_byte(0x6000, 0x80); // PRG-AND
// set inner bank registers
write_prg_byte(0x8000, 0x2); // CHR Bank 2 ($1000-$13FF)
write_prg_byte(0x8001, i);
for (word address = 0x1000; address < 0x1200; address += 512) {
dumpCHR_M2(address); // Read CHR with M2 Pulse
}
// set outer bank registers
write_prg_byte(0x6000, 0x00); // CHR-OR
write_prg_byte(0x6000, 0x00); // PRG-OR
write_prg_byte(0x6000, (((i / 256) << 4) | 0x0F)); // CHR-AND,CHR-OR/PRG-OR
write_prg_byte(0x6000, 0x80); // PRG-AND
// set inner bank registers
write_prg_byte(0x8000, 0x2); // CHR Bank 2 ($1000-$13FF)
write_prg_byte(0x8001, i);
for (word address = 0x1200; address < 0x1400; address += 512) {
dumpCHR_M2(address); // Read CHR with M2 Pulse
}
}
break;
case 58:
case 213:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 + ((i & 0x07) << 3), 0x00);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 60:
for (int i = 0; i < 4; i++) {
write_prg_byte(0x8D8D, i);
delay(500);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 62:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 + (i / 4), i & 3);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 67: // 128K
banks = int_pow(2, chrsize) * 2;
for (int i = 0; i < banks; i += 4) { // 2K Banks
write_prg_byte(0x8800, i); // CHR Bank 0
write_prg_byte(0x9800, i + 1); // CHR Bank 1
write_prg_byte(0xA800, i + 2); // CHR Bank 2
write_prg_byte(0xB800, i + 3); // CHR Bank 3
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 68: // 128K/256K
banks = int_pow(2, chrsize) * 2;
for (int i = 0; i < banks; i += 4) { // 2K Banks
write_prg_byte(0x8000, i); // CHR Bank 0
write_prg_byte(0x9000, i + 1); // CHR Bank 1
write_prg_byte(0xA000, i + 2); // CHR Bank 2
write_prg_byte(0xB000, i + 3); // CHR Bank 3
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 69: // 128K/256K
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000, 0); // Command Register - CHR Bank 0
write_prg_byte(0xA000, i); // Parameter Register - ($0000-$03FF)
for (word address = 0x0; address < 0x400; address += 512) { // 1K Banks
dumpCHR(address);
}
}
break;
case 72: // 128K
banks = int_pow(2, chrsize) / 2;
write_prg_byte(0x8000, 0); // Reset Register
for (int i = 0; i < banks; i++) { // 8K Banks
write_prg_byte(0x8000, i | 0x40); // CHR Command + Bank
write_prg_byte(0x8000, i); // CHR Bank
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 75: // 128K
banks = int_pow(2, chrsize);
for (int i = 0; i < banks; i++) { // 4K Banks
write_reg_byte(0xE000, i); // CHR Bank Low Bits [WRITE RAM SAFE]
write_prg_byte(0x9000, (i & 0x10) >> 3); // High Bit
for (word address = 0x0; address < 0x1000; address += 512) {
dumpCHR(address);
}
}
break;
case 76: // 128K
banks = int_pow(2, chrsize) * 2;
for (int i = 0; i < banks; i += 2) { // 2K Banks
write_prg_byte(0x8000, 2); // CHR Command ($0000-$07FF) 2K Bank
write_prg_byte(0x8001, i); // CHR Bank
write_prg_byte(0x8000, 3); // CHR Command ($0800-$0FFF) 2K Bank
write_prg_byte(0x8001, i + 1); // CHR Bank
for (word address = 0x0000; address < 0x1000; address += 512) {
dumpCHR(address);
}
}
break;
case 77: // 32K
banks = int_pow(2, chrsize) * 2;
for (int i = 0; i < banks; i++) { // 2K Banks
write_prg_byte(0x8000, i << 4); // CHR Bank 0
for (word address = 0x0; address < 0x800; address += 512) {
dumpCHR(address);
}
}
break;
case 78: // 128K
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) { // 8K Banks
write_prg_byte(0x8000, i << 4); // CHR Bank 0
for (word address = 0x0; address < 0x2000; address += 512) { // 8K Banks ($0000-$1FFF)
dumpCHR(address);
}
}
break;
case 79:
case 146:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x4100, i);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 80: // 128K/256K
case 82: // 128K/256K
case 207: // 128K [CART SOMETIMES NEEDS POWERCYCLE]
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i += 4) {
write_prg_byte(0x7EF2, i); // CHR Bank 2 [REGISTERS 0x7EF0/0x7EF1 WON'T WORK]
write_prg_byte(0x7EF3, i + 1); // CHR Bank 3
write_prg_byte(0x7EF4, i + 2); // CHR Bank 4
write_prg_byte(0x7EF5, i + 3); // CHR Bank 5
for (word address = 0x1000; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 85: // 128K
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i += 8) {
write_prg_byte(0xA000, i); // CHR Bank 0
write_prg_byte(0xA008, i + 1); // CHR Bank 1
write_prg_byte(0xB000, i + 2); // CHR Bank 2
write_prg_byte(0xB008, i + 3); // CHR Bank 3
write_prg_byte(0xC000, i + 4); // CHR Bank 4
write_prg_byte(0xC008, i + 5); // CHR Bank 5
write_prg_byte(0xD000, i + 6); // CHR Bank 6
write_prg_byte(0xD008, i + 7); // CHR Bank 7
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 86: // 64K
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) { // 8K Banks
if (i < 4)
write_prg_byte(0x6000, i & 0x3);
else
write_prg_byte(0x6000, (i | 0x40) & 0x43);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 87: // 16K/32K
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) { // 16K/32K
write_prg_byte(0x6000, (((i & 0x1) << 1) | ((i & 0x2) >> 1)));
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 88: // 128K
case 95: // 32K
case 154: // 128K
case 206: // 16K/32K/64K
banks = int_pow(2, chrsize) * 4;
for (int i = 0; i < banks; i += 2) { // 1K Banks
if (i < 64) {
write_prg_byte(0x8000, 0); // CHR Command ($0000-$07FF) 2K Bank
write_prg_byte(0x8001, i & 0x3F); // CHR Bank
for (word address = 0x0; address < 0x800; address += 512) {
dumpCHR(address);
}
} else {
write_prg_byte(0x8000, 2); // CHR Command ($1000-$13FF) 1K Bank
write_prg_byte(0x8001, i); // CHR Bank
write_prg_byte(0x8000, 3); // CHR Command ($1400-$17FF) 1K Bank
write_prg_byte(0x8001, i + 1); // CHR Bank
for (word address = 0x1000; address < 0x1800; address += 512) {
dumpCHR(address);
}
}
}
break;
case 89: // 128K
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) { // 8K Banks
if (i < 8)
write_prg_byte(0x8000, i & 0x7);
else
write_prg_byte(0x8000, (i | 0x80) & 0x87);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 92: // 128K
banks = int_pow(2, chrsize) / 2;
write_prg_byte(0x8000, 0); // Reset Register
for (int i = 0; i < banks; i++) { // 8K Banks
write_prg_byte(0x8000, i | 0x40); // CHR Command + Bank
write_prg_byte(0x8000, i); // CHR Bank
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 140: // 32K/128K
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) { // 8K Banks
write_prg_byte(0x6000, i);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 184: // 16K/32K
banks = int_pow(2, chrsize);
for (int i = 0; i < banks; i++) { // 4K Banks
write_prg_byte(0x6000, i); // CHR LOW (Bits 0-2) ($0000-$0FFF)
for (word address = 0x0; address < 0x1000; address += 512) { // 4K Banks ($0000-$0FFF)
dumpCHR(address);
}
}
break;
case 185: // 8K [READ 32K TO OVERRIDE LOCKOUT]
for (int i = 0; i < 4; i++) { // Read 32K to locate valid 8K
write_prg_byte(0x8000, i);
byte chrcheck = read_chr_byte(0);
for (word address = 0x0; address < 0x2000; address += 512) {
for (int x = 0; x < 512; x++) {
sdBuffer[x] = read_chr_byte(address + x);
}
if (chrcheck != 0xFF)
myFile.write(sdBuffer, 512);
}
}
break;
case 200:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 + (i & 0x07), 0);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 201:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 + (i & 0xFF), 0);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 202:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 | (i << 1), 0);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 203:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000, (i & 0x03));
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 210: // 128K/256K
banks = int_pow(2, chrsize) * 4;
write_prg_byte(0xE800, 0xC0); // CHR RAM DISABLE (Bit 6 and 7) [WRITE NO RAM]
for (int i = 0; i < banks; i += 8) {
write_prg_byte(0x8000, i); // CHR Bank 0
write_prg_byte(0x8800, i + 1); // CHR Bank 1
write_prg_byte(0x9000, i + 2); // CHR Bank 2
write_prg_byte(0x9800, i + 3); // CHR Bank 3
write_prg_byte(0xA000, i + 4); // CHR Bank 4
write_prg_byte(0xA800, i + 5); // CHR Bank 5
write_prg_byte(0xB000, i + 6); // CHR Bank 6
write_prg_byte(0xB800, i + 7); // CHR Bank 7
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 225:
case 255:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x8000 + i, i);
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 229: // BMC 31-IN-1
for (int i = 0; i < 32; i++) {
write_prg_byte(0x8000 + i, i);
for (word address = 0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 240:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i++) {
write_prg_byte(0x5FFF, (i & 0xF));
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
case 246:
banks = int_pow(2, chrsize) / 2;
for (int i = 0; i < banks; i += 4) {
write_prg_byte(0x6004, (i | 0));
write_prg_byte(0x6005, (i | 1));
write_prg_byte(0x6006, (i | 2));
write_prg_byte(0x6007, (i | 3));
for (word address = 0x0; address < 0x2000; address += 512) {
dumpCHR(address);
}
}
break;
}
if (!readrom) {
myFile.flush();
myFile.close();
println_Msg(F("CHR FILE DUMPED!"));
println_Msg(F(""));
display_Update();
#ifndef nointro
calcCRC(fileName, chr * 1024, &chr_crc32, 0);
#endif
}
}
}
set_address(0);
PHI2_HI;
ROMSEL_HI;
LED_GREEN_OFF;
}
/******************************************
RAM Functions
*****************************************/
void readRAM() {
display_Clear();
display_Update();
LED_BLUE_ON;
LED_GREEN_ON;
set_address(0);
_delay_us(1);
if (ramsize == 0) {
println_Msg(F("RAM SIZE 0K"));
display_Update();
} else {
CreateRAMFileInSD();
word base = 0x6000;
if (myFile) {
switch (mapper) {
case 0: // 2K/4K
for (word address = 0x0; address < (0x800 * ramsize); address += 512) { // 2K/4K
dumpPRG(base, address); // SWITCH MUST BE IN OFF POSITION
}
break;
case 1:
case 155: // 8K/16K/32K
banks = int_pow(2, ramsize) / 2; // banks = 1,2,4
for (int i = 0; i < banks; i++) { // 8K Banks ($6000-$7FFF)
write_prg_byte(0x8000, 0x80); // Clear Register
write_mmc1_byte(0x8000, 1 << 3);
write_mmc1_byte(0xE000, 0);
if (banks == 4) // 32K
write_mmc1_byte(0xA000, i << 2);
else
write_mmc1_byte(0xA000, i << 3);
for (word address = 0x0; address < 0x2000; address += 512) { // 8K
dumpPRG(base, address);
}
}
break;
case 4: // 1K/8K (MMC6/MMC3)
if (mmc6) { // MMC6 1K
write_prg_byte(0x8000, 0x20); // PRG RAM ENABLE
write_prg_byte(0xA001, 0x20); // PRG RAM PROTECT - Enable reading RAM at $7000-$71FF
for (word address = 0x1000; address < 0x1200; address += 512) { // 512B
dumpMMC5RAM(base, address);
}
write_prg_byte(0x8000, 0x20); // PRG RAM ENABLE
write_prg_byte(0xA001, 0x80); // PRG RAM PROTECT - Enable reading RAM at $7200-$73FF
for (word address = 0x1200; address < 0x1400; address += 512) { // 512B
dumpMMC5RAM(base, address);
}
write_prg_byte(0x8000, 6); // PRG RAM DISABLE
} else { // MMC3 8K
write_prg_byte(0xA001, 0xC0); // PRG RAM CHIP ENABLE - Chip Enable, Write Protect
for (word address = 0; address < 0x2000; address += 512) { // 8K
dumpPRG(base, address);
}
}
break;
case 5: // 8K/16K/32K
write_prg_byte(0x5100, 3); // 8K PRG Banks
banks = int_pow(2, ramsize) / 2; // banks = 1,2,4
if (banks == 2) { // 16K - Split SRAM Chips 8K/8K
for (int i = 0; i < (banks / 2); i++) { // Chip 1
write_prg_byte(0x5113, i);
for (word address = 0; address < 0x2000; address += 512) { // 8K
dumpMMC5RAM(base, address);
}
}
for (int j = 4; j < (banks / 2) + 4; j++) { // Chip 2
write_prg_byte(0x5113, j);
for (word address = 0; address < 0x2000; address += 512) { // 8K
dumpMMC5RAM(base, address);
}
}
} else { // 8K/32K Single SRAM Chip
for (int i = 0; i < banks; i++) { // banks = 1 or 4
write_prg_byte(0x5113, i);
for (word address = 0; address < 0x2000; address += 512) { // 8K
dumpMMC5RAM(base, address);
}
}
}
break;
case 16: // 256-byte EEPROM 24C02
case 159: // 128-byte EEPROM 24C01 [Little Endian]
if (mapper == 159)
eepsize = 128;
else
eepsize = 256;
for (word address = 0; address < eepsize; address++) {
EepromREAD(address);
}
myFile.write(sdBuffer, eepsize);
// display_Clear(); // TEST PURPOSES - DISPLAY EEPROM DATA
break;
case 19:
if (ramsize == 2) { // PRG RAM 128B
for (int x = 0; x < 128; x++) {
write_ram_byte(0xF800, x); // PRG RAM ENABLE
sdBuffer[x] = read_prg_byte(0x4800); // DATA PORT
}
myFile.write(sdBuffer, 128);
} else { // SRAM 8K
for (int i = 0; i < 64; i++) { // Init Register
write_ram_byte(0xE000, 0);
}
for (word address = 0; address < 0x2000; address += 512) { // 8K
dumpPRG(base, address);
}
}
break;
case 80: // 1K
write_prg_byte(0x7EF8, 0xA3); // PRG RAM ENABLE 0
write_prg_byte(0x7EF9, 0xA3); // PRG RAM ENABLE 1
for (int x = 0; x < 128; x++) { // PRG RAM 1K ($7F00-$7FFF) MIRRORED ONCE
sdBuffer[x] = read_prg_byte(0x7F00 + x);
}
myFile.write(sdBuffer, 128);
write_prg_byte(0x7EF8, 0xFF); // PRG RAM DISABLE 0
write_prg_byte(0x7EF9, 0xFF); // PRG RAM DISABLE 1
break;
case 82: // 5K
write_prg_byte(0x7EF7, 0xCA); // PRG RAM ENABLE 0 ($6000-$67FF)
write_prg_byte(0x7EF8, 0x69); // PRG RAM ENABLE 1 ($6800-$6FFF)
write_prg_byte(0x7EF9, 0x84); // PRG RAM ENABLE 2 ($7000-$73FF)
for (word address = 0x0; address < 0x1400; address += 512) { // PRG RAM 5K ($6000-$73FF)
dumpMMC5RAM(base, address);
}
write_prg_byte(0x7EF7, 0xFF); // PRG RAM DISABLE 0 ($6000-$67FF)
write_prg_byte(0x7EF8, 0xFF); // PRG RAM DISABLE 1 ($6800-$6FFF)
write_prg_byte(0x7EF9, 0xFF); // PRG RAM DISABLE 2 ($7000-$73FF)
break;
default:
if (mapper == 118) // 8K
write_prg_byte(0xA001, 0xC0); // PRG RAM CHIP ENABLE - Chip Enable, Write Protect
else if (mapper == 19) {
for (int i = 0; i < 64; i++) { // Init Register
write_ram_byte(0xE000, 0);
}
} else if ((mapper == 21) || (mapper == 25)) // 8K
write_prg_byte(0x8000, 0);
else if (mapper == 26) // 8K
write_prg_byte(0xB003, 0x80); // PRG RAM ENABLE
else if (mapper == 68) // 8K
write_reg_byte(0xF000, 0x10); // PRG RAM ENABLE [WRITE RAM SAFE]
else if (mapper == 69) { // 8K
write_prg_byte(0x8000, 8); // Command Register - PRG Bank 0
write_prg_byte(0xA000, 0xC0); // Parameter Register - PRG RAM Enabled, PRG RAM, Bank 0 to $6000-$7FFF
} else if (mapper == 85) // 8K
write_ram_byte(0xE000, 0x80); // PRG RAM ENABLE
else if (mapper == 153) // 8K
write_prg_byte(0x800D, 0x20); // PRG RAM Chip Enable
for (word address = 0; address < 0x2000; address += 512) { // 8K
dumpPRG(base, address);
}
if (mapper == 85) // 8K
write_reg_byte(0xE000, 0); // PRG RAM DISABLE [WRITE RAM SAFE]
break;
}
myFile.flush();
myFile.close();
println_Msg(F("RAM FILE DUMPED!"));
println_Msg(F(""));
display_Update();
if ((mapper == 16) || (mapper == 159))
calcCRC(fileName, eepsize, NULL, 0);
else
calcCRC(fileName, ram * 1024, NULL, 0);
}
}
set_address(0);
PHI2_HI;
ROMSEL_HI;
LED_BLUE_OFF;
LED_GREEN_OFF;
}
void writeRAM() {
display_Clear();
if (ramsize == 0) {
print_Error(F("RAM SIZE 0K"), false);
} else {
fileBrowser(F("Select RAM File"));
word base = 0x6000;
sd.chdir();
sprintf(filePath, "%s/%s", filePath, fileName);
display_Clear();
println_Msg(F("Writing File: "));
println_Msg(filePath);
println_Msg(fileName);
display_Update();
//open file on sd card
if (myFile.open(filePath, O_READ)) {
switch (mapper) {
case 0: // 2K/4K
for (word address = 0x0; address < (0x800 * ramsize); address += 512) { // 2K/4K
myFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++) {
write_prg_byte(base + address + x, sdBuffer[x]); // SWITCH MUST BE IN OFF POSITION
}
}
break;
case 1:
case 155:
banks = int_pow(2, ramsize) / 2; // banks = 1,2,4
for (int i = 0; i < banks; i++) { // 8K Banks ($6000-$7FFF)
write_prg_byte(0x8000, 0x80); // Clear Register
write_mmc1_byte(0x8000, 1 << 3); // PRG ROM MODE 32K
write_mmc1_byte(0xE000, 0); // PRG RAM ENABLED
if (banks == 4) // 32K
write_mmc1_byte(0xA000, i << 2);
else
write_mmc1_byte(0xA000, i << 3);
for (word address = 0x0; address < 0x2000; address += 512) { // 8K
myFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++) {
write_prg_byte(base + address + x, sdBuffer[x]);
}
}
}
break;
case 4: // 1K/8K (MMC6/MMC3)
if (mmc6) { // MMC6 1K
write_prg_byte(0x8000, 0x20); // PRG RAM ENABLE
write_prg_byte(0xA001, 0x30); // PRG RAM PROTECT - Enable reading/writing to RAM at $7000-$71FF
for (word address = 0x1000; address < 0x1200; address += 512) { // 512B
myFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++) {
write_wram_byte(base + address + x, sdBuffer[x]);
}
}
write_prg_byte(0x8000, 0x20); // PRG RAM ENABLE
write_prg_byte(0xA001, 0xC0); // PRG RAM PROTECT - Enable reading/writing to RAM at $7200-$73FF
for (word address = 0x1200; address < 0x1400; address += 512) { // 512B
myFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++) {
write_wram_byte(base + address + x, sdBuffer[x]);
}
}
write_prg_byte(0x8000, 0x6); // PRG RAM DISABLE
} else { // MMC3 8K
write_prg_byte(0xA001, 0x80); // PRG RAM CHIP ENABLE - Chip Enable, Allow Writes
for (word address = 0; address < 0x2000; address += 512) { // 8K
myFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++) {
write_prg_byte(base + address + x, sdBuffer[x]);
}
}
write_prg_byte(0xA001, 0xC0); // PRG RAM CHIP ENABLE - Chip Enable, Write Protect
}
break;
case 5: // 8K/16K/32K
write_prg_byte(0x5100, 3); // 8K PRG Banks
banks = int_pow(2, ramsize) / 2; // banks = 1,2,4
if (banks == 2) { // 16K - Split SRAM Chips 8K/8K [ETROM = 16K (ONLY 1ST 8K BATTERY BACKED)]
for (int i = 0; i < (banks / 2); i++) { // Chip 1
write_prg_byte(0x5113, i);
for (word address = 0; address < 0x2000; address += 512) { // 8K
writeMMC5RAM(base, address);
}
}
for (int j = 4; j < (banks / 2) + 4; j++) { // Chip 2
write_prg_byte(0x5113, j);
for (word address = 0; address < 0x2000; address += 512) { // 8K
writeMMC5RAM(base, address);
}
}
} else { // 8K/32K Single SRAM Chip [EKROM = 8K BATTERY BACKED, EWROM = 32K BATTERY BACKED]
for (int i = 0; i < banks; i++) { // banks = 1 or 4
write_prg_byte(0x5113, i);
for (word address = 0; address < 0x2000; address += 512) { // 8K
writeMMC5RAM(base, address);
}
}
}
break;
case 16: // 256-byte EEPROM 24C02
case 159: // 128-byte EEPROM 24C01 [Little Endian]
if (mapper == 159)
eepsize = 128;
else
eepsize = 256;
myFile.read(sdBuffer, eepsize);
for (word address = 0; address < eepsize; address++) {
EepromWRITE(address);
if ((address % 128) == 0)
display_Clear();
print_Msg(F("."));
display_Update();
}
break;
case 19:
if (ramsize == 2) { // PRG RAM 128B
myFile.read(sdBuffer, 128);
for (int x = 0; x < 128; x++) {
write_ram_byte(0xF800, x); // PRG RAM ENABLE
write_prg_byte(0x4800, sdBuffer[x]); // DATA PORT
}
} else { // SRAM 8K
for (int i = 0; i < 64; i++) { // Init Register
write_ram_byte(0xF800, 0x40); // PRG RAM WRITE ENABLE
}
write_ram_byte(0xF800, 0x40); // PRG RAM WRITE ENABLE
for (word address = 0; address < 0x2000; address += 512) { // 8K
myFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++) {
write_prg_byte(base + address + x, sdBuffer[x]);
}
}
write_ram_byte(0xF800, 0x0F); // PRG RAM WRITE PROTECT
}
break;
case 80: // 1K
write_prg_byte(0x7EF8, 0xA3); // PRG RAM ENABLE 0
write_prg_byte(0x7EF9, 0xA3); // PRG RAM ENABLE 1
for (word address = 0x1F00; address < 0x2000; address += 512) { // PRG RAM 1K ($7F00-$7FFF)
myFile.read(sdBuffer, 128);
for (int x = 0; x < 128; x++) {
write_prg_byte(base + address + x, sdBuffer[x]);
}
}
write_prg_byte(0x7EF8, 0xFF); // PRG RAM DISABLE 0
write_prg_byte(0x7EF9, 0xFF); // PRG RAM DISABLE 1
break;
case 82: // 5K
write_prg_byte(0x7EF7, 0xCA); // PRG RAM ENABLE 0 ($6000-$67FF)
write_prg_byte(0x7EF8, 0x69); // PRG RAM ENABLE 1 ($6800-$6FFF)
write_prg_byte(0x7EF9, 0x84); // PRG RAM ENABLE 2 ($7000-$73FF)
for (word address = 0x0; address < 0x1400; address += 1024) { // PRG RAM 5K ($6000-$73FF)
myFile.read(sdBuffer, 512);
firstbyte = sdBuffer[0];
for (int x = 0; x < 512; x++)
write_prg_byte(base + address + x, sdBuffer[x]);
myFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++)
write_prg_byte(base + address + x + 512, sdBuffer[x]);
write_prg_byte(base + address, firstbyte); // REWRITE 1ST BYTE
}
write_prg_byte(0x7EF7, 0xFF); // PRG RAM DISABLE 0 ($6000-$67FF)
write_prg_byte(0x7EF8, 0xFF); // PRG RAM DISABLE 1 ($6800-$6FFF)
write_prg_byte(0x7EF9, 0xFF); // PRG RAM DISABLE 2 ($7000-$73FF)
break;
default:
if (mapper == 118) // 8K
write_prg_byte(0xA001, 0x80); // PRG RAM CHIP ENABLE - Chip Enable, Allow Writes
else if ((mapper == 21) || (mapper == 25)) // 8K
write_prg_byte(0x8000, 0);
else if (mapper == 26) // 8K
write_prg_byte(0xB003, 0x80); // PRG RAM ENABLE
// else if (mapper == 68) // 8K
// write_reg_byte(0xF000, 0x10); // PRG RAM ENABLE [WRITE RAM SAFE]
else if (mapper == 69) { // 8K
write_prg_byte(0x8000, 8); // Command Register - PRG Bank 0
write_prg_byte(0xA000, 0xC0); // Parameter Register - PRG RAM Enabled, PRG RAM, Bank 0 to $6000-$7FFF
} else if (mapper == 85) // 8K
write_ram_byte(0xE000, 0x80); // PRG RAM ENABLE
else if (mapper == 153) // 8K
write_prg_byte(0x800D, 0x20); // PRG RAM Chip Enable
for (word address = 0; address < 0x2000; address += 512) { // 8K
myFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++) {
write_prg_byte(base + address + x, sdBuffer[x]);
}
}
if (mapper == 118) // 8K
write_prg_byte(0xA001, 0xC0); // PRG RAM CHIP ENABLE - Chip Enable, Write Protect
else if (mapper == 26) // 8K
write_prg_byte(0xB003, 0); // PRG RAM DISABLE
// else if (mapper == 68) // 8K
// write_reg_byte(0xF000, 0x00); // PRG RAM DISABLE [WRITE RAM SAFE]
else if (mapper == 69) { // 8K
write_prg_byte(0x8000, 8); // Command Register - PRG Bank 0
write_prg_byte(0xA000, 0); // Parameter Register - PRG RAM Disabled, PRG ROM, Bank 0 to $6000-$7FFF
} else if (mapper == 85) // 8K
write_reg_byte(0xE000, 0); // PRG RAM DISABLE [WRITE RAM SAFE]
break;
}
myFile.close();
LED_GREEN_ON;
println_Msg(F(""));
println_Msg(F("RAM FILE WRITTEN!"));
display_Update();
} else {
print_Error(sd_error_STR, true);
}
}
LED_RED_OFF;
LED_GREEN_OFF;
sd.chdir(); // root
filePath[0] = '\0'; // Reset filePath
}
/******************************************
Eeprom Functions
*****************************************/
// EEPROM MAPPING
// 00-01 FOLDER #
// 02-05 SNES/GB READER SETTINGS
// 06 LED - ON/OFF [SNES/GB]
// 07 MAPPER
// 08 PRG SIZE
// 09 CHR SIZE
// 10 RAM SIZE
void resetEEPROM() {
EEPROM_writeAnything(0, 0); // FOLDER #
EEPROM_writeAnything(2, 0); // CARTMODE
EEPROM_writeAnything(3, 0); // RETRY
EEPROM_writeAnything(4, 0); // STATUS
EEPROM_writeAnything(5, 0); // UNKNOWNCRC
EEPROM_writeAnything(6, 1); // LED (RESET TO ON)
EEPROM_writeAnything(7, 0); // MAPPER
EEPROM_writeAnything(8, 0); // PRG SIZE
EEPROM_writeAnything(9, 0); // CHR SIZE
EEPROM_writeAnything(10, 0); // RAM SIZE
}
void EepromStart_NES() {
write_prg_byte(0x800D, 0x00); // sda low, scl low
write_prg_byte(0x800D, 0x60); // sda, scl high
write_prg_byte(0x800D, 0x20); // sda low, scl high
write_prg_byte(0x800D, 0x00); // START
}
void EepromStop_NES() {
write_prg_byte(0x800D, 0x00); // sda, scl low
write_prg_byte(0x800D, 0x20); // sda low, scl high
write_prg_byte(0x800D, 0x60); // sda, scl high
write_prg_byte(0x800D, 0x40); // sda high, scl low
write_prg_byte(0x800D, 0x00); // STOP
}
void EepromSet0_NES() {
write_prg_byte(0x800D, 0x00); // sda low, scl low
write_prg_byte(0x800D, 0x20); // sda low, scl high // 0
write_prg_byte(0x800D, 0x00); // sda low, scl low
}
void EepromSet1_NES() {
write_prg_byte(0x800D, 0x40); // sda high, scl low
write_prg_byte(0x800D, 0x60); // sda high, scl high // 1
write_prg_byte(0x800D, 0x40); // sda high, scl low
write_prg_byte(0x800D, 0x00); // sda low, scl low
}
void EepromStatus_NES() { // ACK
write_prg_byte(0x800D, 0x40); // sda high, scl low
write_prg_byte(0x800D, 0x60); // sda high, scl high
write_prg_byte(0x800D, 0xE0); // sda high, scl high, read high
byte eepStatus = 1;
do {
eepStatus = (read_prg_byte(0x6000) & 0x10) >> 4;
delayMicroseconds(4);
} while (eepStatus == 1);
write_prg_byte(0x800D, 0x40); // sda high, scl low
}
void EepromReadData_NES() {
// read serial data into buffer
for (int i = 0; i < 8; i++) {
write_prg_byte(0x800D, 0x60); // sda high, scl high, read low
write_prg_byte(0x800D, 0xE0); // sda high, scl high, read high
eepbit[i] = (read_prg_byte(0x6000) & 0x10) >> 4; // Read 0x6000 with Mask 0x10 (bit 4)
write_prg_byte(0x800D, 0x40); // sda high, scl low
}
}
void EepromDevice_NES() { // 24C02 ONLY
EepromSet1_NES();
EepromSet0_NES();
EepromSet1_NES();
EepromSet0_NES();
EepromSet0_NES(); // A2
EepromSet0_NES(); // A1
EepromSet0_NES(); // A0
}
void EepromReadMode_NES() {
EepromSet1_NES(); // READ
EepromStatus_NES(); // ACK
}
void EepromWriteMode_NES() {
EepromSet0_NES(); // WRITE
EepromStatus_NES(); // ACK
}
void EepromFinish_NES() {
write_prg_byte(0x800D, 0x00); // sda low, scl low
write_prg_byte(0x800D, 0x40); // sda high, scl low
write_prg_byte(0x800D, 0x60); // sda high, scl high
write_prg_byte(0x800D, 0x40); // sda high, scl low
write_prg_byte(0x800D, 0x00); // sda low, scl low
}
void EepromSetAddress01(byte address) { // 24C01 [Little Endian]
for (int i = 0; i < 7; i++) {
if (address & 0x1) // Bit is HIGH
EepromSet1_NES();
else // Bit is LOW
EepromSet0_NES();
address >>= 1; // rotate to the next bit
}
}
void EepromSetAddress02(byte address) { // 24C02
for (int i = 0; i < 8; i++) {
if ((address >> 7) & 0x1) // Bit is HIGH
EepromSet1_NES();
else // Bit is LOW
EepromSet0_NES();
address <<= 1; // rotate to the next bit
}
EepromStatus_NES(); // ACK
}
void EepromWriteData01() { // 24C01 [Little Endian]
for (int i = 0; i < 8; i++) {
if (eeptemp & 0x1) // Bit is HIGH
EepromSet1_NES();
else // Bit is LOW
EepromSet0_NES();
eeptemp >>= 1; // rotate to the next bit
}
EepromStatus_NES(); // ACK
}
void EepromWriteData02() { // 24C02
for (int i = 0; i < 8; i++) {
if ((eeptemp >> 7) & 0x1) // Bit is HIGH
EepromSet1_NES();
else // Bit is LOW
EepromSet0_NES();
eeptemp <<= 1; // rotate to the next bit
}
EepromStatus_NES(); // ACK
}
void EepromREAD(byte address) {
EepromStart_NES(); // START
if (mapper == 159) { // 24C01
EepromSetAddress01(address); // 24C01 [Little Endian]
EepromReadMode_NES();
EepromReadData_NES();
EepromFinish_NES();
EepromStop_NES(); // STOP
// OR 8 bits into byte
eeptemp = eepbit[7] << 7 | eepbit[6] << 6 | eepbit[5] << 5 | eepbit[4] << 4 | eepbit[3] << 3 | eepbit[2] << 2 | eepbit[1] << 1 | eepbit[0];
} else { // 24C02
EepromDevice_NES(); // DEVICE [1010] + ADDR [A2-A0]
EepromWriteMode_NES();
EepromSetAddress02(address);
EepromStart_NES(); // START
EepromDevice_NES(); // DEVICE [1010] + ADDR [A2-A0]
EepromReadMode_NES();
EepromReadData_NES();
EepromFinish_NES();
EepromStop_NES(); // STOP
// 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[address] = eeptemp;
}
void EepromWRITE(byte address) {
eeptemp = sdBuffer[address];
EepromStart_NES(); // START
if (mapper == 159) { // 24C01
EepromSetAddress01(address); // 24C01 [Little Endian]
EepromWriteMode_NES();
EepromWriteData01(); // 24C01 [Little Endian]
} else { // 24C02
EepromDevice_NES(); // DEVICE [1010] + ADDR [A2-A0]
EepromWriteMode_NES();
EepromSetAddress02(address);
EepromWriteData02();
}
EepromStop_NES(); // STOP
}
/******************************************
NESmaker Flash Cart [SST 39SF40]
*****************************************/
void NESmaker_ResetFlash() { // Reset Flash
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xAA);
write_prg_byte(0xC000, 0x00);
write_prg_byte(0xAAAA, 0x55);
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xFF); // Reset
}
// SST 39SF040 Software ID
void NESmaker_ID() { // Read Flash ID
NESmaker_ResetFlash();
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xAA);
write_prg_byte(0xC000, 0x00);
write_prg_byte(0xAAAA, 0x55);
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0x90); // Software ID Entry
flashid = read_prg_byte(0x8000) << 8;
flashid |= read_prg_byte(0x8001);
sprintf(flashid_str, "%04X", flashid);
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xAA);
write_prg_byte(0xC000, 0x00);
write_prg_byte(0xAAAA, 0x55);
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xF0); // Software ID Exit
if (flashid == 0xBFB7) // SST 39SF040
flashfound = 1;
}
void NESmaker_SectorErase(byte bank, word address) {
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xAA);
write_prg_byte(0xC000, 0x00);
write_prg_byte(0xAAAA, 0x55);
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0x80);
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xAA);
write_prg_byte(0xC000, 0x00);
write_prg_byte(0xAAAA, 0x55);
write_prg_byte(0xC000, bank); // $00-$1F
write_prg_byte(address, 0x30); // Sector Erase ($8000/$9000/$A000/$B000)
}
void NESmaker_ByteProgram(byte bank, word address, byte data) {
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xAA);
write_prg_byte(0xC000, 0x00);
write_prg_byte(0xAAAA, 0x55);
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xA0);
write_prg_byte(0xC000, bank); // $00-$1F
write_prg_byte(address, data); // $8000-$BFFF
}
// SST 39SF040 Chip Erase [NOT IMPLEMENTED]
void NESmaker_ChipErase() { // Typical 70ms
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xAA);
write_prg_byte(0xC000, 0x00);
write_prg_byte(0xAAAA, 0x55);
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0x80);
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0xAA);
write_prg_byte(0xC000, 0x00);
write_prg_byte(0xAAAA, 0x55);
write_prg_byte(0xC000, 0x01);
write_prg_byte(0x9555, 0x10); // Chip Erase
}
void writeFLASH() {
display_Clear();
if (!flashfound) {
LED_RED_ON;
println_Msg(F("FLASH NOT DETECTED"));
display_Update();
} else {
print_Msg(F("Flash ID: "));
println_Msg(flashid_str);
println_Msg(F(""));
println_Msg(F("NESmaker Flash Found"));
println_Msg(F(""));
display_Update();
delay(100);
fileBrowser(F("Select FLASH File"));
word base = 0x8000;
sd.chdir();
sprintf(filePath, "%s/%s", filePath, fileName);
LED_RED_ON;
display_Clear();
println_Msg(F("Writing File: "));
println_Msg(filePath);
println_Msg(fileName);
display_Update();
//open file on sd card
if (myFile.open(filePath, O_READ)) {
banks = int_pow(2, prgsize); // 256K/512K
for (int i = 0; i < banks; i++) { // 16K Banks
for (word sector = 0; sector < 0x4000; sector += 0x1000) { // 4K Sectors ($8000/$9000/$A000/$B000)
// Sector Erase
NESmaker_SectorErase(i, base + sector);
delay(18); // Typical 18ms
for (byte j = 0; j < 2; j++) { // Confirm erase twice
do {
bytecheck = read_prg_byte(base + sector);
delay(18);
} while (bytecheck != 0xFF);
}
// Program Byte
for (word addr = 0x0; addr < 0x1000; addr += 512) {
myFile.read(sdBuffer, 512);
for (int x = 0; x < 512; x++) {
word location = base + sector + addr + x;
NESmaker_ByteProgram(i, base + sector + addr + x, sdBuffer[x]);
delayMicroseconds(14); // Typical 14us
for (byte k = 0; k < 2; k++) { // Confirm write twice
do {
bytecheck = read_prg_byte(base + sector + addr + x);
delayMicroseconds(14);
} while (bytecheck != sdBuffer[x]);
}
}
}
}
#if (defined(enable_LCD) || defined(enable_OLED))
display.print(F("*"));
display.updateDisplay();
#else
Serial.print(F("*"));
if ((i != 0) && ((i + 1) % 16 == 0))
Serial.println(F(""));
#endif
}
myFile.close();
LED_GREEN_ON;
println_Msg(F(""));
println_Msg(F("FLASH FILE WRITTEN!"));
display_Update();
} else {
LED_RED_ON;
println_Msg(F("SD ERROR"));
display_Update();
}
}
display_Clear();
LED_RED_OFF;
LED_GREEN_OFF;
sd.chdir(); // root
filePath[0] = '\0'; // Reset filePath
}
#endif
//******************************************
// End of File
//******************************************