mirror of
https://github.com/rvtr/TDT.git
synced 2025-10-31 13:51:07 -04:00
438 lines
9.0 KiB
C
438 lines
9.0 KiB
C
#include "main.h"
|
|
#include "rom.h"
|
|
#include "menu.h"
|
|
#include "message.h"
|
|
#include "nand/nandio.h"
|
|
#include "storage.h"
|
|
#include <dirent.h>
|
|
|
|
enum {
|
|
TITLE_MENU_BACKUP,
|
|
TITLE_MENU_DELETE,
|
|
TITLE_MENU_BACK
|
|
};
|
|
|
|
static void generateList(Menu* m);
|
|
static void printItem(Menu* m);
|
|
static int subMenu();
|
|
static void backup(Menu* m);
|
|
static bool delete(Menu* m);
|
|
|
|
void titleMenu()
|
|
{
|
|
Menu* m = newMenu();
|
|
setMenuHeader(m, "INSTALLED TITLES");
|
|
generateList(m);
|
|
|
|
//no titles
|
|
if (m->itemCount <= 0)
|
|
{
|
|
messageBox("No titles found.");
|
|
}
|
|
else
|
|
{
|
|
while (!programEnd)
|
|
{
|
|
swiWaitForVBlank();
|
|
scanKeys();
|
|
|
|
if (moveCursor(m))
|
|
{
|
|
if (m->changePage != 0)
|
|
generateList(m);
|
|
|
|
printMenu(m);
|
|
printItem(m);
|
|
}
|
|
|
|
if (keysDown() & KEY_B || m->itemCount <= 0)
|
|
break;
|
|
|
|
else if (keysDown() & KEY_A)
|
|
{
|
|
switch (subMenu())
|
|
{
|
|
case TITLE_MENU_BACKUP:
|
|
backup(m);
|
|
break;
|
|
|
|
case TITLE_MENU_DELETE:
|
|
{
|
|
if (delete(m))
|
|
{
|
|
resetMenu(m);
|
|
generateList(m);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
printMenu(m);
|
|
}
|
|
}
|
|
}
|
|
|
|
freeMenu(m);
|
|
}
|
|
|
|
static void generateList(Menu* m)
|
|
{
|
|
if (!m) return;
|
|
|
|
const int NUM_OF_DIRS = 3;
|
|
const char* dirs[] = {
|
|
"00030004",
|
|
"00030005",
|
|
"00030015"
|
|
};
|
|
|
|
//Reset menu
|
|
clearMenu(m);
|
|
|
|
m->page += sign(m->changePage);
|
|
m->changePage = 0;
|
|
|
|
bool done = false;
|
|
int count = 0; //used to skip to the right page
|
|
|
|
//search each category directory /title/XXXXXXXX
|
|
for (int i = 0; i < NUM_OF_DIRS && done == false; i++)
|
|
{
|
|
char* dirPath = (char*)malloc(strlen(dirs[i])+15);
|
|
sprintf(dirPath, "%s:/title/%s", sdnandMode ? "sd" : "nand", dirs[i]);
|
|
|
|
struct dirent* ent;
|
|
DIR* dir = opendir(dirPath);
|
|
|
|
if (dir)
|
|
{
|
|
while ( (ent = readdir(dir)) && done == false)
|
|
{
|
|
if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0)
|
|
continue;
|
|
|
|
//blacklisted titles
|
|
if (!sdnandMode && (
|
|
(i == 1 && (
|
|
strncmp(ent->d_name, "484e44", 6) == 0 || // DS Download Play
|
|
strncmp(ent->d_name, "484e45", 6) == 0 || // PictoChat
|
|
strncmp(ent->d_name, "484e49", 6) == 0 || // Nintendo DSi Camera
|
|
strncmp(ent->d_name, "484e4b", 6) == 0 // Nintendo DSi Sound
|
|
)) || (i == 2 && (
|
|
strncmp(ent->d_name, "484e42", 6) == 0 || // System Settings
|
|
strncmp(ent->d_name, "484e46", 6) == 0 // Nintendo DSi Shop
|
|
))))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ent->d_type == DT_DIR)
|
|
{
|
|
//scan content folder /title/XXXXXXXX/content
|
|
char* contentPath = (char*)malloc(strlen(dirPath) + strlen(ent->d_name) + 20);
|
|
sprintf(contentPath, "%s/%s/content", dirPath, ent->d_name);
|
|
|
|
struct dirent* subent;
|
|
DIR* subdir = opendir(contentPath);
|
|
|
|
if (subdir)
|
|
{
|
|
while ( (subent = readdir(subdir)) && done == false)
|
|
{
|
|
if (strcmp(".", subent->d_name) == 0 || strcmp("..", subent->d_name) == 0)
|
|
continue;
|
|
|
|
if (subent->d_type != DT_DIR)
|
|
{
|
|
//found .app file
|
|
if (strstr(subent->d_name, ".app") != NULL)
|
|
{
|
|
//current item is not on page
|
|
if (count < m->page * ITEMS_PER_PAGE)
|
|
count += 1;
|
|
|
|
else
|
|
{
|
|
if (m->itemCount >= ITEMS_PER_PAGE)
|
|
done = true;
|
|
|
|
else
|
|
{
|
|
//found requested title
|
|
char* path = (char*)malloc(strlen(contentPath) + strlen(subent->d_name) + 10);
|
|
sprintf(path, "%s/%s", contentPath, subent->d_name);
|
|
|
|
char title[128];
|
|
getGameTitlePath(path, title, false);
|
|
|
|
addMenuItem(m, title, path, 0);
|
|
|
|
free(path);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
closedir(subdir);
|
|
free(contentPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
closedir(dir);
|
|
free(dirPath);
|
|
}
|
|
|
|
sortMenuItems(m);
|
|
|
|
m->nextPage = done;
|
|
|
|
if (m->cursor >= m->itemCount)
|
|
m->cursor = m->itemCount - 1;
|
|
|
|
printItem(m);
|
|
printMenu(m);
|
|
}
|
|
|
|
static void printItem(Menu* m)
|
|
{
|
|
if (!m) return;
|
|
printRomInfo(m->items[m->cursor].value);
|
|
}
|
|
|
|
static int subMenu()
|
|
{
|
|
int result = -1;
|
|
|
|
Menu* m = newMenu();
|
|
|
|
addMenuItem(m, "Backup", NULL, 0);
|
|
addMenuItem(m, "Delete", NULL, 0);
|
|
addMenuItem(m, "Back - [B]", NULL, 0);
|
|
|
|
printMenu(m);
|
|
|
|
while (!programEnd)
|
|
{
|
|
swiWaitForVBlank();
|
|
scanKeys();
|
|
|
|
if (moveCursor(m))
|
|
printMenu(m);
|
|
|
|
if (keysDown() & KEY_B)
|
|
break;
|
|
|
|
else if (keysDown() & KEY_A)
|
|
{
|
|
result = m->cursor;
|
|
break;
|
|
}
|
|
}
|
|
|
|
freeMenu(m);
|
|
return result;
|
|
}
|
|
|
|
static void backup(Menu* m)
|
|
{
|
|
char* fpath = m->items[m->cursor].value;
|
|
char *backname = NULL;
|
|
|
|
tDSiHeader* h = getRomHeader(fpath);
|
|
|
|
{
|
|
//make backup folder name
|
|
char label[13];
|
|
getRomLabel(h, label);
|
|
|
|
char gamecode[5];
|
|
getRomCode(h, gamecode);
|
|
|
|
backname = (char*)malloc(strlen(label) + strlen(gamecode) + 16);
|
|
sprintf(backname, "%s-%s", label, gamecode);
|
|
|
|
//make sure dir is unused
|
|
char* dstpath = (char*)malloc(strlen(BACKUP_PATH) + strlen(backname) + 32);
|
|
sprintf(dstpath, "%s/%s.nds", BACKUP_PATH, backname);
|
|
|
|
int try = 1;
|
|
while (access(dstpath, F_OK) == 0)
|
|
{
|
|
try += 1;
|
|
sprintf(backname, "%s-%s(%d)", label, gamecode, try);
|
|
sprintf(dstpath, "%s/%s.nds", BACKUP_PATH, backname);
|
|
}
|
|
|
|
free(dstpath);
|
|
}
|
|
|
|
bool choice = NO;
|
|
{
|
|
const char str[] = "Are you sure you want to backup\n";
|
|
char* msg = (char*)malloc(strlen(str) + strlen(backname) + 2);
|
|
sprintf(msg, "%s%s?", str, backname);
|
|
|
|
choice = choiceBox(msg);
|
|
|
|
free(msg);
|
|
}
|
|
|
|
if (choice == YES)
|
|
{
|
|
char srcpath[30];
|
|
sprintf(srcpath, "%s:/title/%08lx/%08lx", sdnandMode ? "sd" : "nand", h->tid_high, h->tid_low);
|
|
|
|
if (getSDCardFree() < getDirSize(srcpath, 0))
|
|
{
|
|
messageBox("Not enough space on SD.");
|
|
}
|
|
else
|
|
{
|
|
//create dirs
|
|
{
|
|
//create subdirectories
|
|
char backupPath[sizeof(BACKUP_PATH)];
|
|
strcpy(backupPath, BACKUP_PATH);
|
|
for (char *slash = strchr(backupPath, '/'); *slash; slash = strchr(slash + 1, '/'))
|
|
{
|
|
char temp = *slash;
|
|
*slash = '\0';
|
|
mkdir(backupPath, 0777);
|
|
*slash = temp;
|
|
}
|
|
mkdir(backupPath, 0777); // sd:/_nds/ntm/backup
|
|
}
|
|
|
|
clearScreen(&bottomScreen);
|
|
|
|
char path[256], dstpath[256];
|
|
|
|
//tmd
|
|
sprintf(path, "%s/content/title.tmd", srcpath);
|
|
sprintf(dstpath, "%s/%s.tmd", BACKUP_PATH, backname);
|
|
nocashMessage(path);
|
|
nocashMessage(dstpath);
|
|
if (access(path, F_OK) == 0)
|
|
{
|
|
//get app version
|
|
FILE *tmd = fopen(path, "rb");
|
|
if (tmd)
|
|
{
|
|
u8 appVersion;
|
|
fseek(tmd, 0x1E7, SEEK_SET);
|
|
fread(&appVersion, sizeof(appVersion), 1, tmd);
|
|
fclose(tmd);
|
|
|
|
iprintf("%s -> \n%s...\n", path, dstpath);
|
|
copyFile(path, dstpath);
|
|
|
|
//app
|
|
sprintf(path, "%s/content/000000%02x.app", srcpath, appVersion);
|
|
sprintf(dstpath, "%s/%s.nds", BACKUP_PATH, backname);
|
|
if (access(path, F_OK) == 0)
|
|
{
|
|
iprintf("%s -> \n%s...\n", path, dstpath);
|
|
copyFile(path, dstpath);
|
|
}
|
|
}
|
|
}
|
|
|
|
//public save
|
|
sprintf(path, "%s/data/public.sav", srcpath);
|
|
sprintf(dstpath, "%s/%s.pub", BACKUP_PATH, backname);
|
|
if (access(path, F_OK) == 0)
|
|
{
|
|
iprintf("%s -> \n%s...\n", path, dstpath);
|
|
copyFile(path, dstpath);
|
|
}
|
|
|
|
//private save
|
|
sprintf(path, "%s/data/private.sav", srcpath);
|
|
sprintf(dstpath, "%s/%s.prv", BACKUP_PATH, backname);
|
|
if (access(path, F_OK) == 0)
|
|
{
|
|
iprintf("%s -> \n%s...\n", path, dstpath);
|
|
copyFile(path, dstpath);
|
|
}
|
|
|
|
//banner save
|
|
sprintf(path, "%s/data/banner.sav", srcpath);
|
|
sprintf(dstpath, "%s/%s.bnr", BACKUP_PATH, backname);
|
|
if (access(path, F_OK) == 0)
|
|
{
|
|
iprintf("%s -> \n%s...\n", path, dstpath);
|
|
copyFile(path, dstpath);
|
|
}
|
|
|
|
messagePrint("\x1B[42m\nBackup finished.\x1B[47m");
|
|
}
|
|
}
|
|
|
|
free(h);
|
|
}
|
|
|
|
static bool delete(Menu* m)
|
|
{
|
|
if (!m) return false;
|
|
|
|
char* fpath = m->items[m->cursor].value;
|
|
|
|
bool result = false;
|
|
bool choice = NO;
|
|
{
|
|
//get app title
|
|
char title[128];
|
|
getGameTitlePath(m->items[m->cursor].value, title, false);
|
|
|
|
char str[] = "Are you sure you want to delete\n";
|
|
char* msg = (char*)malloc(strlen(str) + strlen(title) + 8);
|
|
sprintf(msg, "%s%s", str, title);
|
|
|
|
choice = choiceBox(msg);
|
|
|
|
free(msg);
|
|
}
|
|
|
|
if (choice == YES)
|
|
{
|
|
if (!fpath)
|
|
{
|
|
messageBox("Failed to delete title.\n");
|
|
}
|
|
else
|
|
{
|
|
char dirPath[64];
|
|
sprintf(dirPath, "%.*s", sdnandMode ? 27 : 29, fpath);
|
|
|
|
if (!dirExists(dirPath))
|
|
{
|
|
messageBox("Failed to delete title.\n");
|
|
}
|
|
else
|
|
{
|
|
if (!sdnandMode && !nandio_unlock_writing())
|
|
return false;
|
|
|
|
clearScreen(&bottomScreen);
|
|
|
|
if (deleteDir(dirPath))
|
|
{
|
|
result = true;
|
|
messagePrint("\nTitle deleted.\n");
|
|
}
|
|
else
|
|
{
|
|
messagePrint("\nTitle could not be deleted.\n");
|
|
}
|
|
|
|
if (!sdnandMode)
|
|
nandio_lock_writing();
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
} |