From 01bc082ca0b0b5f7ef78c722a6b269452ee19223 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Fri, 28 Apr 2017 01:24:36 +0200 Subject: [PATCH] Initial support of cartridge savegame reads --- arm9/source/gamecart/gamecart.c | 37 ++- arm9/source/gamecart/gamecart.h | 6 +- arm9/source/gamecart/spi.c | 453 ++++++++++++++++++++++++++++++++ arm9/source/gamecart/spi.h | 89 +++++++ arm9/source/virtual/vcart.c | 12 +- 5 files changed, 591 insertions(+), 6 deletions(-) create mode 100644 arm9/source/gamecart/spi.c create mode 100644 arm9/source/gamecart/spi.h diff --git a/arm9/source/gamecart/gamecart.c b/arm9/source/gamecart/gamecart.c index 4afb2d1..e35a0bb 100644 --- a/arm9/source/gamecart/gamecart.c +++ b/arm9/source/gamecart/gamecart.c @@ -3,6 +3,7 @@ #include "command_ctr.h" #include "command_ntr.h" #include "card_eeprom.h" +#include "spi.h" #include "nds.h" #include "ncch.h" #include "ncsd.h" @@ -23,6 +24,8 @@ typedef struct { u32 cart_id; u64 cart_size; u64 data_size; + u32 save_size; + int save_type; u32 unused_offset; } PACKED_ALIGN(16) CartDataCtr; @@ -37,6 +40,8 @@ typedef struct { u32 cart_id; u64 cart_size; u64 data_size; + u32 save_size; + int save_type; u32 arm9i_rom_offset; } PACKED_ALIGN(16) CartDataNtrTwl; @@ -55,14 +60,14 @@ u32 GetCartName(char* name, CartData* cdata) { return 0; } -u32 InitCardRead(CartData* cdata) { +u32 InitCartRead(CartData* cdata) { memset(cdata, 0x00, sizeof(CartData)); cdata->cart_type = CART_NONE; if (!CART_INSERTED) return 1; Cart_Init(); cdata->cart_id = Cart_GetID(); cdata->cart_type = (cdata->cart_id & 0x10000000) ? CART_CTR : CART_NTR; - if (cdata->cart_type & CART_CTR) { + if (cdata->cart_type & CART_CTR) { // CTR cartridges memset(cdata, 0xFF, 0x4000 + PRIV_HDR_SIZE); // switch the padding to 0xFF // init, NCCH header @@ -93,7 +98,14 @@ u32 InitCardRead(CartData* cdata) { memcpy(priv_header + 0x40, &(cdata->cart_id), 4); memset(priv_header + 0x44, 0x00, 4); memset(priv_header + 0x48, 0xFF, 8); - } else { + + // save data + u32 card2_offset = getle32(cdata->header + 0x200); + if ((card2_offset != 0xFFFFFFFF) || (SPIGetCardType((CardType*) (CardType*) &(cdata->save_type), 0) != 0) || (cdata->save_type < 0)) { + cdata->save_type = -1; + cdata->save_size = 0; + } else cdata->save_size = SPIGetCapacity((CardType) cdata->save_type); + } else { // NTR/TWL cartridges // NTR header TwlHeader* nds_header = (void*)cdata->header; NTR_CmdReadHeader(cdata->header); @@ -121,6 +133,13 @@ u32 InitCardRead(CartData* cdata) { // last safety check if (cdata->data_size > cdata->cart_size) return 1; + + // save data + u32 infrared = (*(nds_header->game_code) == 'I') ? 1 : 0; + if ((SPIGetCardType((CardType*) &(cdata->save_type), infrared) != 0) || (cdata->save_type < 0)) { + cdata->save_type = -1; + cdata->save_size = 0; + } else cdata->save_size = SPIGetCapacity((CardType) cdata->save_type); } return 0; } @@ -221,3 +240,15 @@ u32 ReadCartPrivateHeader(void* buffer, u64 offset, u64 count, CartData* cdata) } return 0; } + +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; +} + +u32 WriteCartSave(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; +} diff --git a/arm9/source/gamecart/gamecart.h b/arm9/source/gamecart/gamecart.h index f2baac3..dedb461 100644 --- a/arm9/source/gamecart/gamecart.h +++ b/arm9/source/gamecart/gamecart.h @@ -17,11 +17,15 @@ typedef struct { u32 cart_id; u64 cart_size; u64 data_size; + u32 save_size; + int save_type; u32 arm9i_rom_offset; // TWL specific } PACKED_ALIGN(16) CartData; u32 GetCartName(char* name, CartData* cdata); -u32 InitCardRead(CartData* cdata); +u32 InitCartRead(CartData* cdata); u32 ReadCartSectors(void* buffer, u32 sector, u32 count, CartData* cdata); u32 ReadCartBytes(void* buffer, u64 offset, u64 count, CartData* cdata); u32 ReadCartPrivateHeader(void* buffer, u64 offset, u64 count, CartData* cdata); +u32 ReadCartSave(u8* buffer, u64 offset, u64 count, CartData* cdata); +u32 WriteCartSave(u8* buffer, u64 offset, u64 count, CartData* cdata); diff --git a/arm9/source/gamecart/spi.c b/arm9/source/gamecart/spi.c new file mode 100644 index 0000000..e66db56 --- /dev/null +++ b/arm9/source/gamecart/spi.c @@ -0,0 +1,453 @@ +/* + * 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 + */ + +#include "spi.h" + +// Deliberately written in C! (except for a few lines) + +// u8* fill_buf = NULL; + +#define CFG_CARDCONF *(vu16 *)0x1000000C + +#define REG_SPICARDCNT *(vu32 *)0x1000D800 +#define REG_SPICARDSIZE *(vu32 *)0x1000D808 +#define REG_SPICARDFIFO *(vu32 *)0x1000D80C +#define REG_SPICARDFIFOSTAT *(vu32 *)0x1000D810 +#define REG_UNK_AT_0x18 *(vu32 *)0x1000D818 + +#define SPICARD_START_IS_BUSY 0x8000 + +//Thanks @Steveice10 for giving me his P9 symbols (IDB file). +//This code is compatible with SPI.c/SPI.h from TWLSaveTool/// Card SPI baud rate. + +//taken over from ctrulib fs.h +typedef enum { + BAUDRATE_512KHZ = 0, ///< 512KHz. + BAUDRATE_1MHZ = 1, ///< 1MHz. + BAUDRATE_2MHZ = 2, ///< 2MHz. + BAUDRATE_4MHZ = 3, ///< 4MHz. + BAUDRATE_8MHZ = 4, ///< 8MHz. + BAUDRATE_16MHZ = 5, ///< 16MHz. +} FS_CardSpiBaudRate; + + +void _SPITransferData(void *data, u32 len, FS_CardSpiBaudRate baudRate, bool write) +{ + REG_SPICARDCNT = (((write) ? 1 : 0) << 13) | (1 << 12) | (u32)baudRate; + REG_UNK_AT_0x18 = 6; + REG_SPICARDCNT |= SPICARD_START_IS_BUSY; //start + + u32 wordCount = (len + 3) >> 2; + + for(u32 i = 0; i < wordCount; i++) + { + u32 nbBytes = (len <= 4) ? len : 4; + + if(write) + { + u32 word = 0; + memcpy(&word, (u32 *)data + i, nbBytes); + while(!(REG_SPICARDFIFOSTAT & 1)); + REG_SPICARDFIFOSTAT = word; + } + + else + { + while(!(REG_SPICARDFIFOSTAT & 1)); + u32 word = REG_SPICARDFIFOSTAT; + memcpy((u32 *)data + i, &word, nbBytes); + } + + while(REG_SPICARDCNT & SPICARD_START_IS_BUSY); + len -= nbBytes; + } +} + +int SPIWriteRead(CardType type, void* cmd, u32 cmdSize, void* answer, u32 answerSize, void* data, u32 dataSize) +{ + bool infra = type == FLASH_512KB_INFRARED || type == FLASH_256KB_INFRARED; + + CFG_CARDCONF |= 0x100; //wake card + + u32 zero = 0; + if(infra) _SPITransferData(&zero, 1, BAUDRATE_1MHZ, true); //header + + if(cmd != NULL) _SPITransferData(cmd, cmdSize, BAUDRATE_4MHZ, true); + if(answer != NULL) _SPITransferData(answer, answerSize, BAUDRATE_4MHZ, false); + if(data != NULL) _SPITransferData(data, dataSize, BAUDRATE_4MHZ, true); + + return 0; +} + +int SPIWaitWriteEnd(CardType type) { + u8 cmd = SPI_CMD_RDSR, statusReg = 0; + int res = 0; + + do{ + res = SPIWriteRead(type, &cmd, 1, &statusReg, 1, 0, 0); + if(res) return res; + } 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, ®, 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 return 256; +} + +u32 SPIGetCapacity(CardType type) { + u32 sz[] = { 9, 13, 16, 17, 18, 18, 19, 19, 20, 23, 19, 19 }; + + if(type == NO_CHIP || type > CHIP_LAST) return 0; + else return 1 << sz[(int) type]; +} + +int SPIWriteSaveData(CardType type, u32 offset, 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 0; // 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: + 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}; + + 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!) + + int i; + + for(i = 0; i < 6; ++i) { + if(jedec == jedecOrderedList[i]) { *type = (CardType)((int) FLASH_256KB_1 + i); return 0; } + } + + *type = NO_CHIP; + return 0; + } +} diff --git a/arm9/source/gamecart/spi.h b/arm9/source/gamecart/spi.h new file mode 100644 index 0000000..a6b06cd --- /dev/null +++ b/arm9/source/gamecart/spi.h @@ -0,0 +1,89 @@ +/* + * 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 + */ + +#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_FLG_WIP 1 +#define SPI_FLG_WEL 2 + +// extern u8* fill_buf; +typedef enum { + NO_CHIP = -1, + + EEPROM_512B = 0, + + EEPROM_8KB = 1, + EEPROM_64KB = 2, + EEPROM_128KB = 3, + EEPROM_STD_DUMMY = 1, + + FLASH_256KB_1 = 4, + FLASH_256KB_2 = 5, + FLASH_512KB_1 = 6, + FLASH_512KB_2 = 7, + FLASH_1MB = 8, + FLASH_8MB = 9, // <- can't restore savegames, and maybe not read them atm + FLASH_STD_DUMMY = 4, + + FLASH_512KB_INFRARED = 10, + FLASH_256KB_INFRARED = 11, // AFAIK, only "Active Health with Carol Vorderman" has such a flash save memory + FLASH_INFRARED_DUMMY = 9, + + CHIP_LAST = 11, +} CardType; + +int SPIWriteRead(CardType type, void* cmd, u32 cmdSize, void* answer, u32 answerSize, 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); + +int SPIWriteSaveData(CardType type, u32 offset, 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/virtual/vcart.c b/arm9/source/virtual/vcart.c index 65671bd..0d79fc8 100644 --- a/arm9/source/virtual/vcart.c +++ b/arm9/source/virtual/vcart.c @@ -2,6 +2,7 @@ #include "gamecart.h" #define FAT_LIMIT 0x100000000 +#define VFLAG_SAVEGAME (1UL<<30) #define VFLAG_PRIV_HDR (1UL<<31) static CartData* cdata = NULL; @@ -11,7 +12,7 @@ static bool cart_checked = false; u32 InitVCartDrive(void) { if (!cart_checked) cart_checked = true; if (!cdata) cdata = (CartData*) malloc(sizeof(CartData)); - cart_init = (cdata && (InitCardRead(cdata) == 0) && (cdata->cart_size <= FAT_LIMIT)); + cart_init = (cdata && (InitCartRead(cdata) == 0) && (cdata->cart_size <= FAT_LIMIT)); if (!cart_init && cdata) { free(cdata); cdata = NULL; @@ -31,7 +32,7 @@ bool ReadVCartDir(VirtualFile* vfile, VirtualDir* vdir) { vfile->keyslot = 0xFF; // unused vfile->flags = VFLAG_READONLY; - while (++vdir->index <= 5) { + while (++vdir->index <= 6) { if ((vdir->index == 0) && (cdata->data_size < FAT_LIMIT)) { // standard full rom snprintf(vfile->name, 32, "%s.%s", name, ext); vfile->size = cdata->cart_size; @@ -55,6 +56,11 @@ bool ReadVCartDir(VirtualFile* vfile, VirtualDir* vdir) { vfile->size = PRIV_HDR_SIZE; vfile->flags |= VFLAG_PRIV_HDR; return true; + } else if ((vdir->index == 6) && (cdata->save_size > 0)) { // savegame + snprintf(vfile->name, 32, "%s.sav", name); + vfile->size = cdata->save_size; + vfile->flags = VFLAG_SAVEGAME; + return true; } } @@ -66,6 +72,8 @@ int ReadVCartFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) if (!cdata) return -1; if (vfile->flags & VFLAG_PRIV_HDR) return ReadCartPrivateHeader(buffer, foffset, count, cdata); + else if (vfile->flags & VFLAG_SAVEGAME) + return ReadCartSave(buffer, foffset, count, cdata); else return ReadCartBytes(buffer, foffset, count, cdata); }