mirror of
https://github.com/rvtr/GodMode9i.git
synced 2025-11-02 00:11:07 -04:00
Add dumping non-SRAM GBA saves
Credit to savegame-manager for code https://code.google.com/archive/p/savegame-manager/
This commit is contained in:
parent
35d6f7fdca
commit
cf0a86fa0b
@ -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,96 @@ 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);
|
||||
|
||||
u8 type = gbaGetSaveType();
|
||||
u32 size = gbaGetSaveSize(type);
|
||||
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, "(<A> yes, <B> 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) {
|
||||
u8 type = gbaGetSaveType();
|
||||
u32 size = gbaGetSaveSize(type);
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
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 +860,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, "(<A> yes, <B> no)");
|
||||
font->print(0, 2, false, "(<A> yes, <B> no, <X> 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 +872,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 +922,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);
|
||||
@ -867,15 +952,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 +987,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 +1010,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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#define DUMPING_H
|
||||
|
||||
void ndsCardSaveRestore(const char *filename);
|
||||
void gbaCartSaveRestore(const char *filename);
|
||||
|
||||
void ndsCardDump(void);
|
||||
void gbaCartDump(void);
|
||||
|
||||
@ -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) {
|
||||
|
||||
574
arm9/source/gba.cpp
Normal file
574
arm9/source/gba.cpp
Normal file
@ -0,0 +1,574 @@
|
||||
/*
|
||||
* 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 <nds.h>
|
||||
#include <fat.h>
|
||||
|
||||
#include <sys/iosupport.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <algorithm>
|
||||
#include <nds/dma.h>
|
||||
|
||||
|
||||
#include <dirent.h>
|
||||
|
||||
#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
|
||||
|
||||
saveTypeGBA GetSlot2SaveType(cartTypeGBA type) {
|
||||
if (type == CART_GBA_NONE)
|
||||
return SAVE_GBA_NONE;
|
||||
|
||||
// Search for any one of the magic version strings in the ROM.
|
||||
uint32 *data = (uint32*)0x08000000;
|
||||
|
||||
for (int i = 0; i < (0x02000000 >> 2); i++, data++) {
|
||||
if (*data == MAGIC_EEPR)
|
||||
return SAVE_GBA_EEPROM_8; // TODO: Try to figure out 512 bytes version...
|
||||
if (*data == MAGIC_SRAM)
|
||||
return SAVE_GBA_SRAM_32;
|
||||
if (*data == MAGIC_FLAS) {
|
||||
uint32 *data2 = data + 1;
|
||||
if (*data2 == MAGIC_H1M_)
|
||||
return SAVE_GBA_FLASH_128;
|
||||
else
|
||||
return SAVE_GBA_FLASH_64;
|
||||
}
|
||||
}
|
||||
return SAVE_GBA_NONE;
|
||||
};
|
||||
|
||||
cartTypeGBA GetSlot2Type(uint32 id)
|
||||
{
|
||||
if (id == 0x53534150)
|
||||
// All conventional GBA flash cards identify themselves as "PASS"
|
||||
return CART_GBA_FLASH;
|
||||
else {
|
||||
return CART_GBA_GAME;
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------
|
||||
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;
|
||||
}
|
||||
|
||||
uint8 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) {
|
||||
// 2 versions: 512 bytes / 8 kB
|
||||
return 2; // TODO: Try to figure out how to ID the 512 bytes version... hard way? write/restore!
|
||||
}
|
||||
if (*data == MAGIC_SRAM) {
|
||||
// *always* 32 kB
|
||||
return 3;
|
||||
}
|
||||
if (*data == MAGIC_FLAS) {
|
||||
// 64 kB oder 128 kB
|
||||
uint32 *data2 = data + 1;
|
||||
if (*data2 == MAGIC_H1M_)
|
||||
return 5;
|
||||
else
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32 gbaGetSaveSizeLog2(uint8 type)
|
||||
{
|
||||
if (type == 255)
|
||||
type = gbaGetSaveType();
|
||||
|
||||
switch (type) {
|
||||
case 1:
|
||||
return 9;
|
||||
case 2:
|
||||
return 13;
|
||||
case 3:
|
||||
return 15;
|
||||
case 4:
|
||||
return 16;
|
||||
case 5:
|
||||
return 17;
|
||||
case 0:
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint32 gbaGetSaveSize(uint8 type)
|
||||
{
|
||||
return 1 << gbaGetSaveSizeLog2(type);
|
||||
}
|
||||
|
||||
// local function
|
||||
void gbaEepromRead8Bytes(u8 *out, u32 addr, bool short_addr = false)
|
||||
{
|
||||
// TODO: this still does not work... figure out somehow how to do it right!
|
||||
|
||||
// waitstates - this is what Rudolph uses...
|
||||
*(volatile unsigned short *)0x04000204 = 0x4317;
|
||||
|
||||
// maximal length of the buffer
|
||||
u16 buf[68];
|
||||
|
||||
// Prepare a "read" command.
|
||||
u16 length;
|
||||
// raw command
|
||||
buf[0] = 1;
|
||||
buf[1] = 1;
|
||||
// address
|
||||
if (short_addr) {
|
||||
length = 9;
|
||||
buf[2] = addr >> 5;
|
||||
buf[3] = addr >> 4;
|
||||
buf[4] = addr >> 3;
|
||||
buf[5] = addr >> 2;
|
||||
buf[6] = addr >> 1;
|
||||
buf[7] = addr;
|
||||
buf[8] = 0;
|
||||
} else {
|
||||
length = 17;
|
||||
buf[2] = addr >> 13;
|
||||
buf[3] = addr >> 12;
|
||||
buf[4] = addr >> 11;
|
||||
buf[5] = addr >> 10;
|
||||
buf[6] = addr >> 9;
|
||||
buf[7] = addr >> 8;
|
||||
buf[8] = addr >> 7;
|
||||
buf[9] = addr >> 6;
|
||||
buf[10] = addr >> 5;
|
||||
buf[11] = addr >> 4;
|
||||
buf[12] = addr >> 3;
|
||||
buf[13] = addr >> 2;
|
||||
buf[14] = addr >> 1;
|
||||
buf[15] = addr;
|
||||
buf[16] = 0;
|
||||
}
|
||||
for (int i = 0; i < 17; i++) {
|
||||
if (buf[i])
|
||||
buf[i] = 255;
|
||||
else
|
||||
buf[i] = 0;
|
||||
}
|
||||
|
||||
static u32 eeprom = 0x09ffff00;
|
||||
|
||||
// send command to eeprom
|
||||
// displayStateF(STR_STR, "Sending command");
|
||||
// commenting this out or not does not have any impact on the EEPROM device. Therefore,
|
||||
// the following command does not "make" it to the hardware!
|
||||
DC_FlushRange(&buf[0], sizeof(buf));
|
||||
DMA_SRC(3) = (uint32)&buf[0];
|
||||
DMA_DEST(3) = (uint32)eeprom;
|
||||
// there is a bit for eeprom access, but it only seems to freeze the transfer!?
|
||||
//DMA_CR(3) = DMA_COPY_HALFWORDS | DMA_START_CARD | length; // this bit is expanding to "3 bits"
|
||||
//DMA_CR(3) = DMA_COPY_HALFWORDS | length;
|
||||
DMA_CR(3) = DMA_COPY_HALFWORDS | (6 << 27) | length;
|
||||
while(DMA_CR(3) & DMA_BUSY);
|
||||
//while ((*(u16*)0x09ffff00 & 1) == 0);
|
||||
|
||||
// get answer from eeprom
|
||||
// displayStateF(STR_STR, "listening");
|
||||
DC_FlushRange(&buf[0], sizeof(buf));
|
||||
DMA_SRC(3) = (uint32)eeprom;
|
||||
DMA_DEST(3) = (uint32)&buf[0];
|
||||
// there is a bit for eeprom access, but it only seems to freeze the transfer!?
|
||||
//DMA_CR(3) = DMA_COPY_HALFWORDS | DMA_START_CARD | 68; // this bit is expanding to "3 bits"
|
||||
//DMA_CR(3) = DMA_COPY_HALFWORDS | 68;
|
||||
DMA_CR(3) = DMA_COPY_HALFWORDS | (6 << 27) | 68;
|
||||
while(DMA_CR(3) & DMA_BUSY);
|
||||
//while ((*(u16*)0x09ffff00 & 1) == 0);
|
||||
|
||||
/*
|
||||
// Extract data (there is only one *bit* per halfword!)
|
||||
u16 *in_pos = &buf[4];
|
||||
u8 *out_pos = out;
|
||||
u8 out_byte;
|
||||
for(s8 byte = 7; byte >= 0; --byte )
|
||||
{
|
||||
out_byte = 0;
|
||||
for(s8 bit = 7; bit >= 0; --bit )
|
||||
{
|
||||
out_byte += ((*in_pos++)&1)<<bit;
|
||||
}
|
||||
*out_pos++ = out_byte;
|
||||
}
|
||||
*/
|
||||
// let us study what the hardware *does* give us!
|
||||
for (u8 i = 0; i < 8; i++)
|
||||
*out++ = buf[i+4];
|
||||
}
|
||||
|
||||
// local function
|
||||
void gbaEepromWrite8Bytes(u8 *out, u32 addr, bool short_addr = false)
|
||||
{
|
||||
// TODO: this still does not work... figure out somehow how to do it right!
|
||||
|
||||
// don't do anything unless we know what we are doing!
|
||||
#if 0
|
||||
// waitstates - this is what Rudolph uses...
|
||||
*(volatile unsigned short *)0x04000204 = 0x4317;
|
||||
|
||||
// maximal length of the buffer
|
||||
u16 buf[68];
|
||||
|
||||
// Prepare a "read" command.
|
||||
u16 length;
|
||||
// raw command
|
||||
buf[0] = 1;
|
||||
buf[1] = 1;
|
||||
// address
|
||||
if (short_addr) {
|
||||
length = 9;
|
||||
buf[2] = addr >> 5;
|
||||
buf[3] = addr >> 4;
|
||||
buf[4] = addr >> 3;
|
||||
buf[5] = addr >> 2;
|
||||
buf[6] = addr >> 1;
|
||||
buf[7] = addr;
|
||||
buf[8] = 0;
|
||||
} else {
|
||||
length = 17;
|
||||
buf[2] = addr >> 13;
|
||||
buf[3] = addr >> 12;
|
||||
buf[4] = addr >> 11;
|
||||
buf[5] = addr >> 10;
|
||||
buf[6] = addr >> 9;
|
||||
buf[7] = addr >> 8;
|
||||
buf[8] = addr >> 7;
|
||||
buf[9] = addr >> 6;
|
||||
buf[10] = addr >> 5;
|
||||
buf[11] = addr >> 4;
|
||||
buf[12] = addr >> 3;
|
||||
buf[13] = addr >> 2;
|
||||
buf[14] = addr >> 1;
|
||||
buf[15] = addr;
|
||||
buf[16] = 0;
|
||||
}
|
||||
|
||||
// send command to eeprom
|
||||
displayStateF(STR_STR, "Sending command");
|
||||
static u32 eeprom = 0x09ffff00;
|
||||
DMA_SRC(3) = (uint32)&buf[0];
|
||||
DMA_DEST(3) = (uint32)eeprom;
|
||||
// there is a bit for eeprom access, but it only seems to freeze the transfer!?
|
||||
//DMA_CR(3) = DMA_COPY_HALFWORDS | DMA_START_CARD | length;
|
||||
DMA_CR(3) = DMA_COPY_HALFWORDS | length;
|
||||
while(DMA_CR(3) & DMA_BUSY);
|
||||
|
||||
// get answer from eeprom
|
||||
displayStateF(STR_STR, "listening");
|
||||
DMA_SRC(3) = (uint32)eeprom;
|
||||
DMA_DEST(3) = (uint32)&buf[0];
|
||||
// there is a bit for eeprom access, but it only seems to freeze the transfer!?
|
||||
DMA_CR(3) = DMA_COPY_HALFWORDS | DMA_START_CARD | 68;
|
||||
//DMA_CR(3) = DMA_COPY_HALFWORDS | 68;
|
||||
while(DMA_CR(3) & DMA_BUSY);
|
||||
|
||||
// Extract data (there is only one *bit* per halfword!)
|
||||
// TODO: convert this to write format
|
||||
u16 *in_pos = &buf[4];
|
||||
u8 *out_pos = out;
|
||||
u8 out_byte;
|
||||
for(s8 byte = 7; byte >= 0; --byte )
|
||||
{
|
||||
out_byte = 0;
|
||||
for(s8 bit = 7; bit >= 0; --bit )
|
||||
{
|
||||
out_byte += ((*in_pos++)&1)<<bit;
|
||||
}
|
||||
*out_pos++ = out_byte;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool gbaReadSave(u8 *dst, u32 src, u32 len, u8 type)
|
||||
{
|
||||
int nbanks = 2; // for type 4,5
|
||||
bool eeprom_long = true;
|
||||
|
||||
switch (type) {
|
||||
case 1: {
|
||||
eeprom_long = false;
|
||||
}
|
||||
case 2: {
|
||||
int start, end;
|
||||
start = src >> 3;
|
||||
end = (src + len - 1) >> 3;
|
||||
u8 *tmp = (u8*)malloc((end-start+1) << 3);
|
||||
u8 *ptr = tmp;
|
||||
for (int j = start; j <= end; j++, ptr+=8) {
|
||||
gbaEepromRead8Bytes(ptr, j, eeprom_long);
|
||||
}
|
||||
memcpy(dst, tmp, len);
|
||||
free(tmp);
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// 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 4:
|
||||
// FLASH - must be opend by register magic, then blind copy
|
||||
nbanks = 1;
|
||||
case 5:
|
||||
for (int j = 0; j < nbanks; j++) {
|
||||
// we need to wait a few cycles before the hardware reacts!
|
||||
*(u8*)0x0a005555 = 0xaa;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a002aaa = 0x55;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a005555 = 0xb0;
|
||||
swiDelay(10);
|
||||
*(u8*)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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gbaIsAtmel()
|
||||
{
|
||||
*(u8*)0x0a005555 = 0xaa;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a002aaa = 0x55;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a005555 = 0x90; // ID mode
|
||||
swiDelay(10);
|
||||
//
|
||||
u8 dev = *(u8*)0x0a000001;
|
||||
u8 man = *(u8*)0x0a000000;
|
||||
//
|
||||
*(u8*)0x0a005555 = 0xaa;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a002aaa = 0x55;
|
||||
swiDelay(10);
|
||||
*(u8*)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, u8 type)
|
||||
{
|
||||
int nbanks = 2; // for type 4,5
|
||||
bool eeprom_long = true;
|
||||
|
||||
switch (type) {
|
||||
case 1: {
|
||||
eeprom_long = false;
|
||||
}
|
||||
case 2: {
|
||||
/*
|
||||
int start, end;
|
||||
start = src >> 3;
|
||||
end = (src + len - 1) >> 3;
|
||||
u8 *tmp = (u8*)malloc((end-start+1) << 3);
|
||||
u8 *ptr = tmp;
|
||||
for (int j = start; j <= end; j++, ptr+=8) {
|
||||
gbaEepromWrite8Bytes(ptr, j, eeprom_long);
|
||||
}
|
||||
memcpy(dst, tmp, len);
|
||||
free(tmp);
|
||||
*/
|
||||
break;
|
||||
}
|
||||
case 3: {
|
||||
// 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 4: {
|
||||
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();
|
||||
*(u8*)0x0a005555 = 0xaa;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a002aaa = 0x55;
|
||||
swiDelay(10);
|
||||
*(u8*)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 5:
|
||||
// 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++) {
|
||||
*(u8*)0x0a005555 = 0xaa;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a002aaa = 0x55;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a005555 = 0xb0;
|
||||
swiDelay(10);
|
||||
*(u8*)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!
|
||||
*(u8*)0x0a005555 = 0xaa;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a002aaa = 0x55;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a005555 = 0xa0; // write byte command
|
||||
swiDelay(10);
|
||||
//
|
||||
*tmpdst = *src;
|
||||
swiDelay(10);
|
||||
//
|
||||
while (*tmpdst != *src) {swiDelay(10);}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool gbaFormatSave(u8 type)
|
||||
{
|
||||
switch (type) {
|
||||
case 1:
|
||||
case 2:
|
||||
// TODO: eeprom is not supported yet
|
||||
break;
|
||||
case 3:
|
||||
{
|
||||
// memset(data, 0, 1 << 15);
|
||||
u8 *data = new u8[1 << 15]();
|
||||
gbaWriteSave(0, data, 1 << 15, 3);
|
||||
delete[] data;
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
case 5:
|
||||
*(u8*)0x0a005555 = 0xaa;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a002aaa = 0x55;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a005555 = 0x80; // erase command
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a005555 = 0xaa;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a002aaa = 0x55;
|
||||
swiDelay(10);
|
||||
*(u8*)0x0a005555 = 0x10; // erase entire chip
|
||||
swiDelay(10);
|
||||
while (*(u8*)0x0a000000 != 0xff)
|
||||
swiDelay(10);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
73
arm9/source/gba.h
Normal file
73
arm9/source/gba.h
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 __SLOT2_H__
|
||||
#define __SLOT2_H__
|
||||
|
||||
enum cartTypeGBA {
|
||||
CART_GBA_NONE = 0,
|
||||
CART_GBA_GAME,
|
||||
CART_GBA_EMULATOR,
|
||||
CART_GBA_3IN1_256_V1,
|
||||
CART_GBA_3IN1_256_V2,
|
||||
CART_GBA_3IN1_512_V3,
|
||||
CART_GBA_FLASH
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
struct dataSlot2 {
|
||||
cartTypeGBA type;
|
||||
saveTypeGBA save;
|
||||
uint32 ez_ID;
|
||||
union {
|
||||
char cid[5];
|
||||
uint32 iid;
|
||||
};
|
||||
char name[13];
|
||||
};
|
||||
|
||||
|
||||
cartTypeGBA GetSlot2Type(uint32 id);
|
||||
saveTypeGBA GetSlot2SaveType(cartTypeGBA type);
|
||||
|
||||
// --------------------
|
||||
bool gbaIsGame();
|
||||
uint8 gbaGetSaveType();
|
||||
uint32 gbaGetSaveSize(uint8 type = 255);
|
||||
uint32 gbaGetSaveSizeLog2(uint8 type = 255);
|
||||
|
||||
bool gbaReadSave(u8 *dst, u32 src, u32 len, u8 type);
|
||||
bool gbaWriteSave(u32 dst, u8 *src, u32 len, u8 type);
|
||||
bool gbaFormatSave(u8 type);
|
||||
|
||||
|
||||
#endif // __SLOT2_H__
|
||||
Loading…
Reference in New Issue
Block a user