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