diff --git a/arm9/source/gamecart/card_spi.c b/arm9/source/gamecart/card_spi.c
new file mode 100644
index 0000000..ffb30a3
--- /dev/null
+++ b/arm9/source/gamecart/card_spi.c
@@ -0,0 +1,522 @@
+/*
+ * This file is based on SPI.cpp from TWLSaveTool. Its copyright notice is
+ * reproduced below.
+ *
+ * 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
+ */
+
+#include "card_spi.h"
+#include
+#include "timer.h"
+
+#define SPI_CMD_RDSR 5
+#define SPI_CMD_WREN 6
+
+#define SPI_512B_EEPROM_CMD_WRLO 2
+#define SPI_512B_EEPROM_CMD_WRHI 10
+#define SPI_512B_EEPROM_CMD_RDLO 3
+#define SPI_512B_EEPROM_CMD_RDHI 11
+
+#define SPI_EEPROM_CMD_WRITE 2
+
+#define SPI_CMD_READ 3
+
+#define SPI_CMD_PP 2
+#define SPI_FLASH_CMD_PW 10
+#define SPI_FLASH_CMD_RDID 0x9f
+#define SPI_FLASH_CMD_SE 0xd8
+#define SPI_FLASH_CMD_PE 0xdb
+#define SPI_FLASH_CMD_MXIC_SE 0x20
+
+#define SPI_FLG_WIP 1
+#define SPI_FLG_WEL 2
+
+// declarations for actual implementations
+int CardSPIEnableWriting_512B(CardSPIType type);
+int CardSPIEnableWriting_regular(CardSPIType type);
+int CardSPIReadSaveData_9bit(CardSPIType type, u32 offset, void* data, u32 size);
+int CardSPIReadSaveData_16bit(CardSPIType type, u32 offset, void* data, u32 size);
+int CardSPIReadSaveData_24bit(CardSPIType type, u32 offset, void* data, u32 size);
+int CardSPIWriteSaveData_9bit(CardSPIType type, u32 offset, const void* data, u32 size);
+int CardSPIWriteSaveData_16bit(CardSPIType type, u32 offset, const void* data, u32 size);
+int CardSPIWriteSaveData_24bit_write(CardSPIType type, u32 offset, const void* data, u32 size);
+int CardSPIWriteSaveData_24bit_erase_program(CardSPIType type, u32 offset, const void* data, u32 size);
+int CardSPIEraseSector_emulated(CardSPIType type, u32 offset);
+int CardSPIEraseSector_real(CardSPIType type, u32 offset);
+
+const CardSPITypeData EEPROM_512B_ = { CardSPIEnableWriting_512B, CardSPIReadSaveData_9bit, CardSPIWriteSaveData_9bit, CardSPIEraseSector_emulated, 0xffffff, 1 << 9, 16, 16, 16, false, 0, 0, 0 };
+
+const CardSPITypeData EEPROM_STD_DUMMY = { CardSPIEnableWriting_regular, CardSPIReadSaveData_16bit, CardSPIWriteSaveData_16bit, CardSPIEraseSector_emulated, 0xffffff, UINT32_MAX, 1, 1, 1, false, SPI_EEPROM_CMD_WRITE, 0, 0 };
+const CardSPITypeData EEPROMTypes[] = {
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_16bit, CardSPIWriteSaveData_16bit, CardSPIEraseSector_emulated, 0xffffff, 1 << 13, 32, 32, 32, false, SPI_EEPROM_CMD_WRITE, 0, 0}, // EEPROM 8 KB
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_16bit, CardSPIWriteSaveData_16bit, CardSPIEraseSector_emulated, 0xffffff, 1 << 16, 128, 128, 128, false, SPI_EEPROM_CMD_WRITE, 0, 0}, // EEPROM 64 KB
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_write, CardSPIEraseSector_emulated, 0xffffff, 1 << 17, 256, 256, 256, false, SPI_EEPROM_CMD_WRITE, 0, 0}, // EEPROM 128 KB
+};
+
+const CardSPITypeData FLASH_STD_DUMMY = { NULL, CardSPIReadSaveData_24bit, NULL, NULL, 0x0, 0, 0, 0, 0, false, 0, 0, 0 };
+const CardSPITypeData FlashStdTypes[] = {
+ // NTR/TWL
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_write, CardSPIEraseSector_real, 0x204012, 1 << 18, 65536, 256, 256, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0x621600, 1 << 18, 65536, 256, 65536, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_write, CardSPIEraseSector_real, 0x204013, 1 << 19, 65536, 256, 256, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_write, CardSPIEraseSector_real, 0x621100, 1 << 19, 65536, 256, 256, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_write, CardSPIEraseSector_real, 0x204014, 1 << 20, 65536, 256, 256, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+ // Untested (but pretty safe bet), for Art Academy
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0x202017, 1 << 23, 65536, 32, 65536, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0x204017, 1 << 23, 65536, 32, 65536, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+ // CTR
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0xC22210, 1 << 16, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0xC22211, 1 << 17, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0xC22212, 1 << 18, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0xC22213, 1 << 19, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0xC22214, 1 << 20, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0xC22215, 1 << 21, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0xC22216, 1 << 22, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0xC22217, 1 << 23, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
+};
+
+const CardSPITypeData FLASH_INFRARED_DUMMY = { NULL, CardSPIReadSaveData_24bit, NULL, NULL, 0x0, 0, 0, 0, 0, true, 0, 0, 0 };
+const CardSPITypeData FlashInfraredTypes[] = {
+ // NTR/TWL
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_write, CardSPIEraseSector_real, 0x204012, 1 << 18, 65536, 256, 256, true, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_erase_program, CardSPIEraseSector_real, 0x621600, 1 << 18, 65536, 256, 65536, true, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_write, CardSPIEraseSector_real, 0x204013, 1 << 19, 65536, 256, 256, true, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+ { CardSPIEnableWriting_regular, CardSPIReadSaveData_24bit, CardSPIWriteSaveData_24bit_write, CardSPIEraseSector_real, 0x621100, 1 << 19, 65536, 256, 256, true, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
+};
+
+const CardSPIType EEPROM_512B = &EEPROM_512B_;
+
+const CardSPIType EEPROM_8KB = EEPROMTypes + 0;
+const CardSPIType EEPROM_64KB = EEPROMTypes + 1;
+const CardSPIType EEPROM_128KB = EEPROMTypes + 2;
+
+const CardSPIType FLASH_256KB_1 = FlashStdTypes + 0;
+const CardSPIType FLASH_256KB_2 = FlashStdTypes + 1;
+const CardSPIType FLASH_512KB_1 = FlashStdTypes + 2;
+const CardSPIType FLASH_512KB_2 = FlashStdTypes + 3;
+const CardSPIType FLASH_1MB = FlashStdTypes + 4;
+const CardSPIType FLASH_8MB_1 = FlashStdTypes + 5;
+const CardSPIType FLASH_8MB_2 = FlashStdTypes + 6;
+
+const CardSPIType FLASH_64KB_CTR = FlashStdTypes + 7;
+const CardSPIType FLASH_128KB_CTR = FlashStdTypes + 8;
+const CardSPIType FLASH_256KB_CTR = FlashStdTypes + 9;
+const CardSPIType FLASH_512KB_CTR = FlashStdTypes + 10;
+const CardSPIType FLASH_1MB_CTR = FlashStdTypes + 11;
+const CardSPIType FLASH_2MB_CTR = FlashStdTypes + 12;
+const CardSPIType FLASH_4MB_CTR = FlashStdTypes + 13;
+const CardSPIType FLASH_8MB_CTR = FlashStdTypes + 14;
+
+const CardSPIType FLASH_256KB_1_INFRARED = FlashInfraredTypes + 0;
+const CardSPIType FLASH_256KB_2_INFRARED = FlashInfraredTypes + 1;
+const CardSPIType FLASH_512KB_1_INFRARED = FlashInfraredTypes + 2;
+const CardSPIType FLASH_512KB_2_INFRARED = FlashInfraredTypes + 3;
+
+#define REG_CFG9_CARDCTL *((vu16*)0x1000000C)
+#define CARDCTL_SPICARD (1u<<8)
+
+int CardSPIWriteRead(CardSPIType type, const void* cmd, u32 cmdSize, void* answer, u32 answerSize, const void* data, u32 dataSize) {
+ u32 headerFooterVal = 0;
+
+ REG_CFG9_CARDCTL |= CARDCTL_SPICARD;
+
+ if (type->infrared) {
+ SPI_XferInfo irXfer = { &headerFooterVal, 1, false };
+ SPI_DoXfer(SPI_DEV_CART_IR, &irXfer, 1, false);
+ }
+
+ SPI_XferInfo transfers[3] = {
+ { (u8*) cmd, cmdSize, false },
+ { answer, answerSize, true },
+ { (u8*) data, dataSize, false },
+ };
+ SPI_DoXfer(SPI_DEV_CART_FLASH, transfers, 3, true);
+
+ REG_CFG9_CARDCTL &= ~CARDCTL_SPICARD;
+
+ return 0;
+}
+
+int CardSPIWaitWriteEnd(CardSPIType type) {
+ u8 cmd = SPI_CMD_RDSR, statusReg = 0;
+ int res = 0;
+ u64 time_start = timer_start();
+
+ do{
+ res = CardSPIWriteRead(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 CardSPIEnableWriting_512B(CardSPIType type) {
+ u8 cmd = SPI_CMD_WREN;
+ return CardSPIWriteRead(type, &cmd, 1, NULL, 0, 0, 0);
+}
+
+int CardSPIEnableWriting_regular(CardSPIType type) {
+ u8 cmd = SPI_CMD_WREN, statusReg = 0;
+ int res = CardSPIWriteRead(type, &cmd, 1, NULL, 0, 0, 0);
+
+ if(res) return res;
+ cmd = SPI_CMD_RDSR;
+
+ do{
+ res = CardSPIWriteRead(type, &cmd, 1, &statusReg, 1, 0, 0);
+ if(res) return res;
+ } while(statusReg & ~SPI_FLG_WEL);
+
+ return 0;
+}
+
+int CardSPIEnableWriting(CardSPIType type) {
+ if(type == NO_CHIP) return 1;
+ return type->enableWriting(type);
+}
+
+int _SPIWriteTransaction(CardSPIType type, void* cmd, u32 cmdSize, const void* data, u32 dataSize) {
+ int res;
+ if( (res = CardSPIEnableWriting(type)) ) return res;
+ if( (res = CardSPIWriteRead(type, cmd, cmdSize, NULL, 0, (void*) ((u8*) data), dataSize)) ) return res;
+ return CardSPIWaitWriteEnd(type);
+}
+
+int CardSPIReadJEDECIDAndStatusReg(CardSPIType type, u32* id, u8* statusReg) {
+ u8 cmd = SPI_FLASH_CMD_RDID;
+ u8 reg = 0;
+ u8 idbuf[3] = { 0 };
+ u32 id_ = 0;
+ int res = CardSPIWaitWriteEnd(type);
+ if(res) return res;
+
+ if((res = CardSPIWriteRead(type, &cmd, 1, idbuf, 3, 0, 0))) return res;
+
+ id_ = (idbuf[0] << 16) | (idbuf[1] << 8) | idbuf[2];
+ cmd = SPI_CMD_RDSR;
+
+ if((res = CardSPIWriteRead(type, &cmd, 1, ®, 1, 0, 0))) return res;
+
+ if(id) *id = id_;
+ if(statusReg) *statusReg = reg;
+
+ return 0;
+}
+
+u32 CardSPIGetPageSize(CardSPIType type) {
+ if(type == NO_CHIP) return 0;
+ return type->pageSize;
+}
+
+u32 CardSPIGetEraseSize(CardSPIType type) {
+ if(type == NO_CHIP) return 0;
+ return type->eraseSize;
+}
+
+u32 CardSPIGetCapacity(CardSPIType type) {
+ if(type == NO_CHIP) return 0;
+ return type->capacity;
+}
+
+int CardSPIWriteSaveData_9bit(CardSPIType type, u32 offset, const void* data, u32 size) {
+ u8 cmd[2] = { (offset >= 0x100) ? SPI_512B_EEPROM_CMD_WRHI : SPI_512B_EEPROM_CMD_WRLO, (u8) offset };
+
+ return _SPIWriteTransaction(type, cmd, 2, (void*) ((u8*) data), size);
+}
+
+int CardSPIWriteSaveData_16bit(CardSPIType type, u32 offset, const void* data, u32 size) {
+ u8 cmd[3] = { type->writeCommand, (u8)(offset >> 8), (u8) offset };
+
+ return _SPIWriteTransaction(type, cmd, 3, (void*) ((u8*) data), size);
+}
+
+int CardSPIWriteSaveData_24bit_write(CardSPIType type, u32 offset, const void* data, u32 size) {
+ u8 cmd[4] = { type->writeCommand, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
+
+ return _SPIWriteTransaction(type, cmd, 4, (void*) ((u8*) data), size);
+}
+
+int CardSPIWriteSaveData_24bit_erase_program(CardSPIType type, u32 offset, const void* data, u32 size) {
+ u8 cmd[4] = { type->programCommand };
+ const u32 pageSize = CardSPIGetPageSize(type);
+ const u32 eraseSize = CardSPIGetEraseSize(type);
+ int res;
+
+ u8 *newData = NULL;
+ if(offset % eraseSize || size < eraseSize) {
+ u32 sectorStart = (offset / eraseSize) * eraseSize;
+ newData = malloc(eraseSize);
+ if(!newData) return 1;
+ if( (res = CardSPIReadSaveData(type, sectorStart, newData, eraseSize)) ) {
+ free(newData);
+ return res;
+ }
+ memcpy(newData + (offset % eraseSize), data, size);
+ data = newData;
+ offset = sectorStart;
+ }
+
+ if( (res = CardSPIEraseSector(type, offset)) ) {
+ free(newData);
+ return res;
+ }
+
+ for(u32 pos = offset; pos < offset + eraseSize; pos += pageSize) {
+ cmd[1] = (u8)(pos >> 16);
+ cmd[2] = (u8)(pos >> 8);
+ cmd[3] = (u8) pos;
+ if( (res = _SPIWriteTransaction(type, cmd, 4, (void*) ((u8*) data - offset + pos), pageSize)) ) {
+ free(newData);
+ return res;
+ }
+ }
+
+ free(newData);
+ return 0;
+}
+
+int CardSPIWriteSaveData(CardSPIType type, u32 offset, const void* data, u32 size) {
+ if(type == NO_CHIP) return 1;
+
+ if(size == 0) return 0;
+ size = min(size, CardSPIGetCapacity(type) - offset);
+ u32 end = offset + size;
+ u32 pos = offset;
+ u32 writeSize = type->writeSize;
+ if(writeSize == 0) return 0xC8E13404;
+
+ int res = CardSPIWaitWriteEnd(type);
+ if(res) return res;
+
+ while(pos < end) {
+ u32 remaining = end - pos;
+ u32 nb = writeSize - (pos % writeSize);
+
+ u32 dataSize = (remaining < nb) ? remaining : nb;
+
+ if( (res = type->writeSaveData(type, pos, (void*) ((u8*) data - offset + pos), dataSize)) ) return res;
+
+ pos = ((pos / writeSize) + 1) * writeSize; // truncate
+ }
+
+ return 0;
+}
+
+int CardSPIReadSaveData_9bit(CardSPIType type, 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 = CardSPIWriteRead(type, 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 = CardSPIWriteRead(type, cmd, cmdSize, (void*)((u8*)data + read), len, NULL, 0);
+
+ if(res) return res;
+ }
+
+ return 0;
+}
+
+int CardSPIReadSaveData_16bit(CardSPIType type, u32 offset, void* data, u32 size) {
+ u8 cmd[3] = { SPI_CMD_READ, (u8)(offset >> 8), (u8) offset };
+
+ return CardSPIWriteRead(type, cmd, 3, data, size, NULL, 0);
+}
+
+int CardSPIReadSaveData_24bit(CardSPIType type, u32 offset, void* data, u32 size) {
+ u8 cmd[4] = { SPI_CMD_READ, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
+
+ return CardSPIWriteRead(type, cmd, 4, data, size, NULL, 0);
+}
+
+int CardSPIReadSaveData(CardSPIType type, u32 offset, void* data, u32 size) {
+ if(type == NO_CHIP) return 1;
+
+ if(size == 0) return 0;
+
+ int res = CardSPIWaitWriteEnd(type);
+ if(res) return res;
+
+ size = (size <= CardSPIGetCapacity(type) - offset) ? size : CardSPIGetCapacity(type) - offset;
+
+ return type->readSaveData(type, offset, data, size);
+}
+
+int CardSPIEraseSector_emulated(CardSPIType type, u32 offset) {
+ u32 blockSize = CardSPIGetEraseSize(type);
+ u8 *fill_buf = malloc(CardSPIGetEraseSize(type));
+ if (!fill_buf) return 1;
+ memset(fill_buf, 0xff, blockSize);
+ offset = (offset / blockSize) * blockSize;
+
+ int res = CardSPIWriteSaveData(type, offset, fill_buf, blockSize);
+ free(fill_buf);
+ return res;
+}
+
+int CardSPIEraseSector_real(CardSPIType type, u32 offset) {
+ u8 cmd[4] = { type->eraseCommand, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
+
+ int res = CardSPIWaitWriteEnd(type);
+ if(res) return res;
+
+ return _SPIWriteTransaction(type, cmd, 4, NULL, 0);
+}
+
+
+int CardSPIEraseSector(CardSPIType type, u32 offset) {
+ if(type == NO_CHIP) return 1;
+ return type->eraseSector(type, offset);
+}
+
+
+// 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(CardSPIType 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 = CardSPIReadSaveData(type, offset0, &buf1, 1)) ) return res;
+ if( (res = CardSPIReadSaveData(type, offset1, &buf2, 1)) ) return res;
+ buf3=~buf1;
+ if( (res = CardSPIWriteSaveData(type, offset0, &buf3, 1)) ) return res;
+ if( (res = CardSPIReadSaveData(type, offset1, &buf4, 1)) ) return res;
+ if( (res = CardSPIWriteSaveData(type, offset0, &buf1, 1)) ) return res;
+
+ *mirrored = buf2 != buf4;
+ return 0;
+}
+
+int CardSPIGetCardSPIType(CardSPIType* type, int infrared) {
+ u8 sr = 0;
+ u32 jedec = 0;
+ u32 tries = 0;
+ CardSPIType t = (infrared == 1) ? &FLASH_INFRARED_DUMMY : &FLASH_STD_DUMMY;
+ int res;
+
+ u32 maxTries = (infrared == -1) ? 2 : 1; // note: infrared = -1 fails 1/3 of the time
+ while(tries < maxTries){
+ res = CardSPIReadJEDECIDAndStatusReg(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;
+ size_t i;
+
+ for(i = 0; i < sizeof(EEPROMTypes) / sizeof(CardSPITypeData) - 1; i++) {
+ if( (res = _SPIIsDataMirrored(t, CardSPIGetCapacity(EEPROMTypes + i), &mirrored)) ) return res;
+ if(mirrored) {
+ *type = EEPROMTypes + i;
+ return 0;
+ }
+ }
+ *type = EEPROMTypes + i;
+ return 0;
+ }
+
+ else if(t == &FLASH_INFRARED_DUMMY) {
+ size_t i;
+
+ if(infrared == 0) *type = NO_CHIP; // did anything go wrong?
+
+ for(i = 0; i < sizeof(FlashInfraredTypes) / sizeof(CardSPITypeData); i++) {
+ if(FlashInfraredTypes[i].jedecId == jedec) {
+ *type = FlashInfraredTypes + i;
+ return 0;
+ }
+ }
+
+ *type = NO_CHIP;
+ return 0;
+ }
+
+ else {
+ size_t i;
+
+ if(infrared == 1) *type = NO_CHIP; // did anything go wrong?
+
+ for(i = 0; i < sizeof(FlashStdTypes) / sizeof(CardSPITypeData); i++) {
+ if(FlashStdTypes[i].jedecId == jedec) {
+ *type = FlashStdTypes + i;
+ return 0;
+ }
+ }
+
+ *type = NO_CHIP;
+ return 0;
+ }
+}
diff --git a/arm9/source/gamecart/card_spi.h b/arm9/source/gamecart/card_spi.h
new file mode 100644
index 0000000..496f47d
--- /dev/null
+++ b/arm9/source/gamecart/card_spi.h
@@ -0,0 +1,95 @@
+/*
+ * This file is based on SPI.h from TWLSaveTool. Its copyright notice is
+ * reproduced below.
+ *
+ * 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
+ */
+
+#pragma once
+#include "common.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct CardSPITypeData CardSPITypeData;
+
+typedef const CardSPITypeData * CardSPIType;
+
+struct CardSPITypeData {
+ int (*enableWriting) (CardSPIType type);
+ int (*readSaveData) (CardSPIType type, u32 offset, void* data, u32 size);
+ int (*writeSaveData) (CardSPIType type, u32 offset, const void* data, u32 size);
+ int (*eraseSector) (CardSPIType type, u32 offset);
+ u32 jedecId;
+ u32 capacity;
+ u32 eraseSize;
+ u32 pageSize;
+ u32 writeSize;
+ bool infrared;
+ u8 writeCommand;
+ u8 programCommand;
+ u8 eraseCommand;
+};
+
+#define NO_CHIP NULL
+
+const CardSPIType EEPROM_512B;
+
+const CardSPIType EEPROM_8KB;
+const CardSPIType EEPROM_64KB;
+const CardSPIType EEPROM_128KB;
+
+const CardSPIType FLASH_256KB_1;
+const CardSPIType FLASH_256KB_2;
+const CardSPIType FLASH_512KB_1;
+const CardSPIType FLASH_512KB_2;
+const CardSPIType FLASH_1MB;
+const CardSPIType FLASH_8MB_1; // <- can't restore savegames, and maybe not read them atm
+const CardSPIType FLASH_8MB_2; // we are also unsure about the ID for this
+
+const CardSPIType FLASH_64KB_CTR; // I am extrapolating from the dataheets, only a few of these have been observed in the wild
+const CardSPIType FLASH_128KB_CTR; // Most common, including Ocarina of time 3D
+const CardSPIType FLASH_256KB_CTR;
+const CardSPIType FLASH_512KB_CTR; // Also common, including Detective Pikachu
+const CardSPIType FLASH_1MB_CTR; // For example Pokemon Ultra Sun
+const CardSPIType FLASH_2MB_CTR;
+const CardSPIType FLASH_4MB_CTR;
+const CardSPIType FLASH_8MB_CTR;
+
+const CardSPIType FLASH_256KB_1_INFRARED; // AFAIK, only "Active Health with Carol Vorderman" has such a flash save memory
+const CardSPIType FLASH_256KB_2_INFRARED;
+const CardSPIType FLASH_512KB_1_INFRARED;
+const CardSPIType FLASH_512KB_2_INFRARED;
+
+int CardSPIWriteRead(CardSPIType type, const void* cmd, u32 cmdSize, void* answer, u32 answerSize, const void* data, u32 dataSize);
+int CardSPIWaitWriteEnd(CardSPIType type);
+int CardSPIEnableWriting(CardSPIType type);
+int CardSPIReadJEDECIDAndStatusReg(CardSPIType type, u32* id, u8* statusReg);
+int CardSPIGetCardSPIType(CardSPIType* type, int infrared);
+u32 CardSPIGetPageSize(CardSPIType type);
+u32 CardSPIGetCapacity(CardSPIType type);
+u32 CardSPIGetEraseSize(CardSPIType type);
+
+int CardSPIWriteSaveData(CardSPIType type, u32 offset, const void* data, u32 size);
+int CardSPIReadSaveData(CardSPIType type, u32 offset, void* data, u32 size);
+
+int CardSPIEraseSector(CardSPIType type, u32 offset);
+int CardSPIErase(CardSPIType type);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/arm9/source/gamecart/gamecart.c b/arm9/source/gamecart/gamecart.c
index 293a650..139a810 100644
--- a/arm9/source/gamecart/gamecart.c
+++ b/arm9/source/gamecart/gamecart.c
@@ -2,8 +2,7 @@
#include "protocol.h"
#include "command_ctr.h"
#include "command_ntr.h"
-#include "card_eeprom.h"
-#include "spi.h"
+#include "card_spi.h"
#include "nds.h"
#include "ncch.h"
#include "ncsd.h"
@@ -25,7 +24,7 @@ typedef struct {
u64 cart_size;
u64 data_size;
u32 save_size;
- CardType save_type;
+ CardSPIType save_type;
u32 unused_offset;
} PACKED_ALIGN(16) CartDataCtr;
@@ -41,7 +40,7 @@ typedef struct {
u64 cart_size;
u64 data_size;
u32 save_size;
- CardType save_type;
+ CardSPIType save_type;
u32 arm9i_rom_offset;
} PACKED_ALIGN(16) CartDataNtrTwl;
@@ -101,10 +100,10 @@ u32 InitCartRead(CartData* cdata) {
// save data
u32 card2_offset = getle32(cdata->header + 0x200);
- if ((card2_offset != 0xFFFFFFFF) || (SPIGetCardType(&(cdata->save_type), 0) != 0)) {
+ if ((card2_offset != 0xFFFFFFFF) || (CardSPIGetCardSPIType(&(cdata->save_type), 0) != 0)) {
cdata->save_type = NO_CHIP;
}
- cdata->save_size = SPIGetCapacity(cdata->save_type);
+ cdata->save_size = CardSPIGetCapacity(cdata->save_type);
} else { // NTR/TWL cartridges
// NTR header
TwlHeader* nds_header = (void*)cdata->header;
@@ -136,10 +135,10 @@ u32 InitCartRead(CartData* cdata) {
// save data
u32 infrared = (*(nds_header->game_code) == 'I') ? 1 : 0;
- if (SPIGetCardType(&(cdata->save_type), infrared) != 0) {
+ if (CardSPIGetCardSPIType(&(cdata->save_type), infrared) != 0) {
cdata->save_type = NO_CHIP;
}
- cdata->save_size = SPIGetCapacity(cdata->save_type);
+ cdata->save_size = CardSPIGetCapacity(cdata->save_type);
}
return 0;
}
@@ -244,13 +243,13 @@ u32 ReadCartPrivateHeader(void* buffer, u64 offset, u64 count, CartData* cdata)
u32 ReadCartSave(u8* buffer, u64 offset, u64 count, CartData* cdata) {
if (offset >= cdata->save_size) return 1;
if (offset + count > cdata->save_size) count = cdata->save_size - offset;
- return (SPIReadSaveData((CardType) cdata->save_type, offset, buffer, count) == 0) ? 0 : 1;
+ return (CardSPIReadSaveData((CardSPIType) cdata->save_type, offset, buffer, count) == 0) ? 0 : 1;
}
u32 WriteCartSave(const u8* buffer, u64 offset, u64 count, CartData* cdata) {
if (offset >= cdata->save_size) return 1;
if (offset + count > cdata->save_size) count = cdata->save_size - offset;
- return (SPIWriteSaveData((CardType) cdata->save_type, offset, buffer, count) == 0) ? 0 : 1;
+ return (CardSPIWriteSaveData((CardSPIType) cdata->save_type, offset, buffer, count) == 0) ? 0 : 1;
}
u32 ReadCartSaveJedecId(u8* buffer, u64 offset, u64 count, CartData* cdata) {
@@ -259,7 +258,7 @@ u32 ReadCartSaveJedecId(u8* buffer, u64 offset, u64 count, CartData* cdata) {
u8 sReg;
if (offset >= JEDECID_AND_SREG_SIZE) return 1;
if (offset + count > JEDECID_AND_SREG_SIZE) count = JEDECID_AND_SREG_SIZE - offset;
- SPIReadJEDECIDAndStatusReg((CardType) cdata->save_type, &id, &sReg);
+ CardSPIReadJEDECIDAndStatusReg((CardSPIType) cdata->save_type, &id, &sReg);
ownBuf[0] = (id >> 16) & 0xff;
ownBuf[1] = (id >> 8) & 0xff;
ownBuf[2] = id & 0xff;
diff --git a/arm9/source/gamecart/gamecart.h b/arm9/source/gamecart/gamecart.h
index 9942e0e..5569c6a 100644
--- a/arm9/source/gamecart/gamecart.h
+++ b/arm9/source/gamecart/gamecart.h
@@ -1,7 +1,7 @@
#pragma once
#include "common.h"
-#include "spi.h"
+#include "card_spi.h"
#define CART_NONE 0
#define CART_CTR (1<<0)
@@ -20,7 +20,7 @@ typedef struct {
u64 cart_size;
u64 data_size;
u32 save_size;
- CardType save_type;
+ CardSPIType save_type;
u32 arm9i_rom_offset; // TWL specific
} PACKED_ALIGN(16) CartData;
diff --git a/arm9/source/gamecart/spi.c b/arm9/source/gamecart/spi.c
deleted file mode 100644
index b7c14b9..0000000
--- a/arm9/source/gamecart/spi.c
+++ /dev/null
@@ -1,500 +0,0 @@
-/*
- * This file is based on SPI.cpp from TWLSaveTool. Its copyright notice is
- * reproduced below.
- *
- * 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
- */
-
-#include "spi.h"
-#include
-#include "timer.h"
-
-// declarations for actual implementations
-int SPIEnableWriting_512B(CardType type);
-int SPIEnableWriting_regular(CardType type);
-int SPIReadSaveData_9bit(CardType type, u32 offset, void* data, u32 size);
-int SPIReadSaveData_16bit(CardType type, u32 offset, void* data, u32 size);
-int SPIReadSaveData_24bit(CardType type, u32 offset, void* data, u32 size);
-int SPIWriteSaveData_9bit(CardType type, u32 offset, const void* data, u32 size);
-int SPIWriteSaveData_16bit(CardType type, u32 offset, const void* data, u32 size);
-int SPIWriteSaveData_24bit_write(CardType type, u32 offset, const void* data, u32 size);
-int SPIWriteSaveData_24bit_erase_program(CardType type, u32 offset, const void* data, u32 size);
-int SPIEraseSector_emulated(CardType type, u32 offset);
-int SPIEraseSector_real(CardType type, u32 offset);
-
-const CardTypeData EEPROM_512B_ = { SPIEnableWriting_512B, SPIReadSaveData_9bit, SPIWriteSaveData_9bit, SPIEraseSector_emulated, 0xffffff, 1 << 9, 16, 16, 16, false, 0, 0, 0 };
-
-const CardTypeData EEPROM_STD_DUMMY = { SPIEnableWriting_regular, SPIReadSaveData_16bit, SPIWriteSaveData_16bit, SPIEraseSector_emulated, 0xffffff, UINT32_MAX, 1, 1, 1, false, SPI_EEPROM_CMD_WRITE, 0, 0 };
-const CardTypeData EEPROMTypes[] = {
- { SPIEnableWriting_regular, SPIReadSaveData_16bit, SPIWriteSaveData_16bit, SPIEraseSector_emulated, 0xffffff, 1 << 13, 32, 32, 32, false, SPI_EEPROM_CMD_WRITE, 0, 0}, // EEPROM 8 KB
- { SPIEnableWriting_regular, SPIReadSaveData_16bit, SPIWriteSaveData_16bit, SPIEraseSector_emulated, 0xffffff, 1 << 16, 128, 128, 128, false, SPI_EEPROM_CMD_WRITE, 0, 0}, // EEPROM 64 KB
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_write, SPIEraseSector_emulated, 0xffffff, 1 << 17, 256, 256, 256, false, SPI_EEPROM_CMD_WRITE, 0, 0}, // EEPROM 128 KB
-};
-
-const CardTypeData FLASH_STD_DUMMY = { NULL, SPIReadSaveData_24bit, NULL, NULL, 0x0, 0, 0, 0, 0, false, 0, 0, 0 };
-const CardTypeData FlashStdTypes[] = {
- // NTR/TWL
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_write, SPIEraseSector_real, 0x204012, 1 << 18, 65536, 256, 256, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0x621600, 1 << 18, 65536, 256, 65536, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_write, SPIEraseSector_real, 0x204013, 1 << 19, 65536, 256, 256, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_write, SPIEraseSector_real, 0x621100, 1 << 19, 65536, 256, 256, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_write, SPIEraseSector_real, 0x204014, 1 << 20, 65536, 256, 256, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
- // Untested (but pretty safe bet), for Art Academy
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0x202017, 1 << 23, 65536, 32, 65536, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0x204017, 1 << 23, 65536, 32, 65536, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
- // CTR
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0xC22210, 1 << 16, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0xC22211, 1 << 17, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0xC22212, 1 << 18, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0xC22213, 1 << 19, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0xC22214, 1 << 20, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0xC22215, 1 << 21, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0xC22216, 1 << 22, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0xC22217, 1 << 23, 4096, 32, 4096, false, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_MXIC_SE },
-};
-
-const CardTypeData FLASH_INFRARED_DUMMY = { NULL, SPIReadSaveData_24bit, NULL, NULL, 0x0, 0, 0, 0, 0, true, 0, 0, 0 };
-const CardTypeData FlashInfraredTypes[] = {
- // NTR/TWL
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_write, SPIEraseSector_real, 0x204012, 1 << 18, 65536, 256, 256, true, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_erase_program, SPIEraseSector_real, 0x621600, 1 << 18, 65536, 256, 65536, true, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_write, SPIEraseSector_real, 0x204013, 1 << 19, 65536, 256, 256, true, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
- { SPIEnableWriting_regular, SPIReadSaveData_24bit, SPIWriteSaveData_24bit_write, SPIEraseSector_real, 0x621100, 1 << 19, 65536, 256, 256, true, SPI_FLASH_CMD_PW, SPI_CMD_PP, SPI_FLASH_CMD_SE },
-};
-
-const CardType EEPROM_512B = &EEPROM_512B_;
-
-const CardType EEPROM_8KB = EEPROMTypes + 0;
-const CardType EEPROM_64KB = EEPROMTypes + 1;
-const CardType EEPROM_128KB = EEPROMTypes + 2;
-
-const CardType FLASH_256KB_1 = FlashStdTypes + 0;
-const CardType FLASH_256KB_2 = FlashStdTypes + 1;
-const CardType FLASH_512KB_1 = FlashStdTypes + 2;
-const CardType FLASH_512KB_2 = FlashStdTypes + 3;
-const CardType FLASH_1MB = FlashStdTypes + 4;
-const CardType FLASH_8MB_1 = FlashStdTypes + 5;
-const CardType FLASH_8MB_2 = FlashStdTypes + 6;
-
-const CardType FLASH_64KB_CTR = FlashStdTypes + 7;
-const CardType FLASH_128KB_CTR = FlashStdTypes + 8;
-const CardType FLASH_256KB_CTR = FlashStdTypes + 9;
-const CardType FLASH_512KB_CTR = FlashStdTypes + 10;
-const CardType FLASH_1MB_CTR = FlashStdTypes + 11;
-const CardType FLASH_2MB_CTR = FlashStdTypes + 12;
-const CardType FLASH_4MB_CTR = FlashStdTypes + 13;
-const CardType FLASH_8MB_CTR = FlashStdTypes + 14;
-
-const CardType FLASH_256KB_1_INFRARED = FlashInfraredTypes + 0;
-const CardType FLASH_256KB_2_INFRARED = FlashInfraredTypes + 1;
-const CardType FLASH_512KB_1_INFRARED = FlashInfraredTypes + 2;
-const CardType FLASH_512KB_2_INFRARED = FlashInfraredTypes + 3;
-
-#define REG_CFG9_CARDCTL *((vu16*)0x1000000C)
-#define CARDCTL_SPICARD (1u<<8)
-
-int SPIWriteRead(CardType type, const void* cmd, u32 cmdSize, void* answer, u32 answerSize, const void* data, u32 dataSize) {
- u32 headerFooterVal = 0;
-
- REG_CFG9_CARDCTL |= CARDCTL_SPICARD;
-
- if (type->infrared) {
- SPI_XferInfo irXfer = { &headerFooterVal, 1, false };
- SPI_DoXfer(SPI_DEV_CART_IR, &irXfer, 1, false);
- }
-
- SPI_XferInfo transfers[3] = {
- { (u8*) cmd, cmdSize, false },
- { answer, answerSize, true },
- { (u8*) data, dataSize, false },
- };
- SPI_DoXfer(SPI_DEV_CART_FLASH, transfers, 3, true);
-
- REG_CFG9_CARDCTL &= ~CARDCTL_SPICARD;
-
- 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_512B(CardType type) {
- u8 cmd = SPI_CMD_WREN;
- return SPIWriteRead(type, &cmd, 1, NULL, 0, 0, 0);
-}
-
-int SPIEnableWriting_regular(CardType type) {
- u8 cmd = SPI_CMD_WREN, statusReg = 0;
- int res = SPIWriteRead(type, &cmd, 1, NULL, 0, 0, 0);
-
- if(res) return res;
- 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 SPIEnableWriting(CardType type) {
- if(type == NO_CHIP) return 1;
- return type->enableWriting(type);
-}
-
-int _SPIWriteTransaction(CardType type, void* cmd, u32 cmdSize, const void* data, u32 dataSize) {
- int res;
- if( (res = SPIEnableWriting(type)) ) return res;
- if( (res = SPIWriteRead(type, cmd, cmdSize, NULL, 0, (void*) ((u8*) data), dataSize)) ) return res;
- return SPIWaitWriteEnd(type);
-}
-
-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, ®, 1, 0, 0))) return res;
-
- if(id) *id = id_;
- if(statusReg) *statusReg = reg;
-
- return 0;
-}
-
-u32 SPIGetPageSize(CardType type) {
- if(type == NO_CHIP) return 0;
- return type->pageSize;
-}
-
-u32 SPIGetEraseSize(CardType type) {
- if(type == NO_CHIP) return 0;
- return type->eraseSize;
-}
-
-u32 SPIGetCapacity(CardType type) {
- if(type == NO_CHIP) return 0;
- return type->capacity;
-}
-
-int SPIWriteSaveData_9bit(CardType type, u32 offset, const void* data, u32 size) {
- u8 cmd[2] = { (offset >= 0x100) ? SPI_512B_EEPROM_CMD_WRHI : SPI_512B_EEPROM_CMD_WRLO, (u8) offset };
-
- return _SPIWriteTransaction(type, cmd, 2, (void*) ((u8*) data), size);
-}
-
-int SPIWriteSaveData_16bit(CardType type, u32 offset, const void* data, u32 size) {
- u8 cmd[3] = { type->writeCommand, (u8)(offset >> 8), (u8) offset };
-
- return _SPIWriteTransaction(type, cmd, 3, (void*) ((u8*) data), size);
-}
-
-int SPIWriteSaveData_24bit_write(CardType type, u32 offset, const void* data, u32 size) {
- u8 cmd[4] = { type->writeCommand, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
-
- return _SPIWriteTransaction(type, cmd, 4, (void*) ((u8*) data), size);
-}
-
-int SPIWriteSaveData_24bit_erase_program(CardType type, u32 offset, const void* data, u32 size) {
- u8 cmd[4] = { type->programCommand };
- const u32 pageSize = SPIGetPageSize(type);
- const u32 eraseSize = SPIGetEraseSize(type);
- int res;
-
- u8 *newData = NULL;
- if(offset % eraseSize || size < eraseSize) {
- u32 sectorStart = (offset / eraseSize) * eraseSize;
- newData = malloc(eraseSize);
- if(!newData) return 1;
- if( (res = SPIReadSaveData(type, sectorStart, newData, eraseSize)) ) {
- free(newData);
- return res;
- }
- memcpy(newData + (offset % eraseSize), data, size);
- data = newData;
- offset = sectorStart;
- }
-
- if( (res = SPIEraseSector(type, offset)) ) {
- free(newData);
- return res;
- }
-
- for(u32 pos = offset; pos < offset + eraseSize; pos += pageSize) {
- cmd[1] = (u8)(pos >> 16);
- cmd[2] = (u8)(pos >> 8);
- cmd[3] = (u8) pos;
- if( (res = _SPIWriteTransaction(type, cmd, 4, (void*) ((u8*) data - offset + pos), pageSize)) ) {
- free(newData);
- return res;
- }
- }
-
- free(newData);
- return 0;
-}
-
-int SPIWriteSaveData(CardType type, u32 offset, const void* data, u32 size) {
- if(type == NO_CHIP) return 1;
-
- if(size == 0) return 0;
- size = min(size, SPIGetCapacity(type) - offset);
- u32 end = offset + size;
- u32 pos = offset;
- u32 writeSize = type->writeSize;
- if(writeSize == 0) return 0xC8E13404;
-
- int res = SPIWaitWriteEnd(type);
- if(res) return res;
-
- while(pos < end) {
- u32 remaining = end - pos;
- u32 nb = writeSize - (pos % writeSize);
-
- u32 dataSize = (remaining < nb) ? remaining : nb;
-
- if( (res = type->writeSaveData(type, pos, (void*) ((u8*) data - offset + pos), dataSize)) ) return res;
-
- pos = ((pos / writeSize) + 1) * writeSize; // truncate
- }
-
- return 0;
-}
-
-int SPIReadSaveData_9bit(CardType type, 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(type, 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(type, cmd, cmdSize, (void*)((u8*)data + read), len, NULL, 0);
-
- if(res) return res;
- }
-
- return 0;
-}
-
-int SPIReadSaveData_16bit(CardType type, u32 offset, void* data, u32 size) {
- u8 cmd[3] = { SPI_CMD_READ, (u8)(offset >> 8), (u8) offset };
-
- return SPIWriteRead(type, cmd, 3, data, size, NULL, 0);
-}
-
-int SPIReadSaveData_24bit(CardType type, u32 offset, void* data, u32 size) {
- u8 cmd[4] = { SPI_CMD_READ, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
-
- return SPIWriteRead(type, cmd, 4, data, size, NULL, 0);
-}
-
-int SPIReadSaveData(CardType type, u32 offset, void* data, u32 size) {
- if(type == NO_CHIP) return 1;
-
- if(size == 0) return 0;
-
- int res = SPIWaitWriteEnd(type);
- if(res) return res;
-
- size = (size <= SPIGetCapacity(type) - offset) ? size : SPIGetCapacity(type) - offset;
-
- return type->readSaveData(type, offset, data, size);
-}
-
-int SPIEraseSector_emulated(CardType type, u32 offset) {
- u32 blockSize = SPIGetEraseSize(type);
- u8 *fill_buf = malloc(SPIGetEraseSize(type));
- if (!fill_buf) return 1;
- memset(fill_buf, 0xff, blockSize);
- offset = (offset / blockSize) * blockSize;
-
- int res = SPIWriteSaveData(type, offset, fill_buf, blockSize);
- free(fill_buf);
- return res;
-}
-
-int SPIEraseSector_real(CardType type, u32 offset) {
- u8 cmd[4] = { type->eraseCommand, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
-
- int res = SPIWaitWriteEnd(type);
- if(res) return res;
-
- return _SPIWriteTransaction(type, cmd, 4, NULL, 0);
-}
-
-
-int SPIEraseSector(CardType type, u32 offset) {
- if(type == NO_CHIP) return 1;
- return type->eraseSector(type, offset);
-}
-
-
-// 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 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;
- size_t i;
-
- for(i = 0; i < sizeof(EEPROMTypes) / sizeof(CardTypeData) - 1; i++) {
- if( (res = _SPIIsDataMirrored(t, SPIGetCapacity(EEPROMTypes + i), &mirrored)) ) return res;
- if(mirrored) {
- *type = EEPROMTypes + i;
- return 0;
- }
- }
- *type = EEPROMTypes + i;
- return 0;
- }
-
- else if(t == &FLASH_INFRARED_DUMMY) {
- size_t i;
-
- if(infrared == 0) *type = NO_CHIP; // did anything go wrong?
-
- for(i = 0; i < sizeof(FlashInfraredTypes) / sizeof(CardTypeData); i++) {
- if(FlashInfraredTypes[i].jedecId == jedec) {
- *type = FlashInfraredTypes + i;
- return 0;
- }
- }
-
- *type = NO_CHIP;
- return 0;
- }
-
- else {
- size_t i;
-
- if(infrared == 1) *type = NO_CHIP; // did anything go wrong?
-
- for(i = 0; i < sizeof(FlashStdTypes) / sizeof(CardTypeData); i++) {
- if(FlashStdTypes[i].jedecId == jedec) {
- *type = FlashStdTypes + i;
- return 0;
- }
- }
-
- *type = NO_CHIP;
- return 0;
- }
-}
diff --git a/arm9/source/gamecart/spi.h b/arm9/source/gamecart/spi.h
deleted file mode 100644
index cfc7e91..0000000
--- a/arm9/source/gamecart/spi.h
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * This file is based on SPI.h from TWLSaveTool. Its copyright notice is
- * reproduced below.
- *
- * 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
- */
-
-#pragma once
-#include "common.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define SPI_CMD_RDSR 5
-#define SPI_CMD_WREN 6
-
-#define SPI_512B_EEPROM_CMD_WRLO 2
-#define SPI_512B_EEPROM_CMD_WRHI 10
-#define SPI_512B_EEPROM_CMD_RDLO 3
-#define SPI_512B_EEPROM_CMD_RDHI 11
-
-#define SPI_EEPROM_CMD_WRITE 2
-
-#define SPI_CMD_READ 3
-
-#define SPI_CMD_PP 2
-#define SPI_FLASH_CMD_PW 10
-#define SPI_FLASH_CMD_RDID 0x9f
-#define SPI_FLASH_CMD_SE 0xd8
-#define SPI_FLASH_CMD_PE 0xdb
-#define SPI_FLASH_CMD_MXIC_SE 0x20
-
-#define SPI_FLG_WIP 1
-#define SPI_FLG_WEL 2
-
-typedef struct CardTypeData CardTypeData;
-
-typedef const CardTypeData * CardType;
-
-struct CardTypeData {
- int (*enableWriting) (CardType type);
- int (*readSaveData) (CardType type, u32 offset, void* data, u32 size);
- int (*writeSaveData) (CardType type, u32 offset, const void* data, u32 size);
- int (*eraseSector) (CardType type, u32 offset);
- u32 jedecId;
- u32 capacity;
- u32 eraseSize;
- u32 pageSize;
- u32 writeSize;
- bool infrared;
- u8 writeCommand;
- u8 programCommand;
- u8 eraseCommand;
-};
-
-#define NO_CHIP NULL
-
-const CardType EEPROM_512B;
-
-const CardType EEPROM_8KB;
-const CardType EEPROM_64KB;
-const CardType EEPROM_128KB;
-
-const CardType FLASH_256KB_1;
-const CardType FLASH_256KB_2;
-const CardType FLASH_512KB_1;
-const CardType FLASH_512KB_2;
-const CardType FLASH_1MB;
-const CardType FLASH_8MB_1; // <- can't restore savegames, and maybe not read them atm
-const CardType FLASH_8MB_2; // we are also unsure about the ID for this
-
-const CardType FLASH_64KB_CTR; // I am extrapolating from the dataheets, only a few of these have been observed in the wild
-const CardType FLASH_128KB_CTR; // Most common, including Ocarina of time 3D
-const CardType FLASH_256KB_CTR;
-const CardType FLASH_512KB_CTR; // Also common, including Detective Pikachu
-const CardType FLASH_1MB_CTR; // For example Pokemon Ultra Sun
-const CardType FLASH_2MB_CTR;
-const CardType FLASH_4MB_CTR;
-const CardType FLASH_8MB_CTR;
-
-const CardType FLASH_256KB_1_INFRARED; // AFAIK, only "Active Health with Carol Vorderman" has such a flash save memory
-const CardType FLASH_256KB_2_INFRARED;
-const CardType FLASH_512KB_1_INFRARED;
-const CardType FLASH_512KB_2_INFRARED;
-
-int SPIWriteRead(CardType type, const void* cmd, u32 cmdSize, void* answer, u32 answerSize, const void* data, u32 dataSize);
-int SPIWaitWriteEnd(CardType type);
-int SPIEnableWriting(CardType type);
-int SPIReadJEDECIDAndStatusReg(CardType type, u32* id, u8* statusReg);
-int SPIGetCardType(CardType* type, int infrared);
-u32 SPIGetPageSize(CardType type);
-u32 SPIGetCapacity(CardType type);
-u32 SPIGetEraseSize(CardType type);
-
-int SPIWriteSaveData(CardType type, u32 offset, const void* data, u32 size);
-int SPIReadSaveData(CardType type, u32 offset, void* data, u32 size);
-
-int SPIEraseSector(CardType type, u32 offset);
-int SPIErase(CardType type);
-
-#ifdef __cplusplus
-}
-#endif
diff --git a/arm9/source/gamecart/spi_test.c b/arm9/source/gamecart/spi_test.c
deleted file mode 100644
index 2e7a425..0000000
--- a/arm9/source/gamecart/spi_test.c
+++ /dev/null
@@ -1,116 +0,0 @@
-#include "spi_test.h"
-#include "timer.h"
-#include "ui.h"
-#include "spi.h"
-
-void SPITestWaitWriteEnd(CardType type) {
- while (SPIWaitWriteEnd(type));
-}
-
-int SPITestEraseSector(CardType type, u32 offset, u8 actualCmd) {
- u8 cmd[4] = { actualCmd, (u8)(offset >> 16), (u8)(offset >> 8), (u8) offset };
- if(type == NO_CHIP) return 0xC8E13404;
-
- int res = SPIWaitWriteEnd(type);
-
- if( (res = SPIEnableWriting(type)) ) return res;
- if( (res = SPIWriteRead(type, cmd, 4, NULL, 0, NULL, 0)) ) return res;
- SPITestWaitWriteEnd(type);
- return 0;
-}
-
-u64 SPITestEraseAll(CardType type, u32 size, u8 actualCmd, u32 eraseSize) {
- u32 offset;
- u64 timer = timer_start();
- for(offset = 0; offset < size; offset += eraseSize) {
- SPITestEraseSector(type, offset, actualCmd);
- }
- return timer_ticks(timer);
-}
-
-u64 SPITestWriteAll(CardType type, u32 size, u8 actualCmd, u32 pageSize, u8 *buf) {
- memset(buf, 0, size);
- u8 cmd[4] = { actualCmd, 0, 0, 0 };
- u32 offset = 0;
- u64 timer = timer_start();
- for(offset = 0; offset < size; offset += pageSize) {
- cmd[1] = (u8)(offset >> 16);
- cmd[2] = (u8)(offset >> 8);
- cmd[3] = (u8) offset;
- SPIEnableWriting(type);
- SPIWriteRead(type, cmd, 4, NULL, 0, buf, pageSize);
- SPIWaitWriteEnd(type);
- }
- return timer_ticks(timer);
-}
-
-int SPITestBytes(CardType t, u32 id, u8 *buf, u8 byte, const char *word, u32 size) {
- u32 offset;
- SPIReadSaveData(t, 0, buf, size);
- for(offset = 0; offset < size; offset++) {
- if(buf[offset] != byte) {
- if(word && !ShowPrompt(true, "ID: 0x%06lX\n1: Could not %s\n*0x%06lX = 0x%02hhX", id, word, offset, buf[offset])) return -1;
- return 1;
- }
- }
- return 0;
-}
-
-void SPIFlashTest(void) {
- CardType t = ShowPrompt(true, "Does the cart have IR?") ? FLASH_512KB_1_INFRARED : FLASH_256KB_1;
- u32 size;
- u32 pageSize;
- u32 jedecid;
- if(SPIReadJEDECIDAndStatusReg(t, &jedecid, NULL)) return;
- if(!ShowPrompt(true, "ID: 0x%06lX\nDo flash test on this cart?\nThis will overwrite it completely\nseveral times!", jedecid)) return;
- size = ShowHexPrompt(0, 8, "Memory size?");
- u8 *buf = calloc(size, 1);
- if (buf == NULL) {
- ShowPrompt(false, "Malloc failed!");
- return;
- }
- pageSize = ShowHexPrompt(0, 8, "Page size?");
- // method 1:
- u64 time1 = SPITestWriteAll(t, size, 0x0A, pageSize, buf);
- int result1 = SPITestBytes(t, jedecid, buf, 0x00, "write", size);
- if(result1 < 0) {
- free(buf);
- return;
- }
- ShowPrompt(false, "ID: 0x%06lX\nWriting all took %llu\nError: %d", jedecid, time1, result1);
- // method 2: Erasing
- u8 eraseCommand;
- u32 eraseSize;
- for(eraseCommand = 0xC0; eraseCommand < 0xE0; eraseCommand++) {
- ShowString("Testing %hhX", eraseCommand);
- SPITestEraseSector(t, 0, eraseCommand);
- SPIReadSaveData(t, 0, buf, size);
- for(eraseSize = 0; eraseSize < size && buf[eraseSize] == 0xff; eraseSize++);
- if(eraseSize) {
- SPITestWriteAll(t, eraseSize, 0x02, pageSize, buf);
- if(SPITestBytes(t, jedecid, buf, 0x00, "reset", size) < 0) {
- free(buf);
- return;
- }
-
- if(ShowPrompt(true, "ID: 0x%06lX\n0x%02hhX erased %lu bytes\nTest?", jedecid, eraseCommand, eraseSize)) {
- u64 eraseTime = SPITestEraseAll(t, size, eraseCommand, eraseSize);
- int eraseResult = SPITestBytes(t, jedecid, buf, 0xFF, "erase", size);
- if(eraseResult < 0) {
- free(buf);
- return;
- }
- u64 writeTime = SPITestWriteAll(t, size, 0x02, pageSize, buf);
- int writeResult = SPITestBytes(t, jedecid, buf, 0x00, "write", size);
- if(eraseResult < 0) {
- free(buf);
- return;
- }
- ShowPrompt(false, "ID: 0x%06lX\n0x%02hhX erases %lu bytes\nErase: %llu (%d)\nWrite: %llu (%d)\nTotal: %llu", jedecid, eraseCommand, eraseSize, eraseTime, eraseResult, writeTime, writeResult, eraseTime + writeTime);
- }
- }
- }
- free(buf);
-
-}
-
diff --git a/arm9/source/gamecart/spi_test.h b/arm9/source/gamecart/spi_test.h
deleted file mode 100644
index 5fc89dc..0000000
--- a/arm9/source/gamecart/spi_test.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#pragma once
-#include "common.h"
-#include "spi.h"
-
-void SPIFlashTest(void);