mirror of
https://github.com/suloku/savegame-manager.git
synced 2026-03-22 02:15:36 -05:00
- Add some very preliminary bits for importing a custom font fileExists - Add "displayDebugF" function for easier debugging - Add some comments on dsiwarehax mode to instructions file
519 lines
11 KiB
C++
519 lines
11 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 LIBNDS_VER ((_LIBNDS_MAJOR_ << 16) | (_LIBNDS_MINOR_ << 8) | (_LIBNDS_PATCH_))
|
|
|
|
// needs libnds version 1.5.4, for the SDHC driver on the DSi.
|
|
#if (LIBNDS_VER < 0x00010504)
|
|
#error "Your libnds version is outdated! Please use 1.5.4 or higher!"
|
|
#endif
|
|
|
|
|
|
#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
|
|
displayMessageF(STR_BOOT_MODE_UNSUPPORTED);
|
|
|
|
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()
|
|
{
|
|
//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);
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
iprintf("Found and parsed INI file.\n");
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
txt[0] = 0;
|
|
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)) {
|
|
sdslot = true; // for sudokuhax
|
|
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)) {
|
|
#ifdef DEBUG
|
|
iprintf("argv folder: %s\n", argv[0]);
|
|
#endif
|
|
loadIniFile(argv[0]);
|
|
} else {
|
|
#ifdef DEBUG
|
|
iprintf("Bad/no argv folder!\n", argv[0]);
|
|
#endif
|
|
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, returned by hwDetect
|
|
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)
|
|
#ifdef DEBUG
|
|
iprintf("Allocating memory buffer.\n");
|
|
#endif
|
|
while (data == NULL) {
|
|
size_buf >>= 1;
|
|
data = (u8*)malloc(size_buf);
|
|
}
|
|
#ifdef DEBUG
|
|
iprintf("Done! We now have %x.\n", size_buf);
|
|
#endif
|
|
|
|
// 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;
|
|
}
|