mirror of
https://github.com/rvtr/GodMode9i.git
synced 2025-11-02 00:11:07 -04:00
Add DS(i) save dumping
This commit is contained in:
parent
d440a238b3
commit
db8bf37a2a
@ -40,7 +40,8 @@ Once everything is downloaded and installed, `git clone` this repository, naviga
|
|||||||

|

|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
* RocketRobz: Creator of GodMode9i.
|
* @RocketRobz: Creator of GodMode9i.
|
||||||
* zacchi4k: Creator of the GodMode9i logo used in v1.3.1 and onwards.
|
* @zacchi4k: Creator of the GodMode9i logo used in v1.3.1 and onwards.
|
||||||
* devkitPro/WinterMute: devkitARM, libnds, original nds-hb-menu code, and screenshot code.
|
* @Edo9300: Save reading code from his save manager tool.
|
||||||
* d0k3: Original GM9 app and name for the Nintendo 3DS, which this is inspired by.
|
* @devkitPro: devkitARM, libnds, original nds-hb-menu code, and screenshot code.
|
||||||
|
* @d0k3: Original GM9 app and name for the Nintendo 3DS, which this is inspired by.
|
||||||
|
|||||||
443
arm9/source/auxspi.cpp
Normal file
443
arm9/source/auxspi.cpp
Normal file
@ -0,0 +1,443 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "auxspi.h"
|
||||||
|
#include "tonccpy.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
using std::max;
|
||||||
|
|
||||||
|
#include "auxspi_core.inc"
|
||||||
|
|
||||||
|
|
||||||
|
static uint8 data[0x8000] = {0};
|
||||||
|
|
||||||
|
#define EXTRA_ARRAY_SIZE 16
|
||||||
|
|
||||||
|
static u32 extra_id[EXTRA_ARRAY_SIZE];
|
||||||
|
static u8 extra_size[EXTRA_ARRAY_SIZE];
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
// local functions
|
||||||
|
uint8 jedec_table(uint32 id)
|
||||||
|
{
|
||||||
|
switch (id) {
|
||||||
|
// 256 kB
|
||||||
|
case 0x204012:
|
||||||
|
case 0x621600:
|
||||||
|
return 0x12;
|
||||||
|
// 512 kB
|
||||||
|
case 0x204013:
|
||||||
|
case 0x621100:
|
||||||
|
return 0x13;
|
||||||
|
// 1 MB
|
||||||
|
case 0x204014:
|
||||||
|
return 0x14;
|
||||||
|
// 2 MB (not sure if this exists, but I vaguely remember something...)
|
||||||
|
case 0x204015:
|
||||||
|
return 0x15;
|
||||||
|
// 8 MB (Band Brothers DX)
|
||||||
|
case 0x202017: // which one? (more work is required to unlock this save chip!)
|
||||||
|
case 0x204017:
|
||||||
|
return 0x17;
|
||||||
|
default: {
|
||||||
|
for (int i = 0; i < EXTRA_ARRAY_SIZE; i++) {
|
||||||
|
if (extra_id[i] == id)
|
||||||
|
return extra_size[i];
|
||||||
|
}
|
||||||
|
return 0; // unknown save type!
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 type2_size(auxspi_extra extra)
|
||||||
|
{
|
||||||
|
static const uint32 offset0 = (8*1024-1); // 8KB
|
||||||
|
static const uint32 offset1 = (2*8*1024-1); // 16KB
|
||||||
|
u8 buf1; // +0k data read -> write
|
||||||
|
u8 buf2; // +8k data read -> read
|
||||||
|
u8 buf3; // +0k ~data write
|
||||||
|
u8 buf4; // +8k data new comp buf2
|
||||||
|
auxspi_read_data(offset0, &buf1, 1, 2, extra);
|
||||||
|
auxspi_read_data(offset1, &buf2, 1, 2, extra);
|
||||||
|
buf3=~buf1;
|
||||||
|
auxspi_write_data(offset0, &buf3, 1, 2, extra);
|
||||||
|
auxspi_read_data (offset1, &buf4, 1, 2, extra);
|
||||||
|
auxspi_write_data(offset0, &buf1, 1, 2, extra);
|
||||||
|
if(buf4!=buf2) // +8k
|
||||||
|
return 0x0d; // 8KB(64kbit)
|
||||||
|
else
|
||||||
|
return 0x10; // 64KB(512kbit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
uint8 auxspi_save_type(auxspi_extra extra)
|
||||||
|
{
|
||||||
|
uint32 jedec = auxspi_save_jedec_id(extra); // 9f
|
||||||
|
int8 sr = auxspi_save_status_register(extra); // 05
|
||||||
|
|
||||||
|
if ((sr & 0xfd) == 0xF0 && (jedec == 0x00ffffff)) return 1;
|
||||||
|
if ((sr & 0xfd) == 0x00 && (jedec == 0x00ffffff)) return 2;
|
||||||
|
if ((sr & 0xfd) == 0x00 && (jedec != 0x00ffffff)) return 3;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 auxspi_save_size(auxspi_extra extra)
|
||||||
|
{
|
||||||
|
return 1 << auxspi_save_size_log_2(extra);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 auxspi_save_size_log_2(auxspi_extra extra)
|
||||||
|
{
|
||||||
|
uint8 type = auxspi_save_type(extra);
|
||||||
|
switch (type) {
|
||||||
|
case 1:
|
||||||
|
return 0x09; // 512 bytes
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
return type2_size(extra);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
return jedec_table(auxspi_save_jedec_id(extra));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 auxspi_save_jedec_id(auxspi_extra extra)
|
||||||
|
{
|
||||||
|
uint32 id = 0;
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
|
||||||
|
auxspi_open(0);
|
||||||
|
auxspi_write(0x9f);
|
||||||
|
id |= auxspi_read() << 16;
|
||||||
|
id |= auxspi_read() << 8;
|
||||||
|
id |= auxspi_read();
|
||||||
|
auxspi_close();
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8 auxspi_save_status_register(auxspi_extra extra)
|
||||||
|
{
|
||||||
|
uint8 sr = 0;
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
auxspi_open(0);
|
||||||
|
auxspi_write(0x05);
|
||||||
|
sr = auxspi_read();
|
||||||
|
auxspi_close();
|
||||||
|
return sr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void auxspi_read_data(uint32 addr, uint8* buf, uint32 cnt, uint8 type, auxspi_extra extra)
|
||||||
|
{
|
||||||
|
if (type == 0)
|
||||||
|
type = auxspi_save_type(extra);
|
||||||
|
if (type == 0)
|
||||||
|
return;
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
auxspi_open(0);
|
||||||
|
auxspi_write(0x03 | ((type == 1) ? addr>>8<<3 : 0));
|
||||||
|
if (type == 3) {
|
||||||
|
auxspi_write((addr >> 16) & 0xFF);
|
||||||
|
}
|
||||||
|
if (type >= 2) {
|
||||||
|
auxspi_write((addr >> 8) & 0xFF);
|
||||||
|
}
|
||||||
|
auxspi_write(addr & 0xFF);
|
||||||
|
while (cnt > 0) {
|
||||||
|
*buf++ = auxspi_read();
|
||||||
|
cnt--;
|
||||||
|
}
|
||||||
|
auxspi_close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void auxspi_write_data(uint32 addr, uint8 *buf, uint32 cnt, uint8 type, auxspi_extra extra)
|
||||||
|
{
|
||||||
|
if (type == 0)
|
||||||
|
type = auxspi_save_type();
|
||||||
|
if (type == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
uint32 addr_end = addr + cnt;
|
||||||
|
unsigned int i;
|
||||||
|
unsigned int maxblocks = 32;
|
||||||
|
if(type == 1) maxblocks = 16;
|
||||||
|
if(type == 2) maxblocks = 32;
|
||||||
|
if(type == 3) maxblocks = 256;
|
||||||
|
|
||||||
|
// we can only write a finite amount of data at once, so we need a separate loop
|
||||||
|
// for multiple passes.
|
||||||
|
while (addr < addr_end) {
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
// swiWaitForVBlank();
|
||||||
|
// for (int i = 0; i < 30; i++) { swiWaitForVBlank(); }
|
||||||
|
auxspi_open(0);
|
||||||
|
// set WEL (Write Enable Latch)
|
||||||
|
auxspi_write(0x06);
|
||||||
|
auxspi_close_lite();
|
||||||
|
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
// swiWaitForVBlank();
|
||||||
|
// for (int i = 0; i < 30; i++) { swiWaitForVBlank(); }
|
||||||
|
auxspi_open(0);
|
||||||
|
// send initial "write" command
|
||||||
|
if(type == 1) {
|
||||||
|
auxspi_write(0x02 | (addr & BIT(8)) >> (8-3));
|
||||||
|
auxspi_write(addr & 0xFF);
|
||||||
|
}
|
||||||
|
else if(type == 2) {
|
||||||
|
auxspi_write(0x02);
|
||||||
|
auxspi_write((addr >> 8) & 0xff);
|
||||||
|
auxspi_write(addr & 0xFF);
|
||||||
|
}
|
||||||
|
else if(type == 3) {
|
||||||
|
auxspi_write(0x02);
|
||||||
|
auxspi_write((addr >> 16) & 0xff);
|
||||||
|
auxspi_write((addr >> 8) & 0xff);
|
||||||
|
auxspi_write(addr & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i=0; addr < addr_end && i < maxblocks; i++, addr++) {
|
||||||
|
auxspi_write(*buf++);
|
||||||
|
}
|
||||||
|
// iprintf("%d\n",addr);
|
||||||
|
auxspi_close_lite();
|
||||||
|
// swiWaitForVBlank();
|
||||||
|
// iprintf("wrote\n");
|
||||||
|
// Delay a second for the DS card to stabilise
|
||||||
|
// swiWaitForVBlank();
|
||||||
|
// for (int i = 0; i < 30; i++) { swiWaitForVBlank(); }
|
||||||
|
// while(1) {
|
||||||
|
// swiWaitForVBlank();
|
||||||
|
// scanKeys();
|
||||||
|
// if(keysDown()&KEY_A) break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// wait programming to finish
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
// swiWaitForVBlank();
|
||||||
|
// for (int i = 0; i < 30; i++) { swiWaitForVBlank(); }
|
||||||
|
auxspi_open(0);
|
||||||
|
auxspi_write(5);
|
||||||
|
auxspi_wait_wip();
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_close();
|
||||||
|
// swiWaitForVBlank();
|
||||||
|
// for (int i = 0; i < 30; i++) { swiWaitForVBlank(); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void auxspi_disable_extra(auxspi_extra extra)
|
||||||
|
{
|
||||||
|
switch (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();
|
||||||
|
break;
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
swiWaitForVBlank();
|
||||||
|
}
|
||||||
|
|
||||||
|
void auxspi_disable_infrared()
|
||||||
|
{
|
||||||
|
auxspi_disable_infrared_core();
|
||||||
|
}
|
||||||
|
|
||||||
|
void auxspi_disable_big_protection()
|
||||||
|
{
|
||||||
|
static bool doonce = false;
|
||||||
|
if (doonce)
|
||||||
|
return;
|
||||||
|
doonce = true;
|
||||||
|
sysSetBusOwners(true, true);
|
||||||
|
|
||||||
|
auxspi_open(3);
|
||||||
|
auxspi_write(0xf1);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_close_lite();
|
||||||
|
|
||||||
|
auxspi_open(3);
|
||||||
|
auxspi_write(0x6);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_close_lite();
|
||||||
|
|
||||||
|
auxspi_open(3);
|
||||||
|
auxspi_write(0xfa);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_write(0x1);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_write(0x31);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_close();
|
||||||
|
// --
|
||||||
|
|
||||||
|
auxspi_open(3);
|
||||||
|
auxspi_write(0x14);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_close_lite();
|
||||||
|
|
||||||
|
auxspi_open(3);
|
||||||
|
auxspi_write(0x6);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_close_lite();
|
||||||
|
|
||||||
|
auxspi_open(3);
|
||||||
|
auxspi_write(0xf8);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_write(0x1);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_write(0x0);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_close();
|
||||||
|
// --
|
||||||
|
|
||||||
|
auxspi_open(3);
|
||||||
|
auxspi_write(0xe);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_close();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
auxspi_extra auxspi_has_extra()
|
||||||
|
{
|
||||||
|
sysSetBusOwners(true, true);
|
||||||
|
|
||||||
|
// Trying to read the save size in IR mode will fail on non-IR devices.
|
||||||
|
// If we have success, it is an IR device.
|
||||||
|
u8 size2 = auxspi_save_size_log_2(AUXSPI_INFRARED);
|
||||||
|
if (size2 > 0)
|
||||||
|
return AUXSPI_INFRARED;
|
||||||
|
|
||||||
|
// It is not an IR game, so maybe it is a regular game.
|
||||||
|
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)
|
||||||
|
|
||||||
|
return AUXSPI_FLASH_CARD;
|
||||||
|
}
|
||||||
|
|
||||||
|
void auxspi_erase(auxspi_extra extra)
|
||||||
|
{
|
||||||
|
uint8 type = auxspi_save_type(extra);
|
||||||
|
if (type == 3) {
|
||||||
|
uint32 size;
|
||||||
|
size = 1 << (auxspi_save_size_log_2(extra) - 16);
|
||||||
|
for (unsigned int i = 0; i < size; i++) {
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
auxspi_open(0);
|
||||||
|
// set WEL (Write Enable Latch)
|
||||||
|
auxspi_write(0x06);
|
||||||
|
auxspi_close_lite();
|
||||||
|
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
auxspi_open(0);
|
||||||
|
auxspi_write(0xd8);
|
||||||
|
auxspi_write(i);
|
||||||
|
auxspi_write(0);
|
||||||
|
auxspi_write(0);
|
||||||
|
auxspi_close_lite();
|
||||||
|
|
||||||
|
// wait for programming to finish
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
auxspi_open(0);
|
||||||
|
auxspi_write(5);
|
||||||
|
auxspi_wait_wip();
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_close();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int32 size = 1 << max(0, (auxspi_save_size_log_2(extra) - 15));
|
||||||
|
toncset(data, 0, 0x8000);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
auxspi_write_data(i << 15, data, 0x8000, type, extra);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void auxspi_erase_sector(u32 sector, auxspi_extra extra)
|
||||||
|
{
|
||||||
|
uint8 type = auxspi_save_type(extra);
|
||||||
|
if (type == 3) {
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
auxspi_open(0);
|
||||||
|
// set WEL (Write Enable Latch)
|
||||||
|
auxspi_write(0x06);
|
||||||
|
auxspi_close_lite();
|
||||||
|
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
auxspi_open(0);
|
||||||
|
auxspi_write(0xd8);
|
||||||
|
auxspi_write(sector & 0xff);
|
||||||
|
auxspi_write((sector >> 8) & 0xff);
|
||||||
|
auxspi_write((sector >> 8) & 0xff);
|
||||||
|
auxspi_close_lite();
|
||||||
|
|
||||||
|
// wait for programming to finish
|
||||||
|
if (extra)
|
||||||
|
auxspi_disable_extra(extra);
|
||||||
|
auxspi_open(0);
|
||||||
|
auxspi_write(5);
|
||||||
|
auxspi_wait_wip();
|
||||||
|
auxspi_wait_busy();
|
||||||
|
auxspi_close();
|
||||||
|
}
|
||||||
|
}
|
||||||
82
arm9/source/auxspi.h
Normal file
82
arm9/source/auxspi.h
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* 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.h: Header for auxspi.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
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
This is a thin reimplementation of the AUXSPI protocol at low levels.
|
||||||
|
It is used to implement various experimental procedures to test accessing the HG/SS save chip. */
|
||||||
|
|
||||||
|
#ifndef SPI_BUS_H
|
||||||
|
#define SPI_BUS_H
|
||||||
|
|
||||||
|
#include <nds.h>
|
||||||
|
|
||||||
|
// This is a handy typedef for nonstandard SPI buses, i.e. games with some
|
||||||
|
// extra hardware on the cartridge. The following values mean:
|
||||||
|
// AUXSPI_DEFAULT: A regular game with no exotic hardware.
|
||||||
|
// AUXSPI_INFRARED: A game with an infrared transceiver.
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// These functions reimplement relevant parts of "card.cpp", in a way that is easier to modify.
|
||||||
|
uint8 auxspi_save_type(auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
uint32 auxspi_save_size(auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
uint8 auxspi_save_size_log_2(auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
uint32 auxspi_save_jedec_id(auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
uint8 auxspi_save_status_register(auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
void auxspi_read_data(uint32 addr, uint8* buf, uint32 cnt, uint8 type = 0,auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
void auxspi_write_data(uint32 addr, uint8 *buf, uint32 cnt, uint8 type = 0,auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
void auxspi_erase(auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
void auxspi_erase_sector(u32 sector, auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
|
||||||
|
// These functions are used to identify exotic hardware.
|
||||||
|
auxspi_extra auxspi_has_extra();
|
||||||
|
//bool auxspi_has_infrared();
|
||||||
|
|
||||||
|
void auxspi_disable_extra(auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
void auxspi_disable_infrared();
|
||||||
|
void auxspi_disable_big_protection();
|
||||||
|
|
||||||
|
// The following function returns true if this is a type 3 save (big saves, Flash memory), but
|
||||||
|
// the JEDEC ID is not known.
|
||||||
|
bool auxspi_is_unknown_type3(auxspi_extra extra = AUXSPI_DEFAULT);
|
||||||
|
|
||||||
|
#endif
|
||||||
92
arm9/source/auxspi_core.inc
Normal file
92
arm9/source/auxspi_core.inc
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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_core.inc: A thin reimplementation of the AUXSPI protocol
|
||||||
|
* (low 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <nds.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
inline void auxspi_wait_busy()
|
||||||
|
{
|
||||||
|
while (REG_AUXSPICNT & 0x80);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void auxspi_wait_wip()
|
||||||
|
{
|
||||||
|
do { REG_AUXSPIDATA = 0; auxspi_wait_busy(); } while (REG_AUXSPIDATA & 0x01); // WIP (Write In Progress) ?
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void auxspi_open(uint8 device)
|
||||||
|
{
|
||||||
|
REG_AUXSPICNT = 0xa040 | (device & 3);
|
||||||
|
auxspi_wait_busy();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void auxspi_close()
|
||||||
|
{
|
||||||
|
REG_AUXSPIDATA = 0;
|
||||||
|
auxspi_wait_busy();
|
||||||
|
REG_AUXSPICNT = 0;
|
||||||
|
auxspi_wait_busy();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void auxspi_close_lite()
|
||||||
|
{
|
||||||
|
REG_AUXSPICNT = 0x40;
|
||||||
|
auxspi_wait_busy();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint8 auxspi_transfer(uint8 out)
|
||||||
|
{
|
||||||
|
REG_AUXSPIDATA = out;
|
||||||
|
auxspi_wait_busy();
|
||||||
|
return REG_AUXSPIDATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void auxspi_write(uint8 out)
|
||||||
|
{
|
||||||
|
auxspi_transfer(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint8 auxspi_read()
|
||||||
|
{
|
||||||
|
return auxspi_transfer(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline uint16 auxspi_read_16()
|
||||||
|
{
|
||||||
|
REG_AUXSPIDATA = 0;
|
||||||
|
auxspi_wait_busy();
|
||||||
|
return REG_AUXSPIDATA;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void auxspi_disable_infrared_core()
|
||||||
|
{
|
||||||
|
auxspi_open(0);
|
||||||
|
swiDelay(1200);
|
||||||
|
auxspi_open(2);
|
||||||
|
auxspi_write(0);
|
||||||
|
swiDelay(1200);
|
||||||
|
}
|
||||||
@ -29,11 +29,9 @@
|
|||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "date.h"
|
#include "date.h"
|
||||||
#include "screenshot.h"
|
#include "screenshot.h"
|
||||||
|
#include "dumpOperations.h"
|
||||||
#include "driveOperations.h"
|
#include "driveOperations.h"
|
||||||
#include "fileOperations.h"
|
#include "fileOperations.h"
|
||||||
#include "ndsheaderbanner.h"
|
|
||||||
#include "read_card.h"
|
|
||||||
#include "tonccpy.h"
|
|
||||||
|
|
||||||
#define SCREEN_COLS 32
|
#define SCREEN_COLS 32
|
||||||
#define ENTRIES_PER_SCREEN 22
|
#define ENTRIES_PER_SCREEN 22
|
||||||
@ -53,212 +51,6 @@ static int dmMaxCursors = -1;
|
|||||||
|
|
||||||
static u8 gbaFixedValue = 0;
|
static u8 gbaFixedValue = 0;
|
||||||
|
|
||||||
void ndsCardDump(void) {
|
|
||||||
int pressed = 0;
|
|
||||||
|
|
||||||
printf ("\x1b[0;27H");
|
|
||||||
printf ("\x1B[42m"); // Print green color
|
|
||||||
printf ("_____"); // Clear time
|
|
||||||
consoleInit(NULL, 1, BgType_Text4bpp, BgSize_T_256x256, 15, 0, false, true);
|
|
||||||
printf ("\x1B[47m"); // Print foreground white color
|
|
||||||
printf("Dump NDS card ROM to\n");
|
|
||||||
printf("\"%s:/gm9i/out\"?\n", (sdMounted ? "sd" : "fat"));
|
|
||||||
printf("(<A> yes, <Y> trim, <B> no)");
|
|
||||||
while (true) {
|
|
||||||
// Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do
|
|
||||||
do {
|
|
||||||
scanKeys();
|
|
||||||
pressed = keysDownRepeat();
|
|
||||||
swiWaitForVBlank();
|
|
||||||
} while (!(pressed & KEY_A) && !(pressed & KEY_Y) && !(pressed & KEY_B));
|
|
||||||
|
|
||||||
if ((pressed & KEY_A) || (pressed & KEY_Y)) {
|
|
||||||
consoleClear();
|
|
||||||
char folderPath[2][256];
|
|
||||||
sprintf(folderPath[0], "%s:/gm9i", (sdMounted ? "sd" : "fat"));
|
|
||||||
sprintf(folderPath[1], "%s:/gm9i/out", (sdMounted ? "sd" : "fat"));
|
|
||||||
if (access(folderPath[0], F_OK) != 0) {
|
|
||||||
printf("Creating directory...");
|
|
||||||
mkdir(folderPath[0], 0777);
|
|
||||||
}
|
|
||||||
if (access(folderPath[1], F_OK) != 0) {
|
|
||||||
printf ("\x1b[0;0H");
|
|
||||||
printf("Creating directory...");
|
|
||||||
mkdir(folderPath[1], 0777);
|
|
||||||
}
|
|
||||||
consoleClear();
|
|
||||||
// Read header
|
|
||||||
sNDSHeaderExt* ndsCardHeader = (sNDSHeaderExt*)malloc(0x1000);
|
|
||||||
if (cardInit (ndsCardHeader) == 0) {
|
|
||||||
printf("Dumping...\n");
|
|
||||||
printf("Do not remove the NDS card.\n");
|
|
||||||
} else {
|
|
||||||
printf("Unable to dump the ROM.\n");
|
|
||||||
for (int i = 0; i < 60*2; i++) {
|
|
||||||
swiWaitForVBlank();
|
|
||||||
}
|
|
||||||
free(ndsCardHeader);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
char gameTitle[13] = {0};
|
|
||||||
tonccpy(gameTitle, ndsCardHeader->gameTitle, 12);
|
|
||||||
char gameCode[7] = {0};
|
|
||||||
tonccpy(gameCode, ndsCardHeader->gameCode, 6);
|
|
||||||
bool trimRom = (pressed & KEY_Y);
|
|
||||||
char romBuffer[0x200];
|
|
||||||
char destPath[256];
|
|
||||||
sprintf(destPath, "%s:/gm9i/out/%s_%s_%x%s.nds", (sdMounted ? "sd" : "fat"), gameTitle, gameCode, ndsCardHeader->romversion, (trimRom ? "_trim" : ""));
|
|
||||||
//char destSavPath[256];
|
|
||||||
//sprintf(destSavPath, "fat:/gm9i/out/%s_%s%s_%x.sav", gbaHeaderGameTitle, gbaHeaderGameCode, gbaHeaderMakerCode, gbaHeaderSoftwareVersion);
|
|
||||||
// Determine ROM size
|
|
||||||
u32 romSize = 0;
|
|
||||||
if (trimRom) {
|
|
||||||
romSize = ((ndsCardHeader->unitCode != 0) && (ndsCardHeader->twlRomSize > 0))
|
|
||||||
? ndsCardHeader->twlRomSize : ndsCardHeader->romSize;
|
|
||||||
} else switch (ndsCardHeader->deviceSize) {
|
|
||||||
case 0x00:
|
|
||||||
romSize = 0x20000;
|
|
||||||
break;
|
|
||||||
case 0x01:
|
|
||||||
romSize = 0x40000;
|
|
||||||
break;
|
|
||||||
case 0x02:
|
|
||||||
romSize = 0x80000;
|
|
||||||
break;
|
|
||||||
case 0x03:
|
|
||||||
romSize = 0x100000;
|
|
||||||
break;
|
|
||||||
case 0x04:
|
|
||||||
romSize = 0x200000;
|
|
||||||
break;
|
|
||||||
case 0x05:
|
|
||||||
romSize = 0x400000;
|
|
||||||
break;
|
|
||||||
case 0x06:
|
|
||||||
romSize = 0x800000;
|
|
||||||
break;
|
|
||||||
case 0x07:
|
|
||||||
romSize = 0x1000000;
|
|
||||||
break;
|
|
||||||
case 0x08:
|
|
||||||
romSize = 0x2000000;
|
|
||||||
break;
|
|
||||||
case 0x09:
|
|
||||||
romSize = 0x4000000;
|
|
||||||
break;
|
|
||||||
case 0x0A:
|
|
||||||
romSize = 0x8000000;
|
|
||||||
break;
|
|
||||||
case 0x0B:
|
|
||||||
romSize = 0x10000000;
|
|
||||||
break;
|
|
||||||
case 0x0C:
|
|
||||||
romSize = 0x20000000;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Dump!
|
|
||||||
remove(destPath);
|
|
||||||
FILE* destinationFile = fopen(destPath, "wb");
|
|
||||||
for (u32 src = 0; src < romSize; src += 0x200) {
|
|
||||||
printf ("\x1b[8;0H");
|
|
||||||
printf ("Progress:\n");
|
|
||||||
printf ("%i/%i Bytes ", (int)src, (int)romSize);
|
|
||||||
cardRead (src, romBuffer);
|
|
||||||
fwrite(romBuffer, 1, 0x200, destinationFile);
|
|
||||||
}
|
|
||||||
fclose(destinationFile);
|
|
||||||
free(ndsCardHeader);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (pressed & KEY_B) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void gbaCartDump(void) {
|
|
||||||
int pressed = 0;
|
|
||||||
|
|
||||||
printf ("\x1b[0;27H");
|
|
||||||
printf ("\x1B[42m"); // Print green color
|
|
||||||
printf ("_____"); // Clear time
|
|
||||||
consoleInit(NULL, 1, BgType_Text4bpp, BgSize_T_256x256, 15, 0, false, true);
|
|
||||||
printf ("\x1B[47m"); // Print foreground white color
|
|
||||||
printf("Dump GBA cart ROM to\n");
|
|
||||||
printf("\"fat:/gm9i/out\"?\n");
|
|
||||||
printf("(<A> yes, <B> no)");
|
|
||||||
while (true) {
|
|
||||||
// Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do
|
|
||||||
do {
|
|
||||||
scanKeys();
|
|
||||||
pressed = keysDownRepeat();
|
|
||||||
swiWaitForVBlank();
|
|
||||||
} while (!(pressed & KEY_A) && !(pressed & KEY_B));
|
|
||||||
|
|
||||||
if (pressed & KEY_A) {
|
|
||||||
consoleClear();
|
|
||||||
if (access("fat:/gm9i", F_OK) != 0) {
|
|
||||||
printf("Creating directory...");
|
|
||||||
mkdir("fat:/gm9i", 0777);
|
|
||||||
}
|
|
||||||
if (access("fat:/gm9i/out", F_OK) != 0) {
|
|
||||||
printf ("\x1b[0;0H");
|
|
||||||
printf("Creating directory...");
|
|
||||||
mkdir("fat:/gm9i/out", 0777);
|
|
||||||
}
|
|
||||||
char gbaHeaderGameTitle[13] = "\0";
|
|
||||||
char gbaHeaderGameCode[5] = "\0";
|
|
||||||
char gbaHeaderMakerCode[3] = "\0";
|
|
||||||
for (int i = 0; i < 12; i++) {
|
|
||||||
gbaHeaderGameTitle[i] = *(char*)(0x080000A0+i);
|
|
||||||
if (*(u8*)(0x080000A0+i) == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 4; i++) {
|
|
||||||
gbaHeaderGameCode[i] = *(char*)(0x080000AC+i);
|
|
||||||
if (*(u8*)(0x080000AC+i) == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
gbaHeaderMakerCode[i] = *(char*)(0x080000B0+i);
|
|
||||||
}
|
|
||||||
u8 gbaHeaderSoftwareVersion = *(u8*)(0x080000BC);
|
|
||||||
char destPath[256];
|
|
||||||
char destSavPath[256];
|
|
||||||
snprintf(destPath, sizeof(destPath), "fat:/gm9i/out/%s_%s%s_%x.gba", gbaHeaderGameTitle, gbaHeaderGameCode, gbaHeaderMakerCode, gbaHeaderSoftwareVersion);
|
|
||||||
snprintf(destSavPath, sizeof(destSavPath), "fat:/gm9i/out/%s_%s%s_%x.sav", gbaHeaderGameTitle, gbaHeaderGameCode, gbaHeaderMakerCode, gbaHeaderSoftwareVersion);
|
|
||||||
consoleClear();
|
|
||||||
printf("Dumping...\n");
|
|
||||||
printf("Do not remove the GBA cart.\n");
|
|
||||||
// Determine ROM size
|
|
||||||
u32 romSize = 0x02000000;
|
|
||||||
for (u32 i = 0x09FE0000; i > 0x08000000; i -= 0x20000) {
|
|
||||||
if (*(u32*)(i) == 0xFFFE0000) {
|
|
||||||
romSize -= 0x20000;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Dump!
|
|
||||||
remove(destPath);
|
|
||||||
FILE* destinationFile = fopen(destPath, "wb");
|
|
||||||
fwrite((void*)0x08000000, 1, romSize, destinationFile);
|
|
||||||
fclose(destinationFile);
|
|
||||||
// Save file
|
|
||||||
remove(destSavPath);
|
|
||||||
destinationFile = fopen(destSavPath, "wb");
|
|
||||||
fwrite((void*)0x0A000000, 1, 0x10000, destinationFile);
|
|
||||||
fclose(destinationFile);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (pressed & KEY_B) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void dm_drawTopScreen(void) {
|
void dm_drawTopScreen(void) {
|
||||||
/*if (!ramDumped) {
|
/*if (!ramDumped) {
|
||||||
printf ("Dumping RAM...");
|
printf ("Dumping RAM...");
|
||||||
|
|||||||
251
arm9/source/dumpOperations.cpp
Normal file
251
arm9/source/dumpOperations.cpp
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
#include <nds.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
#include "auxspi.h"
|
||||||
|
#include "driveOperations.h"
|
||||||
|
#include "ndsheaderbanner.h"
|
||||||
|
#include "read_card.h"
|
||||||
|
#include "tonccpy.h"
|
||||||
|
|
||||||
|
void ndsCardSaveDump(const char* filename) {
|
||||||
|
std::ofstream output(filename, std::ofstream::binary);
|
||||||
|
if(output.is_open()) {
|
||||||
|
auxspi_extra card_type = auxspi_has_extra();
|
||||||
|
consoleClear();
|
||||||
|
printf("Dumping save...\n");
|
||||||
|
printf("Do not remove the NDS card.\n");
|
||||||
|
unsigned char* buffer;
|
||||||
|
if(card_type == AUXSPI_INFRARED) {
|
||||||
|
int size = auxspi_save_size_log_2(card_type);
|
||||||
|
int size_blocks = 1 << std::max(0, (int8(size) - 18));
|
||||||
|
int type = auxspi_save_type(card_type);
|
||||||
|
if(size < 16)
|
||||||
|
size_blocks = 1;
|
||||||
|
else
|
||||||
|
size_blocks = 1 << (size - 16);
|
||||||
|
u32 LEN = std::min(1 << size, 1 << 16);
|
||||||
|
buffer = new unsigned char[LEN*size_blocks];
|
||||||
|
auxspi_read_data(0, buffer, LEN*size_blocks, type, card_type);
|
||||||
|
output.write((char*)buffer, LEN*size_blocks);
|
||||||
|
} else {
|
||||||
|
int type = cardEepromGetType();
|
||||||
|
int size = cardEepromGetSize();
|
||||||
|
buffer = new unsigned char[size];
|
||||||
|
cardReadEeprom(0, buffer, size, type);
|
||||||
|
output.write((char*)buffer, size);
|
||||||
|
}
|
||||||
|
delete[] buffer;
|
||||||
|
}
|
||||||
|
output.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ndsCardDump(void) {
|
||||||
|
int pressed = 0;
|
||||||
|
|
||||||
|
printf ("\x1b[0;27H");
|
||||||
|
printf ("\x1B[42m"); // Print green color
|
||||||
|
printf ("_____"); // Clear time
|
||||||
|
consoleInit(NULL, 1, BgType_Text4bpp, BgSize_T_256x256, 15, 0, false, true);
|
||||||
|
printf ("\x1B[47m"); // Print foreground white color
|
||||||
|
printf("Dump NDS card ROM to\n");
|
||||||
|
printf("\"%s:/gm9i/out\"?\n", (sdMounted ? "sd" : "fat"));
|
||||||
|
printf("(<A> yes, <Y> trim, <B> no)");
|
||||||
|
while (true) {
|
||||||
|
// Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do
|
||||||
|
do {
|
||||||
|
scanKeys();
|
||||||
|
pressed = keysDownRepeat();
|
||||||
|
swiWaitForVBlank();
|
||||||
|
} while (!(pressed & KEY_A) && !(pressed & KEY_Y) && !(pressed & KEY_B));
|
||||||
|
|
||||||
|
if ((pressed & KEY_A) || (pressed & KEY_Y)) {
|
||||||
|
consoleClear();
|
||||||
|
char folderPath[2][256];
|
||||||
|
sprintf(folderPath[0], "%s:/gm9i", (sdMounted ? "sd" : "fat"));
|
||||||
|
sprintf(folderPath[1], "%s:/gm9i/out", (sdMounted ? "sd" : "fat"));
|
||||||
|
if (access(folderPath[0], F_OK) != 0) {
|
||||||
|
printf("Creating directory...");
|
||||||
|
mkdir(folderPath[0], 0777);
|
||||||
|
}
|
||||||
|
if (access(folderPath[1], F_OK) != 0) {
|
||||||
|
printf ("\x1b[0;0H");
|
||||||
|
printf("Creating directory...");
|
||||||
|
mkdir(folderPath[1], 0777);
|
||||||
|
}
|
||||||
|
consoleClear();
|
||||||
|
// Read header
|
||||||
|
sNDSHeaderExt* ndsCardHeader = (sNDSHeaderExt*)malloc(0x1000);
|
||||||
|
if (cardInit (ndsCardHeader) == 0) {
|
||||||
|
printf("Dumping...\n");
|
||||||
|
printf("Do not remove the NDS card.\n");
|
||||||
|
} else {
|
||||||
|
printf("Unable to dump the ROM.\n");
|
||||||
|
for (int i = 0; i < 60*2; i++) {
|
||||||
|
swiWaitForVBlank();
|
||||||
|
}
|
||||||
|
free(ndsCardHeader);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char gameTitle[13] = {0};
|
||||||
|
tonccpy(gameTitle, ndsCardHeader->gameTitle, 12);
|
||||||
|
char gameCode[7] = {0};
|
||||||
|
tonccpy(gameCode, ndsCardHeader->gameCode, 6);
|
||||||
|
bool trimRom = (pressed & KEY_Y);
|
||||||
|
char romBuffer[0x200];
|
||||||
|
char destPath[256];
|
||||||
|
sprintf(destPath, "%s:/gm9i/out/%s_%s_%x%s.nds", (sdMounted ? "sd" : "fat"), gameTitle, gameCode, ndsCardHeader->romversion, (trimRom ? "_trim" : ""));
|
||||||
|
char destSavPath[256];
|
||||||
|
sprintf(destSavPath, "%s:/gm9i/out/%s_%s_%x%s.sav", (sdMounted ? "sd" : "fat"), gameTitle, gameCode, ndsCardHeader->romversion, (trimRom ? "_trim" : ""));
|
||||||
|
// Determine ROM size
|
||||||
|
u32 romSize = 0;
|
||||||
|
if (trimRom) {
|
||||||
|
romSize = ((ndsCardHeader->unitCode != 0) && (ndsCardHeader->twlRomSize > 0))
|
||||||
|
? ndsCardHeader->twlRomSize : ndsCardHeader->romSize;
|
||||||
|
} else switch (ndsCardHeader->deviceSize) {
|
||||||
|
case 0x00:
|
||||||
|
romSize = 0x20000;
|
||||||
|
break;
|
||||||
|
case 0x01:
|
||||||
|
romSize = 0x40000;
|
||||||
|
break;
|
||||||
|
case 0x02:
|
||||||
|
romSize = 0x80000;
|
||||||
|
break;
|
||||||
|
case 0x03:
|
||||||
|
romSize = 0x100000;
|
||||||
|
break;
|
||||||
|
case 0x04:
|
||||||
|
romSize = 0x200000;
|
||||||
|
break;
|
||||||
|
case 0x05:
|
||||||
|
romSize = 0x400000;
|
||||||
|
break;
|
||||||
|
case 0x06:
|
||||||
|
romSize = 0x800000;
|
||||||
|
break;
|
||||||
|
case 0x07:
|
||||||
|
romSize = 0x1000000;
|
||||||
|
break;
|
||||||
|
case 0x08:
|
||||||
|
romSize = 0x2000000;
|
||||||
|
break;
|
||||||
|
case 0x09:
|
||||||
|
romSize = 0x4000000;
|
||||||
|
break;
|
||||||
|
case 0x0A:
|
||||||
|
romSize = 0x8000000;
|
||||||
|
break;
|
||||||
|
case 0x0B:
|
||||||
|
romSize = 0x10000000;
|
||||||
|
break;
|
||||||
|
case 0x0C:
|
||||||
|
romSize = 0x20000000;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Dump!
|
||||||
|
remove(destPath);
|
||||||
|
FILE* destinationFile = fopen(destPath, "wb");
|
||||||
|
for (u32 src = 0; src < romSize; src += 0x200) {
|
||||||
|
printf ("\x1b[8;0H");
|
||||||
|
printf ("Progress:\n");
|
||||||
|
printf ("%i/%i Bytes ", (int)src, (int)romSize);
|
||||||
|
cardRead (src, romBuffer);
|
||||||
|
fwrite(romBuffer, 1, 0x200, destinationFile);
|
||||||
|
}
|
||||||
|
fclose(destinationFile);
|
||||||
|
ndsCardSaveDump(destSavPath);
|
||||||
|
free(ndsCardHeader);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (pressed & KEY_B) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void gbaCartDump(void) {
|
||||||
|
int pressed = 0;
|
||||||
|
|
||||||
|
printf ("\x1b[0;27H");
|
||||||
|
printf ("\x1B[42m"); // Print green color
|
||||||
|
printf ("_____"); // Clear time
|
||||||
|
consoleInit(NULL, 1, BgType_Text4bpp, BgSize_T_256x256, 15, 0, false, true);
|
||||||
|
printf ("\x1B[47m"); // Print foreground white color
|
||||||
|
printf("Dump GBA cart ROM to\n");
|
||||||
|
printf("\"fat:/gm9i/out\"?\n");
|
||||||
|
printf("(<A> yes, <B> no)");
|
||||||
|
while (true) {
|
||||||
|
// Power saving loop. Only poll the keys once per frame and sleep the CPU if there is nothing else to do
|
||||||
|
do {
|
||||||
|
scanKeys();
|
||||||
|
pressed = keysDownRepeat();
|
||||||
|
swiWaitForVBlank();
|
||||||
|
} while (!(pressed & KEY_A) && !(pressed & KEY_B));
|
||||||
|
|
||||||
|
if (pressed & KEY_A) {
|
||||||
|
consoleClear();
|
||||||
|
if (access("fat:/gm9i", F_OK) != 0) {
|
||||||
|
printf("Creating directory...");
|
||||||
|
mkdir("fat:/gm9i", 0777);
|
||||||
|
}
|
||||||
|
if (access("fat:/gm9i/out", F_OK) != 0) {
|
||||||
|
printf ("\x1b[0;0H");
|
||||||
|
printf("Creating directory...");
|
||||||
|
mkdir("fat:/gm9i/out", 0777);
|
||||||
|
}
|
||||||
|
char gbaHeaderGameTitle[13] = "\0";
|
||||||
|
char gbaHeaderGameCode[5] = "\0";
|
||||||
|
char gbaHeaderMakerCode[3] = "\0";
|
||||||
|
for (int i = 0; i < 12; i++) {
|
||||||
|
gbaHeaderGameTitle[i] = *(char*)(0x080000A0+i);
|
||||||
|
if (*(u8*)(0x080000A0+i) == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
gbaHeaderGameCode[i] = *(char*)(0x080000AC+i);
|
||||||
|
if (*(u8*)(0x080000AC+i) == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
gbaHeaderMakerCode[i] = *(char*)(0x080000B0+i);
|
||||||
|
}
|
||||||
|
u8 gbaHeaderSoftwareVersion = *(u8*)(0x080000BC);
|
||||||
|
char destPath[256];
|
||||||
|
char destSavPath[256];
|
||||||
|
snprintf(destPath, sizeof(destPath), "fat:/gm9i/out/%s_%s%s_%x.gba", gbaHeaderGameTitle, gbaHeaderGameCode, gbaHeaderMakerCode, gbaHeaderSoftwareVersion);
|
||||||
|
snprintf(destSavPath, sizeof(destSavPath), "fat:/gm9i/out/%s_%s%s_%x.sav", gbaHeaderGameTitle, gbaHeaderGameCode, gbaHeaderMakerCode, gbaHeaderSoftwareVersion);
|
||||||
|
consoleClear();
|
||||||
|
printf("Dumping...\n");
|
||||||
|
printf("Do not remove the GBA cart.\n");
|
||||||
|
// Determine ROM size
|
||||||
|
u32 romSize = 0x02000000;
|
||||||
|
for (u32 i = 0x09FE0000; i > 0x08000000; i -= 0x20000) {
|
||||||
|
if (*(u32*)(i) == 0xFFFE0000) {
|
||||||
|
romSize -= 0x20000;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Dump!
|
||||||
|
remove(destPath);
|
||||||
|
FILE* destinationFile = fopen(destPath, "wb");
|
||||||
|
fwrite((void*)0x08000000, 1, romSize, destinationFile);
|
||||||
|
fclose(destinationFile);
|
||||||
|
// Save file
|
||||||
|
remove(destSavPath);
|
||||||
|
destinationFile = fopen(destSavPath, "wb");
|
||||||
|
fwrite((void*)0x0A000000, 1, 0x10000, destinationFile);
|
||||||
|
fclose(destinationFile);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (pressed & KEY_B) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
arm9/source/dumpOperations.h
Normal file
7
arm9/source/dumpOperations.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#ifndef DUMPING_H
|
||||||
|
#define DUMPING_H
|
||||||
|
|
||||||
|
extern void ndsCardDump(void);
|
||||||
|
extern void gbaCartDump(void);
|
||||||
|
|
||||||
|
#endif //DUMPING_H
|
||||||
Loading…
Reference in New Issue
Block a user