cartreader/Cart_Reader/MD.ino
PsyK0p4T 417e88d7d1
Update MD.ino
Force saveType to 0 for some aftermarket games not using SRAM
2024-02-25 13:58:06 +01:00

2923 lines
85 KiB
C++

//******************************************
// SEGA MEGA DRIVE MODULE
//******************************************
// Writes to Sega CD Backup RAM Cart require an extra wire from MRES (B02) to VRES (B27)
#ifdef enable_MD
/******************************************
Variables
*****************************************/
unsigned long sramEnd;
word eepSize;
word addrhi;
word addrlo;
word chksum;
boolean is32x = 0;
boolean isSVP = 0;
//***********************************************
// EEPROM SAVE TYPES
// 1 = Acclaim Type 1 [24C02]
// 2 = Acclaim Type 2 [24C02/24C16/24C65]
// 3 = Capcom/SEGA [24C01]
// 4 = EA [24C01]
// 5 = Codemasters [24C08/24C16/24C65]
//***********************************************
byte eepType;
//*********************************************************
// SERIAL EEPROM LOOKUP TABLE
// Format = {chksum, eepType | eepSize}
// chksum is located in ROM at 0x18E (0xC7)
// eepType and eepSize are combined to conserve memory
//*********************************************************
static const word PROGMEM eepid[] = {
// ACCLAIM TYPE 1
0x5B9F, 0x101, // NBA Jam (J)
0x694F, 0x101, // NBA Jam (UE) (Rev 0)
0xBFA9, 0x101, // NBA Jam (UE) (Rev 1)
// ACCLAIM TYPE 2
0x16B2, 0x102, // Blockbuster World Videogame Championship II (U) [NO HEADER SAVE DATA]
0xCC3F, 0x102, // NBA Jam Tournament Edition (W) (Rev 0) [NO HEADER SAVE DATA]
0x8AE1, 0x102, // NBA Jam Tournament Edition (W) (Rev 1) [NO HEADER SAVE DATA]
0xDB97, 0x102, // NBA Jam Tournament Edition 32X (W)
0x7651, 0x102, // NFL Quarterback Club (W)
0xDFE4, 0x102, // NFL Quarterback Club 32X (W)
0x3DE6, 0x802, // NFL Quarterback Club '96 (UE)
0xCB78, 0x2002, // Frank Thomas Big Hurt Baseball (UE)
0x6DD9, 0x2002, // College Slam (U)
// CAPCOM
0xAD23, 0x83, // Mega Man: The Wily Wars (E)
0xEA80, 0x83, // Rockman Megaworld (J)
// SEGA
0x760F, 0x83, // Evander "Real Deal" Holyfield Boxing (JU)
0x95E7, 0x83, // Greatest Heavyweights of the Ring (E)
0x0000, 0x83, // Greatest Heavyweights of the Ring (J) [BLANK CHECKSUM 0000]
0x7270, 0x83, // Greatest Heavyweights of the Ring (U)
0xBACC, 0x83, // Honoo no Toukyuuji Dodge Danpei (J)
0xB939, 0x83, // MLBPA Sports Talk Baseball (U) [BAD HEADER SAVE DATA]
0x487C, 0x83, // Ninja Burai Densetsu (J)
0x740D, 0x83, // Wonder Boy in Monster World (B)
0x0278, 0x83, // Wonder Boy in Monster World (J)
0x9D79, 0x83, // Wonder Boy in Monster World (UE)
// EA
0x8512, 0x84, // Bill Walsh College Football (UE) [BAD HEADER SAVE DATA]
0xA107, 0x84, // John Madden Football '93 (UE) [NO HEADER SAVE DATA]
0x246A, 0x84, // John Madden Football '93 (U) (EA Sports) [NO HEADER SAVE DATA]
0x5807, 0x84, // John Madden Football '93 Championship Edition (U) [NO HEADER SAVE DATA]
0x2799, 0x84, // NHLPA Hockey '93 (UE) (Rev 0) [NO HEADER SAVE DATA]
0xFA57, 0x84, // NHLPA Hockey '93 (UE) (Rev 1) [NO HEADER SAVE DATA]
0x8B9F, 0x84, // Rings of Power (UE) [NO HEADER SAVE DATA]
// CODEMASTERS
0x7E65, 0x405, // Brian Lara Cricket (E) [NO HEADER SAVE DATA]
0x9A5C, 0x2005, // Brian Lara Cricket 96 (E) (Rev 1.0) [NO HEADER SAVE DATA]
0xC4EE, 0x2005, // Brian Lara Cricket 96 (E) (Rev 1.1) [NO HEADER SAVE DATA]
0x7E50, 0x805, // Micro Machines 2 (E) (J-Cart) [NO HEADER SAVE DATA]
0x165E, 0x805, // Micro Machines '96 (E) (J-Cart) (Rev 1.0/1.1) [NO HEADER SAVE DATA]
0x168B, 0x405, // Micro Machines Military (E) (J-Cart) [NO HEADER SAVE DATA]
0x12C1, 0x2005, // Shane Warne Cricket (E) [NO HEADER SAVE DATA]
};
byte eepcount = (sizeof(eepid) / sizeof(eepid[0])) / 2;
word eepdata;
// CD BACKUP RAM
unsigned long bramSize = 0;
// REALTEC MAPPER
boolean realtec = 0;
#ifndef DEFAULT_VALUE_segaSram16bit
#define DEFAULT_VALUE_segaSram16bit 0
#endif
int segaSram16bit = DEFAULT_VALUE_segaSram16bit;
//*****************************************
// SONIC & KNUCKLES LOCK-ON MODE VARIABLES
// SnKmode :
// 0 = Not Sonic & Knuckles
// 1 = Only Sonic & Knucles
// 2 = Sonic & Knucles + Sonic1
// 3 = Sonic & Knucles + Sonic2
// 4 = Sonic & Knucles + Sonic3
// 5 = Sonic & Knucles + Other game
//*****************************************
static byte SnKmode = 0;
static unsigned long cartSizeLockon;
static unsigned long cartSizeSonic2 = 262144;
static word chksumLockon;
static word chksumSonic2 = 0x0635;
/******************************************
Configuration
*****************************************/
#ifdef use_md_conf
void mdLoadConf() {
if (myFile.open("mdconf.txt", O_READ)) {
char line[64];
int n;
int i;
while ((n = myFile.fgets(line, sizeof(line) - 1)) > 0) {
// preprocess
for (i = 0; i < n; i++) {
if (line[i] == ';') {
// comments
line[i] = '\0';
n = i;
break;
} else if (line[i] == '\n' || line[i] == '\r') {
// EOL
line[n] = '\0';
n = i;
break;
}
}
//print_Msg(F("read line: "));
//println_Msg(line);
if (line[0] == '[') {
char* name;
char* value;
i = 1;
name = line + i;
for (; i < sizeof(line); i++) {
if (line[i] == ']') {
line[i] = '\0';
i++;
break;
}
}
if (line[i] != '\0') {
for (; i < sizeof(line); i++) {
if (line[i] != ' ') {
value = line + i;
i++;
break;
}
}
for (; i < sizeof(line) && line[i] != '\0'; i++) {
if (line[i] == ' ') {
line[i] = '\0';
break;
}
}
}
//print_Msg(F("read name: "));
//println_Msg(name);
//print_Msg(F("value: "));
//println_Msg(value);
if (!strcmp("segaSram16bit", name)) {
// Retrode compatible setting
// [segaSram16bit] 1 ; 0=no, 1=yes, 2=y+large
// 0: Output each byte once. Not supported by most emulators.
// 1: Duplicate each byte. Usable by Kega Fusion.
// 2: Duplicate each byte. Pad with 0xFF so that the file size is 64KB.
segaSram16bit = atoi(value);
if (segaSram16bit != 0 && segaSram16bit != 1 && segaSram16bit != 2) {
segaSram16bit = DEFAULT_VALUE_segaSram16bit;
}
print_Msg(F("segaSram16bit: "));
println_Msg(segaSram16bit);
}
}
}
myFile.close();
}
}
#endif
void pulse_clock(int n) {
for (int i = 0; i < n; i++)
PORTH ^= (1 << 1);
}
/******************************************
Menu
*****************************************/
// MD menu items
static const char MDMenuItem1[] PROGMEM = "Game Cartridge";
static const char MDMenuItem2[] PROGMEM = "SegaCD RamCart";
static const char MDMenuItem3[] PROGMEM = "Flash Repro";
//static const char MDMenuItem4[] PROGMEM = "Reset"; (stored in common strings array)
static const char* const menuOptionsMD[] PROGMEM = { MDMenuItem1, MDMenuItem2, MDMenuItem3, string_reset2 };
// Cart menu items
static const char MDCartMenuItem1[] PROGMEM = "Read Rom";
static const char MDCartMenuItem2[] PROGMEM = "Read Sram";
static const char MDCartMenuItem3[] PROGMEM = "Write Sram";
static const char MDCartMenuItem4[] PROGMEM = "Read EEPROM";
static const char MDCartMenuItem5[] PROGMEM = "Write EEPROM";
static const char MDCartMenuItem6[] PROGMEM = "Cycle cart";
//static const char MDCartMenuItem7[] PROGMEM = "Reset"; (stored in common strings array)
static const char* const menuOptionsMDCart[] PROGMEM = { MDCartMenuItem1, MDCartMenuItem2, MDCartMenuItem3, MDCartMenuItem4, MDCartMenuItem5, MDCartMenuItem6, string_reset2 };
// Sega CD Ram Backup Cartridge menu items
static const char SCDMenuItem1[] PROGMEM = "Read Backup RAM";
static const char SCDMenuItem2[] PROGMEM = "Write Backup RAM";
//static const char SCDMenuItem3[] PROGMEM = "Reset"; (stored in common strings array)
static const char* const menuOptionsSCD[] PROGMEM = { SCDMenuItem1, SCDMenuItem2, string_reset2 };
// Sega start menu
void mdMenu() {
// create menu with title and 4 options to choose from
unsigned char mdDev;
// Copy menuOptions out of progmem
convertPgm(menuOptionsMD, 4);
mdDev = question_box(F("Select MD device"), menuOptions, 4, 0);
// wait for user choice to come back from the question box menu
switch (mdDev) {
case 0:
display_Clear();
display_Update();
setup_MD();
mode = mode_MD_Cart;
break;
case 1:
display_Clear();
display_Update();
setup_MD();
mode = mode_SEGA_CD;
break;
#ifdef enable_FLASH
case 2:
display_Clear();
display_Update();
setup_MD();
mode = mode_MD_Cart;
// Change working dir to root
filePath[0] = '\0';
sd.chdir("/");
fileBrowser(F("Select file"));
display_Clear();
// Setting CS(PH3) LOW
PORTH &= ~(1 << 3);
// ID flash
resetFlash_MD();
idFlash_MD();
resetFlash_MD();
print_Msg(F("Flash ID: "));
println_Msg(flashid_str);
if (flashid == 0xC2F1) {
println_Msg(F("MX29F1610 detected"));
flashSize = 2097152;
} else {
print_FatalError(F("Error: Unknown flashrom"));
}
display_Update();
eraseFlash_MD();
resetFlash_MD();
blankcheck_MD();
write29F1610_MD();
resetFlash_MD();
delay(1000);
resetFlash_MD();
delay(1000);
verifyFlash_MD();
// Set CS(PH3) HIGH
PORTH |= (1 << 3);
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;
#endif
case 3:
resetArduino();
break;
}
}
void mdCartMenu() {
// create menu with title and 6 options to choose from
unsigned char mainMenu;
// Copy menuOptions out of progmem
convertPgm(menuOptionsMDCart, 7);
mainMenu = question_box(F("MEGA DRIVE Reader"), menuOptions, 7, 0);
// wait for user choice to come back from the question box menu
switch (mainMenu) {
case 0:
display_Clear();
// common ROM read fail state: no cart inserted - tends to report impossibly large cartSize
// largest known game so far is supposedly "Paprium" at 10MB, so cap sanity check at 16MB
if (cartSize != 0 && cartSize <= 16777216) {
// Change working dir to root
sd.chdir("/");
if (realtec) {
readRealtec_MD();
} else {
readROM_MD();
// Calculate and compare CRC32 with database
if (is32x)
//database, crcString, renamerom, offset
compareCRC("32x.txt", 0, 1, 0);
else
compareCRC("md.txt", 0, 1, 0);
}
} else {
print_Error(F("Cart has no ROM"));
}
#ifdef global_log
save_log();
#endif
break;
case 1:
display_Clear();
// Does cartridge have SRAM
if ((saveType == 1) || (saveType == 2) || (saveType == 3)) {
// Change working dir to root
sd.chdir("/");
println_Msg(F("Reading Sram..."));
display_Update();
enableSram_MD(1);
readSram_MD();
enableSram_MD(0);
} else {
print_Error(F("Cart has no Sram"));
}
break;
case 2:
display_Clear();
// Does cartridge have SRAM
if ((saveType == 1) || (saveType == 2) || (saveType == 3)) {
// Change working dir to root
sd.chdir("/");
// Launch file browser
fileBrowser(F("Select srm file"));
display_Clear();
enableSram_MD(1);
writeSram_MD();
writeErrors = verifySram_MD();
enableSram_MD(0);
if (writeErrors == 0) {
println_Msg(F("Sram verified OK"));
display_Update();
} else {
print_STR(error_STR, 0);
print_Msg(writeErrors);
print_STR(_bytes_STR, 1);
print_Error(did_not_verify_STR);
}
} else {
print_Error(F("Cart has no Sram"));
}
break;
case 3:
display_Clear();
if (saveType == 4)
readEEP_MD();
else {
print_Error(F("Cart has no EEPROM"));
}
break;
case 4:
display_Clear();
if (saveType == 4) {
// Launch file browser
fileBrowser(F("Select eep file"));
display_Clear();
writeEEP_MD();
} else {
print_Error(F("Cart has no EEPROM"));
}
break;
case 5:
// For multi-game carts
// Set reset pin to output (PH0)
DDRH |= (1 << 0);
// Switch RST(PH0) to LOW
PORTH &= ~(1 << 0);
display_Clear();
print_Msg(F("Resetting..."));
display_Update();
delay(3000); // wait 3 secs to switch to next game
resetArduino();
break;
case 6:
// Reset
resetArduino();
break;
}
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
}
void segaCDMenu() {
// create menu with title and 3 options to choose from
unsigned char scdMenu;
// Copy menuOptions out of progmem
convertPgm(menuOptionsSCD, 3);
scdMenu = question_box(F("SEGA CD RAM"), menuOptions, 3, 0);
// wait for user choice to come back from the question box menu
switch (scdMenu) {
case 0:
display_Clear();
if (bramSize > 0)
readBram_MD();
else {
print_Error(F("Not CD Backup RAM Cart"));
}
break;
case 1:
display_Clear();
if (bramSize > 0) {
// Launch file browser
fileBrowser(F("Select brm file"));
display_Clear();
writeBram_MD();
} else {
print_Error(F("Not CD Backup RAM Cart"));
}
break;
case 2:
// Reset
asm volatile(" jmp 0");
break;
}
println_Msg(F(""));
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
}
/******************************************
Setup
*****************************************/
void setup_MD() {
// Request 5V
setVoltage(VOLTS_SET_5V);
#ifdef use_md_conf
mdLoadConf();
#endif
// Set Address Pins to Output
//A0-A7
DDRF = 0xFF;
//A8-A15
DDRK = 0xFF;
//A16-A23
DDRL = 0xFF;
// Set Control Pins to Output RST(PH0) CLK(PH1) CS(PH3) WRH(PH4) WRL(PH5) OE(PH6)
DDRH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
// Set TIME(PJ0), AS(PJ1) to Output
DDRJ |= (1 << 0) | (1 << 1);
//set ASEL(PG5) to Output
DDRG |= (1 << 5);
// Set Data Pins (D0-D15) to Input
DDRC = 0x00;
DDRA = 0x00;
// Setting RST(PH0) CS(PH3) WRH(PH4) WRL(PH5) OE(PH6) HIGH
PORTH |= (1 << 0) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
// Setting TIME(PJ0) AS(PJ1) HIGH
PORTJ |= (1 << 0) | (1 << 1);
// setting ASEL(PG5) HIGH
PORTG |= (1 << 5);
delay(200);
// Print all the info
getCartInfo_MD();
}
/******************************************
I/O Functions
*****************************************/
/******************************************
Low level functions
*****************************************/
void writeWord_MD(unsigned long myAddress, word myData) {
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTL = (myAddress >> 16) & 0xFF;
PORTC = myData;
PORTA = (myData >> 8) & 0xFF;
// Arduino running at 16Mhz -> one nop = 62.5ns
// Wait till output is stable
__asm__("nop\n\t"
"nop\n\t");
// Switch WR(PH5) to LOW
PORTH &= ~(1 << 5);
// Setting CS(PH3) LOW
PORTH &= ~(1 << 3);
// Leave WR low for at least 200ns
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
// Setting CS(PH3) HIGH
PORTH |= (1 << 3);
// Switch WR(PH5) to HIGH
PORTH |= (1 << 5);
// Leave WR high for at least 50ns
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
}
word readWord_MD(unsigned long myAddress) {
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTL = (myAddress >> 16) & 0xFF;
// Arduino running at 16Mhz -> one nop = 62.5ns
NOP;
// Setting CS(PH3) LOW
PORTH &= ~(1 << 3);
// Setting OE(PH6) LOW
PORTH &= ~(1 << 6);
// Setting AS(PJ1) LOW
PORTJ &= ~(1 << 1);
// Setting ASEL(PG5) LOW
PORTG &= ~(1 << 5);
// Pulse CLK(PH1), needed for SVP enhanced carts
pulse_clock(10);
// most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
// Read
word tempWord = ((PINA & 0xFF) << 8) | (PINC & 0xFF);
// Setting CS(PH3) HIGH
PORTH |= (1 << 3);
// Setting OE(PH6) HIGH
PORTH |= (1 << 6);
// Setting AS(PJ1) HIGH
PORTJ |= (1 << 1);
// Setting ASEL(PG5) HIGH
PORTG |= (1 << 5);
// Pulse CLK(PH1), needed for SVP enhanced carts
pulse_clock(10);
// these 6x nop delays have been here before, unknown what they mean
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
return tempWord;
}
void writeFlash_MD(unsigned long myAddress, word myData) {
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTL = (myAddress >> 16) & 0xFF;
PORTC = myData;
PORTA = (myData >> 8) & 0xFF;
// Arduino running at 16Mhz -> one nop = 62.5ns
// Wait till output is stable
__asm__("nop\n\t");
// Switch WE(PH5) to LOW
PORTH &= ~(1 << 5);
// Leave WE low for at least 60ns
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
// Switch WE(PH5)to HIGH
PORTH |= (1 << 5);
// Leave WE high for at least 50ns
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
}
word readFlash_MD(unsigned long myAddress) {
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTL = (myAddress >> 16) & 0xFF;
// Arduino running at 16Mhz -> one nop = 62.5ns
__asm__("nop\n\t");
// Setting OE(PH6) LOW
PORTH &= ~(1 << 6);
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
// Read
word tempWord = ((PINA & 0xFF) << 8) | (PINC & 0xFF);
__asm__("nop\n\t");
// Setting OE(PH6) HIGH
PORTH |= (1 << 6);
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
return tempWord;
}
// Switch data pins to write
void dataOut_MD() {
DDRC = 0xFF;
DDRA = 0xFF;
}
// Switch data pins to read
void dataIn_MD() {
DDRC = 0x00;
DDRA = 0x00;
}
/******************************************
MEGA DRIVE functions
*****************************************/
byte copyToRomName_MD(char* output, const byte* input, byte length) {
byte myLength = 0;
for (byte i = 0; i < 48; i++) {
if (((input[i] >= '0' && input[i] <= '9') || (input[i] >= 'A' && input[i] <= 'z')) && myLength < length) {
output[myLength++] = input[i];
}
}
return myLength;
}
void getCartInfo_MD() {
// Set control
dataIn_MD();
// Get cart size
cartSize = ((long(readWord_MD(0xD2)) << 16) | readWord_MD(0xD3)) + 1;
// Check for 32x compatibility
if ((readWord_MD(0x104 / 2) == 0x2033) && (readWord_MD(0x106 / 2) == 0x3258))
is32x = 1;
else
is32x = 0;
// Get cart checksum
chksum = readWord_MD(0xC7);
// Get cart ID
char id[15];
memset(id, 0, 15);
for (byte c = 0; c < 14; c += 2) {
// split word
word myWord = readWord_MD((0x180 + c) / 2);
byte loByte = myWord & 0xFF;
byte hiByte = myWord >> 8;
// write to buffer
id[c] = hiByte;
id[c + 1] = loByte;
}
//Identify games using SVP chip
if (!strncmp("GM MK-1229 ", id, 11) || !strncmp("GM G-7001 ", id, 11)) // Virtua Racing (E/U/J)
isSVP = 1;
else
isSVP = 0;
// Fix cartridge sizes according to no-intro database
if (cartSize == 0x400000) {
switch (chksum) {
case 0xCE25: // Super Street Fighter 2 (J) 40Mbit
case 0xE41D: // Super Street Fighter 2 (E) 40Mbit
case 0xE017: // Super Street Fighter 2 (U) 40Mbit
cartSize = 0x500000;
break;
case 0x0000: // Demons of Asteborg v1.0 (W) 120Mbit
cartSize = 0xEAF2F4;
break;
case 0xBCBF: // Demons of Asteborg v1.1 (W) 120Mbit
case 0x6E1E: // Demons of Asteborg v1.11 (W) 120Mbit
cartSize = 0xEA0000;
break;
}
}
if (cartSize == 0x300000) {
switch (chksum) {
case 0xBC5F: //Batman Forever (World)
case 0x3CDD: //Donald in Maui Mallard (Brazil) (En)
case 0x44AD: //Donald in Maui Mallard (Europe) (Rev A)
case 0x2D9A: //Foreman for Real (World)
case 0x5648: //Justice League Task Force (World)
case 0x0A29: //Mega 6 Vol. 3 (Europe)
case 0x7651: //NFL Quarterback Club (World)
case 0x74CA: //WWF RAW (World)
cartSize = 0x400000;
break;
}
}
if (cartSize == 0x200000) {
switch (chksum) {
case 0x2078: //Dynamite Headdy (USA, Europe)
chksum = 0x9877;
break;
case 0xAE95: //Winter Olympic Games (USA)
chksum = 0x56A0;
break;
}
}
if (cartSize == 0x180000) {
switch (chksum) {
case 0xFFE2: //Cannon Fodder (Europe)
case 0xF418: //Chaos Engine, The (Europe)
case 0xF71D: //Fatal Fury (Europe, Korea) (En)
case 0xA884: //Flashback (Europe) (En,Fr)
case 0x7D68: //Flashback - The Quest for Identity (USA) (En,Fr)
case 0x030D: //Shining Force (Europe)
case 0xE975: //Shining Force (USA)
cartSize = 0x200000;
break;
}
}
if (cartSize == 0x100000) {
switch (chksum) {
case 0xCDF5: //Life on Mars (Aftermarket)
cartSize = 0x400000;
chksum = 0x603A;
break;
case 0xF85F: //Metal Dragon (Aftermarket)
cartSize = 0x200000;
chksum = 0x6965;
break;
}
}
if (cartSize == 0xC0000) {
switch (chksum) {
case 0x9D79: //Wonder Boy in Monster World (USA, Europe)
cartSize = 0x100000;
break;
}
}
if (cartSize == 0x80000) {
switch (chksum) {
case 0x5B3A: //NHL 98 (USA)
cartSize = 0x200000;
chksum = 0x5613;
break;
case 0xD07D: //Zero Wing (Japan)
cartSize = 0x100000;
chksum = 0xF204;
break;
case 0x95C9: //Zero Wing (Europe)
case 0x9144: //Zoop (Europe)
case 0xB8D4: //Zoop (USA)
cartSize = 0x100000;
break;
case 0xC422: //Jeopardy! (USA)
chksum = 0xC751;
break;
case 0x0C6A: //Monopoly (USA)
chksum = 0xE1AA;
break;
case 0xA760: //Gain Ground (USA)
chksum = 0x97CD;
break;
case 0x1404: //Wonder Boy III - Monster Lair (Japan, Europe) (En)
chksum = 0x53B9;
break;
}
}
if (cartSize == 0x40000) {
switch (chksum) {
case 0x8BC6: //Pac-Attack (USA)
case 0xB344: //Pac-Panic (Europe)
cartSize = 0x100000;
break;
}
}
if (cartSize == 0x20000) {
switch (chksum) {
case 0x7E50: //Micro Machines 2 - Turbo Tournament (Europe)
cartSize = 0x100000;
chksum = 0xD074;
break;
case 0x168B: //Micro Machines - Military (Europe)
cartSize = 0x100000;
chksum = 0xCEE0;
break;
}
}
// Fatman (Japan).md
if (!strncmp("GM T-44013 ", id, 11) && (chksum == 0xFFFF)) {
chksum = 0xC560;
cartSize = 0xA0000;
}
// Beggar Prince (Rev 1)(Aftermarket)
if (!strncmp("SF-001", id, 6) && (chksum == 0x3E08)) {
cartSize = 0x400000;
}
// Legend of Wukong (Aftermarket)
if (!strncmp("SF-002", id, 6) && (chksum == 0x12B0)) {
chksum = 0x45C6;
}
//YM2612 Instrument Editor (Aftermarket)
if (!strncmp("GM 10101010", id, 11) && (chksum == 0xC439)) {
chksum = 0x21B0;
cartSize = 0x100000;
}
//Technoptimistic (Aftermarket)
if (!strncmp("MU REMUTE01", id, 11) && (chksum == 0x0000)) {
chksum = 0xB55C;
cartSize = 0x400000;
}
//Decoder (Aftermarket)
if (!strncmp("GM REMUTE02", id, 11) && (chksum == 0x0000)) {
chksum = 0x5426;
cartSize = 0x400000;
}
//Handy Harvy (Aftermarket)
if (!strncmp("GM HHARVYSG", id, 11) && (chksum == 0x0000)) {
chksum = 0xD9D2;
cartSize = 0x100000;
}
//Jim Power - The Lost Dimension in 3D (Aftermarket)
if (!strncmp("GM T-107036", id, 11) && (chksum == 0x0000)) {
chksum = 0xAA28;
}
//mikeyeldey95 (Aftermarket)
if (!strncmp("GM 00000000-43", id, 14) && (chksum == 0x0000)) {
chksum = 0x921B;
cartSize = 0x400000;
}
//Enryuu Seiken Xiao-Mei (Aftermarket)
if (!strncmp("GM 00000000-00", id, 14) && (chksum == 0x1E0C)) {
chksum = 0xE7E5;
cartSize = 0x400000;
}
// Life on Earth - Reimagined (Aftermarket)
if (!strncmp("GM 00000000-00", id, 14) && (chksum == 0x6BD5)) {
chksum = 0x1FEA;
cartSize = 0x400000;
}
// Sasha Darko's Sacred Line I (Aftermarket)
if (!strncmp("GM 00000005-00", id, 14) && (chksum == 0x9F34)) {
chksum = 0xA094;
cartSize = 0x400000;
}
// Sasha Darko's Sacred Line II (Aftermarket)
if (!strncmp("GM 00000005-00", id, 14) && (chksum == 0x0E9B)) {
chksum = 0x6B4B;
cartSize = 0x400000;
}
// Sonic & Knuckles Check
SnKmode = 0;
if (chksum == 0xDFB3) {
//Sonic & Knuckles ID:GM MK-1563 -00
if (!strcmp("GM MK-1563 -00", id)) {
char labelLockon[17];
memset(labelLockon, 0, 17);
// Get labelLockon
for (byte c = 0; c < 16; c += 2) {
// split word
word myWord = readWord_MD((0x200100 + c) / 2);
byte loByte = myWord & 0xFF;
byte hiByte = myWord >> 8;
// write to buffer
labelLockon[c] = hiByte;
labelLockon[c + 1] = loByte;
}
// check Lock-on game presence
if (!(strcmp("SEGA MEGA DRIVE ", labelLockon) & strcmp("SEGA GENESIS ", labelLockon))) {
char idLockon[15];
memset(idLockon, 0, 15);
// Lock-on cart checksum
chksumLockon = readWord_MD(0x1000C7);
// Lock-on cart size
cartSizeLockon = ((long(readWord_MD(0x1000D2)) << 16) | readWord_MD(0x1000D3)) + 1;
// Get IdLockon
for (byte c = 0; c < 14; c += 2) {
// split word
word myWord = readWord_MD((0x200180 + c) / 2);
byte loByte = myWord & 0xFF;
byte hiByte = myWord >> 8;
// write to buffer
idLockon[c] = hiByte;
idLockon[c + 1] = loByte;
}
if (!strncmp("GM 00001009-0", idLockon, 13) || !strncmp("GM 00004049-0", idLockon, 13)) {
//Sonic1 ID:GM 00001009-0? or GM 00004049-0?
SnKmode = 2;
} else if (!strcmp("GM 00001051-00", idLockon) || !strcmp("GM 00001051-01", idLockon) || !strcmp("GM 00001051-02", idLockon)) {
//Sonic2 ID:GM 00001051-00 or GM 00001051-01 or GM 00001051-02
SnKmode = 3;
// Prepare Sonic2 Banks
writeSSF2Map(0x509878, 1); // 0xA130F1
} else if (!strcmp("GM MK-1079 -00", idLockon)) {
//Sonic3 ID:GM MK-1079 -00
SnKmode = 4;
} else {
//Other game
SnKmode = 5;
}
} else {
SnKmode = 1;
}
}
}
// Serial EEPROM Check
for (int i = 0; i < eepcount; i++) {
int index = i * 2;
word eepcheck = pgm_read_word(eepid + index);
if (eepcheck == chksum) {
eepdata = pgm_read_word(eepid + index + 1);
eepType = eepdata & 0x7;
eepSize = eepdata & 0xFFF8;
saveType = 4; // SERIAL EEPROM
break;
}
}
// Greatest Heavyweights of the Ring (J) has blank chksum 0x0000
// Other non-save carts might have the same blank chksum
// Check header for Serial EEPROM Data
if (chksum == 0x0000) {
if (readWord_MD(0xD9) != 0xE840) { // NOT SERIAL EEPROM
eepType = 0;
eepSize = 0;
saveType = 0;
}
}
// Codemasters EEPROM Check
// Codemasters used the same incorrect header across multiple carts
// Carts with checksum 0x165E or 0x168B could be EEPROM or non-EEPROM
// Check the first DWORD in ROM (0x444E4C44) to identify the EEPROM carts
if ((chksum == 0x165E) || (chksum == 0x168B)) {
if (readWord_MD(0x00) != 0x444E) { // NOT SERIAL EEPROM
eepType = 0;
eepSize = 0;
saveType = 0;
}
}
// CD Backup RAM Cart Check
// 4 = 128KB (2045 Blocks) Sega CD Backup RAM Cart
// 6 = 512KB (8189 Blocks) Ultra CD Backup RAM Cart (Aftermarket)
word bramCheck = readWord_MD(0x00);
if ((((bramCheck & 0xFF) == 0x04) && ((chksum & 0xFF) == 0x04)) || (((bramCheck & 0xFF) == 0x06) && ((chksum & 0xFF) == 0x06))) {
unsigned long p = 1 << (bramCheck & 0xFF);
bramSize = p * 0x2000L;
}
if (saveType != 4) { // NOT SERIAL EEPROM
// Check if cart has sram
saveType = 0;
sramSize = 0;
// FIXED CODE FOR SRAM/FRAM/PARALLEL EEPROM
// 0x5241F820 SRAM (ODD BYTES/EVEN BYTES)
// 0x5241F840 PARALLEL EEPROM - READ AS SRAM
// 0x5241E020 SRAM (BOTH BYTES)
if (readWord_MD(0xD8) == 0x5241) {
word sramType = readWord_MD(0xD9);
if ((sramType == 0xF820) || (sramType == 0xF840)) { // SRAM/FRAM ODD/EVEN BYTES
// Get sram start and end
sramBase = ((long(readWord_MD(0xDA)) << 16) | readWord_MD(0xDB));
sramEnd = ((long(readWord_MD(0xDC)) << 16) | readWord_MD(0xDD));
if (sramBase == 0x20000020 && sramEnd == 0x00010020) { // Fix for Psy-o-blade
sramBase = 0x200001;
sramEnd = 0x203fff;
}
// Check alignment of sram
// 0x300001 for HARDBALL '95 (U)
// 0x3C0001 for Legend of Wukong (Aftermarket)
if ((sramBase == 0x200001) || (sramBase == 0x300001) || (sramBase == 0x3C0001)) {
// low byte
saveType = 1; // ODD
sramSize = (sramEnd - sramBase + 2) / 2;
// Right shift sram base address so [A21] is set to high 0x200000 = 0b001[0]00000000000000000000
sramBase = sramBase >> 1;
} else if (sramBase == 0x200000) {
// high byte
saveType = 2; // EVEN
sramSize = (sramEnd - sramBase + 1) / 2;
// Right shift sram base address so [A21] is set to high 0x200000 = 0b001[0]00000000000000000000
sramBase = sramBase / 2;
} else {
print_Msg(("sramType: "));
print_Msg_PaddedHex16(sramType);
println_Msg(F(""));
print_Msg(("sramBase: "));
print_Msg_PaddedHex32(sramBase);
println_Msg(F(""));
print_Msg(("sramEnd: "));
print_Msg_PaddedHex32(sramEnd);
println_Msg(F(""));
print_FatalError(F("Unknown Sram Base"));
}
} else if (sramType == 0xE020) { // SRAM BOTH BYTES
// Get sram start and end
sramBase = ((long(readWord_MD(0xDA)) << 16) | readWord_MD(0xDB));
sramEnd = ((long(readWord_MD(0xDC)) << 16) | readWord_MD(0xDD));
if (sramBase == 0x200001) {
saveType = 3; // BOTH
sramSize = sramEnd - sramBase + 2;
sramBase = sramBase >> 1;
} else if (sramBase == 0x200000) {
saveType = 3; // BOTH
sramSize = sramEnd - sramBase + 1;
sramBase = sramBase >> 1;
} else if (sramBase == 0x3FFC00) {
// Used for some aftermarket carts without sram
saveType = 0;
}else {
print_Msg(("sramType: "));
print_Msg_PaddedHex16(sramType);
println_Msg(F(""));
print_Msg(("sramBase: "));
print_Msg_PaddedHex32(sramBase);
println_Msg(F(""));
print_Msg(("sramEnd: "));
print_Msg_PaddedHex32(sramEnd);
println_Msg(F(""));
print_FatalError(F("Unknown Sram Base"));
}
}
} else {
// SRAM CARTS WITH BAD/MISSING HEADER SAVE DATA
switch (chksum) {
case 0xC2DB: // Winter Challenge (UE)
saveType = 1; // ODD
sramBase = 0x200001;
sramEnd = 0x200FFF;
break;
case 0xD7B6: // Buck Rogers: Countdown to Doomsday (UE)
case 0xFE3E: // NBA Live '98 (U)
case 0xFDAD: // NFL '94 starring Joe Montana (U)
case 0x632E: // PGA Tour Golf (UE) (Rev 1)
case 0xD2BA: // PGA Tour Golf (UE) (Rev 2)
case 0x44FE: // Super Hydlide (J)
saveType = 1; // ODD
sramBase = 0x200001;
sramEnd = 0x203FFF;
break;
case 0xDB5E: // Might & Magic: Gates to Another World (UE) (Rev 1)
case 0x3428: // Starflight (UE) (Rev 0)
case 0x43EE: // Starflight (UE) (Rev 1)
saveType = 3; // BOTH
sramBase = 0x200001;
sramEnd = 0x207FFF;
break;
case 0xBF72: // College Football USA '96 (U)
case 0x72EF: // FIFA Soccer '97 (UE)
case 0xD723: // Hardball III (U)
case 0x06C1: // Madden NFL '98 (U)
case 0xDB17: // NHL '96 (UE)
case 0x5B3A: // NHL '98 (U)
case 0x2CF2: // NFL Sports Talk Football '93 starring Joe Montana (UE)
case 0xE9B1: // Summer Challenge (U)
case 0xEEE8: // Test Drive II: The Duel (U)
saveType = 1; // ODD
sramBase = 0x200001;
sramEnd = 0x20FFFF;
break;
}
if (saveType == 1) {
sramSize = (sramEnd - sramBase + 2) / 2;
sramBase = sramBase >> 1;
} else if (saveType == 3) {
sramSize = sramEnd - sramBase + 2;
sramBase = sramBase >> 1;
}
}
}
// Get name
for (byte c = 0; c < 48; c += 2) {
// split word
word myWord = readWord_MD((0x150 + c) / 2);
byte loByte = myWord & 0xFF;
byte hiByte = myWord >> 8;
// write to buffer
sdBuffer[c] = hiByte;
sdBuffer[c + 1] = loByte;
}
romName[copyToRomName_MD(romName, sdBuffer, sizeof(romName) - 1)] = 0;
//Get Lock-on cart name
if (SnKmode >= 2) {
char romNameLockon[12];
//Change romName
strcpy(romName, "SnK_");
for (byte c = 0; c < 48; c += 2) {
// split word
word myWord = readWord_MD((0x200150 + c) / 2);
byte loByte = myWord & 0xFF;
byte hiByte = myWord >> 8;
// write to buffer
sdBuffer[c] = hiByte;
sdBuffer[c + 1] = loByte;
}
romNameLockon[copyToRomName_MD(romNameLockon, sdBuffer, sizeof(romNameLockon) - 1)] = 0;
switch (SnKmode) {
case 2: strcat(romName, "SONIC1"); break;
case 3: strcat(romName, "SONIC2"); break;
case 4: strcat(romName, "SONIC3"); break;
case 5: strcat(romName, romNameLockon); break;
}
}
// Realtec Mapper Check
word realtecCheck1 = readWord_MD(0x3F080); // 0x7E100 "SEGA" (BootROM starts at 0x7E000)
word realtecCheck2 = readWord_MD(0x3F081);
if ((realtecCheck1 == 0x5345) && (realtecCheck2 == 0x4741)) {
realtec = 1;
strcpy(romName, "Realtec");
cartSize = 0x80000;
}
// Some games are missing the ROM size in the header, in this case calculate ROM size by looking for mirror of the first line of the ROM
// This does not work for cartridges that have SRAM mapped directly after the maskrom like Striker (Europe)
if ((cartSize < 0x8000) || (cartSize > 0xEAF400)) {
for (cartSize = 0x20000 / 2; cartSize < 0x400000 / 2; cartSize += 0x20000 / 2) {
if ((readWord_MD(0x0) == readWord_MD(cartSize)) && (readWord_MD(0x1) == readWord_MD(0x1 + cartSize)) && (readWord_MD(0x2) == readWord_MD(0x2 + cartSize)) && (readWord_MD(0x3) == readWord_MD(0x3 + cartSize)) && (readWord_MD(0x4) == readWord_MD(0x4 + cartSize)) && (readWord_MD(0x5) == readWord_MD(0x5 + cartSize)) && (readWord_MD(0x6) == readWord_MD(0x6 + cartSize)) && (readWord_MD(0x7) == readWord_MD(0x7 + cartSize)) && (readWord_MD(0x8) == readWord_MD(0x8 + cartSize))) {
break;
}
}
cartSize = cartSize * 2;
}
display_Clear();
println_Msg(F("Cart Info"));
println_Msg(F(" "));
print_Msg(F("Name: "));
println_Msg(romName);
if (bramCheck != 0x00FF) {
print_Msg(F("bramCheck: "));
print_Msg_PaddedHex16(bramCheck);
println_Msg(F(""));
}
if (bramSize > 0) {
print_Msg(F("bramSize(KB): "));
println_Msg(bramSize >> 10);
}
print_Msg(F("Size: "));
print_Msg(cartSize * 8 / 1024 / 1024);
switch (SnKmode) {
case 2:
case 4:
case 5:
print_Msg(F("+"));
print_Msg(cartSizeLockon * 8 / 1024 / 1024);
break;
case 3:
print_Msg(F("+"));
print_Msg(cartSizeLockon * 8 / 1024 / 1024);
print_Msg(F("+"));
print_Msg(cartSizeSonic2 * 8 / 1024 / 1024);
break;
}
println_Msg(F(" MBit"));
print_Msg(F("ChkS: "));
print_Msg_PaddedHexByte((chksum >> 8));
print_Msg_PaddedHexByte((chksum & 0x00ff));
switch (SnKmode) {
case 2:
case 4:
case 5:
print_Msg(F("+"));
print_Msg_PaddedHexByte((chksumLockon >> 8));
print_Msg_PaddedHexByte((chksumLockon & 0x00ff));
break;
case 3:
print_Msg(F("+"));
print_Msg_PaddedHexByte((chksumLockon >> 8));
print_Msg_PaddedHexByte((chksumLockon & 0x00ff));
print_Msg(F("+"));
print_Msg_PaddedHexByte((chksumSonic2 >> 8));
print_Msg_PaddedHexByte((chksumSonic2 & 0x00ff));
break;
}
println_Msg(F(""));
if (saveType == 4) {
print_Msg(F("Serial EEPROM: "));
print_Msg(eepSize * 8 / 1024);
println_Msg(F(" KBit"));
} else {
print_Msg(F("Sram: "));
if (sramSize > 0) {
print_Msg(sramSize * 8 / 1024);
println_Msg(F(" KBit"));
} else
println_Msg(F("None"));
}
println_Msg(F(" "));
// Wait for user input
#if (defined(enable_LCD) || defined(enable_OLED))
// Prints string out of the common strings array either with or without newline
print_STR(press_button_STR, 1);
display_Update();
wait();
#endif
}
void writeSSF2Map(unsigned long myAddress, word myData) {
dataOut_MD();
// Set TIME(PJ0) HIGH
PORTJ |= (1 << 0);
// 0x50987E * 2 = 0xA130FD Bank 6 (0x300000-0x37FFFF)
// 0x50987F * 2 = 0xA130FF Bank 7 (0x380000-0x3FFFFF)
PORTL = (myAddress >> 16) & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTF = myAddress & 0xFF;
PORTC = myData;
PORTA = (myData >> 8) & 0xFF;
// Arduino running at 16Mhz -> one nop = 62.5ns
// Wait till output is stable
__asm__("nop\n\t"
"nop\n\t");
// Strobe TIME(PJ0) LOW to latch the data
PORTJ &= ~(1 << 0);
// Switch WR(PH5) to LOW
PORTH &= ~(1 << 5);
// Leave WR low for at least 200ns
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
// Switch WR(PH5) to HIGH
PORTH |= (1 << 5);
// Set TIME(PJ0) HIGH
PORTJ |= (1 << 0);
dataIn_MD();
}
// Read rom and save to the SD card
void readROM_MD() {
// Checksum
uint16_t calcCKS = 0;
uint16_t calcCKSLockon = 0;
uint16_t calcCKSSonic2 = 0;
// Set control
dataIn_MD();
// Get name, add extension and convert to char array for sd lib
strcpy(fileName, romName);
strcat(fileName, ".BIN");
// create a new folder
EEPROM_readAnything(0, foldern);
sprintf(folder, "MD/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_FatalError(sd_error_STR);
}
byte buffer[1024] = { 0 };
// Phantasy Star/Beyond Oasis with 74HC74 and 74HC139 switch ROM/SRAM at address 0x200000
if (0x200000 < cartSize && cartSize < 0x400000) {
enableSram_MD(0);
}
// Prepare SSF2 Banks
if (cartSize > 0x400000) {
writeSSF2Map(0x50987E, 6); // 0xA130FD
writeSSF2Map(0x50987F, 7); // 0xA130FF
}
byte offsetSSF2Bank = 0;
word d = 0;
//Initialize progress bar
uint32_t processedProgressBar = 0;
uint32_t totalProgressBar = (uint32_t)(cartSize);
if (SnKmode >= 2) totalProgressBar += (uint32_t)cartSizeLockon;
if (SnKmode == 3) totalProgressBar += (uint32_t)cartSizeSonic2;
draw_progressbar(0, totalProgressBar);
for (unsigned long currBuffer = 0; currBuffer < cartSize / 2; currBuffer += 512) {
// Blink led
if (currBuffer % 16384 == 0)
blinkLED();
if (currBuffer == 0x200000) {
writeSSF2Map(0x50987E, 8); // 0xA130FD
writeSSF2Map(0x50987F, 9); // 0xA130FF
offsetSSF2Bank = 1;
}
// Demons of Asteborg Additional Banks
else if (currBuffer == 0x280000) {
writeSSF2Map(0x50987E, 10); // 0xA130FD
writeSSF2Map(0x50987F, 11); // 0xA130FF
offsetSSF2Bank = 2;
} else if (currBuffer == 0x300000) {
writeSSF2Map(0x50987E, 12); // 0xA130FD
writeSSF2Map(0x50987F, 13); // 0xA130FF
offsetSSF2Bank = 3;
} else if (currBuffer == 0x380000) {
writeSSF2Map(0x50987E, 14); // 0xA130FD
writeSSF2Map(0x50987F, 15); // 0xA130FF
offsetSSF2Bank = 4;
} else if (currBuffer == 0x400000) {
writeSSF2Map(0x50987E, 16); // 0xA130FD
writeSSF2Map(0x50987F, 17); // 0xA130FF
offsetSSF2Bank = 5;
} else if (currBuffer == 0x480000) {
writeSSF2Map(0x50987E, 18); // 0xA130FD
writeSSF2Map(0x50987F, 19); // 0xA130FF
offsetSSF2Bank = 6;
} else if (currBuffer == 0x500000) {
writeSSF2Map(0x50987E, 20); // 0xA130FD
writeSSF2Map(0x50987F, 21); // 0xA130FF
offsetSSF2Bank = 7;
} else if (currBuffer == 0x580000) {
writeSSF2Map(0x50987E, 22); // 0xA130FD
writeSSF2Map(0x50987F, 23); // 0xA130FF
offsetSSF2Bank = 8;
} else if (currBuffer == 0x600000) {
writeSSF2Map(0x50987E, 24); // 0xA130FD
writeSSF2Map(0x50987F, 25); // 0xA130FF
offsetSSF2Bank = 9;
} else if (currBuffer == 0x680000) {
writeSSF2Map(0x50987E, 26); // 0xA130FD
writeSSF2Map(0x50987F, 27); // 0xA130FF
offsetSSF2Bank = 10;
} else if (currBuffer == 0x700000) {
writeSSF2Map(0x50987E, 28); // 0xA130FD
writeSSF2Map(0x50987F, 29); // 0xA130FF
offsetSSF2Bank = 11;
}
d = 0;
for (int currWord = 0; currWord < 512; currWord++) {
unsigned long myAddress = currBuffer + currWord - (offsetSSF2Bank * 0x80000);
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTL = (myAddress >> 16) & 0xFF;
// Arduino running at 16Mhz -> one nop = 62.5ns
NOP;
// Setting CS(PH3) LOW
PORTH &= ~(1 << 3);
// Setting OE(PH6) LOW
PORTH &= ~(1 << 6);
// Setting AS(PJ1) LOW
PORTJ &= ~(1 << 1);
// Setting ASEL(PG5) LOW
PORTG &= ~(1 << 5);
// Pulse CLK(PH1)
if (isSVP)
pulse_clock(10);
// most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
// Read
buffer[d] = PINA;
buffer[d + 1] = PINC;
// Setting CS(PH3) HIGH
PORTH |= (1 << 3);
// Setting OE(PH6) HIGH
PORTH |= (1 << 6);
// Setting AS(PJ1) HIGH
PORTJ |= (1 << 1);
// Setting ASEL(PG5) HIGH
PORTG |= (1 << 5);
// Pulse CLK(PH1)
if (isSVP)
pulse_clock(10);
// Skip first 256 words
if (((currBuffer == 0) && (currWord >= 256)) || (currBuffer > 0)) {
calcCKS += ((buffer[d] << 8) | buffer[d + 1]);
}
d += 2;
}
myFile.write(buffer, 1024);
// update progress bar
processedProgressBar += 1024;
draw_progressbar(processedProgressBar, totalProgressBar);
}
if (SnKmode >= 2) {
for (unsigned long currBuffer = 0; currBuffer < cartSizeLockon / 2; currBuffer += 512) {
// Blink led
if (currBuffer % 16384 == 0)
blinkLED();
d = 0;
for (int currWord = 0; currWord < 512; currWord++) {
unsigned long myAddress = currBuffer + currWord + cartSize / 2;
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTL = (myAddress >> 16) & 0xFF;
// Arduino running at 16Mhz -> one nop = 62.5ns
NOP;
// Setting CS(PH3) LOW
PORTH &= ~(1 << 3);
// Setting OE(PH6) LOW
PORTH &= ~(1 << 6);
// Setting AS(PJ1) LOW
PORTJ &= ~(1 << 1);
// Setting ASEL(PG5) LOW
PORTG &= ~(1 << 5);
// Pulse CLK(PH1)
if (isSVP)
pulse_clock(10);
// most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
// Read
buffer[d] = PINA;
buffer[d + 1] = PINC;
// Setting CS(PH3) HIGH
PORTH |= (1 << 3);
// Setting OE(PH6) HIGH
PORTH |= (1 << 6);
// Setting AS(PJ1) HIGH
PORTJ |= (1 << 1);
// Setting ASEL(PG5) HIGH
PORTG |= (1 << 5);
// Pulse CLK(PH1)
if (isSVP)
pulse_clock(10);
// Skip first 256 words
if (((currBuffer == 0) && (currWord >= 256)) || (currBuffer > 0)) {
calcCKSLockon += ((buffer[d] << 8) | buffer[d + 1]);
}
d += 2;
}
myFile.write(buffer, 1024);
// update progress bar
processedProgressBar += 1024;
draw_progressbar(processedProgressBar, totalProgressBar);
}
}
if (SnKmode == 3) {
for (unsigned long currBuffer = 0; currBuffer < cartSizeSonic2 / 2; currBuffer += 512) {
// Blink led
if (currBuffer % 16384 == 0)
blinkLED();
d = 0;
for (int currWord = 0; currWord < 512; currWord++) {
unsigned long myAddress = currBuffer + currWord + (cartSize + cartSizeLockon) / 2;
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTL = (myAddress >> 16) & 0xFF;
// Arduino running at 16Mhz -> one nop = 62.5ns
NOP;
// Setting CS(PH3) LOW
PORTH &= ~(1 << 3);
// Setting OE(PH6) LOW
PORTH &= ~(1 << 6);
// Setting AS(PJ1) LOW
PORTJ &= ~(1 << 1);
// Setting ASEL(PG5) LOW
PORTG &= ~(1 << 5);
// Pulse CLK(PH1)
if (isSVP)
PORTH ^= (1 << 1);
// most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
NOP;
NOP;
NOP;
NOP;
NOP;
NOP;
// Read
buffer[d] = PINA;
buffer[d + 1] = PINC;
// Setting CS(PH3) HIGH
PORTH |= (1 << 3);
// Setting OE(PH6) HIGH
PORTH |= (1 << 6);
// Setting AS(PJ1) HIGH
PORTJ |= (1 << 1);
// Setting ASEL(PG5) HIGH
PORTG |= (1 << 5);
// Pulse CLK(PH1)
if (isSVP)
PORTH ^= (1 << 1);
calcCKSSonic2 += ((buffer[d] << 8) | buffer[d + 1]);
d += 2;
}
myFile.write(buffer, 1024);
// update progress bar
processedProgressBar += 1024;
draw_progressbar(processedProgressBar, totalProgressBar);
}
}
// Close the file:
myFile.close();
// Reset SSF2 Banks
if (cartSize > 0x400000) {
writeSSF2Map(0x50987E, 6); // 0xA130FD
writeSSF2Map(0x50987F, 7); // 0xA130FF
}
// Calculate internal checksum
print_Msg(F("Internal checksum..."));
display_Update();
if (chksum == calcCKS) {
println_Msg(F("OK"));
display_Update();
} else {
println_Msg(F("Error"));
char calcsumStr[5];
sprintf(calcsumStr, "%04X", calcCKS);
println_Msg(calcsumStr);
print_Error(F(""));
display_Update();
}
// More checksums
if (SnKmode >= 2) {
print_Msg(F("Lock-on checksum..."));
if (chksumLockon == calcCKSLockon) {
println_Msg(F("OK"));
display_Update();
} else {
print_Msg(F("Error"));
char calcsumStr[5];
sprintf(calcsumStr, "%04X", calcCKSLockon);
println_Msg(calcsumStr);
print_Error(F(""));
display_Update();
}
}
if (SnKmode == 3) {
print_Msg(F("Adittional checksum..."));
if (chksumSonic2 == calcCKSSonic2) {
println_Msg(F("OK"));
display_Update();
} else {
print_Msg(F("Error"));
char calcsumStr[5];
sprintf(calcsumStr, "%04X", calcCKSSonic2);
println_Msg(calcsumStr);
print_Error(F(""));
display_Update();
}
}
}
/******************************************
SRAM functions
*****************************************/
// Sonic 3 sram enable
void enableSram_MD(boolean enableSram) {
dataOut_MD();
// Set D0 to either 1(enable SRAM) or 0(enable ROM)
PORTC = enableSram;
// Strobe TIME(PJ0) LOW to latch the data
PORTJ &= ~(1 << 0);
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
// Set TIME(PJ0) HIGH
PORTJ |= (1 << 0);
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
dataIn_MD();
}
// Write sram to cartridge
void writeSram_MD() {
dataOut_MD();
// Create filepath
sprintf(filePath, "%s/%s", filePath, fileName);
println_Msg(F("Writing..."));
println_Msg(filePath);
display_Update();
// Open file on sd card
if (myFile.open(filePath, O_READ)) {
// Write to the lower byte
if (saveType == 1) {
for (unsigned long currByte = sramBase; currByte < sramBase + sramSize; currByte++) {
if (segaSram16bit > 0) {
// skip high byte
myFile.read();
}
word data = myFile.read() & 0xFF;
writeWord_MD(currByte, data);
}
}
// Write to the upper byte
else if (saveType == 2) {
for (unsigned long currByte = sramBase; currByte < sramBase + sramSize; currByte++) {
word data = (myFile.read() << 8) & 0xFF00;
writeWord_MD(currByte, data);
if (segaSram16bit > 0) {
// skip low byte
myFile.read();
}
}
}
// Write to both bytes
else if (saveType == 3) {
for (unsigned long currByte = sramBase; currByte < sramBase + sramSize; currByte++) {
word data = (myFile.read() << 8) & 0xFF00;
data |= (myFile.read() & 0xFF);
writeWord_MD(currByte, data);
}
} else
print_Error(F("Unknown save type"));
// Close the file:
myFile.close();
print_STR(done_STR, 1);
display_Update();
} else {
print_FatalError(sd_error_STR);
}
dataIn_MD();
}
// Read sram and save to the SD card
void readSram_MD() {
dataIn_MD();
// Get name, add extension and convert to char array for sd lib
strcpy(fileName, romName);
strcat(fileName, ".srm");
// create a new folder for the save file
EEPROM_readAnything(0, foldern);
sprintf(folder, "MD/SAVE/%s/%d", romName, foldern);
sd.mkdir(folder, true);
sd.chdir(folder);
// write new folder number back to eeprom
foldern = foldern + 1;
EEPROM_writeAnything(0, foldern);
// Open file on sd card
if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
print_FatalError(sd_error_STR);
}
for (unsigned long currBuffer = sramBase; currBuffer < sramBase + sramSize; currBuffer += 256) {
for (int currWord = 0; currWord < 256; currWord++) {
word myWord = readWord_MD(currBuffer + currWord);
if (saveType == 2) {
// Only use the upper byte
if (segaSram16bit > 0) {
sdBuffer[(currWord * 2) + 0] = ((myWord >> 8) & 0xFF);
sdBuffer[(currWord * 2) + 1] = ((myWord >> 8) & 0xFF);
} else {
sdBuffer[currWord] = ((myWord >> 8) & 0xFF);
}
} else if (saveType == 1) {
// Only use the lower byte
if (segaSram16bit > 0) {
sdBuffer[(currWord * 2) + 0] = (myWord & 0xFF);
sdBuffer[(currWord * 2) + 1] = (myWord & 0xFF);
} else {
sdBuffer[currWord] = (myWord & 0xFF);
}
} else if (saveType == 3) { // BOTH
sdBuffer[currWord * 2] = ((myWord >> 8) & 0xFF);
sdBuffer[(currWord * 2) + 1] = (myWord & 0xFF);
}
}
if (saveType == 3 || segaSram16bit > 0)
myFile.write(sdBuffer, 512);
else
myFile.write(sdBuffer, 256);
}
if (segaSram16bit == 2) {
// pad to 64KB
for (int i = 0; i < 512; i++) {
sdBuffer[i] = 0xFF;
}
unsigned long padsize = (1UL << 16) - (sramSize << 1);
unsigned long padblockcount = padsize >> 9; // number of 512 byte blocks
for (unsigned long i = 0; i < padblockcount; i++) {
myFile.write(sdBuffer, 512);
}
}
// Close the file:
myFile.close();
print_Msg(F("Saved to "));
print_Msg(folder);
println_Msg(F("/"));
display_Update();
}
unsigned long verifySram_MD() {
dataIn_MD();
writeErrors = 0;
// Open file on sd card
if (myFile.open(filePath, O_READ)) {
for (unsigned long currBuffer = sramBase; currBuffer < sramBase + sramSize; currBuffer += 256) {
for (int currWord = 0; currWord < 256; currWord++) {
word myWord = readWord_MD(currBuffer + currWord);
if (saveType == 2) {
// Only use the upper byte
sdBuffer[currWord * 2] = ((myWord >> 8) & 0xFF);
} else if (saveType == 1) {
// Only use the lower byte
sdBuffer[currWord * 2] = (myWord & 0xFF);
} else if (saveType == 3) { // BOTH
sdBuffer[(currWord * 2) + 0] = ((myWord >> 8) & 0xFF);
sdBuffer[(currWord * 2) + 1] = (myWord & 0xFF);
}
}
int step = saveType == 3 ? 1 : 2;
// Check sdBuffer content against file on sd card
for (int i = 0; i < 512; i += step) {
if (saveType == 1 && segaSram16bit > 0) {
// skip high byte
myFile.read();
}
byte b = myFile.read();
if (saveType == 2 && segaSram16bit > 0) {
// skip low byte
myFile.read();
}
if (b != sdBuffer[i]) {
writeErrors++;
}
}
}
// Close the file:
myFile.close();
} else {
print_FatalError(sd_error_STR);
}
// Return 0 if verified ok, or number of errors
return writeErrors;
}
#ifdef enable_FLASH
//******************************************
// Flashrom Functions
//******************************************
void resetFlash_MD() {
// Set data pins to output
dataOut_MD();
// Reset command sequence
writeFlash_MD(0x5555, 0xaa);
writeFlash_MD(0x2aaa, 0x55);
writeFlash_MD(0x5555, 0xf0);
// Set data pins to input again
dataIn_MD();
}
void write29F1610_MD() {
// Create filepath
sprintf(filePath, "%s/%s", filePath, fileName);
print_STR(flashing_file_STR, 0);
print_Msg(filePath);
println_Msg(F("..."));
display_Update();
// Open file on sd card
if (myFile.open(filePath, O_READ)) {
// Get rom size from file
fileSize = myFile.fileSize();
if (fileSize > flashSize) {
print_FatalError(file_too_big_STR);
}
// Set data pins to output
dataOut_MD();
// Fill sdBuffer with 1 page at a time then write it repeat until all bytes are written
int d = 0;
for (unsigned long currByte = 0; currByte < fileSize / 2; currByte += 64) {
myFile.read(sdBuffer, 128);
// Blink led
if (currByte % 4096 == 0) {
blinkLED();
}
// Write command sequence
writeFlash_MD(0x5555, 0xaa);
writeFlash_MD(0x2aaa, 0x55);
writeFlash_MD(0x5555, 0xa0);
// Write one full page at a time
for (byte c = 0; c < 64; c++) {
word currWord = ((sdBuffer[d] & 0xFF) << 8) | (sdBuffer[d + 1] & 0xFF);
writeFlash_MD(currByte + c, currWord);
d += 2;
}
d = 0;
// Check if write is complete
delayMicroseconds(100);
busyCheck_MD();
}
// Set data pins to input again
dataIn_MD();
// Close the file:
myFile.close();
} else {
print_STR(open_file_STR, 1);
display_Update();
}
}
void idFlash_MD() {
// Set data pins to output
dataOut_MD();
// ID command sequence
writeFlash_MD(0x5555, 0xaa);
writeFlash_MD(0x2aaa, 0x55);
writeFlash_MD(0x5555, 0x90);
// Set data pins to input again
dataIn_MD();
// Read the two id bytes into a string
flashid = (readFlash_MD(0) & 0xFF) << 8;
flashid |= readFlash_MD(1) & 0xFF;
sprintf(flashid_str, "%04X", flashid);
}
byte readStatusReg_MD() {
// Set data pins to output
dataOut_MD();
// Status reg command sequence
writeFlash_MD(0x5555, 0xaa);
writeFlash_MD(0x2aaa, 0x55);
writeFlash_MD(0x5555, 0x70);
// Set data pins to input again
dataIn_MD();
// Read the status register
byte statusReg = readFlash_MD(0);
return statusReg;
}
void eraseFlash_MD() {
// Set data pins to output
dataOut_MD();
// Erase command sequence
writeFlash_MD(0x5555, 0xaa);
writeFlash_MD(0x2aaa, 0x55);
writeFlash_MD(0x5555, 0x80);
writeFlash_MD(0x5555, 0xaa);
writeFlash_MD(0x2aaa, 0x55);
writeFlash_MD(0x5555, 0x10);
// Set data pins to input again
dataIn_MD();
busyCheck_MD();
}
void blankcheck_MD() {
blank = 1;
for (unsigned long currByte = 0; currByte < flashSize / 2; currByte++) {
if (readFlash_MD(currByte) != 0xFFFF) {
currByte = flashSize / 2;
blank = 0;
}
if (currByte % 4096 == 0) {
blinkLED();
}
}
if (!blank) {
print_Error(F("Error: Not blank"));
}
}
void verifyFlash_MD() {
// Open file on sd card
if (myFile.open(filePath, O_READ)) {
// Get rom size from file
fileSize = myFile.fileSize();
if (fileSize > flashSize) {
print_FatalError(file_too_big_STR);
}
blank = 0;
word d = 0;
for (unsigned long currByte = 0; currByte < fileSize / 2; currByte += 256) {
if (currByte % 4096 == 0) {
blinkLED();
}
//fill sdBuffer
myFile.read(sdBuffer, 512);
for (int c = 0; c < 256; c++) {
word currWord = ((sdBuffer[d] << 8) | sdBuffer[d + 1]);
if (readFlash_MD(currByte + c) != currWord) {
blank++;
}
d += 2;
}
d = 0;
}
if (blank == 0) {
println_Msg(F("Flashrom verified OK"));
display_Update();
} else {
print_STR(error_STR, 0);
print_Msg(blank);
print_STR(_bytes_STR, 1);
print_Error(did_not_verify_STR);
}
// Close the file:
myFile.close();
} else {
print_STR(open_file_STR, 1);
display_Update();
}
}
#endif
// Delay between write operations based on status register
void busyCheck_MD() {
// Set data pins to input
dataIn_MD();
// Read the status register
word statusReg = readFlash_MD(0);
while ((statusReg | 0xFF7F) != 0xFFFF) {
statusReg = readFlash_MD(0);
}
// Set data pins to output
dataOut_MD();
}
//******************************************
// EEPROM Functions
//******************************************
void EepromInit(byte eepmode) { // Acclaim Type 2
PORTF = 0x00; // ADDR A0-A7
PORTK = 0x00; // ADDR A8-A15
PORTL = 0x10; // ADDR A16-A23
PORTA = 0x00; // DATA D8-D15
PORTH |= (1 << 0); // /RES HIGH
PORTC = eepmode; // EEPROM Switch: 0 = Enable (Read EEPROM), 1 = Disable (Read ROM)
PORTH &= ~(1 << 3); // CE LOW
PORTH &= ~(1 << 4) & ~(1 << 5); // /UDSW + /LDSW LOW
PORTH |= (1 << 6); // OE HIGH
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
PORTH |= (1 << 4) | (1 << 5); // /UDSW + /LDSW HIGH
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
}
void writeWord_SDA(unsigned long myAddress, word myData) { /* D0 goes to /SDA when only /LWR is asserted */
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTL = (myAddress >> 16) & 0xFF;
PORTC = myData;
PORTH &= ~(1 << 3); // CE LOW
PORTH &= ~(1 << 5); // /LDSW LOW
PORTH |= (1 << 4); // /UDSW HIGH
PORTH |= (1 << 6); // OE HIGH
if (eepSize > 0x100)
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
else
delayMicroseconds(100);
PORTH |= (1 << 5); // /LDSW HIGH
if (eepSize > 0x100)
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
else
delayMicroseconds(100);
}
void writeWord_SCL(unsigned long myAddress, word myData) { /* D0 goes to /SCL when only /UWR is asserted */
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTL = (myAddress >> 16) & 0xFF;
PORTC = myData;
PORTH &= ~(1 << 3); // CE LOW
PORTH &= ~(1 << 4); // /UDSW LOW
PORTH |= (1 << 5); // /LDSW HIGH
PORTH |= (1 << 6); // OE HIGH
if (eepSize > 0x100)
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
else
delayMicroseconds(100);
PORTH |= (1 << 4); // /UDSW HIGH
if (eepSize > 0x100)
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
else
delayMicroseconds(100);
}
void writeWord_CM(unsigned long myAddress, word myData) { // Codemasters
PORTF = myAddress & 0xFF;
PORTK = (myAddress >> 8) & 0xFF;
PORTL = (myAddress >> 16) & 0xFF;
PORTC = myData;
PORTA = (myData >> 8) & 0xFF;
// Arduino running at 16Mhz -> one nop = 62.5ns
// Wait till output is stable
__asm__("nop\n\t"
"nop\n\t");
// Switch WR(PH4) to LOW
PORTH &= ~(1 << 4);
// Setting CS(PH3) LOW
PORTH &= ~(1 << 3);
// Pulse CLK(PH1)
PORTH ^= (1 << 1);
// Leave WR low for at least 200ns
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
// Pulse CLK(PH1)
PORTH ^= (1 << 1);
// Setting CS(PH3) HIGH
PORTH |= (1 << 3);
// Switch WR(PH4) to HIGH
PORTH |= (1 << 4);
// Leave WR high for at least 50ns
__asm__("nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t"
"nop\n\t");
}
// EEPROM COMMANDS
void EepromStart() {
if (eepType == 2) { // Acclaim Type 2
writeWord_SDA(0x100000, 0x00); // sda low
writeWord_SCL(0x100000, 0x00); // scl low
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x01); // scl high
writeWord_SDA(0x100000, 0x00); // sda low
writeWord_SCL(0x100000, 0x00); // scl low
} else if (eepType == 4) { // EA
writeWord_MD(0x100000, 0x00); // sda low, scl low
writeWord_MD(0x100000, 0xC0); // sda, scl high
writeWord_MD(0x100000, 0x40); // sda low, scl high
writeWord_MD(0x100000, 0x00); // START
} else if (eepType == 5) { // Codemasters
writeWord_CM(0x180000, 0x00); // sda low, scl low
writeWord_CM(0x180000, 0x02); // sda low, scl high
writeWord_CM(0x180000, 0x03); // sda, scl high
writeWord_CM(0x180000, 0x02); // sda low, scl high
writeWord_CM(0x180000, 0x00); // START
} else {
writeWord_MD(0x100000, 0x00); // sda low, scl low
writeWord_MD(0x100000, 0x03); // sda, scl high
writeWord_MD(0x100000, 0x02); // sda low, scl high
writeWord_MD(0x100000, 0x00); // START
}
}
void EepromSet0() {
if (eepType == 2) { // Acclaim Type 2
writeWord_SDA(0x100000, 0x00); // sda low
writeWord_SCL(0x100000, 0x01); // scl high
writeWord_SDA(0x100000, 0x00); // sda low
writeWord_SCL(0x100000, 0x00); // scl low
} else if (eepType == 4) { // EA
writeWord_MD(0x100000, 0x00); // sda low, scl low
writeWord_MD(0x100000, 0x40); // sda low, scl high // 0
writeWord_MD(0x100000, 0x00); // sda low, scl low
} else if (eepType == 5) { // Codemasters
writeWord_CM(0x180000, 0x00); // sda low, scl low
writeWord_CM(0x180000, 0x02); // sda low, scl high // 0
writeWord_CM(0x180000, 0x00); // sda low, scl low
} else {
writeWord_MD(0x100000, 0x00); // sda low, scl low
writeWord_MD(0x100000, 0x02); // sda low, scl high // 0
writeWord_MD(0x100000, 0x00); // sda low, scl low
}
}
void EepromSet1() {
if (eepType == 2) { // Acclaim Type 2
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x01); // scl high
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x00); // scl low
} else if (eepType == 4) { // EA
writeWord_MD(0x100000, 0x80); // sda high, scl low
writeWord_MD(0x100000, 0xC0); // sda high, scl high // 1
writeWord_MD(0x100000, 0x80); // sda high, scl low
writeWord_MD(0x100000, 0x00); // sda low, scl low
} else if (eepType == 5) { // Codemasters
writeWord_CM(0x180000, 0x01); // sda high, scl low
writeWord_CM(0x180000, 0x03); // sda high, scl high // 1
writeWord_CM(0x180000, 0x01); // sda high, scl low
writeWord_CM(0x180000, 0x00); // sda low, scl low
} else {
writeWord_MD(0x100000, 0x01); // sda high, scl low
writeWord_MD(0x100000, 0x03); // sda high, scl high // 1
writeWord_MD(0x100000, 0x01); // sda high, scl low
writeWord_MD(0x100000, 0x00); // sda low, scl low
}
}
void EepromDevice() { // 24C02+
EepromSet1();
EepromSet0();
EepromSet1();
EepromSet0();
}
void EepromSetDeviceAddress(word addrhi) { // 24C02+
for (int i = 0; i < 3; i++) {
if ((addrhi >> 2) & 0x1) // Bit is HIGH
EepromSet1();
else // Bit is LOW
EepromSet0();
addrhi <<= 1; // rotate to the next bit
}
}
void EepromStatus() { // ACK
byte eepStatus = 1;
if (eepType == 1) { // Acclaim Type 1
writeWord_MD(0x100000, 0x01); // sda high, scl low
writeWord_MD(0x100000, 0x03); // sda high, scl high
do {
dataIn_MD();
eepStatus = ((readWord_MD(0x100000) >> 1) & 0x1);
dataOut_MD();
delayMicroseconds(4);
} while (eepStatus == 1);
writeWord_MD(0x100000, 0x01); // sda high, scl low
} else if (eepType == 2) { // Acclaim Type 2
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x01); // scl high
do {
dataIn_MD();
eepStatus = (readWord_MD(0x100000) & 0x1);
dataOut_MD();
delayMicroseconds(4);
} while (eepStatus == 1);
writeWord_SCL(0x100000, 0x00); // scl low
} else if (eepType == 3) { // Capcom/Sega
writeWord_MD(0x100000, 0x01); // sda high, scl low
writeWord_MD(0x100000, 0x03); // sda high, scl high
do {
dataIn_MD();
eepStatus = (readWord_MD(0x100000) & 0x1);
dataOut_MD();
delayMicroseconds(4);
} while (eepStatus == 1);
writeWord_MD(0x100000, 0x01); // sda high, scl low
} else if (eepType == 4) { // EA
writeWord_MD(0x100000, 0x80); // sda high, scl low
writeWord_MD(0x100000, 0xC0); // sda high, scl high
do {
dataIn_MD();
eepStatus = ((readWord_MD(0x100000) >> 7) & 0x1);
dataOut_MD();
delayMicroseconds(4);
} while (eepStatus == 1);
writeWord_MD(0x100000, 0x80); // sda high, scl low
} else if (eepType == 5) { // Codemasters
writeWord_CM(0x180000, 0x01); // sda high, scl low
writeWord_CM(0x180000, 0x03); // sda high, scl high
do {
dataIn_MD();
eepStatus = ((readWord_MD(0x1C0000) >> 7) & 0x1);
dataOut_MD();
delayMicroseconds(4);
} while (eepStatus == 1);
writeWord_CM(0x180000, 0x01); // sda high, scl low
}
}
void EepromReadMode() {
EepromSet1(); // READ
EepromStatus(); // ACK
}
void EepromWriteMode() {
EepromSet0(); // WRITE
EepromStatus(); // ACK
}
void EepromReadData() {
if (eepType == 1) { // Acclaim Type 1
for (int i = 0; i < 8; i++) {
writeWord_MD(0x100000, 0x03); // sda high, scl high
dataIn_MD();
eepbit[i] = ((readWord_MD(0x100000) >> 1) & 0x1); // Read 0x100000 with Mask 0x1 (bit 1)
dataOut_MD();
writeWord_MD(0x100000, 0x01); // sda high, scl low
}
} else if (eepType == 2) { // Acclaim Type 2
for (int i = 0; i < 8; i++) {
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x01); // scl high
dataIn_MD();
eepbit[i] = (readWord_MD(0x100000) & 0x1); // Read 0x100000 with Mask 0x1 (bit 0)
dataOut_MD();
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x00); // scl low
}
} else if (eepType == 3) { // Capcom/Sega
for (int i = 0; i < 8; i++) {
writeWord_MD(0x100000, 0x03); // sda high, scl high
dataIn_MD();
eepbit[i] = (readWord_MD(0x100000) & 0x1); // Read 0x100000 with Mask 0x1 (bit 0)
dataOut_MD();
writeWord_MD(0x100000, 0x01); // sda high, scl low
}
} else if (eepType == 4) { // EA
for (int i = 0; i < 8; i++) {
writeWord_MD(0x100000, 0xC0); // sda high, scl high
dataIn_MD();
eepbit[i] = ((readWord_MD(0x100000) >> 7) & 0x1); // Read 0x100000 with Mask (bit 7)
dataOut_MD();
writeWord_MD(0x100000, 0x80); // sda high, scl low
}
} else if (eepType == 5) { // Codemasters
for (int i = 0; i < 8; i++) {
writeWord_CM(0x180000, 0x03); // sda high, scl high
dataIn_MD();
eepbit[i] = ((readWord_MD(0x1C0000) >> 7) & 0x1); // Read 0x1C0000 with Mask 0x1 (bit 7)
dataOut_MD();
writeWord_CM(0x180000, 0x01); // sda high, scl low
}
}
}
void EepromWriteData(byte data) {
for (int i = 0; i < 8; i++) {
if ((data >> 7) & 0x1) // Bit is HIGH
EepromSet1();
else // Bit is LOW
EepromSet0();
data <<= 1; // rotate to the next bit
}
EepromStatus(); // ACK
}
void EepromFinish() {
if (eepType == 2) { // Acclaim Type 2
writeWord_SDA(0x100000, 0x00); // sda low
writeWord_SCL(0x100000, 0x00); // scl low
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x00); // scl low
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x01); // scl high
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x00); // scl low
writeWord_SDA(0x100000, 0x00); // sda low
writeWord_SCL(0x100000, 0x00); // scl low
} else if (eepType == 4) { // EA
writeWord_MD(0x100000, 0x00); // sda low, scl low
writeWord_MD(0x100000, 0x80); // sda high, scl low
writeWord_MD(0x100000, 0xC0); // sda high, scl high
writeWord_MD(0x100000, 0x80); // sda high, scl low
writeWord_MD(0x100000, 0x00); // sda low, scl low
} else if (eepType == 5) { // Codemasters
writeWord_CM(0x180000, 0x00); // sda low, scl low
writeWord_CM(0x180000, 0x01); // sda high, scl low
writeWord_CM(0x180000, 0x03); // sda high, scl high
writeWord_CM(0x180000, 0x01); // sda high, scl low
writeWord_CM(0x180000, 0x00); // sda low, scl low
} else {
writeWord_MD(0x100000, 0x00); // sda low, scl low
writeWord_MD(0x100000, 0x01); // sda high, scl low
writeWord_MD(0x100000, 0x03); // sda high, scl high
writeWord_MD(0x100000, 0x01); // sda high, scl low
writeWord_MD(0x100000, 0x00); // sda low, scl low
}
}
void EepromStop() {
if (eepType == 2) { // Acclaim Type 2
writeWord_SDA(0x100000, 0x00); // sda low
writeWord_SCL(0x100000, 0x01); // scl high
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x01); // scl high
writeWord_SDA(0x100000, 0x01); // sda high
writeWord_SCL(0x100000, 0x00); // scl low
writeWord_SDA(0x100000, 0x00); // sda low
writeWord_SCL(0x100000, 0x00); // scl low // STOP
} else if (eepType == 4) { // EA
writeWord_MD(0x100000, 0x00); // sda, scl low
writeWord_MD(0x100000, 0x40); // sda low, scl high
writeWord_MD(0x100000, 0xC0); // sda, scl high
writeWord_MD(0x100000, 0x80); // sda high, scl low
writeWord_MD(0x100000, 0x00); // STOP
} else if (eepType == 5) { // Codemasters
writeWord_CM(0x180000, 0x00); // sda low, scl low
writeWord_CM(0x180000, 0x02); // sda low, scl high
writeWord_CM(0x180000, 0x03); // sda, scl high
writeWord_CM(0x180000, 0x01); // sda high, scl low
writeWord_CM(0x180000, 0x00); // STOP
} else {
writeWord_MD(0x100000, 0x00); // sda, scl low
writeWord_MD(0x100000, 0x02); // sda low, scl high
writeWord_MD(0x100000, 0x03); // sda, scl high
writeWord_MD(0x100000, 0x01); // sda high, scl low
writeWord_MD(0x100000, 0x00); // STOP
}
}
void EepromSetAddress(word address) {
if (eepSize > 0x80) { // 24C02+
for (int i = 0; i < 8; i++) {
if ((address >> 7) & 0x1) // Bit is HIGH
EepromSet1();
else // Bit is LOW
EepromSet0();
address <<= 1; // rotate to the next bit
}
EepromStatus(); // ACK
} else { // 24C01
for (int i = 0; i < 7; i++) {
if ((address >> 6) & 0x1) // Bit is HIGH
EepromSet1();
else // Bit is LOW
EepromSet0();
address <<= 1; // rotate to the next bit
}
}
}
void readEepromByte(word address) {
addrhi = address >> 8;
addrlo = address & 0xFF;
dataOut_MD();
if (eepType == 2)
EepromInit(0); // Enable EEPROM
EepromStart(); // START
if (eepSize > 0x80) {
EepromDevice(); // DEVICE [1010]
if (eepSize > 0x800) { // MODE 3 [24C65]
EepromSetDeviceAddress(0);
EepromWriteMode();
EepromSetAddress(addrhi); // ADDR [A15..A8]
} else { // MODE 2 [24C02/24C08/24C16]
EepromSetDeviceAddress(addrhi); // ADDR [A10..A8]
EepromWriteMode();
}
}
EepromSetAddress(addrlo);
if (eepSize > 0x80) {
EepromStart(); // START
EepromDevice(); // DEVICE [1010]
if (eepSize > 0x800) // MODE 3 [24C65]
EepromSetDeviceAddress(0);
else // MODE 2 [24C02/24C08/24C16]
EepromSetDeviceAddress(addrhi); // ADDR [A10..A8]
}
EepromReadMode();
EepromReadData();
EepromFinish();
EepromStop(); // STOP
if (eepType == 2)
EepromInit(1); // Disable EEPROM
// OR 8 bits into byte
eeptemp = eepbit[0] << 7 | eepbit[1] << 6 | eepbit[2] << 5 | eepbit[3] << 4 | eepbit[4] << 3 | eepbit[5] << 2 | eepbit[6] << 1 | eepbit[7];
sdBuffer[addrlo] = eeptemp;
}
void writeEepromByte(word address) {
addrhi = address >> 8;
addrlo = address & 0xFF;
eeptemp = sdBuffer[addrlo];
dataOut_MD();
if (eepType == 2)
EepromInit(0); // Enable EEPROM
EepromStart(); // START
if (eepSize > 0x80) {
EepromDevice(); // DEVICE [1010]
if (eepSize > 0x800) { // MODE 3 [24C65]
EepromSetDeviceAddress(0); // [A2-A0] = 000
EepromWriteMode(); // WRITE
EepromSetAddress(addrhi); // ADDR [A15-A8]
} else { // MODE 2 [24C02/24C08/24C16]
EepromSetDeviceAddress(addrhi); // ADDR [A10-A8]
EepromWriteMode(); // WRITE
}
EepromSetAddress(addrlo);
} else { // 24C01
EepromSetAddress(addrlo);
EepromWriteMode(); // WRITE
}
EepromWriteData(eeptemp);
EepromStop(); // STOP
if (eepType == 2)
EepromInit(1); // Disable EEPROM
}
// Read EEPROM and save to the SD card
void readEEP_MD() {
dataIn_MD();
// Get name, add extension and convert to char array for sd lib
strcpy(fileName, romName);
strcat(fileName, ".eep");
// create a new folder for the save file
EEPROM_readAnything(0, foldern);
sd.chdir();
sprintf(folder, "MD/SAVE/%s/%d", romName, foldern);
sd.mkdir(folder, true);
sd.chdir(folder);
// write new folder number back to eeprom
foldern = foldern + 1;
EEPROM_writeAnything(0, foldern);
println_Msg(F("Reading..."));
display_Update();
// Open file on sd card
if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
print_FatalError(sd_error_STR);
}
if (eepSize > 0x100) { // 24C04+
for (word currByte = 0; currByte < eepSize; currByte += 256) {
print_Msg(F("*"));
display_Update();
for (int i = 0; i < 256; i++) {
readEepromByte(currByte + i);
}
myFile.write(sdBuffer, 256);
}
} else { // 24C01/24C02
for (word currByte = 0; currByte < eepSize; currByte++) {
if ((currByte != 0) && ((currByte + 1) % 16 == 0)) {
print_Msg(F("*"));
display_Update();
}
readEepromByte(currByte);
}
myFile.write(sdBuffer, eepSize);
}
// Close the file:
myFile.close();
println_Msg(F(""));
display_Clear();
print_Msg(F("Saved to "));
print_Msg(folder);
display_Update();
}
void writeEEP_MD() {
dataOut_MD();
// Create filepath
sprintf(filePath, "%s/%s", filePath, fileName);
println_Msg(F("Writing..."));
println_Msg(filePath);
display_Update();
// Open file on sd card
if (myFile.open(filePath, O_READ)) {
if (eepSize > 0x100) { // 24C04+
for (word currByte = 0; currByte < eepSize; currByte += 256) {
myFile.read(sdBuffer, 256);
for (int i = 0; i < 256; i++) {
writeEepromByte(currByte + i);
delay(50); // DELAY NEEDED
}
print_Msg(F("."));
display_Update();
}
} else { // 24C01/24C02
myFile.read(sdBuffer, eepSize);
for (word currByte = 0; currByte < eepSize; currByte++) {
writeEepromByte(currByte);
print_Msg(F("."));
if ((currByte != 0) && ((currByte + 1) % 64 == 0))
println_Msg(F(""));
display_Update(); // ON SERIAL = delay(100)
}
}
// Close the file:
myFile.close();
println_Msg(F(""));
display_Clear();
print_STR(done_STR, 1);
display_Update();
} else {
print_FatalError(sd_error_STR);
}
dataIn_MD();
}
//******************************************
// CD Backup RAM Functions
//******************************************
void readBram_MD() {
dataIn_MD();
// Get name, add extension and convert to char array for sd lib
strcpy(fileName, "Cart.brm");
// create a new folder for the save file
EEPROM_readAnything(0, foldern);
sd.chdir();
sprintf(folder, "MD/RAM/%d", foldern);
sd.mkdir(folder, true);
sd.chdir(folder);
// write new folder number back to eeprom
foldern = foldern + 1;
EEPROM_writeAnything(0, foldern);
println_Msg(F("Reading..."));
display_Update();
// Open file on sd card
if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
print_FatalError(sd_error_STR);
}
for (unsigned long currByte = 0; currByte < bramSize; currByte += 512) {
for (int i = 0; i < 512; i++) {
sdBuffer[i] = readWord_MD(0x300000 + currByte + i);
}
myFile.write(sdBuffer, 512);
}
// Close the file:
myFile.close();
println_Msg(F(""));
display_Clear();
print_Msg(F("Saved to "));
print_Msg(folder);
display_Update();
}
void writeBram_MD() {
dataOut_MD();
// Create filepath
sprintf(filePath, "%s/%s", filePath, fileName);
println_Msg(F("Writing..."));
println_Msg(filePath);
display_Update();
// Open file on sd card
if (myFile.open(filePath, O_READ)) {
// 0x700000-0x7FFFFF: Writes by /LWR latch D0; 1=RAM write enabled, 0=disabled
writeWord_MD(0x380000, 1); // Enable BRAM Writes
for (unsigned long currByte = 0; currByte < bramSize; currByte += 512) {
myFile.read(sdBuffer, 512);
for (int i = 0; i < 512; i++) {
writeWord_MD(0x300000 + currByte + i, sdBuffer[i]);
}
}
writeWord_MD(0x380000, 0); // Disable BRAM Writes
// Close the file:
myFile.close();
println_Msg(F(""));
display_Clear();
print_STR(done_STR, 1);
display_Update();
} else {
print_FatalError(sd_error_STR);
}
dataIn_MD();
}
//******************************************
// Realtec Mapper Functions
//******************************************
void writeRealtec(unsigned long address, byte value) { // Realtec 0x404000 (UPPER)/0x400000 (LOWER)
dataOut_MD();
PORTF = address & 0xFF; // 0x00 ADDR A0-A7
PORTK = (address >> 8) & 0xFF; // ADDR A8-A15
PORTL = (address >> 16) & 0xFF; //0x20 ADDR A16-A23
PORTA = 0x00; // DATA D8-D15
PORTH |= (1 << 0); // /RES HIGH
PORTH |= (1 << 3); // CE HIGH
PORTC = value;
PORTH &= ~(1 << 4) & ~(1 << 5); // /UDSW + /LDSW LOW
PORTH |= (1 << 4) | (1 << 5); // /UDSW + /LDSW HIGH
dataIn_MD();
}
void readRealtec_MD() {
// Set control
dataIn_MD();
// Get name, add extension and convert to char array for sd lib
strcpy(fileName, romName);
strcat(fileName, ".MD");
// create a new folder
EEPROM_readAnything(0, foldern);
sprintf(folder, "MD/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_FatalError(sd_error_STR);
}
// Realtec Registers
writeWord_MD(0x201000, 4); // Number of 128K Blocks 0x402000 (0x201000)
writeRealtec(0x200000, 1); // ROM Lower Address 0x400000 (0x200000)
writeRealtec(0x202000, 0); // ROM Upper Address 0x404000 (0x202000)
word d = 0;
for (unsigned long currBuffer = 0; currBuffer < cartSize / 2; currBuffer += 256) {
// Blink led
if (currBuffer % 16384 == 0)
blinkLED();
for (int currWord = 0; currWord < 256; currWord++) {
word myWord = readWord_MD(currBuffer + currWord);
// Split word into two bytes
// Left
sdBuffer[d] = ((myWord >> 8) & 0xFF);
// Right
sdBuffer[d + 1] = (myWord & 0xFF);
d += 2;
}
myFile.write(sdBuffer, 512);
d = 0;
}
// Close the file:
myFile.close();
}
#endif
//******************************************
// End of File
//******************************************