Balint Kovacs fa741b265b Write support for NTR saves
Why does this work? We are writing flash memories without erasing them,
and writing pages without regard to alignment. Yet it works?
2019-10-11 16:24:08 +02:00

412 lines
11 KiB
C

/*
* This file is part of TWLSaveTool.
* Copyright (C) 2015-2016 TuxSH
*
* TWLSaveTool is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>
*/
#include "spi.h"
#include "spicard.h"
#include "timer.h"
// Deliberately written in C! (except for a few lines)
// u8* fill_buf = NULL;
int SPIWriteRead(CardType type, void* cmd, u32 cmdSize, void* answer, u32 answerSize, void* data, u32 dataSize) {
const u32 headerFooterVal = 0;
bool b = type == FLASH_512KB_INFRARED || type == FLASH_256KB_INFRARED;
SPICARD_init();
if (b) {
SPICARD_writeRead(NSPI_CLK_1MHz, &headerFooterVal, NULL, 1, 0, false);
}
SPICARD_writeRead(NSPI_CLK_4MHz, cmd, answer, cmdSize, answerSize, false);
SPICARD_writeRead(NSPI_CLK_4MHz, data, NULL, dataSize, 0, true);
SPICARD_deinit();
return 0;
}
int SPIWaitWriteEnd(CardType type) {
u8 cmd = SPI_CMD_RDSR, statusReg = 0;
int res = 0;
u64 time_start = timer_start();
do{
res = SPIWriteRead(type, &cmd, 1, &statusReg, 1, 0, 0);
if(res) return res;
if(timer_msec(time_start) > 1000) return 1;
} while(statusReg & SPI_FLG_WIP);
return 0;
}
int SPIEnableWriting(CardType type) {
u8 cmd = SPI_CMD_WREN, statusReg = 0;
int res = SPIWriteRead(type, &cmd, 1, NULL, 0, 0, 0);
if(res || type == EEPROM_512B) return res; // Weird, but works (otherwise we're getting an infinite loop for that chip type).
cmd = SPI_CMD_RDSR;
do{
res = SPIWriteRead(type, &cmd, 1, &statusReg, 1, 0, 0);
if(res) return res;
} while(statusReg & ~SPI_FLG_WEL);
return 0;
}
int SPIReadJEDECIDAndStatusReg(CardType type, u32* id, u8* statusReg) {
u8 cmd = SPI_FLASH_CMD_RDID;
u8 reg = 0;
u8 idbuf[3] = { 0 };
u32 id_ = 0;
int res = SPIWaitWriteEnd(type);
if(res) return res;
if((res = SPIWriteRead(type, &cmd, 1, idbuf, 3, 0, 0))) return res;
id_ = (idbuf[0] << 16) | (idbuf[1] << 8) | idbuf[2];
cmd = SPI_CMD_RDSR;
if((res = SPIWriteRead(type, &cmd, 1, &reg, 1, 0, 0))) return res;
if(id) *id = id_;
if(statusReg) *statusReg = reg;
return 0;
}
u32 SPIGetPageSize(CardType type) {
u32 EEPROMSizes[] = { 16, 32, 128, 256 };
if(type == NO_CHIP || type > CHIP_LAST) return 0;
else if(type < FLASH_256KB_1) return EEPROMSizes[(int) type];
else if(type < FLASH_64KB_CTR ) return 256;
else return 0; // TODO
}
u32 SPIGetCapacity(CardType type) {
u32 sz[] = { 9, 13, 16, 17, 18, 18, 19, 19, 20, 23, 16, 17, 18, 19, 20, 21, 22, 23, 19, 18 };
if(type == NO_CHIP || type > CHIP_LAST) return 0;
else return 1 << sz[(int) type];
}
int SPIWriteSaveData(CardType type, u32 offset, const void* data, u32 size) {
u8 cmd[4] = { 0 };
u32 cmdSize = 4;
u32 end = offset + size;
u32 pos = offset;
if(size == 0) return 0;
u32 pageSize = SPIGetPageSize(type);
if(pageSize == 0) return 0xC8E13404;
int res = SPIWaitWriteEnd(type);
if(res) return res;
size = (size <= SPIGetCapacity(type) - offset) ? size : SPIGetCapacity(type) - offset;
while(pos < end) {
switch(type) {
case EEPROM_512B:
cmdSize = 2;
cmd[0] = (pos >= 0x100) ? SPI_512B_EEPROM_CMD_WRHI : SPI_512B_EEPROM_CMD_WRLO;
cmd[1] = (u8) pos;
break;
case EEPROM_8KB:
case EEPROM_64KB:
cmdSize = 3;
cmd[0] = SPI_EEPROM_CMD_WRITE;
cmd[1] = (u8)(pos >> 8);
cmd[2] = (u8) pos;
break;
case EEPROM_128KB:
cmdSize = 4;
cmd[0] = SPI_EEPROM_CMD_WRITE;
cmd[1] = (u8)(pos >> 16);
cmd[2] = (u8)(pos >> 8);
cmd[3] = (u8) pos;
break;
case FLASH_256KB_1:
/*
This is what is done in the official implementation, but I think it's wrong
cmdSize = 4;
cmd[0] = SPI_CMD_PP;
cmd[1] = (u8)(pos >> 16);
cmd[2] = (u8)(pos >> 8);
cmd[3] = (u8) pos;
break;
*/
case FLASH_256KB_2:
case FLASH_512KB_1:
case FLASH_512KB_2:
case FLASH_1MB:
case FLASH_512KB_INFRARED:
case FLASH_256KB_INFRARED:
cmdSize = 4;
cmd[0] = SPI_FLASH_CMD_PW;
cmd[1] = (u8)(pos >> 16);
cmd[2] = (u8)(pos >> 8);
cmd[3] = (u8) pos;
break;
case FLASH_8MB:
return 0xC8E13404; // writing is unsupported (so is reading? need to test)
default:
return -1; // never happens
}
u32 remaining = end - pos;
u32 nb = pageSize - (pos % pageSize);
u32 dataSize = (remaining < nb) ? remaining : nb;
if( (res = SPIEnableWriting(type)) ) return res;
if( (res = SPIWriteRead(type, cmd, cmdSize, NULL, 0, (void*) ((u8*) data - offset + pos), dataSize)) ) return res;
if( (res = SPIWaitWriteEnd(type)) ) return res;
pos = ((pos / pageSize) + 1) * pageSize; // truncate
}
return 0;
}
int _SPIReadSaveData_512B_impl(u32 pos, void* data, u32 size) {
u8 cmd[4];
u32 cmdSize = 2;
u32 end = pos + size;
u32 read = 0;
if(pos < 0x100) {
u32 len = 0x100 - pos;
cmd[0] = SPI_512B_EEPROM_CMD_RDLO;
cmd[1] = (u8) pos;
int res = SPIWriteRead(EEPROM_512B, cmd, cmdSize, data, len, NULL, 0);
if(res) return res;
read += len;
}
if(end >= 0x100) {
u32 len = end - 0x100;
cmd[0] = SPI_512B_EEPROM_CMD_RDHI;
cmd[1] = (u8)(pos + read);
int res = SPIWriteRead(EEPROM_512B, cmd, cmdSize, (void*)((u8*)data + read), len, NULL, 0);
if(res) return res;
}
return 0;
}
int SPIReadSaveData(CardType type, u32 offset, void* data, u32 size) {
u8 cmd[4] = { SPI_CMD_READ };
u32 cmdSize = 4;
if(size == 0) return 0;
if(type == NO_CHIP) return 0xC8E13404;
int res = SPIWaitWriteEnd(type);
if(res) return res;
size = (size <= SPIGetCapacity(type) - offset) ? size : SPIGetCapacity(type) - offset;
u32 pos = offset;
switch(type) {
case EEPROM_512B:
return _SPIReadSaveData_512B_impl(offset, data, size);
break;
case EEPROM_8KB:
case EEPROM_64KB:
cmdSize = 3;
cmd[1] = (u8)(pos >> 8);
cmd[2] = (u8) pos;
break;
case EEPROM_128KB:
cmdSize = 4;
cmd[1] = (u8)(pos >> 16);
cmd[2] = (u8)(pos >> 8);
cmd[3] = (u8) pos;
break;
case FLASH_256KB_1:
case FLASH_256KB_2:
case FLASH_512KB_1:
case FLASH_512KB_2:
case FLASH_1MB:
case FLASH_8MB:
case FLASH_512KB_INFRARED:
case FLASH_256KB_INFRARED:
case FLASH_64KB_CTR:
case FLASH_128KB_CTR:
case FLASH_256KB_CTR:
case FLASH_512KB_CTR:
case FLASH_1MB_CTR:
case FLASH_2MB_CTR:
case FLASH_4MB_CTR:
case FLASH_8MB_CTR:
cmdSize = 4;
cmd[1] = (u8)(pos >> 16);
cmd[2] = (u8)(pos >> 8);
cmd[3] = (u8) pos;
break;
default:
return 0; // never happens
}
return SPIWriteRead(type, cmd, cmdSize, data, size, NULL, 0);
}
/* int SPIEraseSector(CardType type, u32 offset) {
u8 cmd[4] = { SPI_FLASH_CMD_SE, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
if(type == NO_CHIP || type == FLASH_8MB) return 0xC8E13404;
if(type < FLASH_256KB_1 && fill_buf == NULL) {
fill_buf = new u8[0x10000];
memset(fill_buf, 0xff, 0x10000);
}
int res = SPIWaitWriteEnd(type);
if(type >= FLASH_256KB_1) {
if( (res = SPIEnableWriting(type)) ) return res;
if( (res = SPIWriteRead(type, cmd, 4, NULL, 0, NULL, 0)) ) return res;
if( (res = SPIWaitWriteEnd(type)) ) return res;
}
// Simulate the same behavior on EEPROM chips.
else {
u32 sz = SPIGetCapacity(type);
int res = SPIWriteSaveData(type, 0, fill_buf, (sz < 0x10000) ? sz : 0x10000);
return res;
}
return 0;
} */
// The following routine use code from savegame-manager:
/*
* savegame_manager: a tool to backup and restore savegames from Nintendo
* DS cartridges. Nintendo DS and all derivative names are trademarks
* by Nintendo. EZFlash 3-in-1 is a trademark by EZFlash.
*
* auxspi.cpp: A thin reimplementation of the AUXSPI protocol
* (high level functions)
*
* Copyright (C) Pokedoc (2010)
*/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
int _SPIIsDataMirrored(CardType type, int size, bool* mirrored) {
u32 offset0 = (size-1); // n KB
u32 offset1 = (2*size-1); // 2n KB
u8 buf1; // +0k data read -> write
u8 buf2; // +n k data read -> read
u8 buf3; // +0k ~data write
u8 buf4; // +n k data new comp buf2
int res;
if( (res = SPIReadSaveData(type, offset0, &buf1, 1)) ) return res;
if( (res = SPIReadSaveData(type, offset1, &buf2, 1)) ) return res;
buf3=~buf1;
if( (res = SPIWriteSaveData(type, offset0, &buf3, 1)) ) return res;
if( (res = SPIReadSaveData(type, offset1, &buf4, 1)) ) return res;
if( (res = SPIWriteSaveData(type, offset0, &buf1, 1)) ) return res;
*mirrored = buf2 != buf4;
return 0;
}
int SPIGetCardType(CardType* type, int infrared) {
u8 sr = 0;
u32 jedec = 0;
u32 tries = 0;
CardType t = (infrared == 1) ? FLASH_INFRARED_DUMMY : FLASH_STD_DUMMY;
int res;
u32 jedecOrderedList[] = { 0x204012, 0x621600, 0x204013, 0x621100, 0x204014, 0x202017, 0xC22210, 0xC22211, 0xC22212, 0xC22213, 0xC22214, 0xC22215, 0xC22216, 0xC22217 };
u32 maxTries = (infrared == -1) ? 2 : 1; // note: infrared = -1 fails 1/3 of the time
while(tries < maxTries){
res = SPIReadJEDECIDAndStatusReg(t, &jedec, &sr); // dummy
if(res) return res;
if ((sr & 0xfd) == 0x00 && (jedec != 0x00ffffff)) { break; }
if ((sr & 0xfd) == 0xF0 && (jedec == 0x00ffffff)) { t = EEPROM_512B; break; }
if ((sr & 0xfd) == 0x00 && (jedec == 0x00ffffff)) { t = EEPROM_STD_DUMMY; break; }
++tries;
t = FLASH_INFRARED_DUMMY;
}
if(t == EEPROM_512B) { *type = t; return 0; }
else if(t == EEPROM_STD_DUMMY) {
bool mirrored = false;
if( (res = _SPIIsDataMirrored(t, 8192, &mirrored)) ) return res;
if(mirrored) t = EEPROM_8KB;
else{
if( (res = _SPIIsDataMirrored(t, 65536, &mirrored)) ) return res;
if(mirrored) t = EEPROM_64KB;
else t = EEPROM_128KB;
}
*type = t;
return 0;
}
else if(t == FLASH_INFRARED_DUMMY) {
if(infrared == 0) *type = NO_CHIP; // did anything go wrong?
if(jedec == jedecOrderedList[0] || jedec == jedecOrderedList[1]) *type = FLASH_256KB_INFRARED;
else *type = FLASH_512KB_INFRARED;
return 0;
}
else {
if(infrared == 1) *type = NO_CHIP; // did anything go wrong?
if(jedec == 0x204017) { *type = FLASH_8MB; return 0; } // 8MB. savegame-manager: which one? (more work is required to unlock this save chip!)
size_t i;
for(i = 0; i < sizeof(jedecOrderedList) / sizeof(int); ++i) {
if(jedec == jedecOrderedList[i]) { *type = (CardType)((int) FLASH_256KB_1 + i); return 0; }
}
*type = NO_CHIP;
return 0;
}
}