cartreader/Cart_Reader/WS.ino
sanni cfb9e39cbf V4.9: Improve WS Initialization
Thanks to skaman.
The code does a deeper sanity check of the header data when initializing the cart.  It avoids having to constantly press buttons to reinit the cart.  Carts can still not initialize the MBC properly but that's normal for the WonderSwan.  Clean the pins on the cart and check that the cart and adapter are seated properly.  If the cart doesn't unlock immediately, then let the sketch run for a bit.  For stubborn carts, a power cycle might be necessary.
The sketch fixes a few typos, removes some trailing spaces, and adds another ROM size (used by Benesse Pocket Challenge V2 carts).
2020-04-20 10:35:48 +02:00

1267 lines
29 KiB
C++

//******************************************
// WS MODULE
//******************************************
// Cartridge pinout
// 48P 1.25mm pitch connector
// C1, C48 : GND
// C24, C25 : VDD (+3.3v)
// C16-C23 : D7-D0
// C34-C39 : D8-D13
// C14-C15 : D15-D14
// C26-C29 : A(-1)-A2
// C10-C13 : A6-A3
// C30-C33 : A18-A15
// C2,C3,C4,C5 : A14,A9,A10,A8
// C6,C7,C8,C9 : A7,A12,A13,A11
// C40 : /RST
// C41 : /IO? (only use when unlocking MMC)
// C42 : /MMC (access port on cartridge with both /CART and /MMC = L)
// C43 : /OE
// C44 : /WE
// C45 : /CART? (L when accessing cartridge (ROM/SRAM/PORT))
// C46 : INT (for RTC alarm interrupt)
// C47 : CLK (384KHz on WS)
/******************************************
Menu
*****************************************/
static const char wsMenuItem1[] PROGMEM = "Read Rom";
static const char wsMenuItem2[] PROGMEM = "Read Save";
static const char wsMenuItem3[] PROGMEM = "Write Save";
static const char wsMenuItem4[] PROGMEM = "Reset";
static const char wsMenuItem5[] PROGMEM = "Write WitchOS";
static const char* const menuOptionsWS[] PROGMEM = {wsMenuItem1, wsMenuItem2, wsMenuItem3, wsMenuItem4, wsMenuItem5};
static const uint8_t wwLaunchCode[] PROGMEM = { 0xea, 0x00, 0x00, 0x00, 0xe0, 0x00, 0xff, 0xff };
static uint8_t wsGameOrientation = 0;
static uint8_t wsGameHasRTC = 0;
static uint16_t wsGameChecksum = 0;
static uint8_t wsEepromShiftReg[2];
static boolean wsWitch = false;
void setup_WS()
{
// A-1 - A6
DDRF = 0xff;
// A7 - A14
DDRK = 0xff;
// A15 - A22
DDRL = 0xff;
// D0 - D15
DDRC = 0x00;
DDRA = 0x00;
// controls
DDRH |= ((1 << 0) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6));
PORTH |= ((1 << 0) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6));
// CLK outputs LOW
DDRE |= (1 << 3);
PORTE &= ~(1 << 3);
// IO? as input with internal pull-up enabled
DDRE &= ~(1 << 4);
PORTE |= (1 << 4);
// INT as input with internal pull-up enabled
DDRG &= ~(1 << 5);
PORTG |= (1 << 5);
display_Clear();
// unlock MMC
// if (!unlockMMC2003_WS())
// print_Error(F("Can't initial MMC"), true);
// if (getCartInfo_WS() != 0xea)
// print_Error(F("Rom header read error"), true);
display.println("Initializing...");
display.display();
do {
unlockMMC2003_WS();
}
while (!headerCheck());
getCartInfo_WS();
showCartInfo_WS();
mode = mode_WS;
}
boolean headerCheck() {
dataIn_WS();
for (uint32_t i = 0; i < 16; i += 2)
* ((uint16_t*)(sdBuffer + i)) = readWord_WS(0xffff0 + i);
uint8_t startByte = sdBuffer[0];
if (startByte == 0xEA) { // Start should be 0xEA
uint8_t zeroByte = sdBuffer[5];
if (zeroByte == 0) { // Zero Byte
uint8_t systemByte = sdBuffer[7];
if (systemByte < 2) { // System < 2
uint8_t revisionByte = sdBuffer[9];
if ((revisionByte < 7) || (revisionByte == 0x80)) { // Known Revisions: 0 to 6 and 0x80
uint8_t sizeByte = sdBuffer[10];
if (sizeByte < 10) // Rom Size < 10
return true;
}
}
}
}
return false;
}
void wsMenu()
{
uint8_t mainMenu = (wsWitch ? 5 : 4);
convertPgm(menuOptionsWS, mainMenu);
mainMenu = question_box(F("WS Menu"), menuOptions, mainMenu, 0);
switch (mainMenu)
{
case 0:
{
// Read Rom
sd.chdir("/");
readROM_WS(filePath, FILEPATH_LENGTH);
sd.chdir("/");
compareChecksum_WS(filePath);
break;
}
case 1:
{
// Read Save
sd.chdir("/");
switch (saveType)
{
case 0: println_Msg(F("No save for this game")); break;
case 1: readSRAM_WS(); break;
case 2: readEEPROM_WS(); break;
default: println_Msg(F("Unknown save type")); break;
}
break;
}
case 2:
{
// Write Save
sd.chdir("/");
switch (saveType)
{
case 0: println_Msg(F("No save for this game")); break;
case 1:
{
writeSRAM_WS();
verifySRAM_WS();
break;
}
case 2:
{
writeEEPROM_WS();
verifyEEPROM_WS();
break;
}
default: println_Msg(F("Unknown save type")); break;
}
break;
}
case 4:
{
writeWitchOS_WS();
break;
}
default:
{
// reset
asm volatile (" jmp 0");
break;
}
}
println_Msg(F(""));
println_Msg(F("Press Button..."));
display_Update();
wait();
}
uint8_t getCartInfo_WS()
{
dataIn_WS();
// for (uint32_t i = 0; i < 16; i += 2)
// *((uint16_t*)(sdBuffer + i)) = readWord_WS(0xffff0 + i);
wsGameChecksum = *(uint16_t*)(sdBuffer + 14);
wsWitch = false;
// some game has wrong info in header
// patch here
switch (wsGameChecksum)
{
// games with wrong save type/size
// 256kbits sram
case 0xe600: // BAN007
case 0x8eed: // BANC16
{
sdBuffer[11] = 0x02;
break;
}
// games missing 'COLOR' flag
case 0x26db: // SQRC01
case 0xbfdf: // SUMC07
{
sdBuffer[7] |= 0x01;
break;
}
case 0x7f73: // BAN030
{
// missing developerId and cartId
sdBuffer[6] = 0x01;
sdBuffer[8] = 0x30;
break;
}
case 0xeafd: //BANC33
{
// enable GPIO and set to LOW
dataOut_WS();
writeByte_WSPort(0xcc, 0x03);
writeByte_WSPort(0xcd, 0x00);
break;
}
case 0x0000:
{
// developerId/cartId/checksum are all filled with 0x00 in witch based games
dataIn_WS();
if (readWord_WS(0xf0000) == 0x4c45 && readWord_WS(0xf0002) == 0x5349 && readWord_WS(0xf0004) == 0x0041)
{
// check witch BIOS
if (readWord_WS(0xfff5e) == 0x006c && readWord_WS(0xfff60) == 0x5b1b)
{
// check flashchip
// should be a MBM29DL400TC
dataOut_WS();
writeWord_WS(0x80aaa, 0xaaaa);
writeWord_WS(0x80555, 0x5555);
writeWord_WS(0x80aaa, 0x9090);
dataIn_WS();
if (readWord_WS(0x80000) == 0x0004 && readWord_WS(0x80002) == 0x220c)
wsWitch = true;
dataOut_WS();
writeWord_WS(0x80000, 0xf0f0);
dataIn_WS();
// 7AC003
sdBuffer[6] = 0x7a;
sdBuffer[8] = 0x03;
}
// check service menu
else if (readWord_WS(0xfff22) == 0x006c && readWord_WS(0xfff24) == 0x5b1b)
{
if (readWord_WS(0x93246) == 0x4a2f && readWord_WS(0x93248) == 0x5353 && readWord_WS(0x9324a) == 0x2e32)
{
// jss2
sdBuffer[6] = 0xff; // WWGP
sdBuffer[8] = 0x1a; // 2001A
sdBuffer[7] = 0x01; // color only
if (readWord_WS(0x93e9c) == 0x4648 && readWord_WS(0x93e9e) == 0x0050)
{
// WWGP2001A3 -> HFP Version
sdBuffer[9] = 0x03;
wsGameChecksum = 0x4870;
}
else
{
// TODO check other jss2 version
}
}
else if (readWord_WS(0xe4260) == 0x6b64 && readWord_WS(0xe4262) == 0x696e)
{
// dknight
sdBuffer[6] = 0xff; // WWGP
sdBuffer[8] = 0x2b; // 2002B
sdBuffer[7] = 0x01; // color only
sdBuffer[9] = 0x00;
wsGameChecksum = 0x8b1c;
}
}
}
break;
}
}
romType = (sdBuffer[7] & 0x01); // wsc only = 1
romVersion = sdBuffer[9];
romSize = sdBuffer[10];
sramSize = sdBuffer[11];
wsGameOrientation = (sdBuffer[12] & 0x01);
wsGameHasRTC = (sdBuffer[13] & 0x01);
getDeveloperName(sdBuffer[6], vendorID, 5);
snprintf(cartID, 5, "%c%02X", (romType ? 'C' : '0'), sdBuffer[8]);
snprintf(checksumStr, 5, "%04X", wsGameChecksum);
snprintf(romName, 17, "%s%s", vendorID, cartID);
switch (romSize)
{
case 0x01: cartSize = 131072 * 2; break;
case 0x02: cartSize = 131072 * 4; break;
case 0x03: cartSize = 131072 * 8; break;
case 0x04: cartSize = 131072 * 16; break;
// case 0x05: cartSize = 131072 * 24; break;
case 0x06: cartSize = 131072 * 32; break;
// case 0x07: cartSize = 131072 * 48; break;
case 0x08: cartSize = 131072 * 64; break;
case 0x09: cartSize = 131072 * 128; break;
default: cartSize = 0; break;
}
switch (sramSize)
{
case 0x00: saveType = 0; sramSize = 0; break;
case 0x01: saveType = 1; sramSize = 64; break;
case 0x02: saveType = 1; sramSize = 256; break;
case 0x03: saveType = 1; sramSize = 1024; break;
case 0x04: saveType = 1; sramSize = 2048; break;
case 0x05: saveType = 1; sramSize = 4096; break;
case 0x10: saveType = 2; sramSize = 1; break;
case 0x20: saveType = 2; sramSize = 16; break;
case 0x50: saveType = 2; sramSize = 8; break;
default: saveType = 0xff; break;
}
if (saveType == 2)
unprotectEEPROM();
// should be 0xea (JMPF instruction)
return sdBuffer[0];
}
void showCartInfo_WS()
{
display_Clear();
println_Msg(F("WS Cart Info"));
print_Msg(F("Game: "));
println_Msg(romName);
print_Msg(F("Rom Size: "));
if (cartSize == 0x00)
println_Msg(romSize, HEX);
else
{
print_Msg((cartSize >> 17));
println_Msg(F(" Mb"));
}
print_Msg(F("Save: "));
switch (saveType)
{
case 0: println_Msg(F("None")); break;
case 1: print_Msg(F("Sram ")); print_Msg(sramSize); println_Msg(F(" Kb")); break;
case 2: print_Msg(F("Eeprom ")); print_Msg(sramSize); println_Msg(F(" Kb")); break;
default: println_Msg(sramSize, HEX); break;
}
print_Msg(F("Version: 1."));
println_Msg(romVersion, HEX);
print_Msg(F("Checksum: "));
println_Msg(checksumStr);
println_Msg(F("Press Button..."));
display_Update();
wait();
}
void getDeveloperName(uint8_t id, char *buf, size_t length)
{
if (buf == NULL)
return;
char *devName = NULL;
switch (id)
{
case 0x01: devName = PSTR("BAN"); break;
case 0x0b: devName = PSTR("SUM"); break;
case 0x12: devName = PSTR("KNM"); break;
case 0x18: devName = PSTR("KGT"); break;
case 0x1d: devName = PSTR("BEC"); break;
case 0x24: devName = PSTR("OMM"); break;
case 0x28: devName = PSTR("SQR"); break;
case 0x31: devName = PSTR("VGD"); break;
// TODO add more developer
// custom developerId
case 0x7a: devName = PSTR("7AC"); break; // witch
case 0xff: devName = PSTR("WWGP"); break; // WWGP series (jss2, dknight)
// if not found, use id
default: snprintf(buf, length, "%02X", id); return;
}
strlcpy_P(buf, devName, length);
}
void readROM_WS(char *outPathBuf, size_t bufferSize)
{
// generate fullname of rom file
snprintf(fileName, FILENAME_LENGTH, "%s.ws%c", romName, ((romType & 1) ? 'c' : '\0'));
// create a new folder for storing rom file
EEPROM_readAnything(0, foldern);
snprintf(folder, sizeof(folder), "WS/ROM/%s/%d", romName, foldern);
sd.mkdir(folder, true);
sd.chdir(folder);
// filling output file path to buffer
if (outPathBuf != NULL && bufferSize > 0)
snprintf(outPathBuf, bufferSize, "%s/%s", folder, fileName);
display_Clear();
print_Msg(F("Saving to "));
print_Msg(folder);
println_Msg(F("/..."));
display_Update();
// open file on sdcard
if (!myFile.open(fileName, O_RDWR | O_CREAT))
print_Error(F("Can't create file on SD"), true);
// write new folder number back to EEPROM
foldern++;
EEPROM_writeAnything(0, foldern);
// get correct starting rom bank
uint16_t bank = (256 - (cartSize >> 16));
// start reading rom
for (; bank <= 0xff; bank++)
{
// switch bank on segment 0x2
dataOut_WS();
writeByte_WSPort(0xc2, bank);
// blink LED on cartridge (only for BANC33)
if (wsGameChecksum == 0xeafd)
writeByte_WSPort(0xcd, (bank & 0x03));
dataIn_WS();
for (uint32_t addr = 0; addr < 0x10000; addr += 512)
{
// blink LED
if ((addr & ((1 << 14) - 1)) == 0)
PORTB ^= (1 << 4);
for (uint32_t w = 0; w < 512; w += 2)
* ((uint16_t*)(sdBuffer + w)) = readWord_WS(0x20000 + addr + w);
myFile.write(sdBuffer, 512);
}
}
// turn off LEDs (only for BANC33)
if (wsGameChecksum == 0xeafd)
{
dataOut_WS();
writeByte_WSPort(0xcd, 0x00);
}
myFile.close();
}
void readSRAM_WS()
{
// generate fullname of rom file
snprintf(fileName, FILENAME_LENGTH, "%s.sav", romName);
// create a new folder for storing rom file
EEPROM_readAnything(0, foldern);
snprintf(folder, sizeof(folder), "WS/SAVE/%s/%d", romName, foldern);
sd.mkdir(folder, true);
sd.chdir(folder);
display_Clear();
print_Msg(F("Saving "));
print_Msg(folder);
println_Msg(F("/..."));
display_Update();
foldern++;
EEPROM_writeAnything(0, foldern);
if (!myFile.open(fileName, O_RDWR | O_CREAT))
print_Error(F("Can't create file on SD"), true);
uint32_t bank_size = (sramSize << 7);
uint16_t end_bank = (bank_size >> 16); // 64KB per bank
if (bank_size > 0x10000)
bank_size = 0x10000;
uint16_t bank = 0;
do
{
dataOut_WS();
writeByte_WSPort(0xc1, bank);
dataIn_WS();
for (uint32_t addr = 0; addr < bank_size; addr += 512)
{
// blink LED
if ((addr & ((1 << 14) - 1)) == 0)
PORTB ^= (1 << 4);
// SRAM data on D0-D7, with A-1 to select high/low byte
for (uint32_t w = 0; w < 512; w++)
sdBuffer[w] = readByte_WS(0x10000 + addr + w);
myFile.write(sdBuffer, 512);
}
} while (++bank < end_bank);
myFile.close();
println_Msg(F("Done"));
display_Update();
}
void verifySRAM_WS()
{
print_Msg(F("Verifying... "));
display_Update();
if (myFile.open(filePath, O_READ))
{
uint32_t bank_size = (sramSize << 7);
uint16_t end_bank = (bank_size >> 16); // 64KB per bank
uint16_t bank = 0;
uint32_t write_errors = 0;
if (bank_size > 0x10000)
bank_size = 0x10000;
do
{
dataOut_WS();
writeByte_WSPort(0xc1, bank);
dataIn_WS();
for (uint32_t addr = 0; addr < bank_size && myFile.available(); addr += 512)
{
myFile.read(sdBuffer, 512);
// SRAM data on D0-D7, with A-1 to select high/low byte
for (uint32_t w = 0; w < 512; w++)
{
if (readByte_WS(0x10000 + addr + w) != sdBuffer[w])
write_errors++;
}
}
} while (++bank < end_bank);
myFile.close();
if (write_errors == 0)
{
println_Msg(F("passed"));
}
else
{
println_Msg(F("failed"));
print_Msg(F("Error: "));
print_Msg(write_errors);
println_Msg(F(" bytes "));
print_Error(F("did not verify."), false);
}
}
else
{
print_Error(F("File doesn't exist"), false);
}
}
void writeSRAM_WS()
{
filePath[0] = 0;
sd.chdir("/");
fileBrowser(F("Select sav file"));
snprintf(filePath, FILEPATH_LENGTH, "%s/%s", filePath, fileName);
display_Clear();
print_Msg(F("Writing "));
print_Msg(filePath);
println_Msg(F("..."));
display_Update();
if (myFile.open(filePath, O_READ))
{
uint32_t bank_size = (sramSize << 7);
uint16_t end_bank = (bank_size >> 16); // 64KB per bank
if (bank_size > 0x10000)
bank_size = 0x10000;
uint16_t bank = 0;
dataOut_WS();
do
{
writeByte_WSPort(0xc1, bank);
for (uint32_t addr = 0; addr < bank_size && myFile.available(); addr += 512)
{
// blink LED
if ((addr & ((1 << 14) - 1)) == 0)
PORTB ^= (1 << 4);
myFile.read(sdBuffer, 512);
// SRAM data on D0-D7, with A-1 to select high/low byte
for (uint32_t w = 0; w < 512; w++)
writeByte_WS(0x10000 + addr + w, sdBuffer[w]);
}
} while (++bank < end_bank);
myFile.close();
println_Msg(F("Writing finished"));
display_Update();
}
else
{
print_Error(F("File doesn't exist"), false);
}
}
void readEEPROM_WS()
{
// generate fullname of eep file
snprintf(fileName, FILENAME_LENGTH, "%s.eep", romName);
// create a new folder for storing eep file
EEPROM_readAnything(0, foldern);
snprintf(folder, sizeof(folder), "WS/SAVE/%s/%d", romName, foldern);
sd.mkdir(folder, true);
sd.chdir(folder);
display_Clear();
print_Msg(F("Saving "));
print_Msg(folder);
println_Msg(F("/..."));
display_Update();
foldern++;
EEPROM_writeAnything(0, foldern);
if (!myFile.open(fileName, O_RDWR | O_CREAT))
print_Error(F("Can't create file on SD"), true);
uint32_t eepromSize = (sramSize << 7);
uint32_t bufSize = (eepromSize < 512 ? eepromSize : 512);
for (uint32_t i = 0; i < eepromSize; i += bufSize)
{
for (uint32_t j = 0; j < bufSize; j += 2)
{
// blink LED
if ((j & 0x1f) == 0x00)
PORTB ^= (1 << 4);
generateEepromInstruction_WS(wsEepromShiftReg, 0x2, ((i + j) >> 1));
dataOut_WS();
writeByte_WSPort(0xc6, wsEepromShiftReg[0]);
writeByte_WSPort(0xc7, wsEepromShiftReg[1]);
writeByte_WSPort(0xc8, 0x10);
// MMC will shift out from port 0xc7 to 0xc6
// and shift in 16bits into port 0xc5 to 0xc4
pulseCLK_WS(1 + 32 + 3);
dataIn_WS();
sdBuffer[j] = readByte_WSPort(0xc4);
sdBuffer[j + 1] = readByte_WSPort(0xc5);
}
myFile.write(sdBuffer, bufSize);
}
myFile.close();
println_Msg(F("Done"));
}
void verifyEEPROM_WS()
{
print_Msg(F("Verifying... "));
display_Update();
if (myFile.open(filePath, O_READ))
{
uint32_t write_errors = 0;
uint32_t eepromSize = (sramSize << 7);
uint32_t bufSize = (eepromSize < 512 ? eepromSize : 512);
for (uint32_t i = 0; i < eepromSize; i += bufSize)
{
myFile.read(sdBuffer, bufSize);
for (uint32_t j = 0; j < bufSize; j += 2)
{
// blink LED
if ((j & 0x1f) == 0x00)
PORTB ^= (1 << 4);
generateEepromInstruction_WS(wsEepromShiftReg, 0x2, ((i + j) >> 1));
dataOut_WS();
writeByte_WSPort(0xc6, wsEepromShiftReg[0]);
writeByte_WSPort(0xc7, wsEepromShiftReg[1]);
writeByte_WSPort(0xc8, 0x10);
// MMC will shift out from port 0xc7 to 0xc6
// and shift in 16bits into port 0xc5 to 0xc4
pulseCLK_WS(1 + 32 + 3);
dataIn_WS();
if (readByte_WSPort(0xc4) != sdBuffer[j])
write_errors++;
if (readByte_WSPort(0xc5) != sdBuffer[j + 1])
write_errors++;
}
}
myFile.close();
if (write_errors == 0)
{
println_Msg(F("passed"));
}
else
{
println_Msg(F("failed"));
print_Msg(F("Error: "));
print_Msg(write_errors);
println_Msg(F(" bytes "));
print_Error(F("did not verify."), false);
}
}
else
{
print_Error(F("File doesn't exist"), false);
}
}
void writeEEPROM_WS()
{
filePath[0] = 0;
sd.chdir("/");
fileBrowser(F("Select eep file"));
snprintf(filePath, FILEPATH_LENGTH, "%s/%s", filePath, fileName);
display_Clear();
print_Msg(F("Writing "));
print_Msg(filePath);
println_Msg(F("..."));
display_Update();
if (myFile.open(filePath, O_READ))
{
uint32_t eepromSize = (sramSize << 7);
uint32_t bufSize = (eepromSize < 512 ? eepromSize : 512);
for (uint32_t i = 0; i < eepromSize; i += bufSize)
{
myFile.read(sdBuffer, bufSize);
for (uint32_t j = 0; j < bufSize; j += 2)
{
// blink LED
if ((j & 0x1f) == 0x00)
PORTB ^= (1 << 4);
generateEepromInstruction_WS(wsEepromShiftReg, 0x1, ((i + j) >> 1));
dataOut_WS();
writeByte_WSPort(0xc6, wsEepromShiftReg[0]);
writeByte_WSPort(0xc7, wsEepromShiftReg[1]);
writeByte_WSPort(0xc4, sdBuffer[j]);
writeByte_WSPort(0xc5, sdBuffer[j + 1]);
writeByte_WSPort(0xc8, 0x20);
// MMC will shift out from port 0xc7 to 0xc4
pulseCLK_WS(1 + 32 + 3);
dataIn_WS();
do {
pulseCLK_WS(128);
}
while ((readByte_WSPort(0xc8) & 0x02) == 0x00);
}
}
myFile.close();
println_Msg(F("Done"));
}
else
{
print_Error(F("File doesn't exist"), false);
}
}
void writeWitchOS_WS()
{
// make sure that OS sectors not protected
dataOut_WS();
writeWord_WS(0x80aaa, 0xaaaa);
writeWord_WS(0x80555, 0x5555);
writeWord_WS(0xe0aaa, 0x9090);
dataIn_WS();
if (readWord_WS(0xe0004) || readWord_WS(0xe4004) || readWord_WS(0xec004) || readWord_WS(0xee004))
{
display_Clear();
print_Error(F("OS sectors are protected!"), false);
}
else
{
filePath[0] = 0;
sd.chdir("/");
fileBrowser(F("Select fbin file"));
snprintf(filePath, FILEPATH_LENGTH, "%s/%s", filePath, fileName);
display_Clear();
if (myFile.open(filePath, O_READ))
{
println_Msg(F("Erasing OS..."));
display_Update();
eraseWitchFlashSector_WS(0xe0000);
eraseWitchFlashSector_WS(0xe4000);
eraseWitchFlashSector_WS(0xec000);
eraseWitchFlashSector_WS(0xee000);
print_Msg(F("Flashing OS "));
print_Msg(filePath);
println_Msg(F("..."));
display_Update();
uint32_t fbin_length = myFile.fileSize();
uint32_t i, bytes_read;
uint16_t pd;
uint8_t key;
// OS size seems limit to 64KBytes
// last 16 bytes contains jmpf code and block count (written by BIOS)
if (fbin_length > 65520)
fbin_length = 65520;
// enter fast program mode
dataOut_WS();
writeWord_WS(0x80aaa, 0xaaaa);
writeWord_WS(0x80555, 0x5555);
writeWord_WS(0x80aaa, 0x2020);
// 128bytes per block
for (i = 0; i < fbin_length; i += 128)
{
// blink LED
if ((i & 0x3ff) == 0)
PORTB ^= (1 << 4);
// reset key
key = 0xff;
bytes_read = myFile.read(sdBuffer, 128);
for (uint32_t j = 0; j < bytes_read; j += 2)
{
// for each decoded[n] = encoded[n] ^ key
// where key = encoded[n - 1]
// key = 0xff when n = 0, 0 <= n < 128
pd = ((sdBuffer[j] ^ key) | ((sdBuffer[j + 1] ^ sdBuffer[j]) << 8));
key = sdBuffer[j + 1];
fastProgramWitchFlash_WS(0xe0000 + i + j, pd);
}
}
// write jmpf instruction and block counts at 0xe0000
memcpy_P(sdBuffer, wwLaunchCode, 8);
*((uint16_t*)(sdBuffer + 6)) = ((i >> 7) & 0xffff);
for (uint32_t i = 0; i < 8; i += 2)
fastProgramWitchFlash_WS(0xefff0 + i, *((uint16_t*)(sdBuffer + i)));
// leave fast program mode
dataOut_WS();
writeWord_WS(0xe0000, 0x9090);
writeWord_WS(0xe0000, 0xf0f0);
myFile.close();
println_Msg(F("Done"));
}
else
{
print_Error(F("File doesn't exist"), false);
}
}
dataOut_WS();
writeWord_WS(0x80000, 0xf0f0);
}
void fastProgramWitchFlash_WS(uint32_t addr, uint16_t data)
{
dataOut_WS();
writeWord_WS(addr, 0xa0a0);
writeWord_WS(addr, data);
dataIn_WS();
while (readWord_WS(addr) != data);
}
void eraseWitchFlashSector_WS(uint32_t sector_addr)
{
// blink LED
PORTB ^= (1 << 4);
dataOut_WS();
writeWord_WS(0x80aaa, 0xaaaa);
writeWord_WS(0x80555, 0x5555);
writeWord_WS(0x80aaa, 0x8080);
writeWord_WS(0x80aaa, 0xaaaa);
writeWord_WS(0x80555, 0x5555);
writeWord_WS(sector_addr, 0x3030);
dataIn_WS();
while ((readWord_WS(sector_addr) & 0x0080) == 0x0000);
}
boolean compareChecksum_WS(const char *wsFilePath)
{
if (wsFilePath == NULL)
return 0;
println_Msg(F("Calculating Checksum"));
display_Update();
if (!myFile.open(wsFilePath, O_READ))
{
print_Error(F("Failed to open file"), false);
return 0;
}
uint32_t calLength = myFile.fileSize() - 512;
uint32_t checksum = 0;
if (wsWitch)
{
// only calcuate last 128Kbytes for wonderwitch (OS and BIOS region)
myFile.seekCur(myFile.fileSize() - 131072);
calLength = 131072 - 512;
}
for (uint32_t i = 0; i < calLength; i += 512)
{
myFile.read(sdBuffer, 512);
for (uint32_t j = 0; j < 512; j++)
checksum += sdBuffer[j];
}
// last 512 bytes
myFile.read(sdBuffer, 512);
// skip last 2 bytes (checksum value)
for (uint32_t j = 0; j < 510; j++)
checksum += sdBuffer[j];
myFile.close();
checksum &= 0x0000ffff;
calLength = wsGameChecksum;
// don't know why formating string "%04X(%04X)" always output "xxxx(0000)"
// so split into two snprintf
char result[11];
snprintf(result, 5, "%04X", calLength);
snprintf(result + 4, 11 - 4, "(%04X)", checksum);
print_Msg(F("Result: "));
println_Msg(result);
if (checksum == calLength)
{
println_Msg(F("Checksum matches"));
display_Update();
return 1;
}
else
{
print_Error(F("Checksum Error"), false);
return 0;
}
}
void writeByte_WSPort(uint8_t port, uint8_t data)
{
PORTF = (port & 0x0f);
PORTL = (port >> 4);
// switch CART(PH3), MMC(PH4) to LOW
PORTH &= ~((1 << 3) | (1 << 4));
// set data
PORTC = data;
// switch WE(PH5) to LOW
PORTH &= ~(1 << 5);
NOP;
// switch WE(PH5) to HIGH
PORTH |= (1 << 5);
NOP; NOP;
// switch CART(PH3), MMC(PH4) to HIGH
PORTH |= ((1 << 3) | (1 << 4));
}
uint8_t readByte_WSPort(uint8_t port)
{
PORTF = (port & 0x0f);
PORTL = (port >> 4);
// switch CART(PH3), MMC(PH4) to LOW
PORTH &= ~((1 << 3) | (1 << 4));
// switch OE(PH6) to LOW
PORTH &= ~(1 << 6);
NOP; NOP; NOP;
uint8_t ret = PINC;
// switch OE(PH6) to HIGH
PORTH |= (1 << 6);
// switch CART(PH3), MMC(PH4) to HIGH
PORTH |= ((1 << 3) | (1 << 4));
return ret;
}
void writeWord_WS(uint32_t addr, uint16_t data)
{
PORTF = addr & 0xff;
PORTK = (addr >> 8) & 0xff;
PORTL = (addr >> 16) & 0x0f;
PORTC = data & 0xff;
PORTA = (data >> 8);
// switch CART(PH3) and WE(PH5) to LOW
PORTH &= ~((1 << 3) | (1 << 5));
NOP;
// switch CART(PH3) and WE(PH5) to HIGH
PORTH |= (1 << 3) | (1 << 5);
NOP; NOP;
}
uint16_t readWord_WS(uint32_t addr)
{
PORTF = addr & 0xff;
PORTK = (addr >> 8) & 0xff;
PORTL = (addr >> 16) & 0x0f;
// switch CART(PH3) and OE(PH6) to LOW
PORTH &= ~((1 << 3) | (1 << 6));
NOP; NOP; NOP;
uint16_t ret = ((PINA << 8) | PINC);
// switch CART(PH3) and OE(PH6) to HIGH
PORTH |= (1 << 3) | (1 << 6);
return ret;
}
void writeByte_WS(uint32_t addr, uint8_t data)
{
PORTF = addr & 0xff;
PORTK = (addr >> 8) & 0xff;
PORTL = (addr >> 16) & 0x0f;
PORTC = data;
// switch CART(PH3) and WE(PH5) to LOW
PORTH &= ~((1 << 3) | (1 << 5));
NOP;
// switch CART(PH3) and WE(PH5) to HIGH
PORTH |= (1 << 3) | (1 << 5);
NOP; NOP;
}
uint8_t readByte_WS(uint32_t addr)
{
PORTF = addr & 0xff;
PORTK = (addr >> 8) & 0xff;
PORTL = (addr >> 16) & 0x0f;
// switch CART(PH3) and OE(PH6) to LOW
PORTH &= ~((1 << 3) | (1 << 6));
NOP; NOP; NOP;
uint8_t ret = PINC;
// switch CART(PH3) and OE(PH6) to HIGH
PORTH |= (1 << 3) | (1 << 6);
return ret;
}
void unprotectEEPROM()
{
generateEepromInstruction_WS(wsEepromShiftReg, 0x0, 0x3);
dataOut_WS();
writeByte_WSPort(0xc6, wsEepromShiftReg[0]);
writeByte_WSPort(0xc7, wsEepromShiftReg[1]);
writeByte_WSPort(0xc8, 0x40);
// MMC will shift out port 0xc7 to 0xc6 to EEPROM
pulseCLK_WS(1 + 16 + 3);
}
// generate data for port 0xc6 to 0xc7
// number of CLK pulses needed for each instruction is 1 + (16 or 32) + 3
void generateEepromInstruction_WS(uint8_t *instruction, uint8_t opcode, uint16_t addr)
{
uint8_t addr_bits = (sramSize > 1 ? 10 : 6);
uint16_t *ptr = (uint16_t*)instruction;
*ptr = 0x0001; // initial with a start bit
if (opcode == 0)
{
// 2bits opcode = 0x00
*ptr <<= 2;
// 2bits ext cmd (from addr)
*ptr <<= 2;
*ptr |= (addr & 0x0003);
*ptr <<= (addr_bits - 2);
}
else
{
// 2bits opcode
*ptr <<= 2;
*ptr |= (opcode & 0x03);
// address bits
*ptr <<= addr_bits;
*ptr |= (addr & ((1 << addr_bits) - 1));
}
}
// 2003 MMC need to be unlock,
// or it will reject all reading and bank switching
// All signals' timing are analyzed by using LogicAnalyzer
boolean unlockMMC2003_WS()
{
// initialize all control pin state
// RST(PH0) and CLK(PE3) to LOW
// CART(PH3) MMC(PH4) WE(PH5) OE(PH6) to HIGH
PORTH &= ~(1 << 0);
PORTE &= ~(1 << 3);
PORTH |= ((1 << 3) | (1 << 4) | (1 << 5) | (1 << 6));
// switch RST(PH0) to HIGH
PORTH |= (1 << 0);
PORTF = 0x0a;
PORTL = 0x05;
pulseCLK_WS(3);
PORTF = 0x05;
PORTL = 0x0a;
pulseCLK_WS(4);
// MMC is outputing something on IO? pin synchronized with CLK
// so still need to pulse CLK until MMC is ok to work
pulseCLK_WS(18);
// unlock procedure finished
// see if we can set bank number to MMC
dataOut_WS();
writeByte_WSPort(0xc2, 0xaa);
writeByte_WSPort(0xc3, 0x55);
dataIn_WS();
if (readByte_WSPort(0xc2) == 0xaa && readByte_WSPort(0xc3) == 0x55)
{
// now set initial bank number to MMC
dataOut_WS();
writeByte_WSPort(0xc0, 0x2f);
writeByte_WSPort(0xc1, 0x3f);
writeByte_WSPort(0xc2, 0xff);
writeByte_WSPort(0xc3, 0xff);
return true;
}
return false;
}
// doing a L->H on CLK(PE3) pin
void pulseCLK_WS(uint8_t count)
{
register uint8_t tic;
// about 384KHz, 50% duty cycle
asm volatile
("L0_%=:\n\t"
"cpi %[count], 0\n\t"
"breq L3_%=\n\t"
"dec %[count]\n\t"
"cbi %[porte], 3\n\t"
"ldi %[tic], 6\n\t"
"L1_%=:\n\t"
"dec %[tic]\n\t"
"brne L1_%=\n\t"
"sbi %[porte], 3\n\t"
"ldi %[tic], 5\n\t"
"L2_%=:\n\t"
"dec %[tic]\n\t"
"brne L2_%=\n\t"
"rjmp L0_%=\n\t"
"L3_%=:\n\t"
: [tic] "=a" (tic)
: [count] "a" (count), [porte] "I" (_SFR_IO_ADDR(PORTE))
);
}
void dataIn_WS()
{
DDRC = 0x00;
DDRA = 0x00;
PORTC = 0xff;
PORTA = 0xff;
}
void dataOut_WS()
{
DDRC = 0xff;
DDRA = 0xff;
}