From 294c4bee1ca936d796211ffa7f085f81bc815c5c Mon Sep 17 00:00:00 2001 From: Pk11 Date: Wed, 6 Oct 2021 00:51:38 -0500 Subject: [PATCH] Fix NAND save writing and restoring --- arm9/source/dumpOperations.cpp | 238 ++++++++++++++++++++------------- arm9/source/read_card.c | 105 ++++++++++++--- arm9/source/read_card.h | 7 +- 3 files changed, 240 insertions(+), 110 deletions(-) diff --git a/arm9/source/dumpOperations.cpp b/arm9/source/dumpOperations.cpp index 0bf4057..9eb0dd9 100644 --- a/arm9/source/dumpOperations.cpp +++ b/arm9/source/dumpOperations.cpp @@ -30,6 +30,26 @@ void dumpFailMsg(bool save) { } } +void saveWriteFailMsg(void) { + const std::string_view sizeError = "The size of this save doesn't match the size of the inserted game card.\n\nWrite cancelled!"; + + font->clear(false); + font->print(0, 0, false, sizeError, Alignment::left, Palette::red); + font->print(0, font->calcHeight(sizeError) + 1, false, "( OK)"); + font->update(false); + + u16 pressed; + do { + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); + + scanKeys(); + pressed = keysDownRepeat(); + swiWaitForVBlank(); + } while (!(pressed & KEY_A)); +} + //--------------------------------------------------------------------------------- // https://github.com/devkitPro/libnds/blob/master/source/common/cardEeprom.c#L74 // with Pokémon Mystery Dungeon - Explorers of Sky (128 KiB EEPROM) fixed @@ -215,7 +235,7 @@ void ndsCardSaveDump(const char* filename) { font->update(false); for (u32 i = 0; i < 0x8000; i += 0x200) { - cardRead(ndsCardHeader.nandRwStart + src + i, copyBuf + i); + cardRead(cardNandRwStart + src + i, copyBuf + i, true); } if (fwrite(copyBuf, 1, (currentSize >= 0x8000 ? 0x8000 : currentSize), destinationFile) < 1) { dumpFailMsg(true); @@ -273,104 +293,144 @@ void ndsCardSaveRestore(const char *filename) { } while (!(pressed & (KEY_A | KEY_B))); if(pressed & KEY_A) { - auxspi_extra card_type = auxspi_has_extra(); - bool auxspi = card_type == AUXSPI_INFRARED; - FILE *in = fopen(filename, "rb"); - if(in) { - unsigned char *buffer; - int size; - int type; - int length; - unsigned int num_blocks = 0, shift = 0, LEN = 0; - if(auxspi) { - size = auxspi_save_size_log_2(card_type); - type = auxspi_save_type(card_type); - switch(type) { - case 1: - shift = 4; // 16 bytes - break; - case 2: - shift = 5; // 32 bytes - break; - case 3: - shift = 8; // 256 bytes - break; - default: - return; + int type = cardEepromGetTypeFixed(); + + if(type == -1) { // NAND + if (cardInit(&ndsCardHeader) != 0) { + font->clear(false); + font->print(0, 0, false, "Unable to restore the save."); + font->update(false); + for (int i = 0; i < 60 * 2; i++) { + swiWaitForVBlank(); } - LEN = 1 << shift; - num_blocks = 1 << (size - shift); - } else { - type = cardEepromGetTypeFixed(); - size = cardEepromGetSizeFixed(); + return; } + + u32 saveSize = cardNandGetSaveSize(); + + if(saveSize == 0) { + dumpFailMsg(true); + return; + } + + FILE* in = fopen(filename, "rb"); + fseek(in, 0, SEEK_END); - length = ftell(in); + size_t length = ftell(in); fseek(in, 0, SEEK_SET); - if(length != (auxspi ? (int)(LEN * num_blocks) : size)) { + if(length != saveSize) { fclose(in); - const std::string_view sizeError = "The size of this save doesn't match the size of the inserted game card.\n\nWrite cancelled!"; + saveWriteFailMsg(); + return; + } - font->clear(false); - font->print(0, 0, false, sizeError, Alignment::left, Palette::red); - font->print(0, font->calcHeight(sizeError) + 1, false, "( OK)"); - font->update(false); + u32 currentSize = saveSize; + if (in) { - do { + font->print(0, 4, false, "Progress:"); + font->print(0, 5, false, "["); + font->print(-1, 5, false, "]"); + for (u32 src = 0; src < saveSize; src += 0x8000) { // Print time font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); font->update(true); - scanKeys(); - pressed = keysDownRepeat(); - swiWaitForVBlank(); - } while (!(pressed & KEY_A)); - return; - } - - font->clear(false); - font->print(0, 0, false, "Restoring save..."); - font->print(0, 1, false, "Do not remove the NDS card."); - font->print(0, 4, false, "Progress:"); - font->update(false); - - if(type == 3) { - if(auxspi) - auxspi_erase(card_type); - else - cardEepromChipEraseFixed(); - } - if(auxspi){ - buffer = new unsigned char[LEN]; - font->print(0, 5, false, "["); - font->print(-1, 5, false, "]"); - for(unsigned int i = 0; i < num_blocks; i++) { - font->print((i * (SCREEN_COLS - 2) / num_blocks) + 1, 5, false, "="); - font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", i * LEN, length); + font->print((src / (saveSize / (SCREEN_COLS - 2))) + 1, 5, false, "="); + font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", src, saveSize); font->update(false); - fread(buffer, 1, LEN, in); - auxspi_write_data(i << shift, buffer, LEN, type, card_type); - } - } else { - int blocks = size / 32; - int written = 0; - buffer = new unsigned char[blocks]; - font->print(0, 5, false, "["); - font->print(-1, 5, false, "]"); - for(unsigned int i = 0; i < 32; i++) { - font->print((i * (SCREEN_COLS - 2) / 32) + 1, 5, false, "="); - font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", written, size); - font->update(false); - - fread(buffer, 1, blocks, in); - cardWriteEeprom(written, buffer, blocks, type); - written += blocks; + fread(copyBuf, 1, 0x8000, in); + for (u32 i = 0; i < 0x8000; i += 0x800) { + cardWriteNand(copyBuf + i, cardNandRwStart + src + i); + } + currentSize -= 0x8000; } + fclose(in); + } + } else { // SPI + auxspi_extra card_type = auxspi_has_extra(); + bool auxspi = card_type == AUXSPI_INFRARED; + FILE *in = fopen(filename, "rb"); + if(in) { + unsigned char *buffer; + int size; + int length; + unsigned int num_blocks = 0, shift = 0, LEN = 0; + if(auxspi) { + size = auxspi_save_size_log_2(card_type); + type = auxspi_save_type(card_type); + switch(type) { + case 1: + shift = 4; // 16 bytes + break; + case 2: + shift = 5; // 32 bytes + break; + case 3: + shift = 8; // 256 bytes + break; + default: + return; + } + LEN = 1 << shift; + num_blocks = 1 << (size - shift); + } else { + size = cardEepromGetSizeFixed(); + } + fseek(in, 0, SEEK_END); + length = ftell(in); + fseek(in, 0, SEEK_SET); + if(length != (auxspi ? (int)(LEN * num_blocks) : size)) { + fclose(in); + + saveWriteFailMsg(); + return; + } + + font->clear(false); + font->print(0, 0, false, "Restoring save..."); + font->print(0, 1, false, "Do not remove the NDS card."); + font->print(0, 4, false, "Progress:"); + font->update(false); + + if(type == 3) { + if(auxspi) + auxspi_erase(card_type); + else + cardEepromChipEraseFixed(); + } + if(auxspi){ + buffer = new unsigned char[LEN]; + font->print(0, 5, false, "["); + font->print(-1, 5, false, "]"); + for(unsigned int i = 0; i < num_blocks; i++) { + font->print((i * (SCREEN_COLS - 2) / num_blocks) + 1, 5, false, "="); + font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", i * LEN, length); + font->update(false); + + fread(buffer, 1, LEN, in); + auxspi_write_data(i << shift, buffer, LEN, type, card_type); + } + } else { + int blocks = size / 32; + int written = 0; + buffer = new unsigned char[blocks]; + font->print(0, 5, false, "["); + font->print(-1, 5, false, "]"); + for(unsigned int i = 0; i < 32; i++) { + font->print((i * (SCREEN_COLS - 2) / 32) + 1, 5, false, "="); + font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", written, size); + font->update(false); + + fread(buffer, 1, blocks, in); + cardWriteEeprom(written, buffer, blocks, type); + written += blocks; + } + } + delete[] buffer; + fclose(in); } - delete[] buffer; - fclose(in); } } } @@ -604,7 +664,7 @@ void ndsCardDump(void) { iprintf ("\x1b[8;0H"); iprintf ("Read:\n"); iprintf ("%i/%i Bytes ", (int)src, (int)romSize); - cardRead (src, (void*)0x09000000+(src % 0x800000)); + cardRead (src, (void*)0x09000000+(src % 0x800000), false); } iprintf("\x1b[15;0H"); iprintf("Please switch to the\nflashcard, then press A.\n"); @@ -664,17 +724,12 @@ void ndsCardDump(void) { font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); font->update(true); - // TODO: Remove, just for testing - scanKeys(); - if(keysDown() & KEY_B) - break; - font->print((src / (romSize / (SCREEN_COLS - 2))) + 1, 5, false, "="); font->printf(0, 6, false, Alignment::left, Palette::white, "%d/%d Bytes", src, romSize); font->update(false); for (u32 i = 0; i < 0x8000; i += 0x200) { - cardRead (src+i, copyBuf+i); + cardRead (src+i, copyBuf+i, false); } if (fwrite(copyBuf, 1, (currentSize>=0x8000 ? 0x8000 : currentSize), destinationFile) < 1) { dumpFailMsg(false); @@ -686,8 +741,7 @@ void ndsCardDump(void) { } else { dumpFailMsg(false); } - // TODO: Uncomment, just commented for testing - // ndsCardSaveDump(destSavPath); + ndsCardSaveDump(destSavPath); //} } } diff --git a/arm9/source/read_card.c b/arm9/source/read_card.c index 0689d8a..818c8a4 100644 --- a/arm9/source/read_card.c +++ b/arm9/source/read_card.c @@ -45,7 +45,7 @@ enum { // NAND Card commands // https://problemkaputt.de/gbatek-ds-cartridge-nand.htm #define CARD_CMD_NAND_WRITE_BUFFER 0x81 -#define CARD_CMD_NAND_FLUSH_BUFFER 0x82 +#define CARD_CMD_NAND_COMMIT_BUFFER 0x82 #define CARD_CMD_NAND_DISCARD_BUFFER 0x84 #define CARD_CMD_NAND_WRITE_ENABLE 0x85 #define CARD_CMD_NAND_ROM_MODE 0x8B @@ -63,18 +63,48 @@ typedef union static bool twlBlowfish = false; static bool normalChip = false; // As defined by GBAtek, normal chip secure area is accessed in blocks of 0x200, other chip in blocks of 0x1000 -static bool nandChip = false; -static int nandRomMode = -1; static u32 portFlags = 0; static u32 headerData[0x1000/sizeof(u32)] = {0}; static u32 secureArea[CARD_SECURE_AREA_SIZE/sizeof(u32)] = {0}; +static bool nandChip = false; +static int nandSection = -1; // -1 = ROM, above that is the current 128 KiB section in RW +u32 cardNandRomEnd = 0; +u32 cardNandRwStart = 0; + static const u8 cardSeedBytes[] = {0xE8, 0x4D, 0x5A, 0xB1, 0x17, 0x8F, 0x99, 0xD5}; static u32 getRandomNumber(void) { return rand(); } +//--------------------------------------------------------------------------------- +// https://github.com/devkitPro/libnds/blob/105d4943dbac8f2bd99a47b22cd3ed48f96af083/source/common/card.c#L47-L62 +// but modified to write if CARD_WR is set. +static void cardPolledTransferWrite(u32 flags, u32 *buffer, u32 length, const u8 *command) { +//--------------------------------------------------------------------------------- + cardWriteCommand(command); + REG_ROMCTRL = flags | CARD_BUSY; + u32 * target = buffer + length; + do { + // Read/write data if available + if (REG_ROMCTRL & CARD_DATA_READY) { + if (flags & CARD_WR) { // Write + if (NULL != buffer && buffer < target) + REG_CARD_DATA_RD = *buffer++; + else + REG_CARD_DATA_RD = 0; + } else { // Read + u32 data = REG_CARD_DATA_RD; + if (NULL != buffer && buffer < target) + *buffer++ = REG_CARD_DATA_RD; + else + (void)data; + } + } + } while (REG_ROMCTRL & CARD_BUSY); +} + static void decryptSecureArea (u32 gameCode, u32* secureArea, int iCardDevice) { init_keycode (gameCode, 2, 8, iCardDevice); @@ -291,7 +321,7 @@ int cardInit (sNDSHeaderExt* ndsHeader) u32 portFlagsKey1, portFlagsSecRead; normalChip = false; // As defined by GBAtek, normal chip secure area and header are accessed in blocks of 0x200, other chip in blocks of 0x1000 nandChip = false; - nandRomMode = -1; + nandSection = -1; int secureBlockNumber; int i; u8 cmdData[8] __attribute__ ((aligned)); @@ -313,9 +343,6 @@ int cardInit (sNDSHeaderExt* ndsHeader) NULL, 0); } - // TODO: This was only done in DS mode, but fixes NAND in DSi mode - // see if only part of this is needed or if it causes problems to do - // all of it in DSi mode. REG_ROMCTRL=0; REG_AUXSPICNT=0; //ioDelay2(167550); @@ -467,10 +494,23 @@ int cardInit (sNDSHeaderExt* ndsHeader) //return normalChip ? ERR_SEC_NORM : ERR_SEC_OTHR; } + // Set NAND card section location variables + if (nandChip) { + if(ndsHeader->nandRomEnd != 0) { + // TWL cards (Face Training) multiply by 0x80000 instead of 0x20000 + cardNandRomEnd = ndsHeader->nandRomEnd * (ndsHeader->unitCode == 0 ? 0x20000 : 0x80000); + cardNandRwStart = ndsHeader->nandRwStart * (ndsHeader->unitCode == 0 ? 0x20000 : 0x80000); + } else { + // Jam with the Band (J) (大合奏!バンドブラザーズ) doesn't have the RW section in the header + cardNandRomEnd = 0x7200000; + cardNandRwStart = 0x7200000; + } + } + return ERR_NONE; } -void cardRead (u32 src, void* dest) +void cardRead (u32 src, void* dest, bool nandSave) { sNDSHeaderExt* ndsHeader = (sNDSHeaderExt*)headerData; @@ -491,15 +531,17 @@ void cardRead (u32 src, void* dest) return; } - // if (nandChip) { - // if (src < ndsHeader->nandRomEnd * 0x20000 /*dsi: 80000h?*/ && nandRomMode != CARD_CMD_NAND_ROM_MODE) { - // cardParamCommand(CARD_CMD_NAND_ROM_MODE, 0, CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW | CARD_DELAY1(0x1FFF) | CARD_DELAY2(0x3F), NULL, 0); - // nandRomMode = CARD_CMD_NAND_ROM_MODE; - // } else if (src > ndsHeader->nandRwStart * 0x20000 /*dsi: 80000h?*/ && nandRomMode != CARD_CMD_NAND_RW_MODE) { - // cardParamCommand(CARD_CMD_NAND_RW_MODE, 0, CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW | CARD_DELAY1(0x1FFF) | CARD_DELAY2(0x3F), NULL, 0); - // nandRomMode = CARD_CMD_NAND_RW_MODE; - // } - // } + if (nandChip) { + if ((src < cardNandRomEnd || !nandSave) && nandSection != -1) { + cardParamCommand(CARD_CMD_NAND_ROM_MODE, 0, portFlags | CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW, NULL, 0); + nandSection = -1; + } else if (src >= cardNandRwStart && nandSection != (src - cardNandRwStart) / (128 << 10)) { + if(nandSection != -1) // Need to switch back to ROM mode before switching to another RW section + cardParamCommand(CARD_CMD_NAND_ROM_MODE, 0, portFlags | CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW, NULL, 0); + cardParamCommand(CARD_CMD_NAND_RW_MODE, src, portFlags | CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW, NULL, 0); + nandSection = (src - cardNandRwStart) / (128 << 10); + } + } cardParamCommand (CARD_CMD_DATA_READ, src, portFlags | CARD_ACTIVATE | CARD_nRESET | CARD_BLK_SIZE(1), @@ -510,3 +552,32 @@ void cardRead (u32 src, void* dest) } } +// src must be a 0x800 byte array +void cardWriteNand (void* src, u32 dest) +{ + if (dest < cardNandRwStart || !nandChip) + return; + + if (nandSection != (dest - cardNandRwStart) / (128 << 10)) { + if(nandSection != -1) // Need to switch back to ROM mode before switching to another RW section + cardParamCommand(CARD_CMD_NAND_ROM_MODE, 0, portFlags | CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW, NULL, 0); + cardParamCommand(CARD_CMD_NAND_RW_MODE, dest, portFlags | CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW, NULL, 0); + nandSection = (dest - cardNandRwStart) / (128 << 10); + } + + cardParamCommand(CARD_CMD_NAND_WRITE_ENABLE, 0, portFlags | CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW, NULL, 0); + + const u8 cmdData[8] = {0, 0, 0, dest, dest >> 8, dest >> 16, dest >> 24, CARD_CMD_NAND_WRITE_BUFFER}; + for (int i = 0; i < 4; i++) { + cardPolledTransferWrite(portFlags | CARD_ACTIVATE | CARD_WR | CARD_nRESET | CARD_BLK_SIZE(1), src + (i * 0x200), 0x200 / sizeof(u32), cmdData); + } + + cardParamCommand(CARD_CMD_NAND_COMMIT_BUFFER, 0, portFlags | CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW, NULL, 0); + + u32 status; + do { + cardParamCommand(CARD_CMD_NAND_READ_STATUS, 0, portFlags | CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW | CARD_BLK_SIZE(7), &status, 1); + } while((status & BIT(5)) == 0); + + cardParamCommand(CARD_CMD_NAND_DISCARD_BUFFER, 0, portFlags | CARD_ACTIVATE | CARD_nRESET | CARD_CLK_SLOW, NULL, 0); +} diff --git a/arm9/source/read_card.h b/arm9/source/read_card.h index e54d37a..56f94a8 100644 --- a/arm9/source/read_card.h +++ b/arm9/source/read_card.h @@ -36,9 +36,14 @@ extern "C" { #endif +extern u32 cardNandRomEnd; +extern u32 cardNandRwStart; + int cardInit (sNDSHeaderExt* ndsHeader); -void cardRead (u32 src, void* dest); +void cardRead (u32 src, void* dest, bool nandSave); + +void cardWriteNand (void* src, u32 dest); #ifdef __cplusplus }