mirror of
https://github.com/suloku/savegame-manager.git
synced 2026-03-23 10:57:57 -05:00
- fix missing string output under various conditions - add "extra" field to version in title string (alpha/beta/rc/...) - don't force reboot after working on a GBA game - some updates to instruction file - delete pnps file, which is not a part of the code (I hope this is all that was changed. Tried to fix one final bug, didn't work, tried to revert everything that did not work.)
493 lines
10 KiB
C++
493 lines
10 KiB
C++
/*
|
|
* 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.
|
|
*
|
|
* main.cpp: main file
|
|
*
|
|
* 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 <nds/arm9/dldi.h>
|
|
|
|
#include <sys/dir.h>
|
|
#include <nds/arm9/console.h>
|
|
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include <dirent.h>
|
|
|
|
#include <algorithm>
|
|
|
|
#include "gba.h"
|
|
#include "dsi.h"
|
|
#include "display.h"
|
|
#include "dsCard.h"
|
|
#include "hardware.h"
|
|
#include "fileselect.h"
|
|
#include "strings.h"
|
|
|
|
#include "libini.h"
|
|
|
|
#include "globals.h"
|
|
|
|
using std::max;
|
|
|
|
|
|
char bootdir[256] = "/";
|
|
|
|
|
|
#define REBOOT_WIFI
|
|
|
|
|
|
// ============================================================================
|
|
void mode_dsi()
|
|
{
|
|
// TODO: test is SD card is present!
|
|
|
|
// use 3in1 to buffer data
|
|
displayStateF(STR_EMPTY);
|
|
displayPrintUpper(true);
|
|
displayPrintLower();
|
|
|
|
// DSi mode, does nothing at the moment
|
|
displayWarning2F(STR_BOOT_MODE_UNSUPPORTED);
|
|
while(1);
|
|
|
|
touchPosition touchXY;
|
|
while(1) {
|
|
swiWaitForVBlank();
|
|
touchRead(&touchXY);
|
|
|
|
// backup
|
|
if ((touchXY.py > 8*0) && (touchXY.py < 8*8)) {
|
|
hwBackupDSi();
|
|
}
|
|
|
|
// restore
|
|
if ((touchXY.py > 8*8) && (touchXY.py < 8*16)) {
|
|
hwRestoreDSi();
|
|
}
|
|
|
|
// erase
|
|
if ((touchXY.py > 8*16) && (touchXY.py < 8*24)) {
|
|
swap_cart();
|
|
displayPrintUpper();
|
|
hwErase();
|
|
}
|
|
}
|
|
}
|
|
|
|
void mode_slot2()
|
|
{
|
|
// use slot2 DLDI device to store data
|
|
displayStateF(STR_EMPTY);
|
|
displayPrintUpper(true);
|
|
displayPrintLower();
|
|
|
|
touchPosition touchXY;
|
|
while(1) {
|
|
swiWaitForVBlank();
|
|
touchRead(&touchXY);
|
|
|
|
// backup
|
|
if ((touchXY.py > 8*0) && (touchXY.py < 8*8)) {
|
|
hwBackupSlot2();
|
|
displayPrintLower();
|
|
}
|
|
|
|
// restore
|
|
if ((touchXY.py > 8*8) && (touchXY.py < 8*16)) {
|
|
hwRestoreSlot2();
|
|
displayPrintLower();
|
|
}
|
|
|
|
// erase
|
|
if ((touchXY.py > 8*16) && (touchXY.py < 8*24)) {
|
|
swap_cart();
|
|
displayPrintUpper();
|
|
hwErase();
|
|
displayPrintLower();
|
|
}
|
|
}
|
|
}
|
|
|
|
void mode_3in1()
|
|
{
|
|
displayPrintUpper(true);
|
|
|
|
dsCardData data2;
|
|
uint32 ime = hwGrab3in1();
|
|
ReadNorFlash(data, 0, 0x8000);
|
|
hwRelease3in1(ime);
|
|
memcpy(&data2, &data[0x1000], sizeof(data2));
|
|
if ((data2.data[0] == RS_BACKUP) && (data2.data[1] == 0) && (data2.data[3] == 0xffff00ff)) {
|
|
uint32 size = data2.data[2];
|
|
char name[13];
|
|
memcpy(&name[0], &data2.name[0], 12);
|
|
name[12] = 0;
|
|
displayMessageF(STR_HW_3IN1_CLEAR_FLAG); // lower screen is used for file browser
|
|
hwFormatNor(0, 1); // clear reboot flag
|
|
hwDump3in1(size, name);
|
|
}
|
|
displayMessageF(STR_EMPTY);
|
|
displayProgressBar(0,0);
|
|
displayPrintLower();
|
|
|
|
touchPosition touchXY;
|
|
while(1) {
|
|
swiWaitForVBlank();
|
|
touchRead(&touchXY);
|
|
|
|
// backup
|
|
if ((touchXY.py > 8*0) && (touchXY.py < 8*8)) {
|
|
hwBackup3in1();
|
|
}
|
|
|
|
// restore
|
|
if ((touchXY.py > 8*8) && (touchXY.py < 8*16)) {
|
|
hwRestore3in1();
|
|
}
|
|
|
|
// erase
|
|
if ((touchXY.py > 8*16) && (touchXY.py < 8*24)) {
|
|
swap_cart();
|
|
displayPrintUpper();
|
|
hwErase();
|
|
displayPrintLower();
|
|
}
|
|
}
|
|
}
|
|
|
|
void mode_gba()
|
|
{
|
|
// This function takes some time, since it needs to parse the entire GBA module
|
|
// for a magic string. It is only called when truly necessary. (i.e. once for now)
|
|
u8 gbatype = gbaGetSaveType();
|
|
|
|
// use 3in1 to buffer data
|
|
displayStateF(STR_EMPTY);
|
|
gbatype = gbaGetSaveType();
|
|
displayPrintUpper(true);
|
|
displayPrintLower();
|
|
|
|
touchPosition touchXY;
|
|
while(1) {
|
|
swiWaitForVBlank();
|
|
touchRead(&touchXY);
|
|
|
|
// backup
|
|
if ((touchXY.py > 8*0) && (touchXY.py < 8*8)) {
|
|
displayPrintUpper();
|
|
hwBackupGBA(gbatype);
|
|
displayPrintLower();
|
|
}
|
|
|
|
// restore
|
|
if ((touchXY.py > 8*8) && (touchXY.py < 8*16)) {
|
|
displayPrintUpper();
|
|
hwRestoreGBA();
|
|
displayPrintLower();
|
|
}
|
|
|
|
// erase
|
|
if ((touchXY.py > 8*16) && (touchXY.py < 8*24)) {
|
|
displayPrintUpper();
|
|
hwEraseGBA();
|
|
displayPrintLower();
|
|
}
|
|
}
|
|
}
|
|
|
|
void mode_wifi()
|
|
{
|
|
// use 3in1 to buffer data
|
|
displayStateF(STR_EMPTY);
|
|
displayPrintUpper(true);
|
|
displayPrintLower();
|
|
|
|
touchPosition touchXY;
|
|
while(1) {
|
|
swiWaitForVBlank();
|
|
touchRead(&touchXY);
|
|
|
|
// backup
|
|
if ((touchXY.py > 8*0) && (touchXY.py < 8*8)) {
|
|
hwBackupFTP();
|
|
#ifdef REBOOT_WIFI
|
|
displayMessage2F(STR_HW_PLEASE_REBOOT);
|
|
while(1);
|
|
#endif
|
|
displayPrintLower();
|
|
}
|
|
|
|
// restore
|
|
if ((touchXY.py > 8*8) && (touchXY.py < 8*16)) {
|
|
hwRestoreFTP();
|
|
#ifdef REBOOT_WIFI
|
|
displayMessage2F(STR_HW_PLEASE_REBOOT);
|
|
while(1);
|
|
#endif
|
|
displayPrintLower();
|
|
}
|
|
|
|
// erase
|
|
if ((touchXY.py > 8*16) && (touchXY.py < 8*24)) {
|
|
swap_cart();
|
|
displayPrintUpper();
|
|
hwErase();
|
|
displayPrintLower();
|
|
}
|
|
}
|
|
}
|
|
|
|
void mode_dlp()
|
|
{
|
|
// use non-flash card based exploits (download play or Sudoku?). untested, and does not work yet!
|
|
displayStateF(STR_EMPTY);
|
|
displayPrintUpper();
|
|
displayPrintLower();
|
|
|
|
// DSi mode, does nothing at the moment
|
|
displayMessage2F(STR_STR, "I did not expect that you can trigger this mode at all!");
|
|
while(1);
|
|
|
|
touchPosition touchXY;
|
|
while(1) {
|
|
swiWaitForVBlank();
|
|
touchRead(&touchXY);
|
|
|
|
// backup
|
|
if ((touchXY.py > 8*0) && (touchXY.py < 8*8)) {
|
|
hwBackupFTP(true);
|
|
}
|
|
|
|
// restore
|
|
if ((touchXY.py > 8*8) && (touchXY.py < 8*16)) {
|
|
hwRestoreFTP(true);
|
|
}
|
|
|
|
// erase
|
|
if ((touchXY.py > 8*16) && (touchXY.py < 8*24)) {
|
|
displayPrintUpper();
|
|
hwErase();
|
|
displayWarning2F(STR_HW_DID_DELETE);
|
|
while(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool loadIniFile(char* path)
|
|
{
|
|
// Don't try to load ini file in DLP mode, we need to get our options from a different source.
|
|
// Still trying to figure something out...
|
|
if (mode == 5)
|
|
return true;
|
|
|
|
ini_fd_t ini = 0;
|
|
|
|
// load ini file...
|
|
char inipath[256];
|
|
if (path) {
|
|
char *last = strrchr(path, '/');
|
|
int len = (last - path)+1;
|
|
strncpy(bootdir, path, len);
|
|
sprintf(inipath, "%s/savegame_manager.ini", bootdir);
|
|
if (fileExists(inipath))
|
|
ini = ini_open(inipath, "r", "");
|
|
}
|
|
if (!ini) {
|
|
sprintf(inipath, "/savegame_manager.ini");
|
|
if (fileExists(inipath))
|
|
ini = ini_open(inipath, "r", "");
|
|
}
|
|
if (!ini) {
|
|
//displayWarning2F(STR_BOOT_NO_INI);
|
|
iprintf("could not find ini file!");
|
|
while (1);
|
|
}
|
|
|
|
ini_locateHeading(ini, "");
|
|
ini_locateKey(ini, "ftp_ip");
|
|
ini_readString(ini, ftp_ip, 16);
|
|
ini_locateKey(ini, "ftp_user");
|
|
ini_readString(ini, ftp_user, 64);
|
|
ini_locateKey(ini, "ftp_pass");
|
|
ini_readString(ini, ftp_pass, 64);
|
|
ini_locateKey(ini, "ftp_port");
|
|
ini_readInt(ini, &ftp_port);
|
|
|
|
ini_locateKey(ini, "ir_delay");
|
|
ini_readInt(ini, &ir_delay);
|
|
ir_delay = max(ir_delay, 1000);
|
|
|
|
if (ini_locateKey(ini, "slot2"))
|
|
ini_readInt(ini, &slot2);
|
|
|
|
// load additional Flash chip signatures (JEDEC IDs)
|
|
ini_locateHeading(ini, "new chips");
|
|
for (int i = 0; i < EXTRA_ARRAY_SIZE; i++) {
|
|
int tmp;
|
|
sprintf(txt, "%i-id", i);
|
|
ini_locateKey(ini, txt);
|
|
ini_readString(ini, txt, 256);
|
|
sscanf(txt, "%x", &tmp);
|
|
extra_id[i] = (u32)tmp;
|
|
//
|
|
sprintf(txt, "%i-size", i);
|
|
ini_locateKey(ini, txt);
|
|
ini_readInt(ini, &tmp);
|
|
extra_size[i] = tmp;
|
|
}
|
|
|
|
ini_locateHeading(ini, "");
|
|
ini_locateKey(ini, "language");
|
|
ini_readString(ini, txt, 256);
|
|
|
|
ini_close(ini);
|
|
|
|
// delete temp file (which is a remnant of inilib)
|
|
remove("/tmpfile");
|
|
|
|
return true;
|
|
}
|
|
|
|
// This is a new attempt to reliably identify if the flash card has argv support or not,
|
|
// including R4 clones which may have *bad* argv support.
|
|
bool has_argv(int argc, char* argv[])
|
|
{
|
|
// no argv support
|
|
if (argc == 0)
|
|
return false;
|
|
|
|
// bad argv support: if argc is larger than unity, your flash card can't even do
|
|
// a proper negative indicator. let us test for 2 argv arguments, just to be safe.
|
|
if (argc > 2)
|
|
return false;
|
|
|
|
/*
|
|
// test if argv[0] pointer is valid (scan main memory and WRAM)
|
|
if ((argv[0] < 0x02000000) || (argv[0] >= 0x04000000))
|
|
return false;
|
|
*/
|
|
|
|
// test if argv[0] contains a valid string
|
|
char *arg0 = argv[0];
|
|
// test for possible "/path", "fat*:/path" and "sd:/*".
|
|
if (*arg0 == 'f')
|
|
if (!strncasecmp(argv[0], "fat:/", 5) || !strncasecmp(argv[0], "fat1:/", 6)
|
|
|| !strncasecmp(argv[0], "fat2:/", 6))
|
|
return true;
|
|
|
|
if (*arg0 == 's')
|
|
if (!strncasecmp(argv[0], "sd:/", 4))
|
|
return true;
|
|
|
|
if (*arg0 == '/')
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
sysSetBusOwners(true, true);
|
|
|
|
// Init the screens
|
|
displayInit();
|
|
|
|
// Init DLDI (file system driver)
|
|
sysSetBusOwners(true, true);
|
|
int fat = fatInitDefault();
|
|
if (fat == 0) {
|
|
//displayWarning2F(STR_BOOT_DLDI_ERROR);
|
|
iprintf("DLDI error");
|
|
while (1);
|
|
}
|
|
#ifdef DEBUG
|
|
iprintf("Found DLDI: %s\n", io_dldi_data->friendlyName);
|
|
#endif
|
|
|
|
// detect hardware
|
|
mode = hwDetect();
|
|
|
|
// Load the ini file with the FTP settings and more options
|
|
for (int i = 0; i < EXTRA_ARRAY_SIZE; i++) {
|
|
extra_id[i] = 0xff000000;
|
|
extra_size[i] = 0;
|
|
}
|
|
#ifdef DEBUG
|
|
iprintf("Loading INI file\n");
|
|
#endif
|
|
if (has_argv(argc, argv))
|
|
loadIniFile(argv[0]);
|
|
else
|
|
loadIniFile(0);
|
|
#ifdef DEBUG
|
|
iprintf("Done!\n");
|
|
#endif
|
|
|
|
if (slot2 > 0)
|
|
mode = 4;
|
|
|
|
// load strings
|
|
// "txt" is used as a temp buffer for the language file name
|
|
stringsLoadFile(txt);
|
|
|
|
// prepare the global data buffer
|
|
data = (u8*)malloc(size_buf);
|
|
// On my Cyclops, no 2MB buffer is available after loading strings! bad memory management?
|
|
// Anyway, we can't do anything except for the following (which restricts the usefulness
|
|
// of FTP safe mode)
|
|
while (data == NULL) {
|
|
size_buf >>= 1;
|
|
data = (u8*)malloc(size_buf);
|
|
}
|
|
|
|
// Init the screens
|
|
displayTitle();
|
|
|
|
// okay, we got our HW identified; now branch to the corresponding main function/event handler
|
|
switch (mode) {
|
|
case 1:
|
|
mode_gba();
|
|
break;
|
|
case 2:
|
|
mode_3in1();
|
|
break;
|
|
case 3:
|
|
mode_dsi();
|
|
break;
|
|
case 4:
|
|
mode_slot2();
|
|
break;
|
|
case 5:
|
|
mode_dlp();
|
|
break;
|
|
default:
|
|
mode_wifi();
|
|
}
|
|
|
|
return 0;
|
|
}
|