mirror of
https://github.com/sanni/cartreader.git
synced 2024-12-26 13:01:52 +01:00
2912 lines
85 KiB
C++
2912 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
|
|
//*********************************************************
|
|
const byte MDSize[] PROGMEM = { 1, 2, 4, 8, 12, 16, 20, 24, 32, 40 };
|
|
|
|
static const word PROGMEM eepid[] = {
|
|
// ACCLAIM TYPE 1
|
|
0x5B9F, 0x101, // NBA Jam (J)
|
|
0x694F, 0x101, // NBA Jam (UE) (Rev 0)
|
|
0xBFA9, 0x101, // NBA Jam (UE) (Rev 1)
|
|
// ACCLAIM TYPE 2
|
|
0x16B2, 0x102, // Blockbuster World Videogame Championship II (U) [NO HEADER SAVE DATA]
|
|
0xCC3F, 0x102, // NBA Jam Tournament Edition (W) (Rev 0) [NO HEADER SAVE DATA]
|
|
0x8AE1, 0x102, // NBA Jam Tournament Edition (W) (Rev 1) [NO HEADER SAVE DATA]
|
|
0xDB97, 0x102, // NBA Jam Tournament Edition 32X (W)
|
|
0x7651, 0x102, // NFL Quarterback Club (W)
|
|
0xDFE4, 0x102, // NFL Quarterback Club 32X (W)
|
|
0x3DE6, 0x802, // NFL Quarterback Club '96 (UE)
|
|
0xCB78, 0x2002, // Frank Thomas Big Hurt Baseball (UE)
|
|
0x6DD9, 0x2002, // College Slam (U)
|
|
// CAPCOM
|
|
0xAD23, 0x83, // Mega Man: The Wily Wars (E)
|
|
0xEA80, 0x83, // Rockman Megaworld (J)
|
|
// SEGA
|
|
0x760F, 0x83, // Evander "Real Deal" Holyfield Boxing (JU)
|
|
0x95E7, 0x83, // Greatest Heavyweights of the Ring (E)
|
|
0x0000, 0x83, // Greatest Heavyweights of the Ring (J) [BLANK CHECKSUM 0000]
|
|
0x7270, 0x83, // Greatest Heavyweights of the Ring (U)
|
|
0xBACC, 0x83, // Honoo no Toukyuuji Dodge Danpei (J)
|
|
0xB939, 0x83, // MLBPA Sports Talk Baseball (U) [BAD HEADER SAVE DATA]
|
|
0x487C, 0x83, // Ninja Burai Densetsu (J)
|
|
0x740D, 0x83, // Wonder Boy in Monster World (B)
|
|
0x0278, 0x83, // Wonder Boy in Monster World (J)
|
|
0x9D79, 0x83, // Wonder Boy in Monster World (UE)
|
|
// EA
|
|
0x8512, 0x84, // Bill Walsh College Football (UE) [BAD HEADER SAVE DATA]
|
|
0xA107, 0x84, // John Madden Football '93 (UE) [NO HEADER SAVE DATA]
|
|
0x246A, 0x84, // John Madden Football '93 (U) (EA Sports) [NO HEADER SAVE DATA]
|
|
0x5807, 0x84, // John Madden Football '93 Championship Edition (U) [NO HEADER SAVE DATA]
|
|
0x2799, 0x84, // NHLPA Hockey '93 (UE) (Rev 0) [NO HEADER SAVE DATA]
|
|
0xFA57, 0x84, // NHLPA Hockey '93 (UE) (Rev 1) [NO HEADER SAVE DATA]
|
|
0x8B9F, 0x84, // Rings of Power (UE) [NO HEADER SAVE DATA]
|
|
// CODEMASTERS
|
|
0x7E65, 0x405, // Brian Lara Cricket (E) [NO HEADER SAVE DATA]
|
|
0x9A5C, 0x2005, // Brian Lara Cricket 96 (E) (Rev 1.0) [NO HEADER SAVE DATA]
|
|
0xC4EE, 0x2005, // Brian Lara Cricket 96 (E) (Rev 1.1) [NO HEADER SAVE DATA]
|
|
0x7E50, 0x805, // Micro Machines 2 (E) (J-Cart) [NO HEADER SAVE DATA]
|
|
0x165E, 0x805, // Micro Machines '96 (E) (J-Cart) (Rev 1.0/1.1) [NO HEADER SAVE DATA]
|
|
0x168B, 0x405, // Micro Machines Military (E) (J-Cart) [NO HEADER SAVE DATA]
|
|
0x12C1, 0x2005, // Shane Warne Cricket (E) [NO HEADER SAVE DATA]
|
|
};
|
|
|
|
byte eepcount = (sizeof(eepid) / sizeof(eepid[0])) / 2;
|
|
word eepdata;
|
|
|
|
// CD BACKUP RAM
|
|
unsigned long bramSize = 0;
|
|
|
|
// REALTEC MAPPER
|
|
boolean realtec = 0;
|
|
|
|
#if defined(ENABLED_CONFIG)
|
|
|
|
int segaSram16bit = 0;
|
|
|
|
#else /* !ENABLED_CONFIG */
|
|
|
|
#ifndef OPTION_MD_DEFAULT_SAVE_TYPE
|
|
#define OPTION_MD_DEFAULT_SAVE_TYPE 0
|
|
#endif /* !OPTION_MD_DEFAULT_SAVE_TYPE */
|
|
|
|
int segaSram16bit = OPTION_MD_DEFAULT_SAVE_TYPE;
|
|
|
|
#endif /* ENABLED_CONFIG */
|
|
|
|
//*****************************************
|
|
// SONIC & KNUCKLES LOCK-ON MODE VARIABLES
|
|
// SnKmode :
|
|
// 0 = Not Sonic & Knuckles
|
|
// 1 = Only Sonic & Knucles
|
|
// 2 = Sonic & Knucles + Sonic1
|
|
// 3 = Sonic & Knucles + Sonic2
|
|
// 4 = Sonic & Knucles + Sonic3
|
|
// 5 = Sonic & Knucles + Other game
|
|
//*****************************************
|
|
static byte SnKmode = 0;
|
|
static unsigned long cartSizeLockon;
|
|
static unsigned long cartSizeSonic2 = 262144;
|
|
static word chksumLockon;
|
|
static word chksumSonic2 = 0x0635;
|
|
|
|
/******************************************
|
|
Configuration
|
|
*****************************************/
|
|
#if defined(use_md_conf) && !defined(ENABLE_CONFIG)
|
|
|
|
#warning "DEPRECATED: use_md_conf is deprecated. You should use ENABLE_CONFIG instead."
|
|
|
|
void mdLoadConf() {
|
|
if (myFile.open("mdconf.txt", O_READ)) {
|
|
char line[64];
|
|
unsigned int n;
|
|
unsigned int i;
|
|
while ((n = myFile.fgets(line, sizeof(line) - 1)) > 0) {
|
|
// preprocess
|
|
for (i = 0; i < n; i++) {
|
|
if (line[i] == ';') {
|
|
// comments
|
|
line[i] = '\0';
|
|
n = i;
|
|
break;
|
|
} else if (line[i] == '\n' || line[i] == '\r') {
|
|
// EOL
|
|
line[n] = '\0';
|
|
n = i;
|
|
break;
|
|
}
|
|
}
|
|
//print_Msg(F("read line: "));
|
|
//println_Msg(line);
|
|
if (line[0] == '[') {
|
|
char* name;
|
|
char* value;
|
|
i = 1;
|
|
name = line + i;
|
|
for (; i < sizeof(line); i++) {
|
|
if (line[i] == ']') {
|
|
line[i] = '\0';
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
if (line[i] != '\0') {
|
|
for (; i < sizeof(line); i++) {
|
|
if (line[i] != ' ') {
|
|
value = line + i;
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
for (; i < sizeof(line) && line[i] != '\0'; i++) {
|
|
if (line[i] == ' ') {
|
|
line[i] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//print_Msg(F("read name: "));
|
|
//println_Msg(name);
|
|
//print_Msg(F("value: "));
|
|
//println_Msg(value);
|
|
if (!strcmp("segaSram16bit", name)) {
|
|
// Retrode compatible setting
|
|
// [segaSram16bit] 1 ; 0=no, 1=yes, 2=y+large
|
|
// 0: Output each byte once. Not supported by most emulators.
|
|
// 1: Duplicate each byte. Usable by Kega Fusion.
|
|
// 2: Duplicate each byte. Pad with 0xFF so that the file size is 64KB.
|
|
segaSram16bit = atoi(value);
|
|
if (segaSram16bit != 0 && segaSram16bit != 1 && segaSram16bit != 2) {
|
|
segaSram16bit = DEFAULT_VALUE_SAVE_TYPE;
|
|
}
|
|
print_Msg(F("segaSram16bit: "));
|
|
println_Msg(segaSram16bit);
|
|
}
|
|
}
|
|
}
|
|
myFile.close();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void pulse_clock(int n) {
|
|
for (int i = 0; i < n; i++)
|
|
PORTH ^= (1 << 1);
|
|
}
|
|
|
|
/******************************************
|
|
Menu
|
|
*****************************************/
|
|
// MD menu items
|
|
static const char MDMenuItem1[] PROGMEM = "Game Cartridge";
|
|
static const char MDMenuItem2[] PROGMEM = "SegaCD RamCart";
|
|
static const char MDMenuItem3[] PROGMEM = "Flash Repro";
|
|
static const char* const menuOptionsMD[] PROGMEM = { MDMenuItem1, MDMenuItem2, MDMenuItem3, FSTRING_RESET };
|
|
|
|
// Cart menu items
|
|
static const char MDCartMenuItem4[] PROGMEM = "Force ROM size";
|
|
static const char* const menuOptionsMDCart[] PROGMEM = { FSTRING_READ_ROM, FSTRING_READ_SAVE, FSTRING_WRITE_SAVE, MDCartMenuItem4, FSTRING_REFRESH_CART, FSTRING_RESET };
|
|
|
|
// Sega CD Ram Backup Cartridge menu items
|
|
static const char SCDMenuItem1[] PROGMEM = "Read Backup RAM";
|
|
static const char SCDMenuItem2[] PROGMEM = "Write Backup RAM";
|
|
static const char* const menuOptionsSCD[] PROGMEM = { SCDMenuItem1, SCDMenuItem2, FSTRING_RESET };
|
|
|
|
// Sega start menu
|
|
void mdMenu() {
|
|
// create menu with title and 4 options to choose from
|
|
unsigned char mdDev;
|
|
// Copy menuOptions out of progmem
|
|
convertPgm(menuOptionsMD, 4);
|
|
mdDev = question_box(F("Select MD device"), menuOptions, 4, 0);
|
|
|
|
// wait for user choice to come back from the question box menu
|
|
switch (mdDev) {
|
|
case 0:
|
|
display_Clear();
|
|
display_Update();
|
|
setup_MD();
|
|
mode = CORE_MD_CART;
|
|
break;
|
|
|
|
case 1:
|
|
display_Clear();
|
|
display_Update();
|
|
setup_MD();
|
|
mode = CORE_SEGA_CD;
|
|
break;
|
|
|
|
#ifdef ENABLE_FLASH
|
|
case 2:
|
|
display_Clear();
|
|
display_Update();
|
|
setup_MD();
|
|
mode = CORE_MD_CART;
|
|
// Change working dir to root
|
|
filePath[0] = '\0';
|
|
sd.chdir("/");
|
|
fileBrowser(FS(FSTRING_SELECT_FILE));
|
|
display_Clear();
|
|
// Setting CS(PH3) LOW
|
|
PORTH &= ~(1 << 3);
|
|
|
|
// ID flash
|
|
resetFlash_MD();
|
|
idFlash_MD();
|
|
resetFlash_MD();
|
|
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(FS(FSTRING_EMPTY));
|
|
// Prints string out of the common strings array either with or without newline
|
|
print_STR(press_button_STR, 1);
|
|
display_Update();
|
|
wait();
|
|
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, 6);
|
|
mainMenu = question_box(F("MEGA DRIVE Reader"), menuOptions, 6, 0);
|
|
|
|
// wait for user choice to come back from the question box menu
|
|
switch (mainMenu) {
|
|
case 0:
|
|
display_Clear();
|
|
|
|
// common ROM read fail state: no cart inserted - tends to report impossibly large cartSize
|
|
// largest known game so far is supposedly "Paprium" at 10MB, so cap sanity check at 16MB
|
|
if (cartSize != 0 && cartSize <= 16777216) {
|
|
// Change working dir to root
|
|
sd.chdir("/");
|
|
if (realtec) {
|
|
readRealtec_MD();
|
|
} else {
|
|
readROM_MD();
|
|
// Calculate and compare CRC32 with database
|
|
if (is32x)
|
|
//database, crcString, renamerom, offset
|
|
compareCRC("32x.txt", 0, 1, 0);
|
|
else
|
|
compareCRC("md.txt", 0, 1, 0);
|
|
}
|
|
} else {
|
|
print_Error(F("Cart has no ROM"));
|
|
}
|
|
#ifdef ENABLE_GLOBAL_LOG
|
|
save_log();
|
|
#endif
|
|
break;
|
|
|
|
case 1:
|
|
display_Clear();
|
|
// Does cartridge have SRAM
|
|
if ((saveType == 1) || (saveType == 2) || (saveType == 3)) {
|
|
// Change working dir to root
|
|
sd.chdir("/");
|
|
println_Msg(F("Reading Sram..."));
|
|
display_Update();
|
|
enableSram_MD(1);
|
|
readSram_MD();
|
|
enableSram_MD(0);
|
|
} else if (saveType == 4) {
|
|
readEEP_MD();
|
|
} else {
|
|
print_Error(F("Cart has no Save"));
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
display_Clear();
|
|
// Does cartridge have SRAM
|
|
if ((saveType == 1) || (saveType == 2) || (saveType == 3)) {
|
|
// Change working dir to root
|
|
sd.chdir("/");
|
|
// Launch file browser
|
|
fileBrowser(F("Select srm file"));
|
|
display_Clear();
|
|
enableSram_MD(1);
|
|
writeSram_MD();
|
|
writeErrors = verifySram_MD();
|
|
enableSram_MD(0);
|
|
if (writeErrors == 0) {
|
|
println_Msg(F("Sram verified OK"));
|
|
display_Update();
|
|
} else {
|
|
print_STR(error_STR, 0);
|
|
print_Msg(writeErrors);
|
|
print_STR(_bytes_STR, 1);
|
|
print_Error(did_not_verify_STR);
|
|
}
|
|
} else if (saveType == 4) {
|
|
// Launch file browser
|
|
fileBrowser(F("Select eep file"));
|
|
display_Clear();
|
|
writeEEP_MD();
|
|
} else {
|
|
print_Error(F("Cart has no Save"));
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
display_Clear();
|
|
force_cartSize_MD();
|
|
break;
|
|
|
|
case 4:
|
|
// For multi-game carts
|
|
// Set reset pin to output (PH0)
|
|
DDRH |= (1 << 0);
|
|
// Switch RST(PH0) to LOW
|
|
PORTH &= ~(1 << 0);
|
|
|
|
display_Clear();
|
|
print_Msg(F("Resetting..."));
|
|
display_Update();
|
|
delay(3000); // wait 3 secs to switch to next game
|
|
resetArduino();
|
|
break;
|
|
|
|
case 5:
|
|
// Reset
|
|
resetArduino();
|
|
break;
|
|
}
|
|
// Prints string out of the common strings array either with or without newline
|
|
print_STR(press_button_STR, 1);
|
|
display_Update();
|
|
wait();
|
|
}
|
|
|
|
void segaCDMenu() {
|
|
// create menu with title and 3 options to choose from
|
|
unsigned char scdMenu;
|
|
// Copy menuOptions out of progmem
|
|
convertPgm(menuOptionsSCD, 3);
|
|
scdMenu = question_box(F("SEGA CD RAM"), menuOptions, 3, 0);
|
|
|
|
// wait for user choice to come back from the question box menu
|
|
switch (scdMenu) {
|
|
case 0:
|
|
display_Clear();
|
|
if (bramSize > 0)
|
|
readBram_MD();
|
|
else {
|
|
print_Error(F("Not CD Backup RAM Cart"));
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
display_Clear();
|
|
if (bramSize > 0) {
|
|
// Launch file browser
|
|
fileBrowser(F("Select brm file"));
|
|
display_Clear();
|
|
writeBram_MD();
|
|
} else {
|
|
print_Error(F("Not CD Backup RAM Cart"));
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
// Reset
|
|
asm volatile(" jmp 0");
|
|
break;
|
|
}
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
// Prints string out of the common strings array either with or without newline
|
|
print_STR(press_button_STR, 1);
|
|
display_Update();
|
|
wait();
|
|
}
|
|
|
|
/******************************************
|
|
Setup
|
|
*****************************************/
|
|
void setup_MD() {
|
|
// Request 5V
|
|
setVoltage(VOLTS_SET_5V);
|
|
|
|
#if defined(ENABLE_CONFIG)
|
|
segaSram16bit = configGetLong(F("md.saveType"));
|
|
#elif defined(use_md_conf)
|
|
mdLoadConf();
|
|
#endif /*ENABLE_CONFIG*/
|
|
|
|
// Set Address Pins to Output
|
|
//A0-A7
|
|
DDRF = 0xFF;
|
|
//A8-A15
|
|
DDRK = 0xFF;
|
|
//A16-A23
|
|
DDRL = 0xFF;
|
|
|
|
// Set Control Pins to Output RST(PH0) CLK(PH1) CS(PH3) WRH(PH4) WRL(PH5) OE(PH6)
|
|
DDRH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
|
|
|
|
// Set TIME(PJ0), AS(PJ1) to Output
|
|
DDRJ |= (1 << 0) | (1 << 1);
|
|
|
|
//set ASEL(PG5) to Output
|
|
DDRG |= (1 << 5);
|
|
|
|
// Set Data Pins (D0-D15) to Input
|
|
DDRC = 0x00;
|
|
DDRA = 0x00;
|
|
|
|
// Setting RST(PH0) CS(PH3) WRH(PH4) WRL(PH5) OE(PH6) HIGH
|
|
PORTH |= (1 << 0) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);
|
|
|
|
// Setting TIME(PJ0) AS(PJ1) HIGH
|
|
PORTJ |= (1 << 0) | (1 << 1);
|
|
|
|
// setting ASEL(PG5) HIGH
|
|
PORTG |= (1 << 5);
|
|
|
|
delay(200);
|
|
|
|
// Print all the info
|
|
getCartInfo_MD();
|
|
}
|
|
|
|
/******************************************
|
|
I/O Functions
|
|
*****************************************/
|
|
|
|
/******************************************
|
|
Low level functions
|
|
*****************************************/
|
|
void writeWord_MD(unsigned long myAddress, word myData) {
|
|
PORTF = myAddress & 0xFF;
|
|
PORTK = (myAddress >> 8) & 0xFF;
|
|
PORTL = (myAddress >> 16) & 0xFF;
|
|
PORTC = myData;
|
|
PORTA = (myData >> 8) & 0xFF;
|
|
|
|
// Arduino running at 16Mhz -> one nop = 62.5ns
|
|
// Wait till output is stable
|
|
__asm__("nop\n\t"
|
|
"nop\n\t");
|
|
|
|
// Switch WR(PH5) to LOW
|
|
PORTH &= ~(1 << 5);
|
|
// Setting CS(PH3) LOW
|
|
PORTH &= ~(1 << 3);
|
|
|
|
// Leave WR low for at least 200ns
|
|
__asm__("nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t");
|
|
|
|
// Setting CS(PH3) HIGH
|
|
PORTH |= (1 << 3);
|
|
// Switch WR(PH5) to HIGH
|
|
PORTH |= (1 << 5);
|
|
|
|
// Leave WR high for at least 50ns
|
|
__asm__("nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t");
|
|
}
|
|
|
|
word readWord_MD(unsigned long myAddress) {
|
|
PORTF = myAddress & 0xFF;
|
|
PORTK = (myAddress >> 8) & 0xFF;
|
|
PORTL = (myAddress >> 16) & 0xFF;
|
|
|
|
// Arduino running at 16Mhz -> one nop = 62.5ns
|
|
NOP;
|
|
|
|
// Setting CS(PH3) LOW
|
|
PORTH &= ~(1 << 3);
|
|
// Setting OE(PH6) LOW
|
|
PORTH &= ~(1 << 6);
|
|
// Setting AS(PJ1) LOW
|
|
PORTJ &= ~(1 << 1);
|
|
// Setting ASEL(PG5) LOW
|
|
PORTG &= ~(1 << 5);
|
|
// Pulse CLK(PH1), needed for SVP enhanced carts
|
|
pulse_clock(10);
|
|
|
|
// most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
|
|
// Read
|
|
word tempWord = ((PINA & 0xFF) << 8) | (PINC & 0xFF);
|
|
|
|
// Setting CS(PH3) HIGH
|
|
PORTH |= (1 << 3);
|
|
// Setting OE(PH6) HIGH
|
|
PORTH |= (1 << 6);
|
|
// Setting AS(PJ1) HIGH
|
|
PORTJ |= (1 << 1);
|
|
// Setting ASEL(PG5) HIGH
|
|
PORTG |= (1 << 5);
|
|
// Pulse CLK(PH1), needed for SVP enhanced carts
|
|
pulse_clock(10);
|
|
|
|
// these 6x nop delays have been here before, unknown what they mean
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
|
|
return tempWord;
|
|
}
|
|
|
|
void writeFlash_MD(unsigned long myAddress, word myData) {
|
|
PORTF = myAddress & 0xFF;
|
|
PORTK = (myAddress >> 8) & 0xFF;
|
|
PORTL = (myAddress >> 16) & 0xFF;
|
|
PORTC = myData;
|
|
PORTA = (myData >> 8) & 0xFF;
|
|
|
|
// Arduino running at 16Mhz -> one nop = 62.5ns
|
|
// Wait till output is stable
|
|
__asm__("nop\n\t");
|
|
|
|
// Switch WE(PH5) to LOW
|
|
PORTH &= ~(1 << 5);
|
|
|
|
// Leave WE low for at least 60ns
|
|
__asm__("nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t");
|
|
|
|
// Switch WE(PH5)to HIGH
|
|
PORTH |= (1 << 5);
|
|
|
|
// Leave WE high for at least 50ns
|
|
__asm__("nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t");
|
|
}
|
|
|
|
word readFlash_MD(unsigned long myAddress) {
|
|
PORTF = myAddress & 0xFF;
|
|
PORTK = (myAddress >> 8) & 0xFF;
|
|
PORTL = (myAddress >> 16) & 0xFF;
|
|
|
|
// Arduino running at 16Mhz -> one nop = 62.5ns
|
|
__asm__("nop\n\t");
|
|
|
|
// Setting OE(PH6) LOW
|
|
PORTH &= ~(1 << 6);
|
|
|
|
__asm__("nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t");
|
|
|
|
// Read
|
|
word tempWord = ((PINA & 0xFF) << 8) | (PINC & 0xFF);
|
|
|
|
__asm__("nop\n\t");
|
|
|
|
// Setting OE(PH6) HIGH
|
|
PORTH |= (1 << 6);
|
|
__asm__("nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t");
|
|
|
|
return tempWord;
|
|
}
|
|
|
|
// Switch data pins to write
|
|
void dataOut_MD() {
|
|
DDRC = 0xFF;
|
|
DDRA = 0xFF;
|
|
}
|
|
|
|
// Switch data pins to read
|
|
void dataIn_MD() {
|
|
DDRC = 0x00;
|
|
DDRA = 0x00;
|
|
}
|
|
|
|
/******************************************
|
|
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 0x06C1: // Madden NFL 98 (USA)
|
|
cartSize = 0x200000;
|
|
chksum = 0x8473;
|
|
break;
|
|
case 0x5B3A: // NHL 98 (USA)
|
|
cartSize = 0x200000;
|
|
chksum = 0x5613;
|
|
break;
|
|
case 0xD07D: // Zero Wing (Japan)
|
|
cartSize = 0x100000;
|
|
chksum = 0xF204;
|
|
break;
|
|
case 0x95C9: // Zero Wing (Europe)
|
|
case 0x9144: // Zoop (Europe)
|
|
case 0xB8D4: // Zoop (USA)
|
|
cartSize = 0x100000;
|
|
break;
|
|
case 0xC422: // Jeopardy! (USA)
|
|
chksum = 0xC751;
|
|
break;
|
|
case 0x0C6A: // Monopoly (USA)
|
|
chksum = 0xE1AA;
|
|
break;
|
|
case 0xA760: // Gain Ground (USA)
|
|
chksum = 0x97CD;
|
|
break;
|
|
case 0x1404: // Wonder Boy III - Monster Lair (Japan, Europe) (En)
|
|
chksum = 0x53B9;
|
|
break;
|
|
}
|
|
}
|
|
if (cartSize == 0x40000) {
|
|
switch (chksum) {
|
|
case 0x8BC6: // Pac-Attack (USA)
|
|
case 0xB344: // Pac-Panic (Europe)
|
|
cartSize = 0x100000;
|
|
break;
|
|
}
|
|
}
|
|
if (cartSize == 0x20000) {
|
|
switch (chksum) {
|
|
case 0x7E50: // Micro Machines 2 - Turbo Tournament (Europe)
|
|
cartSize = 0x100000;
|
|
chksum = 0xD074;
|
|
break;
|
|
case 0x168B: // Micro Machines - Military (Europe)
|
|
cartSize = 0x100000;
|
|
chksum = 0xCEE0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Fatman (Japan).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;
|
|
}
|
|
|
|
// Sasha Darko's Sacred Line (Watermelon Release) (Aftermarket)
|
|
if (!strncmp("GM T-574323-00", id, 14) && (chksum == 0xAEDD)) {
|
|
cartSize = 0x400000;
|
|
}
|
|
|
|
// Kromasphere (Aftermarket)
|
|
if (!strncmp("GM MK-0000 -00", id, 14) && (chksum == 0xC536)) {
|
|
chksum = 0xFAB1;
|
|
cartSize = 0x200000;
|
|
}
|
|
|
|
// YM2017 (Aftermarket)
|
|
if (!strncmp("GM CSET0001-02", id, 14) && (chksum == 0x0000)) {
|
|
chksum = 0xE3A9;
|
|
}
|
|
|
|
// The Curse of Illmore Bay (Aftermarket)
|
|
if (!strncmp("1774 ", id, 14) && (chksum == 0x0000)) {
|
|
chksum = 0x6E34;
|
|
cartSize = 0x400000;
|
|
}
|
|
|
|
// Coffee Crisis (Aftermarket)
|
|
if (!strncmp("JN-20160131-03", id, 14) && (chksum == 0x0000)) {
|
|
chksum = 0x8040;
|
|
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(FS(FSTRING_EMPTY));
|
|
print_Msg(("sramBase: "));
|
|
print_Msg_PaddedHex32(sramBase);
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
print_Msg(("sramEnd: "));
|
|
print_Msg_PaddedHex32(sramEnd);
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
print_FatalError(F("Unknown Sram Base"));
|
|
}
|
|
} else if (sramType == 0xE020) { // SRAM BOTH BYTES
|
|
// Get sram start and end
|
|
sramBase = ((long(readWord_MD(0xDA)) << 16) | readWord_MD(0xDB));
|
|
sramEnd = ((long(readWord_MD(0xDC)) << 16) | readWord_MD(0xDD));
|
|
|
|
if (sramBase == 0x200001) {
|
|
saveType = 3; // BOTH
|
|
sramSize = sramEnd - sramBase + 2;
|
|
sramBase = sramBase >> 1;
|
|
} else if (sramBase == 0x200000) {
|
|
saveType = 3; // BOTH
|
|
sramSize = sramEnd - sramBase + 1;
|
|
sramBase = sramBase >> 1;
|
|
} else if (sramBase == 0x3FFC00) {
|
|
// Used for some aftermarket carts without sram
|
|
saveType = 0;
|
|
} else {
|
|
print_Msg(("sramType: "));
|
|
print_Msg_PaddedHex16(sramType);
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
print_Msg(("sramBase: "));
|
|
print_Msg_PaddedHex32(sramBase);
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
print_Msg(("sramEnd: "));
|
|
print_Msg_PaddedHex32(sramEnd);
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
print_FatalError(F("Unknown Sram Base"));
|
|
}
|
|
}
|
|
} else {
|
|
// SRAM CARTS WITH BAD/MISSING HEADER SAVE DATA
|
|
switch (chksum) {
|
|
case 0xC2DB: // Winter Challenge (UE)
|
|
saveType = 1; // ODD
|
|
sramBase = 0x200001;
|
|
sramEnd = 0x200FFF;
|
|
break;
|
|
|
|
case 0xD7B6: // Buck Rogers: Countdown to Doomsday (UE)
|
|
case 0xFE3E: // NBA Live '98 (U)
|
|
case 0xFDAD: // NFL '94 starring Joe Montana (U)
|
|
case 0x632E: // PGA Tour Golf (UE) (Rev 1)
|
|
case 0xD2BA: // PGA Tour Golf (UE) (Rev 2)
|
|
case 0x44FE: // Super Hydlide (J)
|
|
saveType = 1; // ODD
|
|
sramBase = 0x200001;
|
|
sramEnd = 0x203FFF;
|
|
break;
|
|
|
|
case 0xDB5E: // Might & Magic: Gates to Another World (UE) (Rev 1)
|
|
case 0x3428: // Starflight (UE) (Rev 0)
|
|
case 0x43EE: // Starflight (UE) (Rev 1)
|
|
saveType = 3; // BOTH
|
|
sramBase = 0x200001;
|
|
sramEnd = 0x207FFF;
|
|
break;
|
|
|
|
case 0xBF72: // College Football USA '96 (U)
|
|
case 0x72EF: // FIFA Soccer '97 (UE)
|
|
case 0xD723: // Hardball III (U)
|
|
case 0x06C1: // Madden NFL '98 (U)
|
|
case 0xDB17: // NHL '96 (UE)
|
|
case 0x5B3A: // NHL '98 (U)
|
|
case 0x2CF2: // NFL Sports Talk Football '93 starring Joe Montana (UE)
|
|
case 0xE9B1: // Summer Challenge (U)
|
|
case 0xEEE8: // Test Drive II: The Duel (U)
|
|
saveType = 1; // ODD
|
|
sramBase = 0x200001;
|
|
sramEnd = 0x20FFFF;
|
|
break;
|
|
}
|
|
if (saveType == 1) {
|
|
sramSize = (sramEnd - sramBase + 2) / 2;
|
|
sramBase = sramBase >> 1;
|
|
} else if (saveType == 3) {
|
|
sramSize = sramEnd - sramBase + 2;
|
|
sramBase = sramBase >> 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get 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;
|
|
|
|
//Check for Slaughter Sport
|
|
if (!strncmp("GMT5604600jJ", romName, 12) && (chksum == 0xFFFF)) {
|
|
strcpy(romName, "SLAUGHTERSPORT");
|
|
chksum = 0x6BAE;
|
|
}
|
|
|
|
//Get Lock-on cart name
|
|
if (SnKmode >= 2) {
|
|
char romNameLockon[12];
|
|
|
|
//Change romName
|
|
strcpy(romName, "SnK_");
|
|
|
|
for (byte c = 0; c < 48; c += 2) {
|
|
// split word
|
|
word myWord = readWord_MD((0x200150 + c) / 2);
|
|
byte loByte = myWord & 0xFF;
|
|
byte hiByte = myWord >> 8;
|
|
|
|
// write to buffer
|
|
sdBuffer[c] = hiByte;
|
|
sdBuffer[c + 1] = loByte;
|
|
}
|
|
romNameLockon[copyToRomName_MD(romNameLockon, sdBuffer, sizeof(romNameLockon) - 1)] = 0;
|
|
|
|
switch (SnKmode) {
|
|
case 2: strcat(romName, "SONIC1"); break;
|
|
case 3: strcat(romName, "SONIC2"); break;
|
|
case 4: strcat(romName, "SONIC3"); break;
|
|
case 5: strcat(romName, romNameLockon); break;
|
|
}
|
|
}
|
|
|
|
// Realtec Mapper Check
|
|
word realtecCheck1 = readWord_MD(0x3F080); // 0x7E100 "SEGA" (BootROM starts at 0x7E000)
|
|
word realtecCheck2 = readWord_MD(0x3F081);
|
|
if ((realtecCheck1 == 0x5345) && (realtecCheck2 == 0x4741)) {
|
|
realtec = 1;
|
|
strcpy(romName, "Realtec");
|
|
cartSize = 0x80000;
|
|
}
|
|
|
|
// Some games are missing the ROM size in the header, in this case calculate ROM size by looking for mirror of the first line of the ROM
|
|
// This does not work for cartridges that have SRAM mapped directly after the maskrom like Striker (Europe)
|
|
if ((cartSize < 0x8000) || (cartSize > 0xEAF400)) {
|
|
for (cartSize = 0x20000 / 2; cartSize < 0x400000 / 2; cartSize += 0x20000 / 2) {
|
|
if ((readWord_MD(0x0) == readWord_MD(cartSize)) && (readWord_MD(0x1) == readWord_MD(0x1 + cartSize)) && (readWord_MD(0x2) == readWord_MD(0x2 + cartSize)) && (readWord_MD(0x3) == readWord_MD(0x3 + cartSize)) && (readWord_MD(0x4) == readWord_MD(0x4 + cartSize)) && (readWord_MD(0x5) == readWord_MD(0x5 + cartSize)) && (readWord_MD(0x6) == readWord_MD(0x6 + cartSize)) && (readWord_MD(0x7) == readWord_MD(0x7 + cartSize)) && (readWord_MD(0x8) == readWord_MD(0x8 + cartSize))) {
|
|
break;
|
|
}
|
|
}
|
|
cartSize = cartSize * 2;
|
|
}
|
|
|
|
display_Clear();
|
|
println_Msg(F("Cart Info"));
|
|
println_Msg(FS(FSTRING_SPACE));
|
|
print_Msg(FS(FSTRING_NAME));
|
|
println_Msg(romName);
|
|
if (bramCheck != 0x00FF) {
|
|
print_Msg(F("bramCheck: "));
|
|
print_Msg_PaddedHex16(bramCheck);
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
}
|
|
if (bramSize > 0) {
|
|
print_Msg(F("bramSize(KB): "));
|
|
println_Msg(bramSize >> 10);
|
|
}
|
|
print_Msg(FS(FSTRING_SIZE));
|
|
print_Msg(cartSize * 8 / 1024 / 1024);
|
|
switch (SnKmode) {
|
|
case 2:
|
|
case 4:
|
|
case 5:
|
|
print_Msg(F("+"));
|
|
print_Msg(cartSizeLockon * 8 / 1024 / 1024);
|
|
break;
|
|
case 3:
|
|
print_Msg(F("+"));
|
|
print_Msg(cartSizeLockon * 8 / 1024 / 1024);
|
|
print_Msg(F("+"));
|
|
print_Msg(cartSizeSonic2 * 8 / 1024 / 1024);
|
|
break;
|
|
}
|
|
println_Msg(F(" MBit"));
|
|
print_Msg(F("ChkS: "));
|
|
print_Msg_PaddedHexByte((chksum >> 8));
|
|
print_Msg_PaddedHexByte((chksum & 0x00ff));
|
|
switch (SnKmode) {
|
|
case 2:
|
|
case 4:
|
|
case 5:
|
|
print_Msg(F("+"));
|
|
print_Msg_PaddedHexByte((chksumLockon >> 8));
|
|
print_Msg_PaddedHexByte((chksumLockon & 0x00ff));
|
|
break;
|
|
case 3:
|
|
print_Msg(F("+"));
|
|
print_Msg_PaddedHexByte((chksumLockon >> 8));
|
|
print_Msg_PaddedHexByte((chksumLockon & 0x00ff));
|
|
print_Msg(F("+"));
|
|
print_Msg_PaddedHexByte((chksumSonic2 >> 8));
|
|
print_Msg_PaddedHexByte((chksumSonic2 & 0x00ff));
|
|
break;
|
|
}
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
if (saveType == 4) {
|
|
print_Msg(F("Serial EEPROM: "));
|
|
print_Msg(eepSize * 8 / 1024);
|
|
println_Msg(F(" KBit"));
|
|
} else {
|
|
print_Msg(F("Sram: "));
|
|
if (sramSize > 0) {
|
|
print_Msg(sramSize * 8 / 1024);
|
|
println_Msg(F(" KBit"));
|
|
} else
|
|
println_Msg(F("None"));
|
|
}
|
|
println_Msg(FS(FSTRING_SPACE));
|
|
|
|
// Wait for user input
|
|
#if (defined(ENABLE_LCD) || defined(ENABLE_OLED))
|
|
// Prints string out of the common strings array either with or without newline
|
|
print_STR(press_button_STR, 1);
|
|
display_Update();
|
|
wait();
|
|
#endif
|
|
}
|
|
|
|
void writeSSF2Map(unsigned long myAddress, word myData) {
|
|
dataOut_MD();
|
|
|
|
// Set TIME(PJ0) HIGH
|
|
PORTJ |= (1 << 0);
|
|
|
|
// 0x50987E * 2 = 0xA130FD Bank 6 (0x300000-0x37FFFF)
|
|
// 0x50987F * 2 = 0xA130FF Bank 7 (0x380000-0x3FFFFF)
|
|
PORTL = (myAddress >> 16) & 0xFF;
|
|
PORTK = (myAddress >> 8) & 0xFF;
|
|
PORTF = myAddress & 0xFF;
|
|
PORTC = myData;
|
|
PORTA = (myData >> 8) & 0xFF;
|
|
|
|
// Arduino running at 16Mhz -> one nop = 62.5ns
|
|
// Wait till output is stable
|
|
__asm__("nop\n\t"
|
|
"nop\n\t");
|
|
|
|
// Strobe TIME(PJ0) LOW to latch the data
|
|
PORTJ &= ~(1 << 0);
|
|
// Switch WR(PH5) to LOW
|
|
PORTH &= ~(1 << 5);
|
|
|
|
// Leave WR low for at least 200ns
|
|
__asm__("nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t");
|
|
|
|
// Switch WR(PH5) to HIGH
|
|
PORTH |= (1 << 5);
|
|
// Set TIME(PJ0) HIGH
|
|
PORTJ |= (1 << 0);
|
|
|
|
dataIn_MD();
|
|
}
|
|
|
|
// Read rom and save to the SD card
|
|
void readROM_MD() {
|
|
// Checksum
|
|
uint16_t calcCKS = 0;
|
|
uint16_t calcCKSLockon = 0;
|
|
uint16_t calcCKSSonic2 = 0;
|
|
|
|
// Set control
|
|
dataIn_MD();
|
|
|
|
// Get name, add extension and convert to char array for sd lib
|
|
createFolderAndOpenFile("MD", "ROM", romName, "BIN");
|
|
|
|
byte buffer[1024] = { 0 };
|
|
|
|
// Phantasy Star/Beyond Oasis with 74HC74 and 74HC139 switch ROM/SRAM at address 0x200000
|
|
if (0x200000 < cartSize && cartSize < 0x400000) {
|
|
enableSram_MD(0);
|
|
}
|
|
|
|
// Prepare SSF2 Banks
|
|
if (cartSize > 0x400000) {
|
|
writeSSF2Map(0x50987E, 6); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 7); // 0xA130FF
|
|
}
|
|
byte offsetSSF2Bank = 0;
|
|
word d = 0;
|
|
|
|
//Initialize progress bar
|
|
uint32_t processedProgressBar = 0;
|
|
uint32_t totalProgressBar = (uint32_t)(cartSize);
|
|
if (SnKmode >= 2) totalProgressBar += (uint32_t)cartSizeLockon;
|
|
if (SnKmode == 3) totalProgressBar += (uint32_t)cartSizeSonic2;
|
|
draw_progressbar(0, totalProgressBar);
|
|
|
|
for (unsigned long currBuffer = 0; currBuffer < cartSize / 2; currBuffer += 512) {
|
|
// Blink led
|
|
if (currBuffer % 16384 == 0)
|
|
blinkLED();
|
|
|
|
if (currBuffer == 0x200000) {
|
|
writeSSF2Map(0x50987E, 8); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 9); // 0xA130FF
|
|
offsetSSF2Bank = 1;
|
|
}
|
|
|
|
// Demons of Asteborg Additional Banks
|
|
else if (currBuffer == 0x280000) {
|
|
writeSSF2Map(0x50987E, 10); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 11); // 0xA130FF
|
|
offsetSSF2Bank = 2;
|
|
} else if (currBuffer == 0x300000) {
|
|
writeSSF2Map(0x50987E, 12); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 13); // 0xA130FF
|
|
offsetSSF2Bank = 3;
|
|
} else if (currBuffer == 0x380000) {
|
|
writeSSF2Map(0x50987E, 14); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 15); // 0xA130FF
|
|
offsetSSF2Bank = 4;
|
|
} else if (currBuffer == 0x400000) {
|
|
writeSSF2Map(0x50987E, 16); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 17); // 0xA130FF
|
|
offsetSSF2Bank = 5;
|
|
} else if (currBuffer == 0x480000) {
|
|
writeSSF2Map(0x50987E, 18); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 19); // 0xA130FF
|
|
offsetSSF2Bank = 6;
|
|
} else if (currBuffer == 0x500000) {
|
|
writeSSF2Map(0x50987E, 20); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 21); // 0xA130FF
|
|
offsetSSF2Bank = 7;
|
|
} else if (currBuffer == 0x580000) {
|
|
writeSSF2Map(0x50987E, 22); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 23); // 0xA130FF
|
|
offsetSSF2Bank = 8;
|
|
} else if (currBuffer == 0x600000) {
|
|
writeSSF2Map(0x50987E, 24); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 25); // 0xA130FF
|
|
offsetSSF2Bank = 9;
|
|
} else if (currBuffer == 0x680000) {
|
|
writeSSF2Map(0x50987E, 26); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 27); // 0xA130FF
|
|
offsetSSF2Bank = 10;
|
|
} else if (currBuffer == 0x700000) {
|
|
writeSSF2Map(0x50987E, 28); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 29); // 0xA130FF
|
|
offsetSSF2Bank = 11;
|
|
}
|
|
|
|
d = 0;
|
|
|
|
for (int currWord = 0; currWord < 512; currWord++) {
|
|
unsigned long myAddress = currBuffer + currWord - (offsetSSF2Bank * 0x80000);
|
|
PORTF = myAddress & 0xFF;
|
|
PORTK = (myAddress >> 8) & 0xFF;
|
|
PORTL = (myAddress >> 16) & 0xFF;
|
|
|
|
// Arduino running at 16Mhz -> one nop = 62.5ns
|
|
NOP;
|
|
// Setting CS(PH3) LOW
|
|
PORTH &= ~(1 << 3);
|
|
// Setting OE(PH6) LOW
|
|
PORTH &= ~(1 << 6);
|
|
// Setting AS(PJ1) LOW
|
|
PORTJ &= ~(1 << 1);
|
|
// Setting ASEL(PG5) LOW
|
|
PORTG &= ~(1 << 5);
|
|
// Pulse CLK(PH1)
|
|
if (isSVP)
|
|
pulse_clock(10);
|
|
|
|
// most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
|
|
// Read
|
|
buffer[d] = PINA;
|
|
buffer[d + 1] = PINC;
|
|
|
|
// Setting CS(PH3) HIGH
|
|
PORTH |= (1 << 3);
|
|
// Setting OE(PH6) HIGH
|
|
PORTH |= (1 << 6);
|
|
// Setting AS(PJ1) HIGH
|
|
PORTJ |= (1 << 1);
|
|
// Setting ASEL(PG5) HIGH
|
|
PORTG |= (1 << 5);
|
|
// Pulse CLK(PH1)
|
|
if (isSVP)
|
|
pulse_clock(10);
|
|
|
|
// Skip first 256 words
|
|
if (((currBuffer == 0) && (currWord >= 256)) || (currBuffer > 0)) {
|
|
calcCKS += ((buffer[d] << 8) | buffer[d + 1]);
|
|
}
|
|
d += 2;
|
|
}
|
|
myFile.write(buffer, 1024);
|
|
|
|
// update progress bar
|
|
processedProgressBar += 1024;
|
|
draw_progressbar(processedProgressBar, totalProgressBar);
|
|
}
|
|
if (SnKmode >= 2) {
|
|
for (unsigned long currBuffer = 0; currBuffer < cartSizeLockon / 2; currBuffer += 512) {
|
|
// Blink led
|
|
if (currBuffer % 16384 == 0)
|
|
blinkLED();
|
|
|
|
d = 0;
|
|
|
|
for (int currWord = 0; currWord < 512; currWord++) {
|
|
unsigned long myAddress = currBuffer + currWord + cartSize / 2;
|
|
PORTF = myAddress & 0xFF;
|
|
PORTK = (myAddress >> 8) & 0xFF;
|
|
PORTL = (myAddress >> 16) & 0xFF;
|
|
|
|
// Arduino running at 16Mhz -> one nop = 62.5ns
|
|
NOP;
|
|
// Setting CS(PH3) LOW
|
|
PORTH &= ~(1 << 3);
|
|
// Setting OE(PH6) LOW
|
|
PORTH &= ~(1 << 6);
|
|
// Setting AS(PJ1) LOW
|
|
PORTJ &= ~(1 << 1);
|
|
// Setting ASEL(PG5) LOW
|
|
PORTG &= ~(1 << 5);
|
|
// Pulse CLK(PH1)
|
|
if (isSVP)
|
|
pulse_clock(10);
|
|
|
|
// most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
|
|
// Read
|
|
buffer[d] = PINA;
|
|
buffer[d + 1] = PINC;
|
|
|
|
// Setting CS(PH3) HIGH
|
|
PORTH |= (1 << 3);
|
|
// Setting OE(PH6) HIGH
|
|
PORTH |= (1 << 6);
|
|
// Setting AS(PJ1) HIGH
|
|
PORTJ |= (1 << 1);
|
|
// Setting ASEL(PG5) HIGH
|
|
PORTG |= (1 << 5);
|
|
// Pulse CLK(PH1)
|
|
if (isSVP)
|
|
pulse_clock(10);
|
|
|
|
// Skip first 256 words
|
|
if (((currBuffer == 0) && (currWord >= 256)) || (currBuffer > 0)) {
|
|
calcCKSLockon += ((buffer[d] << 8) | buffer[d + 1]);
|
|
}
|
|
d += 2;
|
|
}
|
|
myFile.write(buffer, 1024);
|
|
|
|
// update progress bar
|
|
processedProgressBar += 1024;
|
|
draw_progressbar(processedProgressBar, totalProgressBar);
|
|
}
|
|
}
|
|
if (SnKmode == 3) {
|
|
for (unsigned long currBuffer = 0; currBuffer < cartSizeSonic2 / 2; currBuffer += 512) {
|
|
// Blink led
|
|
if (currBuffer % 16384 == 0)
|
|
blinkLED();
|
|
|
|
d = 0;
|
|
|
|
for (int currWord = 0; currWord < 512; currWord++) {
|
|
unsigned long myAddress = currBuffer + currWord + (cartSize + cartSizeLockon) / 2;
|
|
PORTF = myAddress & 0xFF;
|
|
PORTK = (myAddress >> 8) & 0xFF;
|
|
PORTL = (myAddress >> 16) & 0xFF;
|
|
|
|
// Arduino running at 16Mhz -> one nop = 62.5ns
|
|
NOP;
|
|
// Setting CS(PH3) LOW
|
|
PORTH &= ~(1 << 3);
|
|
// Setting OE(PH6) LOW
|
|
PORTH &= ~(1 << 6);
|
|
// Setting AS(PJ1) LOW
|
|
PORTJ &= ~(1 << 1);
|
|
// Setting ASEL(PG5) LOW
|
|
PORTG &= ~(1 << 5);
|
|
// Pulse CLK(PH1)
|
|
if (isSVP)
|
|
PORTH ^= (1 << 1);
|
|
|
|
// most MD ROMs are 200ns, comparable to SNES > use similar access delay of 6 x 62.5 = 375ns
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
NOP;
|
|
|
|
// Read
|
|
buffer[d] = PINA;
|
|
buffer[d + 1] = PINC;
|
|
|
|
// Setting CS(PH3) HIGH
|
|
PORTH |= (1 << 3);
|
|
// Setting OE(PH6) HIGH
|
|
PORTH |= (1 << 6);
|
|
// Setting AS(PJ1) HIGH
|
|
PORTJ |= (1 << 1);
|
|
// Setting ASEL(PG5) HIGH
|
|
PORTG |= (1 << 5);
|
|
// Pulse CLK(PH1)
|
|
if (isSVP)
|
|
PORTH ^= (1 << 1);
|
|
|
|
calcCKSSonic2 += ((buffer[d] << 8) | buffer[d + 1]);
|
|
d += 2;
|
|
}
|
|
myFile.write(buffer, 1024);
|
|
|
|
// update progress bar
|
|
processedProgressBar += 1024;
|
|
draw_progressbar(processedProgressBar, totalProgressBar);
|
|
}
|
|
}
|
|
// Close the file:
|
|
myFile.close();
|
|
|
|
// Reset SSF2 Banks
|
|
if (cartSize > 0x400000) {
|
|
writeSSF2Map(0x50987E, 6); // 0xA130FD
|
|
writeSSF2Map(0x50987F, 7); // 0xA130FF
|
|
}
|
|
|
|
// Calculate internal checksum
|
|
print_Msg(F("Internal checksum..."));
|
|
display_Update();
|
|
if (chksum == calcCKS) {
|
|
println_Msg(FS(FSTRING_OK));
|
|
display_Update();
|
|
} else {
|
|
println_Msg(F("Error"));
|
|
char calcsumStr[5];
|
|
sprintf(calcsumStr, "%04X", calcCKS);
|
|
println_Msg(calcsumStr);
|
|
print_Error(FS(FSTRING_EMPTY));
|
|
display_Update();
|
|
}
|
|
|
|
// More checksums
|
|
if (SnKmode >= 2) {
|
|
print_Msg(F("Lock-on checksum..."));
|
|
if (chksumLockon == calcCKSLockon) {
|
|
println_Msg(FS(FSTRING_OK));
|
|
display_Update();
|
|
} else {
|
|
print_Msg(F("Error"));
|
|
char calcsumStr[5];
|
|
sprintf(calcsumStr, "%04X", calcCKSLockon);
|
|
println_Msg(calcsumStr);
|
|
print_Error(FS(FSTRING_EMPTY));
|
|
display_Update();
|
|
}
|
|
}
|
|
if (SnKmode == 3) {
|
|
print_Msg(F("Adittional checksum..."));
|
|
if (chksumSonic2 == calcCKSSonic2) {
|
|
println_Msg(FS(FSTRING_OK));
|
|
display_Update();
|
|
} else {
|
|
print_Msg(F("Error"));
|
|
char calcsumStr[5];
|
|
sprintf(calcsumStr, "%04X", calcCKSSonic2);
|
|
println_Msg(calcsumStr);
|
|
print_Error(FS(FSTRING_EMPTY));
|
|
display_Update();
|
|
}
|
|
}
|
|
}
|
|
|
|
/******************************************
|
|
SRAM functions
|
|
*****************************************/
|
|
// Sonic 3 sram enable
|
|
void enableSram_MD(boolean enableSram) {
|
|
dataOut_MD();
|
|
|
|
// Set D0 to either 1(enable SRAM) or 0(enable ROM)
|
|
PORTC = enableSram;
|
|
|
|
// Strobe TIME(PJ0) LOW to latch the data
|
|
PORTJ &= ~(1 << 0);
|
|
__asm__("nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t");
|
|
// Set TIME(PJ0) HIGH
|
|
PORTJ |= (1 << 0);
|
|
__asm__("nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t"
|
|
"nop\n\t");
|
|
|
|
dataIn_MD();
|
|
}
|
|
|
|
// Write sram to cartridge
|
|
void writeSram_MD() {
|
|
dataOut_MD();
|
|
|
|
// Create filepath
|
|
sprintf(filePath, "%s/%s", filePath, fileName);
|
|
println_Msg(F("Writing..."));
|
|
println_Msg(filePath);
|
|
display_Update();
|
|
|
|
// Open file on sd card
|
|
if (myFile.open(filePath, O_READ)) {
|
|
// Write to the lower byte
|
|
if (saveType == 1) {
|
|
for (unsigned long currByte = sramBase; currByte < sramBase + sramSize; currByte++) {
|
|
if (segaSram16bit > 0) {
|
|
// skip high byte
|
|
myFile.read();
|
|
}
|
|
word data = myFile.read() & 0xFF;
|
|
writeWord_MD(currByte, data);
|
|
}
|
|
}
|
|
// Write to the upper byte
|
|
else if (saveType == 2) {
|
|
for (unsigned long currByte = sramBase; currByte < sramBase + sramSize; currByte++) {
|
|
word data = (myFile.read() << 8) & 0xFF00;
|
|
writeWord_MD(currByte, data);
|
|
if (segaSram16bit > 0) {
|
|
// skip low byte
|
|
myFile.read();
|
|
}
|
|
}
|
|
}
|
|
// Write to both bytes
|
|
else if (saveType == 3) {
|
|
for (unsigned long currByte = sramBase; currByte < sramBase + sramSize; currByte++) {
|
|
word data = (myFile.read() << 8) & 0xFF00;
|
|
data |= (myFile.read() & 0xFF);
|
|
writeWord_MD(currByte, data);
|
|
}
|
|
} else
|
|
print_Error(F("Unknown save type"));
|
|
|
|
// Close the file:
|
|
myFile.close();
|
|
print_STR(done_STR, 1);
|
|
display_Update();
|
|
} else {
|
|
print_FatalError(sd_error_STR);
|
|
}
|
|
dataIn_MD();
|
|
}
|
|
|
|
// Read sram and save to the SD card
|
|
void readSram_MD() {
|
|
dataIn_MD();
|
|
|
|
// Get name, add extension and convert to char array for sd lib
|
|
createFolder("MD", "SAVE", romName, "srm");
|
|
|
|
// write new folder number back to eeprom
|
|
foldern = foldern + 1;
|
|
EEPROM_writeAnything(0, foldern);
|
|
|
|
// Open file on sd card
|
|
if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
|
|
print_FatalError(sd_error_STR);
|
|
}
|
|
|
|
for (unsigned long currBuffer = sramBase; currBuffer < sramBase + sramSize; currBuffer += 256) {
|
|
for (int currWord = 0; currWord < 256; currWord++) {
|
|
word myWord = readWord_MD(currBuffer + currWord);
|
|
|
|
if (saveType == 2) {
|
|
// Only use the upper byte
|
|
if (segaSram16bit > 0) {
|
|
sdBuffer[(currWord * 2) + 0] = ((myWord >> 8) & 0xFF);
|
|
sdBuffer[(currWord * 2) + 1] = ((myWord >> 8) & 0xFF);
|
|
} else {
|
|
sdBuffer[currWord] = ((myWord >> 8) & 0xFF);
|
|
}
|
|
} else if (saveType == 1) {
|
|
// Only use the lower byte
|
|
if (segaSram16bit > 0) {
|
|
sdBuffer[(currWord * 2) + 0] = (myWord & 0xFF);
|
|
sdBuffer[(currWord * 2) + 1] = (myWord & 0xFF);
|
|
} else {
|
|
sdBuffer[currWord] = (myWord & 0xFF);
|
|
}
|
|
} else if (saveType == 3) { // BOTH
|
|
sdBuffer[currWord * 2] = ((myWord >> 8) & 0xFF);
|
|
sdBuffer[(currWord * 2) + 1] = (myWord & 0xFF);
|
|
}
|
|
}
|
|
if (saveType == 3 || segaSram16bit > 0)
|
|
myFile.write(sdBuffer, 512);
|
|
else
|
|
myFile.write(sdBuffer, 256);
|
|
}
|
|
if (segaSram16bit == 2) {
|
|
// pad to 64KB
|
|
for (int i = 0; i < 512; i++) {
|
|
sdBuffer[i] = 0xFF;
|
|
}
|
|
unsigned long padsize = (1UL << 16) - (sramSize << 1);
|
|
unsigned long padblockcount = padsize >> 9; // number of 512 byte blocks
|
|
for (unsigned long i = 0; i < padblockcount; i++) {
|
|
myFile.write(sdBuffer, 512);
|
|
}
|
|
}
|
|
// Close the file:
|
|
myFile.close();
|
|
print_Msg(F("Saved to "));
|
|
print_Msg(folder);
|
|
println_Msg(F("/"));
|
|
display_Update();
|
|
}
|
|
|
|
unsigned long verifySram_MD() {
|
|
dataIn_MD();
|
|
writeErrors = 0;
|
|
|
|
// Open file on sd card
|
|
if (myFile.open(filePath, O_READ)) {
|
|
for (unsigned long currBuffer = sramBase; currBuffer < sramBase + sramSize; currBuffer += 256) {
|
|
for (int currWord = 0; currWord < 256; currWord++) {
|
|
word myWord = readWord_MD(currBuffer + currWord);
|
|
|
|
if (saveType == 2) {
|
|
// Only use the upper byte
|
|
sdBuffer[currWord * 2] = ((myWord >> 8) & 0xFF);
|
|
} else if (saveType == 1) {
|
|
// Only use the lower byte
|
|
sdBuffer[currWord * 2] = (myWord & 0xFF);
|
|
} else if (saveType == 3) { // BOTH
|
|
sdBuffer[(currWord * 2) + 0] = ((myWord >> 8) & 0xFF);
|
|
sdBuffer[(currWord * 2) + 1] = (myWord & 0xFF);
|
|
}
|
|
}
|
|
int step = saveType == 3 ? 1 : 2;
|
|
// Check sdBuffer content against file on sd card
|
|
for (int i = 0; i < 512; i += step) {
|
|
if (saveType == 1 && segaSram16bit > 0) {
|
|
// skip high byte
|
|
myFile.read();
|
|
}
|
|
byte b = myFile.read();
|
|
if (saveType == 2 && segaSram16bit > 0) {
|
|
// skip low byte
|
|
myFile.read();
|
|
}
|
|
if (b != sdBuffer[i]) {
|
|
writeErrors++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Close the file:
|
|
myFile.close();
|
|
} else {
|
|
print_FatalError(sd_error_STR);
|
|
}
|
|
// Return 0 if verified ok, or number of errors
|
|
return writeErrors;
|
|
}
|
|
|
|
#ifdef ENABLE_FLASH
|
|
//******************************************
|
|
// Flashrom Functions
|
|
//******************************************
|
|
void resetFlash_MD() {
|
|
// Set data pins to output
|
|
dataOut_MD();
|
|
|
|
// Reset command sequence
|
|
writeFlash_MD(0x5555, 0xaa);
|
|
writeFlash_MD(0x2aaa, 0x55);
|
|
writeFlash_MD(0x5555, 0xf0);
|
|
|
|
// Set data pins to input again
|
|
dataIn_MD();
|
|
}
|
|
|
|
void write29F1610_MD() {
|
|
// Create filepath
|
|
sprintf(filePath, "%s/%s", filePath, fileName);
|
|
print_STR(flashing_file_STR, 0);
|
|
print_Msg(filePath);
|
|
println_Msg(F("..."));
|
|
display_Update();
|
|
|
|
// Open file on sd card
|
|
if (myFile.open(filePath, O_READ)) {
|
|
// Get rom size from file
|
|
fileSize = myFile.fileSize();
|
|
if (fileSize > flashSize) {
|
|
print_FatalError(file_too_big_STR);
|
|
}
|
|
// Set data pins to output
|
|
dataOut_MD();
|
|
|
|
// 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
|
|
createFolder("MD", "SAVE", romName, "eep");
|
|
|
|
// write new folder number back to eeprom
|
|
foldern = foldern + 1;
|
|
EEPROM_writeAnything(0, foldern);
|
|
|
|
println_Msg(F("Reading..."));
|
|
display_Update();
|
|
|
|
// Open file on sd card
|
|
if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
|
|
print_FatalError(sd_error_STR);
|
|
}
|
|
if (eepSize > 0x100) { // 24C04+
|
|
for (word currByte = 0; currByte < eepSize; currByte += 256) {
|
|
print_Msg(F("*"));
|
|
display_Update();
|
|
for (int i = 0; i < 256; i++) {
|
|
readEepromByte(currByte + i);
|
|
}
|
|
myFile.write(sdBuffer, 256);
|
|
}
|
|
} else { // 24C01/24C02
|
|
for (word currByte = 0; currByte < eepSize; currByte++) {
|
|
if ((currByte != 0) && ((currByte + 1) % 16 == 0)) {
|
|
print_Msg(F("*"));
|
|
display_Update();
|
|
}
|
|
readEepromByte(currByte);
|
|
}
|
|
myFile.write(sdBuffer, eepSize);
|
|
}
|
|
// Close the file:
|
|
myFile.close();
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
display_Clear();
|
|
print_Msg(F("Saved to "));
|
|
print_Msg(folder);
|
|
|
|
display_Update();
|
|
}
|
|
|
|
void writeEEP_MD() {
|
|
dataOut_MD();
|
|
|
|
// Create filepath
|
|
sprintf(filePath, "%s/%s", filePath, fileName);
|
|
println_Msg(F("Writing..."));
|
|
println_Msg(filePath);
|
|
display_Update();
|
|
|
|
// Open file on sd card
|
|
if (myFile.open(filePath, O_READ)) {
|
|
if (eepSize > 0x100) { // 24C04+
|
|
for (word currByte = 0; currByte < eepSize; currByte += 256) {
|
|
myFile.read(sdBuffer, 256);
|
|
for (int i = 0; i < 256; i++) {
|
|
writeEepromByte(currByte + i);
|
|
delay(50); // DELAY NEEDED
|
|
}
|
|
print_Msg(F("."));
|
|
display_Update();
|
|
}
|
|
} else { // 24C01/24C02
|
|
myFile.read(sdBuffer, eepSize);
|
|
for (word currByte = 0; currByte < eepSize; currByte++) {
|
|
writeEepromByte(currByte);
|
|
print_Msg(F("."));
|
|
if ((currByte != 0) && ((currByte + 1) % 64 == 0))
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
display_Update(); // ON SERIAL = delay(100)
|
|
}
|
|
}
|
|
// Close the file:
|
|
myFile.close();
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
display_Clear();
|
|
print_STR(done_STR, 1);
|
|
display_Update();
|
|
} else {
|
|
print_FatalError(sd_error_STR);
|
|
}
|
|
dataIn_MD();
|
|
}
|
|
|
|
//******************************************
|
|
// CD Backup RAM Functions
|
|
//******************************************
|
|
void readBram_MD() {
|
|
dataIn_MD();
|
|
|
|
// Get name, add extension and convert to char array for sd lib
|
|
createFolder("MD", "RAM", "Cart", "brm");
|
|
|
|
// write new folder number back to eeprom
|
|
foldern = foldern + 1;
|
|
EEPROM_writeAnything(0, foldern);
|
|
|
|
println_Msg(F("Reading..."));
|
|
display_Update();
|
|
|
|
// Open file on sd card
|
|
if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
|
|
print_FatalError(sd_error_STR);
|
|
}
|
|
|
|
for (unsigned long currByte = 0; currByte < bramSize; currByte += 512) {
|
|
for (int i = 0; i < 512; i++) {
|
|
sdBuffer[i] = readWord_MD(0x300000 + currByte + i);
|
|
}
|
|
myFile.write(sdBuffer, 512);
|
|
}
|
|
|
|
// Close the file:
|
|
myFile.close();
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
display_Clear();
|
|
print_Msg(F("Saved to "));
|
|
print_Msg(folder);
|
|
|
|
display_Update();
|
|
}
|
|
|
|
void writeBram_MD() {
|
|
dataOut_MD();
|
|
|
|
// Create filepath
|
|
sprintf(filePath, "%s/%s", filePath, fileName);
|
|
println_Msg(F("Writing..."));
|
|
println_Msg(filePath);
|
|
display_Update();
|
|
|
|
// Open file on sd card
|
|
if (myFile.open(filePath, O_READ)) {
|
|
|
|
// 0x700000-0x7FFFFF: Writes by /LWR latch D0; 1=RAM write enabled, 0=disabled
|
|
writeWord_MD(0x380000, 1); // Enable BRAM Writes
|
|
|
|
for (unsigned long currByte = 0; currByte < bramSize; currByte += 512) {
|
|
myFile.read(sdBuffer, 512);
|
|
for (int i = 0; i < 512; i++) {
|
|
writeWord_MD(0x300000 + currByte + i, sdBuffer[i]);
|
|
}
|
|
}
|
|
writeWord_MD(0x380000, 0); // Disable BRAM Writes
|
|
// Close the file:
|
|
myFile.close();
|
|
println_Msg(FS(FSTRING_EMPTY));
|
|
display_Clear();
|
|
print_STR(done_STR, 1);
|
|
display_Update();
|
|
} else {
|
|
print_FatalError(sd_error_STR);
|
|
}
|
|
dataIn_MD();
|
|
}
|
|
|
|
//******************************************
|
|
// Realtec Mapper Functions
|
|
//******************************************
|
|
void writeRealtec(unsigned long address, byte value) { // Realtec 0x404000 (UPPER)/0x400000 (LOWER)
|
|
dataOut_MD();
|
|
PORTF = address & 0xFF; // 0x00 ADDR A0-A7
|
|
PORTK = (address >> 8) & 0xFF; // ADDR A8-A15
|
|
PORTL = (address >> 16) & 0xFF; //0x20 ADDR A16-A23
|
|
PORTA = 0x00; // DATA D8-D15
|
|
PORTH |= (1 << 0); // /RES HIGH
|
|
|
|
PORTH |= (1 << 3); // CE HIGH
|
|
PORTC = value;
|
|
PORTH &= ~(1 << 4) & ~(1 << 5); // /UDSW + /LDSW LOW
|
|
PORTH |= (1 << 4) | (1 << 5); // /UDSW + /LDSW HIGH
|
|
dataIn_MD();
|
|
}
|
|
|
|
void readRealtec_MD() {
|
|
// Set control
|
|
dataIn_MD();
|
|
|
|
// Get name, add extension and convert to char array for sd lib
|
|
createFolderAndOpenFile("MD", "ROM", romName, "MD");
|
|
|
|
// Realtec Registers
|
|
writeWord_MD(0x201000, 4); // Number of 128K Blocks 0x402000 (0x201000)
|
|
writeRealtec(0x200000, 1); // ROM Lower Address 0x400000 (0x200000)
|
|
writeRealtec(0x202000, 0); // ROM Upper Address 0x404000 (0x202000)
|
|
|
|
word d = 0;
|
|
for (unsigned long currBuffer = 0; currBuffer < cartSize / 2; currBuffer += 256) {
|
|
// Blink led
|
|
if (currBuffer % 16384 == 0)
|
|
blinkLED();
|
|
|
|
for (int currWord = 0; currWord < 256; currWord++) {
|
|
word myWord = readWord_MD(currBuffer + currWord);
|
|
// Split word into two bytes
|
|
// Left
|
|
sdBuffer[d] = ((myWord >> 8) & 0xFF);
|
|
// Right
|
|
sdBuffer[d + 1] = (myWord & 0xFF);
|
|
d += 2;
|
|
}
|
|
myFile.write(sdBuffer, 512);
|
|
d = 0;
|
|
}
|
|
// Close the file:
|
|
myFile.close();
|
|
}
|
|
|
|
void printRomSize_MD(int index) {
|
|
display_Clear();
|
|
print_Msg(FS(FSTRING_ROM_SIZE));
|
|
print_Msg(pgm_read_byte(&(MDSize[index])));
|
|
println_Msg(F(" Mbit"));
|
|
}
|
|
|
|
void force_cartSize_MD() {
|
|
cartSize = navigateMenu(0, 9, &printRomSize_MD);
|
|
cartSize = pgm_read_byte(&(MDSize[cartSize])) * 131072;
|
|
display.setCursor(0, 56); // Display selection at bottom
|
|
print_Msg(FS(FSTRING_ROM_SIZE));
|
|
print_Msg(cartSize / 131072);
|
|
println_Msg(F(" Mbit"));
|
|
display_Update();
|
|
delay(1000);
|
|
}
|
|
|
|
#endif
|
|
|
|
//******************************************
|
|
// End of File
|
|
//******************************************
|