diff --git a/arm9/source/auxspi.cpp b/arm9/source/auxspi.cpp index 13f12e6..e00e550 100644 --- a/arm9/source/auxspi.cpp +++ b/arm9/source/auxspi.cpp @@ -268,10 +268,6 @@ void auxspi_disable_extra(auxspi_extra extra) case AUXSPI_INFRARED: auxspi_disable_infrared_core(); break; - case AUXSPI_BBDX: - // TODO: - auxspi_disable_big_protection(); - break; case AUXSPI_BLUETOOTH: // TODO //auxspi_disable_bluetooth(); @@ -355,15 +351,6 @@ auxspi_extra auxspi_has_extra() u8 size1 = auxspi_save_size_log_2(); if (size1 > 0) return AUXSPI_DEFAULT; - -#if 0 - // EXPERIMENTAL: verify that flash cards do not answer with the same signature! - // Look for BBDX (which always returns "ff" on every command) - uint32 jedec = auxspi_save_jedec_id(); // 9f - //int8 sr = auxspi_save_status_register(); // 05 - if (jedec == 0x00ffffff) - return AUXSPI_BBDX; -#endif // TODO: add support for Pokemon Typing DS (as soon as we figure out how) diff --git a/arm9/source/auxspi.h b/arm9/source/auxspi.h index e60fe3b..9b017c8 100644 --- a/arm9/source/auxspi.h +++ b/arm9/source/auxspi.h @@ -38,20 +38,12 @@ // Games known to use this hardware: // - Personal Trainer: Walking (aka Laufrhytmus DS, Walk With Me, ...) // - Pokemon HeartGold/SoulSilver/Black/White -// AUXSPI_BBDX: A game with what seems to be an extra protection against reading -// out the chip. Exclusively found on Band Brothers DX. // AUXSPI_BLUETOOTH: A game with a Bluetooth transceiver. The only game using this // hardware is Pokemon Typing DS. // -// NOTE: This library does *not* support BBDX (I do have the game, but did not find the -// time to reverse engineer this; besides, a separate homebrew for this game already exists), -// and also *not* BLUETOOTH (the game is Japan-only, and I am from Europe. Plus I can't -// read Japanese. And it is unlikely that this game will ever make it here.) -// typedef enum { AUXSPI_DEFAULT, AUXSPI_INFRARED, - AUXSPI_BBDX, AUXSPI_BLUETOOTH, AUXSPI_FLASH_CARD = 999 } auxspi_extra; diff --git a/arm9/source/dumpOperations.cpp b/arm9/source/dumpOperations.cpp index b784c1a..504cebc 100644 --- a/arm9/source/dumpOperations.cpp +++ b/arm9/source/dumpOperations.cpp @@ -4,6 +4,7 @@ #include "date.h" #include "driveOperations.h" #include "font.h" +#include "gba.h" #include "ndsheaderbanner.h" #include "read_card.h" #include "tonccpy.h" @@ -30,8 +31,9 @@ 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!"; +void saveWriteFailMsg(bool gba) { + char sizeError[256]; + snprintf(sizeError, sizeof(sizeError), "The size of this save doesn't match the size of the inserted game %s.\n\nWrite cancelled!", gba ? "pak" : "card"); font->clear(false); font->print(0, 0, false, sizeError, Alignment::left, Palette::red); @@ -325,7 +327,7 @@ void ndsCardSaveRestore(const char *filename) { if(length != saveSize) { fclose(in); - saveWriteFailMsg(); + saveWriteFailMsg(false); return; } @@ -387,7 +389,7 @@ void ndsCardSaveRestore(const char *filename) { if(length != (auxspi ? (int)(LEN * num_blocks) : size)) { fclose(in); - saveWriteFailMsg(); + saveWriteFailMsg(false); return; } @@ -491,8 +493,7 @@ void ndsCardDump(void) { char destSavPath[256]; sprintf(destSavPath, "%s:/gm9i/out/%s_%s_%02x.sav", (sdMounted ? "sd" : "fat"), gameTitle, gameCode, ndsCardHeader.romversion); ndsCardSaveDump(destSavPath); - } else - if ((pressed & KEY_A) || (pressed & KEY_Y)) { + } else if ((pressed & KEY_A) || (pressed & KEY_Y)) { bool trimRom = (pressed & KEY_Y); char folderPath[2][256]; sprintf(folderPath[0], "%s:/gm9i", (sdMounted ? "sd" : "fat")); @@ -750,6 +751,116 @@ void ndsCardDump(void) { } +void gbaCartSaveDump(const char *filename) { + font->clear(false); + font->print(0, 0, false, "Dumping save..."); + font->print(0, 1, false, "Do not remove the GBA cart."); + font->update(false); + + saveTypeGBA type = gbaGetSaveType(); + + if(type == SAVE_GBA_EEPROM_05 || type == SAVE_GBA_EEPROM_8) { + font->clear(false); + font->print(0, 0, false, "EEPROM saves are not currently supported!\n\nUse GBA Backup Tool to backup/restore this game's save.\nhttps://gamebrew.org/wiki/GBA_Backup_Tool\n\n( OK)"); + font->update(false); + + do { + scanKeys(); + swiWaitForVBlank(); + } while(!(keysDown() & KEY_A)); + return; + } + + u32 size = gbaGetSaveSize(type); + if(size == 0) + return; + + u8 *buffer = new u8[size]; + gbaReadSave(buffer, 0, size, type); + + remove(filename); + FILE *destinationFile = fopen(filename, "wb"); + fwrite(buffer, 1, size, destinationFile); + fclose(destinationFile); + delete[] buffer; +} + +void gbaCartSaveRestore(const char *filename) { + font->clear(false); + font->print(0, 0, false, "Restore the selected save to the inserted game pak?"); + font->print(0, 2, false, "( yes, no)\n"); + font->update(false); + + // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do + 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 | KEY_B))); + + if (pressed & KEY_A) { + saveTypeGBA type = gbaGetSaveType(); + u32 size = gbaGetSaveSize(type); + if(size == 0) + return; + + FILE *sourceFile = fopen(filename, "rb"); + u8 *buffer = new u8[size]; + if(!buffer || !sourceFile) { + if(buffer) delete[] buffer; + if(sourceFile) fclose(sourceFile); + + font->clear(false); + font->print(0, 0, false, "Failed to open save."); + font->update(false); + + for (int i = 0; i < 60 * 2; i++) + swiWaitForVBlank(); + return; + } + + fseek(sourceFile, 0, SEEK_END); + size_t length = ftell(sourceFile); + fseek(sourceFile, 0, SEEK_SET); + if(length != size) { + delete[] buffer; + fclose(sourceFile); + + saveWriteFailMsg(true); + return; + } + + if (fread(buffer, 1, size, sourceFile) != size) { + delete[] buffer; + fclose(sourceFile); + + font->clear(false); + font->print(0, 0, false, "Failed to read save."); + font->update(false); + + for (int i = 0; i < 60 * 2; i++) + swiWaitForVBlank(); + return; + } + + font->clear(false); + font->print(0, 0, false, "Restoring save..."); + font->print(0, 1, false, "Do not remove the GBA cart."); + font->update(false); + + gbaFormatSave(type); + gbaWriteSave(0, buffer, size, type); + + delete[] buffer; + fclose(sourceFile); + } +} + void writeChange(const u32* buffer) { // Input registers are at 0x08800000 - 0x088001FF *(vu32*) 0x08800184 = buffer[1]; @@ -769,7 +880,7 @@ void gbaCartDump(void) { font->clear(false); font->printf(0, 0, false, Alignment::left, Palette::white, "Dump GBA cart ROM to\n\"%s:/gm9i/out\"?", sdMounted ? "sd" : "fat"); - font->print(0, 2, false, "( yes, no)"); + font->print(0, 2, false, "( yes, no, save only)"); font->update(false); // Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do @@ -781,16 +892,44 @@ void gbaCartDump(void) { scanKeys(); pressed = keysDownRepeat(); swiWaitForVBlank(); - } while (!(pressed & KEY_A) && !(pressed & KEY_B)); + } while (!(pressed & (KEY_A | KEY_B | KEY_X))); - if (pressed & KEY_A) { - // Clear time - font->print(-1, 0, true, " ", Alignment::right, Palette::blackGreen); - font->update(true); + // Get name + char gbaHeaderGameTitle[13] = {0}; + tonccpy(gbaHeaderGameTitle, (u8*)(0x080000A0), 12); + char gbaHeaderGameCode[5] = {0}; + tonccpy(gbaHeaderGameCode, (u8*)(0x080000AC), 4); + char gbaHeaderMakerCode[3] = {0}; + tonccpy(gbaHeaderMakerCode, (u8*)(0x080000B0), 2); + if (gbaHeaderGameTitle[0] == 0 || gbaHeaderGameTitle[0] == 0xFF) { + sprintf(gbaHeaderGameTitle, "NO-TITLE"); + } else { + for(uint i = 0; i < sizeof(gbaHeaderGameTitle); i++) { + switch(gbaHeaderGameTitle[i]) { + case '>': + case '<': + case ':': + case '"': + case '/': + case '\\': + case '|': + case '?': + case '*': + gbaHeaderGameTitle[i] = '_'; + } + } } + if (gbaHeaderGameCode[0] == 0 || gbaHeaderGameCode[0] == 0xFF) { + sprintf(gbaHeaderGameCode, "NONE"); + } + if (gbaHeaderMakerCode[0] == 0 || gbaHeaderMakerCode[0] == 0xFF) { + sprintf(gbaHeaderMakerCode, "00"); + } + u8 gbaHeaderSoftwareVersion = *(u8*)(0x080000BC); + char fileName[32] = {0}; + sprintf(fileName, "%s_%s%s_%x", gbaHeaderGameTitle, gbaHeaderGameCode, gbaHeaderMakerCode, gbaHeaderSoftwareVersion); if (pressed & KEY_A) { - consoleClear(); if (access("fat:/gm9i", F_OK) != 0) { font->clear(false); font->print(0, 0, false, "Creating directory..."); @@ -803,43 +942,9 @@ void gbaCartDump(void) { font->update(false); mkdir("fat:/gm9i/out", 0777); } - char gbaHeaderGameTitle[13] = "\0"; - tonccpy(gbaHeaderGameTitle, (u8*)(0x080000A0), 12); - char gbaHeaderGameCode[5] = "\0"; - tonccpy(gbaHeaderGameCode, (u8*)(0x080000AC), 4); - char gbaHeaderMakerCode[3] = "\0"; - tonccpy(gbaHeaderMakerCode, (u8*)(0x080000B0), 2); - if (gbaHeaderGameTitle[0] == 0 || gbaHeaderGameTitle[0] == 0xFF) { - sprintf(gbaHeaderGameTitle, "NO-TITLE"); - } else { - for(uint i = 0; i < sizeof(gbaHeaderGameTitle); i++) { - switch(gbaHeaderGameTitle[i]) { - case '>': - case '<': - case ':': - case '"': - case '/': - case '\x5C': - case '|': - case '?': - case '*': - gbaHeaderGameTitle[i] = '_'; - } - } - } - if (gbaHeaderGameCode[0] == 0 || gbaHeaderGameCode[0] == 0xFF) { - sprintf(gbaHeaderGameCode, "NONE"); - } - if (gbaHeaderMakerCode[0] == 0 || gbaHeaderMakerCode[0] == 0xFF) { - sprintf(gbaHeaderMakerCode, "00"); - } - u8 gbaHeaderSoftwareVersion = *(u8*)(0x080000BC); - char fileName[32] = {0}; - sprintf(fileName, "%s_%s%s_%x", gbaHeaderGameTitle, gbaHeaderGameCode, gbaHeaderMakerCode, gbaHeaderSoftwareVersion); + char destPath[256] = {0}; - char destSavPath[256] = {0}; sprintf(destPath, "fat:/gm9i/out/%s.gba", fileName); - sprintf(destSavPath, "fat:/gm9i/out/%s.sav", fileName); font->clear(false); font->printf(0, 0, false, Alignment::left, Palette::white, "%s.gba\nis dumping...", fileName); @@ -847,14 +952,20 @@ void gbaCartDump(void) { font->update(false); // Determine ROM size - u32 romSize = 0x02000000; - for (u32 i = 0x09FE0000; i > 0x08000000; i -= 0x20000) { - if (*(u32*)(i) == 0xFFFE0000) { - romSize -= 0x20000; - } else { - break; + u32 romSize; + for (romSize = (1 << 20); romSize < (1 << 25); romSize <<= 1) { + vu16 *rompos = (vu16*)(0x08000000 + romSize); + bool romend = true; + for (int j = 0; j < 0x1000; j++) { + if (rompos[j] != j) { + romend = false; + break; + } } + if (romend) + break; } + // Dump! remove(destPath); // Reset data at virtual address @@ -867,15 +978,33 @@ void gbaCartDump(void) { writeChange(rstCmd); FILE* destinationFile = fopen(destPath, "wb"); if (destinationFile) { - if (fwrite(GBAROM, 1, romSize, destinationFile) < 1) { - dumpFailMsg(false); - } else + bool failed = false; + + font->print(0, 4, false, "Progress:"); + font->print(0, 5, false, "["); + font->print(-1, 5, false, "]"); + for (u32 src = 0; src < romSize; src += 0x8000) { + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); + + 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); + + if (fwrite(GBAROM + src / sizeof(u16), 1, 0x8000, destinationFile) != 0x8000) { + dumpFailMsg(false); + failed = true; + break; + } + } + // Check for 64MB GBA Video ROM - if (strncmp((char*)0x080000AC, "MSAE", 4)==0 // Shark Tale - || strncmp((char*)0x080000AC, "MSKE", 4)==0 // Shrek - || strncmp((char*)0x080000AC, "MSTE", 4)==0 // Shrek & Shark Tale - || strncmp((char*)0x080000AC, "M2SE", 4)==0 // Shrek 2 - ) { + if ((strncmp((char*)0x080000AC, "MSAE", 4) == 0 // Shark Tale + || strncmp((char*)0x080000AC, "MSKE", 4) == 0 // Shrek + || strncmp((char*)0x080000AC, "MSTE", 4) == 0 // Shrek & Shark Tale + || strncmp((char*)0x080000AC, "M2SE", 4) == 0 // Shrek 2 + ) && !failed) { // Dump last 32MB u32 cmd[4] = { 0x11, // Command @@ -884,8 +1013,17 @@ void gbaCartDump(void) { 0x8, // Size (in 0x200 byte blocks) }; - size_t i; - for (i = 0x02000000; i < 0x04000000; i += 0x1000) { + font->print(0, 5, false, "["); + font->print(-1, 5, false, "]"); + for (size_t i = 0x02000000; i < 0x04000000; i += 0x1000) { + // Print time + font->print(-1, 0, true, RetTime(), Alignment::right, Palette::blackGreen); + font->update(true); + + font->print((i / (0x04000000 / (SCREEN_COLS - 2))) + 1, 5, false, "="); + font->printf(0, 7, false, Alignment::left, Palette::white, "%d/%d Bytes", i - 0x02000000, 0x04000000 - 0x02000000); + font->update(false); + cmd[1] = i, writeChange(cmd); readChange(); @@ -898,12 +1036,27 @@ void gbaCartDump(void) { fclose(destinationFile); } else { dumpFailMsg(false); + return; + } + } + + if(pressed & (KEY_A | KEY_X)) { + if (access("fat:/gm9i", F_OK) != 0) { + font->clear(false); + font->print(0, 0, false, "Creating directory..."); + font->update(false); + mkdir("fat:/gm9i", 0777); + } + if (access("fat:/gm9i/out", F_OK) != 0) { + font->clear(false); + font->print(0, 0, false, "Creating directory..."); + font->update(false); + mkdir("fat:/gm9i/out", 0777); } - // Save file - remove(destSavPath); - destinationFile = fopen(destSavPath, "wb"); - fwrite((void*)0x0A000000, 1, 0x10000, destinationFile); - fclose(destinationFile); + char destSavPath[256] = {0}; + sprintf(destSavPath, "fat:/gm9i/out/%s.sav", fileName); + + gbaCartSaveDump(destSavPath); } } diff --git a/arm9/source/dumpOperations.h b/arm9/source/dumpOperations.h index 5bdcaef..71ba8ab 100644 --- a/arm9/source/dumpOperations.h +++ b/arm9/source/dumpOperations.h @@ -2,6 +2,7 @@ #define DUMPING_H void ndsCardSaveRestore(const char *filename); +void gbaCartSaveRestore(const char *filename); void ndsCardDump(void); void gbaCartDump(void); diff --git a/arm9/source/file_browse.cpp b/arm9/source/file_browse.cpp index 5688616..6b6c566 100644 --- a/arm9/source/file_browse.cpp +++ b/arm9/source/file_browse.cpp @@ -323,7 +323,11 @@ FileOperation fileBrowse_A(DirEntry* entry, char path[PATH_MAX]) { return FileOperation::bootFile; break; } case FileOperation::restoreSave: { - ndsCardSaveRestore(entry->name.c_str()); + if(isDSiMode()) { + ndsCardSaveRestore(entry->name.c_str()); + } else { + gbaCartSaveRestore(entry->name.c_str()); + } break; } case FileOperation::copySdOut: { if (access("sd:/gm9i", F_OK) != 0) { diff --git a/arm9/source/gba.cpp b/arm9/source/gba.cpp new file mode 100644 index 0000000..c3b7875 --- /dev/null +++ b/arm9/source/gba.cpp @@ -0,0 +1,466 @@ +/* + * 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. + * + * gba.cpp: Functions for working with the GBA-slot on a Nintendo DS. + * EZFlash 3-in-1 functions are found in dsCard.h/.cpp + * + * 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 + */ + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + + +#include + +#include "gba.h" +// #include "dsCard.h" + +// #include "display.h" +// #include "globals.h" +// #include "strings.h" + +inline u32 min(u32 i, u32 j) { return (i < j) ? i : j;} +inline u32 max(u32 i, u32 j) { return (i > j) ? i : j;} + + + +// ----------------------------------------------------- +#define MAGIC_EEPR 0x52504545 +#define MAGIC_SRAM 0x4d415253 +#define MAGIC_FLAS 0x53414c46 + +#define MAGIC_H1M_ 0x5f4d3148 + +#define EEPROM_ADDRESS (0x0DFFFF00) +#define REG_EEPROM *(vu16 *)(EEPROM_ADDRESS) + + +// ----------------------------------------------------------- +bool gbaIsGame() +{ + // look for some magic bytes of the compressed Nintendo logo + uint32 *data = (uint32*)0x08000004; + + if (*data == 0x51aeff24) { + data ++; data ++; + if (*data == 0x0a82843d) + return true; + } + return false; +} + +void EEPROM_SendPacket(u16 *packet, int size) +{ + REG_EXMEMCNT = (REG_EXMEMCNT & 0xFFE3) | 0x000C; + DMA3_SRC = (u32)packet; + DMA3_DEST = EEPROM_ADDRESS; + DMA3_CR = 0x80000000 + size; + while((DMA3_CR & 0x80000000) != 0); +} + +void EEPROM_ReceivePacket(u16 *packet, int size) +{ + REG_EXMEMCNT = (REG_EXMEMCNT & 0xFFE3) | 0x000C; + DMA3_SRC = EEPROM_ADDRESS; + DMA3_DEST = (u32)packet; + DMA3_CR = 0x80000000 + size; + while((DMA3_CR & 0x80000000) != 0); +} + +// local function +void gbaEepromRead8Bytes(u8 *out, u16 addr, bool short_addr) +{ + u16 packet[68]; + + memset(packet, 0, 68 * 2); + + // Read request + packet[0] = 1; + packet[1] = 1; + + // 6 or 14 bytes eeprom address (MSB first) + for(int i = 2, shift = (short_addr ? 5 : 13); i < (short_addr ? 8 : 16); i++, shift--) { + packet[i] = (addr >> shift) & 1; + } + + // End of request + packet[short_addr ? 8 : 16] = 0; + + // Do transfers + EEPROM_SendPacket(packet, short_addr ? 9 : 17); + memset(packet, 0, 68 * 2); + EEPROM_ReceivePacket(packet, 68); + + // Extract data + u16 *in_pos = &packet[4]; + for(int byte = 7; byte >= 0; --byte) { + u8 out_byte = 0; + for(int bit = 7; bit >= 0; --bit) { + // out_byte += (*in_pos++) << bit; + out_byte += ((*in_pos++) & 1) << bit; + } + *out++ = out_byte; + } +} + +// local function +void gbaEepromWrite8Bytes(u8 *in, u16 addr, bool short_addr = false) +{ + u16 packet_length = short_addr ? 73 : 81; + u16 packet[packet_length]; + + memset( packet, 0, packet_length * 2); + + // Write request + packet[0] = 1; + packet[1] = 0; + + // 6 or 14 bytes eeprom address (MSB first) + for(int i = 2, shift = (short_addr ? 5 : 13); i < (short_addr ? 8 : 16); i++, shift--) { + packet[i] = (addr >> shift) & 1; + } + + // Extract data + u16 *out_pos = &packet[short_addr ? 8 : 16]; + for(int byte = 7; byte >= 0; --byte) { + u8 in_byte = *in++; + for(int bit = 7; bit >= 0; --bit) { + *out_pos++ = (in_byte >> bit) & 1; + } + } + + // End of request + packet[packet_length - 1] = 0; + + // Do transfers + EEPROM_SendPacket(packet, packet_length); + + // Wait for EEPROM to finish (should timeout after 10 ms) + while((REG_EEPROM & 1) == 0); +} + +saveTypeGBA gbaGetSaveType() { + // Search for any one of the magic version strings in the ROM. They are always dword-aligned. + uint32 *data = (uint32*)0x08000000; + + for (int i = 0; i < (0x02000000 >> 2); i++, data++) { + if (*data == MAGIC_EEPR) { + u8 *buf = new u8[0x2000]; + u8 *ptr = buf; + for (int j = 0; j < 0x400; j++, ptr += 8) { + gbaEepromRead8Bytes(ptr, j, false); + for(int sleep=0;sleep<512000;sleep++); + } + for(int j = 8; j < 0x800; j += 8) { + if(memcmp(buf, buf + j, 8) != 0) { + delete[] buf; + return SAVE_GBA_EEPROM_8; + } + } + delete[] buf; + return SAVE_GBA_EEPROM_05; + } else if (*data == MAGIC_SRAM) { + // *always* 32 kB + return SAVE_GBA_SRAM_32; + } else if (*data == MAGIC_FLAS) { + // 64 kB oder 128 kB + uint32 *data2 = data + 1; + if (*data2 == MAGIC_H1M_) + return SAVE_GBA_FLASH_128; + else + return SAVE_GBA_FLASH_64; + } + } + + return SAVE_GBA_NONE; +} + +uint32 gbaGetSaveSizeLog2(saveTypeGBA type) +{ + if (type == SAVE_GBA_NONE) + type = gbaGetSaveType(); + + switch (type) { + case SAVE_GBA_EEPROM_05: + return 9; + case SAVE_GBA_EEPROM_8: + return 13; + case SAVE_GBA_SRAM_32: + return 15; + case SAVE_GBA_FLASH_64: + return 16; + case SAVE_GBA_FLASH_128: + return 17; + case SAVE_GBA_NONE: + default: + return 0; + } +} + +uint32 gbaGetSaveSize(saveTypeGBA type) +{ + if (type == SAVE_GBA_NONE) + return 0; + else + return 1 << gbaGetSaveSizeLog2(type); +} + +bool gbaReadSave(u8 *dst, u32 src, u32 len, saveTypeGBA type) +{ + int nbanks = 2; // for type 4,5 + bool eeprom_long = true; + + switch (type) { + case SAVE_GBA_EEPROM_05: { + eeprom_long = false; + } + case SAVE_GBA_EEPROM_8: { + int start, end; + start = src >> 3; + end = (src + len) >> 3; + u8 *ptr = dst; + for (int j = start; j < end; j++, ptr += 8) { + gbaEepromRead8Bytes(ptr, j, !eeprom_long); + for(int sleep=0;sleep<512000;sleep++); + } + break; + } + case SAVE_GBA_SRAM_32: { + // SRAM: blind copy + int start = 0x0a000000 + src; + u8 *tmpsrc = (u8*)start; + sysSetBusOwners(true, true); + for (u32 i = 0; i < len; i++, tmpsrc++, dst++) + *dst = *tmpsrc; + break; + } + case SAVE_GBA_FLASH_64: + // FLASH - must be opend by register magic, then blind copy + nbanks = 1; + case SAVE_GBA_FLASH_128: + for (int j = 0; j < nbanks; j++) { + // we need to wait a few cycles before the hardware reacts! + *(vu8*)0x0a005555 = 0xaa; + swiDelay(10); + *(vu8*)0x0a002aaa = 0x55; + swiDelay(10); + *(vu8*)0x0a005555 = 0xb0; + swiDelay(10); + *(vu8*)0x0a000000 = (u8)j; + swiDelay(10); + u32 start, sublen; + if (j == 0) { + start = 0x0a000000 + src; + sublen = (src < 0x10000) ? min(len, (1 << 16) - src) : 0; + } else if (j == 1) { + start = max(0x09ff0000 + src, 0x0a000000); + sublen = (src + len < 0x10000) ? 0 : min(len, len - (0x10000 - src)); + } + u8 *tmpsrc = (u8*)start; + sysSetBusOwners(true, true); + for (u32 i = 0; i < sublen; i++, tmpsrc++, dst++) + *dst = *tmpsrc; + } + break; + case SAVE_GBA_NONE: + break; + } + return true; +} + +bool gbaIsAtmel() +{ + *(vu8*)0x0a005555 = 0xaa; + swiDelay(10); + *(vu8*)0x0a002aaa = 0x55; + swiDelay(10); + *(vu8*)0x0a005555 = 0x90; // ID mode + swiDelay(10); + // + u8 dev = *(u8*)0x0a000001; + u8 man = *(u8*)0x0a000000; + // + *(vu8*)0x0a005555 = 0xaa; + swiDelay(10); + *(vu8*)0x0a002aaa = 0x55; + swiDelay(10); + *(vu8*)0x0a005555 = 0xf0; // leave ID mode + swiDelay(10); + // + //char txt[128]; + // sprintf(txt, "Man: %x, Dev: %x", man, dev); + // displayStateF(STR_STR, txt); + if ((man == 0x3d) && (dev == 0x1f)) + return true; + else + return false; +} + +bool gbaWriteSave(u32 dst, u8 *src, u32 len, saveTypeGBA type) +{ + int nbanks = 2; // for type 4,5 + bool eeprom_long = true; + + switch (type) { + case SAVE_GBA_EEPROM_05: { + eeprom_long = false; + } + case SAVE_GBA_EEPROM_8: { + /* + int start, end; + start = dst >> 3; + end = (dst + len) >> 3; + u8 *ptr = src; + for (int j = start; j < end; j++, ptr+=8) { + gbaEepromWrite8Bytes(ptr, j, eeprom_long); + } + */ + break; + } + case SAVE_GBA_SRAM_32: { + // SRAM: blind write + u32 start = 0x0a000000 + dst; + u8 *tmpdst = (u8*)start; + sysSetBusOwners(true, true); + for (u32 i = 0; i < len; i++, tmpdst++, src++) { + *tmpdst = *src; + swiDelay(10); // mabe we don't need this, but better safe than sorry + } + break; + } + case SAVE_GBA_FLASH_64: { + bool atmel = gbaIsAtmel(); + if (atmel) { + // only 64k, no bank switching required + u32 len7 = len >> 7; + u8 *tmpdst = (u8*)(0x0a000000+dst); + for (u32 j = 0; j < len7; j++) { + u32 ime = enterCriticalSection(); + *(vu8*)0x0a005555 = 0xaa; + swiDelay(10); + *(vu8*)0x0a002aaa = 0x55; + swiDelay(10); + *(vu8*)0x0a005555 = 0xa0; + swiDelay(10); + for (int i = 0; i < 128; i++) { + *tmpdst = *src; + swiDelay(10); + } + leaveCriticalSection(ime); + while (*tmpdst != *src) {swiDelay(10);} + } + break; + } + nbanks = 1; + } + case SAVE_GBA_FLASH_128: + // FLASH - must be opend by register magic, erased and then rewritten + // FIXME: currently, you can only write "all or nothing" + nbanks = 2; + for (int j = 0; j < nbanks; j++) { + *(vu8*)0x0a005555 = 0xaa; + swiDelay(10); + *(vu8*)0x0a002aaa = 0x55; + swiDelay(10); + *(vu8*)0x0a005555 = 0xb0; + swiDelay(10); + *(vu8*)0x0a000000 = (u8)j; + swiDelay(10); + // + u32 start, sublen; + if (j == 0) { + start = 0x0a000000 + dst; + sublen = (dst < 0x10000) ? min(len, (1 << 16) - dst) : 0; + } else if (j == 1) { + start = max(0x09ff0000 + dst, 0x0a000000); + sublen = (dst + len < 0x10000) ? 0 : min(len, len - (0x10000 - dst)); + } + u8 *tmpdst = (u8*)start; + sysSetBusOwners(true, true); + for (u32 i = 0; i < sublen; i++, tmpdst++, src++) { + // we need to wait a few cycles before the hardware reacts! + *(vu8*)0x0a005555 = 0xaa; + swiDelay(10); + *(vu8*)0x0a002aaa = 0x55; + swiDelay(10); + *(vu8*)0x0a005555 = 0xa0; // write byte command + swiDelay(10); + // + *tmpdst = *src; + swiDelay(10); + // + while (*tmpdst != *src) {swiDelay(10);} + } + } + break; + case SAVE_GBA_NONE: + break; + } + return true; +} + +bool gbaFormatSave(saveTypeGBA type) +{ + switch (type) { + case SAVE_GBA_EEPROM_05: + case SAVE_GBA_EEPROM_8: + // TODO: eeprom is not supported yet + break; + case SAVE_GBA_SRAM_32: + { + // memset(data, 0, 1 << 15); + u8 *data = new u8[1 << 15](); + gbaWriteSave(0, data, 1 << 15, SAVE_GBA_SRAM_32); + delete[] data; + } + break; + case SAVE_GBA_FLASH_64: + case SAVE_GBA_FLASH_128: + *(vu8*)0x0a005555 = 0xaa; + swiDelay(10); + *(vu8*)0x0a002aaa = 0x55; + swiDelay(10); + *(vu8*)0x0a005555 = 0x80; // erase command + swiDelay(10); + *(vu8*)0x0a005555 = 0xaa; + swiDelay(10); + *(vu8*)0x0a002aaa = 0x55; + swiDelay(10); + *(vu8*)0x0a005555 = 0x10; // erase entire chip + swiDelay(10); + while (*(u8*)0x0a000000 != 0xff) + swiDelay(10); + break; + case SAVE_GBA_NONE: + break; + } + return true; +} diff --git a/arm9/source/gba.h b/arm9/source/gba.h new file mode 100644 index 0000000..469973e --- /dev/null +++ b/arm9/source/gba.h @@ -0,0 +1,49 @@ +/* + * 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. + * + * gba.h: header file for gba.cpp + * + * 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 + */ +#ifndef __GBA_H__ +#define __GBA_H__ + +enum saveTypeGBA { + SAVE_GBA_NONE = 0, + SAVE_GBA_EEPROM_05, // 512 bytes + SAVE_GBA_EEPROM_8, // 8k + SAVE_GBA_SRAM_32, // 32k + SAVE_GBA_FLASH_64, // 64k + SAVE_GBA_FLASH_128 // 128k +}; + + +// -------------------- +bool gbaIsGame(); +saveTypeGBA gbaGetSaveType(); +uint32 gbaGetSaveSize(saveTypeGBA type = SAVE_GBA_NONE); +uint32 gbaGetSaveSizeLog2(saveTypeGBA type = SAVE_GBA_NONE); + +bool gbaReadSave(u8 *dst, u32 src, u32 len, saveTypeGBA type); +bool gbaWriteSave(u32 dst, u8 *src, u32 len, saveTypeGBA type); +bool gbaFormatSave(saveTypeGBA type); + + +#endif // __SLOT2_H__ \ No newline at end of file