mirror of
https://github.com/rvtr/unlaunch-installer_dev.git
synced 2026-01-26 13:43:08 -05:00
Remove not needed NTM files
This commit is contained in:
parent
c00569d5c5
commit
664e7a2090
@ -1,275 +0,0 @@
|
||||
#include "install.h"
|
||||
#include "main.h"
|
||||
#include "menu.h"
|
||||
#include "rom.h"
|
||||
#include "storage.h"
|
||||
#include "message.h"
|
||||
#include "nand/nandio.h"
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
enum {
|
||||
BACKUP_MENU_RESTORE,
|
||||
BACKUP_MENU_DELETE,
|
||||
BACKUP_MENU_BACK
|
||||
};
|
||||
|
||||
static void generateList(Menu* m);
|
||||
static void printItem(Menu* m);
|
||||
static int subMenu();
|
||||
static bool delete(Menu* m);
|
||||
|
||||
void backupMenu()
|
||||
{
|
||||
clearScreen(&topScreen);
|
||||
|
||||
Menu* m = newMenu();
|
||||
setMenuHeader(m, "BACKUP MENU");
|
||||
generateList(m);
|
||||
|
||||
//no files found
|
||||
if (m->itemCount <= 0)
|
||||
{
|
||||
messageBox("\x1B[33mNo backups found.\n\x1B[47m");
|
||||
}
|
||||
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 BACKUP_MENU_RESTORE:
|
||||
install(m->items[m->cursor].value, false);
|
||||
break;
|
||||
|
||||
case BACKUP_MENU_DELETE:
|
||||
{
|
||||
if (delete(m))
|
||||
{
|
||||
resetMenu(m);
|
||||
generateList(m);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
printMenu(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeMenu(m);
|
||||
}
|
||||
|
||||
static int subMenu()
|
||||
{
|
||||
int result = -1;
|
||||
|
||||
Menu* m = newMenu();
|
||||
|
||||
addMenuItem(m, "Restore", 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 generateList(Menu* m)
|
||||
{
|
||||
if (!m) return;
|
||||
|
||||
//reset menu
|
||||
clearMenu(m);
|
||||
|
||||
m->page += sign(m->changePage);
|
||||
m->changePage = 0;
|
||||
|
||||
bool done = false;
|
||||
|
||||
struct dirent* ent;
|
||||
DIR* dir = opendir(BACKUP_PATH);
|
||||
|
||||
if (dir)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while ( (ent = readdir(dir)) && !done)
|
||||
{
|
||||
if (ent->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
if (ent->d_type == DT_DIR)
|
||||
{
|
||||
if (count < m->page * ITEMS_PER_PAGE)
|
||||
count += 1;
|
||||
|
||||
else
|
||||
{
|
||||
if (m->itemCount >= ITEMS_PER_PAGE)
|
||||
done = true;
|
||||
|
||||
else
|
||||
{
|
||||
char* fpath = (char*)malloc(strlen(BACKUP_PATH) + strlen(ent->d_name) + 8);
|
||||
sprintf(fpath, "%s/%s", BACKUP_PATH, ent->d_name);
|
||||
|
||||
addMenuItem(m, ent->d_name, fpath, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strcasecmp(strrchr(ent->d_name, '.'), ".nds") == 0 ||
|
||||
strcasecmp(strrchr(ent->d_name, '.'), ".app") == 0 ||
|
||||
strcasecmp(strrchr(ent->d_name, '.'), ".dsi") == 0 ||
|
||||
strcasecmp(strrchr(ent->d_name, '.'), ".ids") == 0 ||
|
||||
strcasecmp(strrchr(ent->d_name, '.'), ".srl") == 0 ||
|
||||
strcasecmp(strrchr(ent->d_name, '.'), ".cia") == 0)
|
||||
{
|
||||
if (count < m->page * ITEMS_PER_PAGE)
|
||||
count += 1;
|
||||
|
||||
else
|
||||
{
|
||||
if (m->itemCount >= ITEMS_PER_PAGE)
|
||||
done = true;
|
||||
|
||||
else
|
||||
{
|
||||
char* fpath = (char*)malloc(strlen(BACKUP_PATH) + strlen(ent->d_name) + 8);
|
||||
sprintf(fpath, "%s/%s", BACKUP_PATH, ent->d_name);
|
||||
|
||||
addMenuItem(m, ent->d_name, fpath, 0);
|
||||
|
||||
free(fpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
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;
|
||||
if (m->itemCount <= 0) return;
|
||||
|
||||
if (m->items[m->cursor].directory)
|
||||
clearScreen(&topScreen);
|
||||
else
|
||||
printRomInfo(m->items[m->cursor].value);
|
||||
}
|
||||
|
||||
static bool delete(Menu* m)
|
||||
{
|
||||
if (!m) return false;
|
||||
|
||||
char* label = m->items[m->cursor].label;
|
||||
char* fpath = m->items[m->cursor].value;
|
||||
|
||||
bool result = false;
|
||||
bool choice = NO;
|
||||
{
|
||||
const char str[] = "Are you sure you want to delete\n";
|
||||
char* msg = (char*)malloc(strlen(str) + strlen(label) + 2);
|
||||
sprintf(msg, "%s%s?", str, label);
|
||||
|
||||
choice = choiceBox(msg);
|
||||
|
||||
free(msg);
|
||||
}
|
||||
|
||||
if (choice == YES)
|
||||
{
|
||||
if (!fpath)
|
||||
{
|
||||
messageBox("\x1B[31mFailed to delete backup.\n\x1B[47m");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (access(fpath, F_OK) != 0)
|
||||
{
|
||||
messageBox("\x1B[31mFailed to delete backup.\n\x1B[47m");
|
||||
}
|
||||
else
|
||||
{
|
||||
clearScreen(&bottomScreen);
|
||||
|
||||
//app
|
||||
remove(fpath);
|
||||
|
||||
//tmd
|
||||
strcpy(strrchr(fpath, '.'), ".tmd");
|
||||
remove(fpath);
|
||||
|
||||
//public save
|
||||
strcpy(strrchr(fpath, '.'), ".pub");
|
||||
remove(fpath);
|
||||
|
||||
//private save
|
||||
strcpy(strrchr(fpath, '.'), ".prv");
|
||||
remove(fpath);
|
||||
|
||||
//banner save
|
||||
strcpy(strrchr(fpath, '.'), ".bnr");
|
||||
remove(fpath);
|
||||
|
||||
result = true;
|
||||
messagePrint("\x1B[42m\nBackup deleted.\n\x1B[47m");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -1,871 +0,0 @@
|
||||
#include "install.h"
|
||||
#include "sav.h"
|
||||
#include "main.h"
|
||||
#include "message.h"
|
||||
#include "maketmd.h"
|
||||
#include "nand/crypto.h"
|
||||
#include "nand/nandio.h"
|
||||
#include "nand/ticket0.h"
|
||||
#include "rom.h"
|
||||
#include "storage.h"
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
static bool _titleIsUsed(tDSiHeader* h)
|
||||
{
|
||||
if (!h) return false;
|
||||
|
||||
char path[64];
|
||||
sprintf(path, "%s:/title/%08x/%08x/", sdnandMode ? "sd" : "nand", (unsigned int)h->tid_high, (unsigned int)h->tid_low);
|
||||
|
||||
return dirExists(path);
|
||||
}
|
||||
|
||||
//patch homebrew roms if gameCode is #### or null
|
||||
static bool _patchGameCode(tDSiHeader* h)
|
||||
{
|
||||
if (!h) return false;
|
||||
|
||||
if ((strcmp(h->ndshdr.gameCode, "####") == 0 && h->tid_low == 0x23232323) || (!*h->ndshdr.gameCode && h->tid_low == 0))
|
||||
{
|
||||
iprintf("Fixing Game Code...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
//set as standard app
|
||||
h->tid_high = 0x00030004;
|
||||
|
||||
do {
|
||||
do {
|
||||
//generate a random game code
|
||||
for (int i = 0; i < 4; i++)
|
||||
h->ndshdr.gameCode[i] = 'A' + (rand() % 26);
|
||||
}
|
||||
while (h->ndshdr.gameCode[0] == 'A'); //first letter shouldn't be A
|
||||
|
||||
//correct title id
|
||||
h->tid_low = ( (h->ndshdr.gameCode[0] << 24) | (h->ndshdr.gameCode[1] << 16) | (h->ndshdr.gameCode[2] << 8) | h->ndshdr.gameCode[3] );
|
||||
}
|
||||
while (_titleIsUsed(h));
|
||||
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _iqueHack(tDSiHeader* h)
|
||||
{
|
||||
if (!h) return false;
|
||||
|
||||
if (h->ndshdr.reserved1[8] == 0x80)
|
||||
{
|
||||
iprintf("iQue Hack...");
|
||||
|
||||
h->ndshdr.reserved1[8] = 0x00;
|
||||
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static unsigned long long _getSaveDataSize(tDSiHeader* h)
|
||||
{
|
||||
unsigned long long size = 0;
|
||||
|
||||
if (h)
|
||||
{
|
||||
size += h->public_sav_size;
|
||||
size += h->private_sav_size;
|
||||
|
||||
//banner.sav
|
||||
if (h->appflags & 0x4)
|
||||
size += 0x4000;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static bool _checkSdSpace(unsigned long long size)
|
||||
{
|
||||
iprintf("Enough room on SD card?...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
if (getSDCardFree() < size)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("No\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
return false;
|
||||
}
|
||||
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Yes\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _checkDsiSpace(unsigned long long size, bool systemApp)
|
||||
{
|
||||
iprintf("Enough room on DSi?...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
//ensure there's at least 1 MiB free, to leave margin for error
|
||||
if (((systemApp ? getDsiRealFree() : getDsiFree()) < size) || (((systemApp ? getDsiRealFree() : getDsiFree()) - size) < (1 << 20)))
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("No\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
return false;
|
||||
}
|
||||
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Yes\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool _openMenuSlot()
|
||||
{
|
||||
iprintf("Open DSi menu slot?...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
if (getMenuSlotsFree() <= 0)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("No\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
return false;
|
||||
}
|
||||
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Yes\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
return true;
|
||||
}
|
||||
|
||||
static void _createPublicSav(tDSiHeader* h, char* dataPath)
|
||||
{
|
||||
if (!h) return;
|
||||
|
||||
if (h->public_sav_size > 0)
|
||||
{
|
||||
iprintf("Creating public.sav...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
if (!dataPath)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
else
|
||||
{
|
||||
char* publicPath = (char*)malloc(strlen(dataPath) + strlen("/public.sav") + 1);
|
||||
sprintf(publicPath, "%s/public.sav", dataPath);
|
||||
|
||||
FILE* f = fopen(publicPath, "wb");
|
||||
|
||||
if (!f)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
else
|
||||
{
|
||||
fseek(f, h->public_sav_size-1, SEEK_SET);
|
||||
fputc(0, f);
|
||||
initFatHeader(f);
|
||||
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
free(publicPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _createPrivateSav(tDSiHeader* h, char* dataPath)
|
||||
{
|
||||
if (!h) return;
|
||||
|
||||
if (h->private_sav_size > 0)
|
||||
{
|
||||
iprintf("Creating private.sav...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
if (!dataPath)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
else
|
||||
{
|
||||
char* privatePath = (char*)malloc(strlen(dataPath) + strlen("/private.sav") + 1);
|
||||
sprintf(privatePath, "%s/private.sav", dataPath);
|
||||
|
||||
FILE* f = fopen(privatePath, "wb");
|
||||
|
||||
if (!f)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
else
|
||||
{
|
||||
fseek(f, h->private_sav_size-1, SEEK_SET);
|
||||
fputc(0, f);
|
||||
initFatHeader(f);
|
||||
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
free(privatePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _createBannerSav(tDSiHeader* h, char* dataPath)
|
||||
{
|
||||
if (!h) return;
|
||||
|
||||
if (h->appflags & 0x4)
|
||||
{
|
||||
iprintf("Creating banner.sav...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
if (!dataPath)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
else
|
||||
{
|
||||
char* bannerPath = (char*)malloc(strlen(dataPath) + strlen("/banner.sav") + 1);
|
||||
sprintf(bannerPath, "%s/banner.sav", dataPath);
|
||||
|
||||
FILE* f = fopen(bannerPath, "wb");
|
||||
|
||||
if (!f)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
else
|
||||
{
|
||||
fseek(f, 0x4000 - 1, SEEK_SET);
|
||||
fputc(0, f);
|
||||
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
free(bannerPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _createTicket(tDSiHeader *h, char* ticketPath)
|
||||
{
|
||||
if (!h) return;
|
||||
|
||||
iprintf("Forging ticket...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
if (!ticketPath)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
else
|
||||
{
|
||||
const u32 encryptedSize = sizeof(ticket_v0_t) + 0x20;
|
||||
u8 *buffer = (u8*)memalign(4, encryptedSize); //memalign might be needed for encryption, but not sure
|
||||
memset(buffer, 0, encryptedSize);
|
||||
ticket_v0_t *ticket = (ticket_v0_t*)buffer;
|
||||
ticket->sig_type[0] = 0x00;
|
||||
ticket->sig_type[1] = 0x01;
|
||||
ticket->sig_type[2] = 0x00;
|
||||
ticket->sig_type[3] = 0x01;
|
||||
strcpy(ticket->issuer, "Root-CA00000001-XS00000006");
|
||||
PUT_UINT32_BE(h->tid_high, ticket->title_id, 0);
|
||||
PUT_UINT32_BE(h->tid_low, ticket->title_id, 4);
|
||||
memset(ticket->content_access_permissions, 0xFF, 0x20);
|
||||
|
||||
// Encrypt
|
||||
if (dsi_es_block_crypt(buffer, encryptedSize, ENCRYPT) != 0)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
free(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
FILE *file = fopen(ticketPath, "wb");
|
||||
if (!file)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
free(buffer);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fwrite(buffer, 1, encryptedSize, file) != encryptedSize)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
else
|
||||
{
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
bool install(char* fpath, bool systemTitle)
|
||||
{
|
||||
bool result = false;
|
||||
|
||||
//check battery level
|
||||
while (batteryLevel < 7 && !charging)
|
||||
{
|
||||
if (choiceBox("\x1B[47mBattery is too low!\nPlease plug in the console.\n\nContinue?") == NO)
|
||||
return false;
|
||||
}
|
||||
|
||||
//start installation
|
||||
clearScreen(&bottomScreen);
|
||||
|
||||
tDSiHeader* h = getRomHeader(fpath);
|
||||
|
||||
if (!h)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Error: ");
|
||||
iprintf("\x1B[33m"); //yellow
|
||||
iprintf("Could not open file.\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
goto error;
|
||||
}
|
||||
else
|
||||
{
|
||||
bool fixHeader = false;
|
||||
|
||||
if (_patchGameCode(h))
|
||||
fixHeader = true;
|
||||
|
||||
//title id must be one of these
|
||||
if (h->tid_high == 0x00030004 || // DSiWare
|
||||
h->tid_high == 0x00030005 || // "unimportant" system titles
|
||||
h->tid_high == 0x00030011 || // SRLs in the TWL SDK
|
||||
h->tid_high == 0x00030015 || // system titles
|
||||
h->tid_high == 0x00030017) // Launcher
|
||||
{}
|
||||
else
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Error: ");
|
||||
iprintf("\x1B[33m"); //yellow
|
||||
iprintf("This is not a DSi rom.\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
goto error;
|
||||
}
|
||||
|
||||
//patch dev titles to system titles on SysNAND.
|
||||
//
|
||||
//software released through the TWL SDK usually comes as a TAD and an SRL
|
||||
//things like NandFiler have a TAD with a TID of 0x00030015 and an SRL with 0x00030011
|
||||
//the TAD is the installable version, so I'm assuming that 0x00030015 is what the console wants to see on NAND
|
||||
//this changes the SRL TID accordingly
|
||||
//not entirely sure why there's even any difference. I think the installed TAD and SRL the same as each other (minus the TID)
|
||||
if(!sdnandMode && h->tid_high == 0x00030011)
|
||||
{
|
||||
h->tid_high = 0x00030015;
|
||||
fixHeader = true;
|
||||
}
|
||||
|
||||
//offer to patch system titles to normal DSiWare on SysNAND
|
||||
if(!sdnandMode && h->tid_high != 0x00030004 && h->tid_high != 0x00030017) //do not allow patching home menus to be normal DSiWare! This will trigger "ERROR! - 0x0000000000000008 HWINFO_SECURE" on prototype launchers. May also cause issues on the prod versions.
|
||||
{
|
||||
if(choiceBox("This is set as a system/dev\ntitle, would you like to patch\nit to be a normal DSiWare?\n\nThis is safer, but invalidates\nRSA checks and may not work.\n\nIf the title is homebrew this isstrongly recommended.") == YES)
|
||||
{
|
||||
h->tid_high = 0x00030004;
|
||||
fixHeader = true;
|
||||
}
|
||||
}
|
||||
|
||||
//offer to patch home menus to be system titles on SysNAND
|
||||
if(!sdnandMode && h->tid_high == 0x00030017)
|
||||
{
|
||||
if(choiceBox("This title is a home menu.\nWould you like to patch it to bea system title?\n\nThis is safer and prevents your\nhome menu from being hidden.") == YES)
|
||||
{
|
||||
h->tid_high = 0x00030015;
|
||||
fixHeader = true;
|
||||
}
|
||||
}
|
||||
|
||||
//no system titles without Unlaunch
|
||||
if (!unlaunchFound && h->tid_high != 0x00030004)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Error: ");
|
||||
iprintf("\x1B[33m"); //yellow
|
||||
iprintf("This title cannot be\ninstalled without Unlaunch.\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
goto error;
|
||||
}
|
||||
|
||||
//blacklisted titles
|
||||
{
|
||||
//tid without region
|
||||
u32 tidLow = (h->tid_low & 0xFFFFFF00);
|
||||
if (!sdnandMode && (
|
||||
(h->tid_high == 0x00030005 && (
|
||||
tidLow == 0x484e4400 || // DS Download Play
|
||||
tidLow == 0x484e4500 || // PictoChat
|
||||
tidLow == 0x484e4900 || // Nintendo DSi Camera
|
||||
tidLow == 0x484e4a00 || // Nintendo Zone
|
||||
tidLow == 0x484e4b00 // Nintendo DSi Sound
|
||||
)) || (h->tid_high == 0x00030011 && (
|
||||
tidLow == 0x30535500 || // Twl SystemUpdater
|
||||
tidLow == 0x34544e00 || // TwlNmenu
|
||||
tidLow == 0x54574c00 // TWL EVA
|
||||
)) || (h->tid_high == 0x00030015 && (
|
||||
tidLow == 0x484e4200 || // System Settings
|
||||
tidLow == 0x484e4600 || // Nintendo DSi Shop
|
||||
tidLow == 0x34544e00 // TwlNmenu
|
||||
)) || (h->tid_high == 0x00030017 && (
|
||||
tidLow == 0x484e4100 // Launcher
|
||||
))) && (
|
||||
(h->tid_low & 0xFF) == region || // Only blacklist console region, or the following programs that have all-region codes:
|
||||
h->tid_low == 0x484e4541 || // PictoChat
|
||||
h->tid_low == 0x484e4441 || // Download Play
|
||||
h->tid_low == 0x30535541 || // Twl SystemUpdater (iirc one version fits in NAND)
|
||||
h->tid_low == 0x34544e41 || // TwlNmenu (blocking due to potential to uninstall system titles)
|
||||
h->tid_low == 0x54574c41 || // TWL EVA
|
||||
region == 0 //if the region check failed somehow, blacklist everything
|
||||
))
|
||||
{
|
||||
//check if title exists, if it does then show any error
|
||||
//otherwise allow reinstalling it
|
||||
char path[PATH_MAX];
|
||||
sprintf(path, "nand:/title/%08lx/%08lx/content/title.tmd", h->tid_high, h->tid_low);
|
||||
if (access(path, F_OK) == 0)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Error: ");
|
||||
iprintf("\x1B[33m"); //yellow
|
||||
iprintf("This title cannot be\ninstalled to SysNAND.\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//confirmation message
|
||||
{
|
||||
const char system[] = "\x1B[41mWARNING:\x1B[47m This is a system app,\ninstalling it is potentially\nmore risky than regular DSiWare.\n\x1B[33m";
|
||||
const char areYouSure[] = "Are you sure you want to install\n";
|
||||
char* msg = (char*)malloc(strlen(system) + strlen(areYouSure) + strlen(fpath) + 2);
|
||||
if (sdnandMode || h->tid_high == 0x00030004)
|
||||
sprintf(msg, "%s%s?\n", areYouSure, fpath);
|
||||
else
|
||||
sprintf(msg, "%s%s%s?\n", system, areYouSure, fpath);
|
||||
|
||||
bool choice = choiceBox(msg);
|
||||
free(msg);
|
||||
|
||||
if (choice == NO)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!sdnandMode && !nandio_unlock_writing())
|
||||
return false;
|
||||
|
||||
clearScreen(&bottomScreen);
|
||||
iprintf("Installing %s\n\n", fpath); swiWaitForVBlank();
|
||||
|
||||
//check for legit TMD, if found we'll generate a ticket which increases the size
|
||||
int extensionPos = strrchr(fpath, '.') - fpath;
|
||||
char tmdPath[PATH_MAX];
|
||||
strcpy(tmdPath, fpath);
|
||||
strcpy(tmdPath + extensionPos, ".tmd");
|
||||
//DSi TMDs are 520, TMDs from NUS are 2,312. If 2,312 we can simply trim it to 520
|
||||
int tmdSize = getFileSizePath(tmdPath);
|
||||
bool tmdFound = (tmdSize == 520) || (tmdSize == 2312);
|
||||
if (access(tmdPath, F_OK) == 0 && !tmdFound)
|
||||
{
|
||||
if (choicePrint("Incorrect TMD.\nInstall anyway?") == YES)
|
||||
tmdFound = false;
|
||||
else
|
||||
goto error;
|
||||
}
|
||||
else if(!sdnandMode && !unlaunchPatches && access(tmdPath, F_OK) != 0)
|
||||
{
|
||||
if (choicePrint("TMD not found, game cannot be\nplayed without Unlaunch's\nlauncher patches.\nSee wiki for how to get a TMD.\n\nInstall anyway?") == YES)
|
||||
tmdFound = false;
|
||||
else
|
||||
goto error;
|
||||
}
|
||||
|
||||
//get install size
|
||||
iprintf("Install Size: ");
|
||||
swiWaitForVBlank();
|
||||
|
||||
u32 clusterSize = getDsiClusterSize();
|
||||
unsigned long long fileSize = getRomSize(fpath), fileSizeOnDisk = fileSize;
|
||||
if ((fileSizeOnDisk % clusterSize) != 0)
|
||||
fileSizeOnDisk += clusterSize - (fileSizeOnDisk % clusterSize);
|
||||
//file + saves + TMD (rounded up to cluster size)
|
||||
unsigned long long installSize = fileSizeOnDisk + _getSaveDataSize(h) + clusterSize;
|
||||
if (tmdFound) installSize += clusterSize; //ticket, rounded up to cluster size
|
||||
|
||||
printBytes(installSize);
|
||||
iprintf("\n");
|
||||
|
||||
if (sdnandMode && !_checkSdSpace(installSize))
|
||||
goto error;
|
||||
|
||||
//system title patch
|
||||
if (systemTitle)
|
||||
{
|
||||
iprintf("System Title Patch...");
|
||||
swiWaitForVBlank();
|
||||
h->tid_high = 0x00030015;
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
|
||||
fixHeader = true;
|
||||
}
|
||||
|
||||
//check that there's space on nand
|
||||
if (!_checkDsiSpace(installSize, (h->tid_high != 0x00030004)))
|
||||
{
|
||||
if (sdnandMode && choicePrint("Install as system title?"))
|
||||
{
|
||||
h->tid_high = 0x00030015;
|
||||
fixHeader = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
//check for saves
|
||||
char pubPath[PATH_MAX];
|
||||
strcpy(pubPath, fpath);
|
||||
strcpy(pubPath + extensionPos, ".pub");
|
||||
bool pubFound = getFileSizePath(pubPath) == h->public_sav_size;
|
||||
if (access(pubPath, F_OK) == 0 && !pubFound)
|
||||
{
|
||||
if (choicePrint("Incorrect public save.\nInstall anyway?") == YES)
|
||||
pubFound = false;
|
||||
else
|
||||
goto error;
|
||||
}
|
||||
|
||||
char prvPath[PATH_MAX];
|
||||
strcpy(prvPath, fpath);
|
||||
strcpy(prvPath + extensionPos, ".prv");
|
||||
bool prvFound = getFileSizePath(prvPath) == h->private_sav_size;
|
||||
if (access(prvPath, F_OK) == 0 && !prvFound)
|
||||
{
|
||||
if (choicePrint("Incorrect private save.\nInstall anyway?") == YES)
|
||||
prvFound = false;
|
||||
else
|
||||
goto error;
|
||||
}
|
||||
|
||||
char bnrPath[PATH_MAX];
|
||||
strcpy(bnrPath, fpath);
|
||||
strcpy(bnrPath + extensionPos, ".bnr");
|
||||
bool bnrFound = getFileSizePath(bnrPath) == 0x4000;
|
||||
if (access(bnrPath, F_OK) == 0 && !bnrFound)
|
||||
{
|
||||
if (choicePrint("Incorrect banner save.\nInstall anyway?") == YES)
|
||||
bnrFound = false;
|
||||
else
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (_iqueHack(h))
|
||||
fixHeader = true;
|
||||
|
||||
if (fixHeader && tmdFound)
|
||||
{
|
||||
if (choicePrint("Legit TMD cannot be used.\nInstall anyway?") == YES)
|
||||
tmdFound = false;
|
||||
else
|
||||
goto error;
|
||||
}
|
||||
|
||||
//create title directory /title/XXXXXXXX/XXXXXXXX
|
||||
char dirPath[32];
|
||||
mkdir(sdnandMode ? "sd:/title" : "nand:/title", 0777);
|
||||
|
||||
sprintf(dirPath, "%s:/title/%08x", sdnandMode ? "sd" : "nand", (unsigned int)h->tid_high);
|
||||
mkdir(dirPath, 0777);
|
||||
|
||||
sprintf(dirPath, "%s:/title/%08x/%08x", sdnandMode ? "sd" : "nand", (unsigned int)h->tid_high, (unsigned int)h->tid_low);
|
||||
|
||||
//check if title is free
|
||||
if (_titleIsUsed(h))
|
||||
{
|
||||
char msg[64];
|
||||
sprintf(msg, "Title %08x is already used.\nInstall anyway?", (unsigned int)h->tid_low);
|
||||
|
||||
if (choicePrint(msg) == NO)
|
||||
goto error;
|
||||
|
||||
else
|
||||
{
|
||||
iprintf("\nDeleting:\n");
|
||||
deleteDir(dirPath);
|
||||
iprintf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (!_openMenuSlot())
|
||||
goto error;
|
||||
|
||||
mkdir(dirPath, 0777);
|
||||
|
||||
//content folder /title/XXXXXXXX/XXXXXXXXX/content
|
||||
{
|
||||
char contentPath[64];
|
||||
sprintf(contentPath, "%s/content", dirPath);
|
||||
|
||||
mkdir(contentPath, 0777);
|
||||
|
||||
u8 appVersion = 0;
|
||||
if (tmdFound)
|
||||
{
|
||||
FILE *file = fopen(tmdPath, "rb");
|
||||
if (file)
|
||||
{
|
||||
fseek(file, 0x1E7, SEEK_SET);
|
||||
fread(&appVersion, sizeof(appVersion), 1, file);
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
//create 000000##.app
|
||||
{
|
||||
iprintf("Creating 000000%02x.app...", appVersion);
|
||||
swiWaitForVBlank();
|
||||
|
||||
char appPath[80];
|
||||
sprintf(appPath, "%s/000000%02x.app", contentPath, appVersion);
|
||||
|
||||
//copy nds file to app
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
if (!romIsCia(fpath))
|
||||
result = copyFile(fpath, appPath);
|
||||
else
|
||||
result = copyFilePart(fpath, 0x3900, fileSize, appPath);
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[33m"); //yellow
|
||||
iprintf("%s\n", appPath);
|
||||
iprintf("%s\n", strerror(errno));
|
||||
iprintf("\x1B[47m"); //white
|
||||
|
||||
goto error;
|
||||
}
|
||||
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
|
||||
//pad out banner if it is the last part of the file
|
||||
{
|
||||
if (h->ndshdr.bannerOffset > (fileSize - 0x23C0))
|
||||
{
|
||||
iprintf("Padding banner...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
if (padFile(appPath, h->ndshdr.bannerOffset + 0x23C0 - fileSize) == false)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
else
|
||||
{
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//update header
|
||||
{
|
||||
if (fixHeader)
|
||||
{
|
||||
iprintf("Fixing header...");
|
||||
swiWaitForVBlank();
|
||||
|
||||
//fix header checksum
|
||||
h->ndshdr.headerCRC16 = swiCRC16(0xFFFF, h, 0x15E);
|
||||
|
||||
//fix RSA signature
|
||||
u8 buffer[20];
|
||||
swiSHA1Calc(&buffer, h, 0xE00);
|
||||
memcpy(&(h->rsa_signature[0x6C]), buffer, 20);
|
||||
|
||||
FILE* f = fopen(appPath, "r+");
|
||||
|
||||
if (!f)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Failed\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
else
|
||||
{
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fwrite(h, sizeof(tDSiHeader), 1, f);
|
||||
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
//make/copy TMD
|
||||
char newTmdPath[80];
|
||||
sprintf(newTmdPath, "%s/title.tmd", contentPath);
|
||||
if (tmdFound)
|
||||
{
|
||||
if (copyFilePart(tmdPath, 0, 520, newTmdPath) != 0)
|
||||
goto error;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (maketmd(appPath, newTmdPath) != 0)
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//data folder
|
||||
{
|
||||
char dataPath[64];
|
||||
sprintf(dataPath, "%s/data", dirPath);
|
||||
|
||||
mkdir(dataPath, 0777);
|
||||
|
||||
if (pubFound)
|
||||
{
|
||||
char newPubPath[80];
|
||||
sprintf(newPubPath, "%s/public.sav", dataPath);
|
||||
copyFile(pubPath, newPubPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_createPublicSav(h, dataPath);
|
||||
}
|
||||
|
||||
if (prvFound)
|
||||
{
|
||||
char newPrvPath[80];
|
||||
sprintf(newPrvPath, "%s/private.sav", dataPath);
|
||||
copyFile(prvPath, newPrvPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_createPrivateSav(h, dataPath);
|
||||
}
|
||||
|
||||
if (bnrFound)
|
||||
{
|
||||
char newBnrPath[80];
|
||||
sprintf(newBnrPath, "%s/banner.sav", dataPath);
|
||||
copyFile(bnrPath, newBnrPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
_createBannerSav(h, dataPath);
|
||||
}
|
||||
}
|
||||
|
||||
//ticket folder /ticket/XXXXXXXX
|
||||
if (tmdFound)
|
||||
{
|
||||
//ensure folders exist
|
||||
char ticketPath[32];
|
||||
siprintf(ticketPath, "%s:/ticket", sdnandMode ? "sd" : "nand");
|
||||
mkdir(ticketPath, 0777);
|
||||
siprintf(ticketPath, "%s/%08lx", ticketPath, h->tid_high);
|
||||
mkdir(ticketPath, 0777);
|
||||
|
||||
//actual tik path
|
||||
siprintf(ticketPath, "%s/%08lx.tik", ticketPath, h->tid_low);
|
||||
|
||||
if (access(ticketPath, F_OK) != 0 || (choicePrint("Ticket already exists.\nKeep it? (recommended)") == NO && choicePrint("Are you sure?") == YES))
|
||||
_createTicket(h, ticketPath);
|
||||
}
|
||||
|
||||
//end
|
||||
result = true;
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("\nInstallation complete.\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
iprintf("Back - [B]\n");
|
||||
keyWait(KEY_A | KEY_B);
|
||||
|
||||
goto complete;
|
||||
}
|
||||
|
||||
error:
|
||||
messagePrint("\x1B[31m\nInstallation failed.\n\x1B[47m");
|
||||
|
||||
complete:
|
||||
free(h);
|
||||
|
||||
if (!sdnandMode)
|
||||
nandio_lock_writing();
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
#ifndef INSTALL_H
|
||||
#define INSTALL_H
|
||||
|
||||
#include <nds/ndstypes.h>
|
||||
|
||||
bool install(char* fpath, bool systemTitle);
|
||||
|
||||
#endif
|
||||
@ -1,320 +0,0 @@
|
||||
#include "main.h"
|
||||
#include "rom.h"
|
||||
#include "install.h"
|
||||
#include "menu.h"
|
||||
#include "storage.h"
|
||||
#include "message.h"
|
||||
#include <dirent.h>
|
||||
|
||||
enum {
|
||||
INSTALL_MENU_INSTALL,
|
||||
INSTALL_MENU_SYSTEM_TITLE,
|
||||
INSTALL_MENU_DELETE,
|
||||
INSTALL_MENU_BACK
|
||||
};
|
||||
|
||||
static char currentDir[512] = "";
|
||||
|
||||
static void generateList(Menu* m);
|
||||
static void printItem(Menu* m);
|
||||
static int subMenu();
|
||||
static bool delete(Menu* m);
|
||||
|
||||
static void _setHeader(Menu* m)
|
||||
{
|
||||
if (!m) return;
|
||||
if (currentDir[0] == '\0')
|
||||
setMenuHeader(m, "sd:/");
|
||||
else
|
||||
setMenuHeader(m, currentDir);
|
||||
}
|
||||
|
||||
void installMenu()
|
||||
{
|
||||
Menu* m = newMenu();
|
||||
_setHeader(m);
|
||||
generateList(m);
|
||||
|
||||
//no files found
|
||||
/* if (m->itemCount <= 0)
|
||||
{
|
||||
clearScreen(&bottomScreen);
|
||||
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("No files found.\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
iprintf("\nBack - [B]\n");
|
||||
|
||||
keyWait(KEY_B | KEY_A | KEY_START);
|
||||
}
|
||||
else*/
|
||||
{
|
||||
while (!programEnd)
|
||||
{
|
||||
swiWaitForVBlank();
|
||||
scanKeys();
|
||||
|
||||
if (moveCursor(m))
|
||||
{
|
||||
if (m->changePage != 0)
|
||||
generateList(m);
|
||||
|
||||
printMenu(m);
|
||||
printItem(m);
|
||||
}
|
||||
|
||||
//back
|
||||
if (keysDown() & KEY_B)
|
||||
{
|
||||
char* ptr = strrchr(currentDir, '/');
|
||||
|
||||
if (ptr)
|
||||
{
|
||||
*ptr = '\0';
|
||||
_setHeader(m);
|
||||
resetMenu(m);
|
||||
generateList(m);
|
||||
printMenu(m);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
else if (keysDown() & KEY_X)
|
||||
break;
|
||||
|
||||
//selection
|
||||
else if (keysDown() & KEY_A)
|
||||
{
|
||||
if (m->itemCount > 0)
|
||||
{
|
||||
if (m->items[m->cursor].directory == false)
|
||||
{
|
||||
//nds file
|
||||
switch (subMenu())
|
||||
{
|
||||
case INSTALL_MENU_INSTALL:
|
||||
install(m->items[m->cursor].value, false);
|
||||
break;
|
||||
|
||||
case INSTALL_MENU_SYSTEM_TITLE:
|
||||
if (sdnandMode)
|
||||
install(m->items[m->cursor].value, true);
|
||||
break;
|
||||
|
||||
case INSTALL_MENU_DELETE:
|
||||
{
|
||||
if (delete(m))
|
||||
{
|
||||
resetMenu(m);
|
||||
generateList(m);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case INSTALL_MENU_BACK:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//directory
|
||||
sprintf(currentDir, "%s", m->items[m->cursor].value);
|
||||
_setHeader(m);
|
||||
resetMenu(m);
|
||||
generateList(m);
|
||||
}
|
||||
|
||||
printMenu(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeMenu(m);
|
||||
}
|
||||
|
||||
static void generateList(Menu* m)
|
||||
{
|
||||
if (!m) return;
|
||||
|
||||
//reset menu
|
||||
clearMenu(m);
|
||||
|
||||
m->page += sign(m->changePage);
|
||||
m->changePage = 0;
|
||||
|
||||
bool done = false;
|
||||
|
||||
struct dirent* ent;
|
||||
DIR* dir = NULL;
|
||||
|
||||
if (currentDir[0] == '\0')
|
||||
dir = opendir("sd:/");
|
||||
else
|
||||
dir = opendir(currentDir);
|
||||
|
||||
if (dir)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
while ( (ent = readdir(dir)) && !done)
|
||||
{
|
||||
if (ent->d_name[0] == '.')
|
||||
continue;
|
||||
|
||||
if (ent->d_type == DT_DIR)
|
||||
{
|
||||
if (count < m->page * ITEMS_PER_PAGE)
|
||||
count += 1;
|
||||
|
||||
else
|
||||
{
|
||||
if (m->itemCount >= ITEMS_PER_PAGE)
|
||||
done = true;
|
||||
|
||||
else
|
||||
{
|
||||
char* fpath = (char*)malloc(strlen(currentDir) + strlen(ent->d_name) + 8);
|
||||
sprintf(fpath, "%s/%s", currentDir, ent->d_name);
|
||||
|
||||
addMenuItem(m, ent->d_name, fpath, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strcasecmp(strrchr(ent->d_name, '.'), ".nds") == 0 ||
|
||||
strcasecmp(strrchr(ent->d_name, '.'), ".app") == 0 ||
|
||||
strcasecmp(strrchr(ent->d_name, '.'), ".dsi") == 0 ||
|
||||
strcasecmp(strrchr(ent->d_name, '.'), ".ids") == 0 ||
|
||||
strcasecmp(strrchr(ent->d_name, '.'), ".srl") == 0 ||
|
||||
strcasecmp(strrchr(ent->d_name, '.'), ".cia") == 0)
|
||||
{
|
||||
if (count < m->page * ITEMS_PER_PAGE)
|
||||
count += 1;
|
||||
|
||||
else
|
||||
{
|
||||
if (m->itemCount >= ITEMS_PER_PAGE)
|
||||
done = true;
|
||||
|
||||
else
|
||||
{
|
||||
char* fpath = (char*)malloc(strlen(currentDir) + strlen(ent->d_name) + 8);
|
||||
sprintf(fpath, "%s/%s", currentDir, ent->d_name);
|
||||
|
||||
addMenuItem(m, ent->d_name, fpath, 0);
|
||||
|
||||
free(fpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
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;
|
||||
if (m->itemCount <= 0) return;
|
||||
|
||||
if (m->items[m->cursor].directory)
|
||||
clearScreen(&topScreen);
|
||||
else
|
||||
printRomInfo(m->items[m->cursor].value);
|
||||
}
|
||||
|
||||
static int subMenu()
|
||||
{
|
||||
int result = -1;
|
||||
|
||||
Menu* m = newMenu();
|
||||
|
||||
addMenuItem(m, "Install", NULL, 0);
|
||||
addMenuItem(m, sdnandMode ? "Install as System Title" : "\x1B[37m[Disabled]\x1B[47m", 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)
|
||||
{
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
else if (keysDown() & KEY_A)
|
||||
{
|
||||
result = m->cursor;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
freeMenu(m);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool delete(Menu* m)
|
||||
{
|
||||
if (!m) return false;
|
||||
|
||||
char* fpath = m->items[m->cursor].value;
|
||||
|
||||
bool result = false;
|
||||
bool choice = NO;
|
||||
{
|
||||
char str[] = "Are you sure you want to delete\n";
|
||||
char* msg = (char*)malloc(strlen(str) + strlen(fpath) + 1);
|
||||
sprintf(msg, "%s%s", str, fpath);
|
||||
|
||||
choice = choiceBox(msg);
|
||||
|
||||
free(msg);
|
||||
}
|
||||
|
||||
if (choice == YES)
|
||||
{
|
||||
if (!fpath)
|
||||
{
|
||||
messageBox("\x1B[31mCould not delete file.\x1B[47m");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (remove(fpath) == 0)
|
||||
{
|
||||
result = true;
|
||||
messageBox("\x1B[42mFile deleted.\x1B[47m");
|
||||
}
|
||||
else
|
||||
{
|
||||
messageBox("\x1B[31mCould not delete file.\x1B[47m");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
156
arm9/src/main.c
156
arm9/src/main.c
@ -9,34 +9,21 @@
|
||||
#include <dirent.h>
|
||||
#include <time.h>
|
||||
|
||||
bool programEnd = false;
|
||||
bool sdnandMode = true;
|
||||
bool retailLauncherTmdPresentAndToBePatched = true;
|
||||
bool retailConsole = true;
|
||||
bool unlaunchInstallerFound = false;
|
||||
bool unlaunchFound = false;
|
||||
bool unlaunchPatches = false;
|
||||
bool devkpFound = false;
|
||||
bool launcherDSiFound = false;
|
||||
volatile bool programEnd = false;
|
||||
static bool unlaunchFound = false;
|
||||
static bool retailLauncherTmdPresentAndToBePatched = true;
|
||||
static bool retailConsole = true;
|
||||
static bool unlaunchInstallerFound = false;
|
||||
bool arm7Exiting = false;
|
||||
bool charging = false;
|
||||
u8 batteryLevel = 0;
|
||||
u8 region = 0xFF;
|
||||
|
||||
PrintConsole topScreen;
|
||||
PrintConsole bottomScreen;
|
||||
|
||||
enum {
|
||||
MAIN_MENU_MODE,
|
||||
MAIN_MENU_INSTALL,
|
||||
MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL,
|
||||
MAIN_MENU_SAFE_UNLAUNCH_INSTALL,
|
||||
MAIN_MENU_TITLES,
|
||||
MAIN_MENU_BACKUP,
|
||||
MAIN_MENU_TEST,
|
||||
MAIN_MENU_FIX,
|
||||
MAIN_MENU_DATA_MANAGEMENT,
|
||||
MAIN_MENU_LANGUAGE_PATCHER,
|
||||
MAIN_MENU_EXIT
|
||||
};
|
||||
|
||||
@ -78,20 +65,10 @@ static int _mainMenu(int cursor)
|
||||
Menu* m = newMenu();
|
||||
setMenuHeader(m, "MAIN MENU");
|
||||
|
||||
char modeStr[32], datamanStr[32], launcherStr[32];
|
||||
sprintf(modeStr, "Mode: %s", sdnandMode ? "SDNAND" : "\x1B[41mSysNAND\x1B[47m");
|
||||
sprintf(datamanStr, "\x1B[%02omEnable Data Management", devkpFound ? 037 : 047);
|
||||
sprintf(launcherStr, "\x1B[%02omUninstall region mod", launcherDSiFound ? 047 : 037);
|
||||
addMenuItem(m, modeStr, NULL, 0);
|
||||
addMenuItem(m, "Install", NULL, 0);
|
||||
addMenuItem(m, "Safe unlaunch uninstall", NULL, 0);
|
||||
addMenuItem(m, "Safe unlaunch install", NULL, 0);
|
||||
addMenuItem(m, "Titles", NULL, 0);
|
||||
addMenuItem(m, "Restore", NULL, 0);
|
||||
addMenuItem(m, "Test", NULL, 0);
|
||||
addMenuItem(m, "Fix FAT copy mismatch", NULL, 0);
|
||||
addMenuItem(m, datamanStr, NULL, 0);
|
||||
addMenuItem(m, launcherStr, NULL, 0);
|
||||
char uninstallStr[32];
|
||||
sprintf(uninstallStr, "\x1B[%02omSafe unlaunch uninstall", unlaunchFound ? 047 : 037);
|
||||
addMenuItem(m, uninstallStr, NULL, 0);
|
||||
addMenuItem(m, "\x1B[47mSafe unlaunch install", NULL, 0);
|
||||
addMenuItem(m, "\x1B[47mExit", NULL, 0);
|
||||
|
||||
m->cursor = cursor;
|
||||
@ -132,40 +109,6 @@ void fifoHandlerBattery(u32 value32, void* userdata)
|
||||
charging = (value32 & BIT(7)) != 0;
|
||||
}
|
||||
|
||||
bool checkIfUnlaunchHasPatches(const char* path)
|
||||
{
|
||||
//check if launcher patches are enabled
|
||||
const static u32 tidValues[][2] = {
|
||||
// {location, value}
|
||||
{0xE439, 0x382E3176}, // 1.8
|
||||
{0xB07C, 0x17484E41}, // 1.9
|
||||
{0xB099, 0x17484E41}, // 2.0 (Normal)
|
||||
{0xB079, 0x484E1841}, // 2.0 (Patched)
|
||||
};
|
||||
|
||||
bool patched = false;
|
||||
|
||||
FILE *tmd = fopen(path, "rb");
|
||||
if (tmd)
|
||||
{
|
||||
for (int i = 0; i < sizeof(tidValues) / sizeof(tidValues[0]); i++)
|
||||
{
|
||||
if (fseek(tmd, tidValues[i][0], SEEK_SET) == 0)
|
||||
{
|
||||
u32 tidVal;
|
||||
fread(&tidVal, sizeof(u32), 1, tmd);
|
||||
if (tidVal == tidValues[i][1])
|
||||
{
|
||||
patched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(tmd);
|
||||
return patched;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
srand(time(0));
|
||||
@ -196,9 +139,10 @@ int main(int argc, char **argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
unlaunchInstallerFound = (access("sd:/unlaunch.dsi", F_OK) == 0);
|
||||
unlaunchInstallerFound = fileExists("sd:/unlaunch.dsi");
|
||||
|
||||
//check for unlaunch and region
|
||||
u8 region = 0xff;
|
||||
char retailLauncherTmdPath[64];
|
||||
const char* hnaaTmdPath = "nand:/title/00030017/484e4141/content/title.tmd";
|
||||
{
|
||||
@ -218,7 +162,6 @@ int main(int argc, char **argv)
|
||||
if (tmdSize > 520)
|
||||
{
|
||||
unlaunchFound = true;
|
||||
unlaunchPatches = checkIfUnlaunchHasPatches(retailLauncherTmdPath);
|
||||
}
|
||||
else if(tmdSize != 520)
|
||||
{
|
||||
@ -242,25 +185,9 @@ int main(int argc, char **argv)
|
||||
if (tmdSize > 520)
|
||||
{
|
||||
unlaunchFound = true;
|
||||
unlaunchPatches = checkIfUnlaunchHasPatches(hnaaTmdPath);
|
||||
}
|
||||
}
|
||||
|
||||
if (!unlaunchFound)
|
||||
{
|
||||
messageBox("Unlaunch not found. TMD files\nwill be required and there\nis a greater risk something\ncould go wrong.\n\nSee \x1B[46mhttps://dsi.cfw.guide/\x1B[47m to\ninstall.");
|
||||
}
|
||||
else if (!unlaunchPatches)
|
||||
{
|
||||
messageBox("Unlaunch's Launcher Patches are\nnot enabled. You will need to\nprovide TMD files or reinstall.\n\n\x1B[46mhttps://dsi.cfw.guide/\x1B[47m");
|
||||
}
|
||||
}
|
||||
|
||||
//check for dev.kp (Data Management visible)
|
||||
devkpFound = (access("sd:/sys/dev.kp", F_OK) == 0);
|
||||
|
||||
//check for launcher.dsi (Language patcher)
|
||||
launcherDSiFound = (access("nand:/launcher.dsi", F_OK) == 0);
|
||||
|
||||
messageBox("\x1B[41mWARNING:\x1B[47m This tool can write to\nyour internal NAND!\n\nThis always has a risk, albeit\nlow, of \x1B[41mbricking\x1B[47m your system\nand should be done with caution!\n\nIf you have not yet done so,\nyou should make a NAND backup.");
|
||||
|
||||
@ -274,15 +201,6 @@ int main(int argc, char **argv)
|
||||
|
||||
switch (cursor)
|
||||
{
|
||||
case MAIN_MENU_MODE:
|
||||
sdnandMode = !sdnandMode;
|
||||
devkpFound = (access(sdnandMode ? "sd:/sys/dev.kp" : "nand:/sys/dev.kp", F_OK) == 0);
|
||||
break;
|
||||
|
||||
case MAIN_MENU_INSTALL:
|
||||
installMenu();
|
||||
break;
|
||||
|
||||
case MAIN_MENU_SAFE_UNLAUNCH_UNINSTALL:
|
||||
if(!unlaunchFound)
|
||||
{
|
||||
@ -291,6 +209,7 @@ int main(int argc, char **argv)
|
||||
if(uninstallUnlaunch(retailConsole, retailLauncherTmdPath))
|
||||
{
|
||||
messageBox("Uninstall successful!\n");
|
||||
unlaunchFound = false;
|
||||
} else {
|
||||
messageBox("\x1B[31mError:\x1B[33m Uninstall failed\n");
|
||||
}
|
||||
@ -306,6 +225,7 @@ int main(int argc, char **argv)
|
||||
if(installUnlaunch(retailConsole, retailLauncherTmdPresentAndToBePatched ? retailLauncherTmdPath : NULL))
|
||||
{
|
||||
messageBox("Install successful!\n");
|
||||
unlaunchFound = true;
|
||||
} else {
|
||||
messageBox("\x1B[31mError:\x1B[33m Install failed\n");
|
||||
}
|
||||
@ -313,56 +233,6 @@ int main(int argc, char **argv)
|
||||
}
|
||||
break;
|
||||
|
||||
case MAIN_MENU_TITLES:
|
||||
titleMenu();
|
||||
break;
|
||||
|
||||
case MAIN_MENU_BACKUP:
|
||||
backupMenu();
|
||||
break;
|
||||
|
||||
case MAIN_MENU_TEST:
|
||||
testMenu();
|
||||
break;
|
||||
|
||||
case MAIN_MENU_FIX:
|
||||
if (nandio_unlock_writing())
|
||||
{
|
||||
nandio_force_fat_fix();
|
||||
nandio_lock_writing();
|
||||
messageBox("Mismatch in FAT copies will be\nfixed on close.\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case MAIN_MENU_DATA_MANAGEMENT:
|
||||
if (!devkpFound && (choiceBox("Make Data Management visible\nin System Settings?") == YES) && (sdnandMode || nandio_unlock_writing()))
|
||||
{
|
||||
//ensure sys folder exists
|
||||
if(access(sdnandMode ? "sd:/sys" : "nand:/sys", F_OK) != 0)
|
||||
mkdir(sdnandMode ? "sd:/sys" : "nand:/sys", 0777);
|
||||
|
||||
//create empty file
|
||||
FILE *file = fopen(sdnandMode ? "sd:/sys/dev.kp" : "nand:/sys/dev.kp", "wb");
|
||||
fclose(file);
|
||||
|
||||
if(!sdnandMode)
|
||||
nandio_lock_writing();
|
||||
devkpFound = (access(sdnandMode ? "sd:/sys/dev.kp" : "nand:/sys/dev.kp", F_OK) == 0);
|
||||
messageBox("Data Management is now visible\nin System Settings.\n");
|
||||
}
|
||||
break;
|
||||
case MAIN_MENU_LANGUAGE_PATCHER:
|
||||
if (launcherDSiFound && (choiceBox("Uninstall the language patched\nDSi Menu? (launcher.dsi)") == YES) && nandio_unlock_writing())
|
||||
{
|
||||
//delete launcher.dsi
|
||||
remove("nand:/launcher.dsi");
|
||||
|
||||
nandio_lock_writing();
|
||||
launcherDSiFound = (access("nand:/launcher.dsi", F_OK) == 0);
|
||||
messageBox("The language patched DSi Menu\nhas been removed.\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case MAIN_MENU_EXIT:
|
||||
programEnd = true;
|
||||
break;
|
||||
|
||||
@ -5,26 +5,13 @@
|
||||
#include <fat.h>
|
||||
#include <stdio.h>
|
||||
|
||||
extern bool programEnd;
|
||||
extern bool sdnandMode;
|
||||
extern bool unlaunchFound;
|
||||
extern bool unlaunchPatches;
|
||||
extern volatile bool programEnd;
|
||||
extern bool charging;
|
||||
extern u8 batteryLevel;
|
||||
extern u8 region;
|
||||
|
||||
void installMenu();
|
||||
void titleMenu();
|
||||
void backupMenu();
|
||||
void testMenu();
|
||||
|
||||
extern PrintConsole topScreen;
|
||||
extern PrintConsole bottomScreen;
|
||||
|
||||
void clearScreen(PrintConsole* screen);
|
||||
|
||||
#define abs(X) ( (X) < 0 ? -(X): (X) )
|
||||
#define sign(X) ( ((X) > 0) - ((X) < 0) )
|
||||
#define repeat(X) for (int _I_ = 0; _I_ < (X); _I_++)
|
||||
|
||||
#endif
|
||||
@ -1,191 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------
|
||||
|
||||
maketmd.cpp -- TMD Creator for DSiWare Homebrew
|
||||
|
||||
Copyright (C) 2018
|
||||
Przemyslaw Skryjomski (Tuxality)
|
||||
|
||||
Big thanks to:
|
||||
Apache Thunder
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any
|
||||
damages arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any
|
||||
purpose, including commercial applications, and to alter it and
|
||||
redistribute it freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you
|
||||
must not claim that you wrote the original software. If you use
|
||||
this software in a product, an acknowledgment in the product
|
||||
documentation would be appreciated but is not required.
|
||||
|
||||
2. Altered source versions must be plainly marked as such, and
|
||||
must not be misrepresented as being the original software.
|
||||
|
||||
3. This notice may not be removed or altered from any source
|
||||
distribution.
|
||||
|
||||
---------------------------------------------------------------------------------*/
|
||||
|
||||
/* September 2018 - Jeff - Translated from C++ to C and uses libnds instead of openssl
|
||||
Original: github.com/Tuxality/maketmd
|
||||
*/
|
||||
|
||||
#include "maketmd.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <nds/sha1.h>
|
||||
#include <nds/ndstypes.h>
|
||||
#include <machine/endian.h>
|
||||
|
||||
//#define TMD_CREATOR_VER "0.2"
|
||||
|
||||
#define TMD_SIZE 0x208
|
||||
#define SHA_BUFFER_SIZE 0x200
|
||||
#define SHA_DIGEST_LENGTH 0x14
|
||||
|
||||
void tmd_create(uint8_t* tmd, FILE* app)
|
||||
{
|
||||
// Phase 1 - offset 0x18C (Title ID, first part)
|
||||
{
|
||||
fseek(app, 0x234, SEEK_SET);
|
||||
|
||||
uint32_t value;
|
||||
fread(&value, 4, 1, app);
|
||||
value = __bswap32(value);
|
||||
|
||||
memcpy(tmd + 0x18c, &value, 4);
|
||||
}
|
||||
|
||||
// Phase 2 - offset 0x190 (Title ID, second part)
|
||||
{
|
||||
// We can take this also from 0x230, but reversed
|
||||
fseek(app, 0x0C, SEEK_SET);
|
||||
fread((char*)&tmd[0x190], 4, 1, app);
|
||||
}
|
||||
|
||||
// Phase 3 - offset 0x198 (Group ID = '01')
|
||||
{
|
||||
fseek(app, 0x10, SEEK_SET);
|
||||
fread((char*)&tmd[0x198], 2, 1, app);
|
||||
}
|
||||
|
||||
// Phase 4 - offset 0x1AA (fill-in 0x80 value, 0x10 times)
|
||||
{
|
||||
for (size_t i = 0; i<0x10; i++)
|
||||
{
|
||||
tmd[0x1AA + i] = 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 5 - offset 0x1DE (number of contents = 1)
|
||||
{
|
||||
tmd[0x1DE] = 0x00;
|
||||
tmd[0x1DF] = 0x01;
|
||||
}
|
||||
|
||||
// Phase 6 - offset 0x1EA (type of content = 1)
|
||||
{
|
||||
tmd[0x1EA] = 0x00;
|
||||
tmd[0x1EB] = 0x01;
|
||||
}
|
||||
|
||||
// Phase 7 - offset, 0x1EC (file size, 8B)
|
||||
uint32_t filesize = 0;
|
||||
uint32_t fileread = 0;
|
||||
{
|
||||
fseek(app, 0, SEEK_END);
|
||||
filesize = ftell(app);
|
||||
uint32_t size = __bswap32(filesize);
|
||||
|
||||
// We only use 4B for size as for now
|
||||
memcpy((tmd + 0x1F0), &size, sizeof(u32));
|
||||
}
|
||||
|
||||
// Phase 8 - offset, 0x1F4 (SHA1 sum, 20B)
|
||||
{
|
||||
// Makes use of libnds
|
||||
fseek(app, 0, SEEK_SET);
|
||||
|
||||
uint8_t buffer[SHA_BUFFER_SIZE] = { 0 };
|
||||
uint32_t buffer_read = 0;
|
||||
|
||||
swiSHA1context_t ctx;
|
||||
swiSHA1Init(&ctx);
|
||||
|
||||
do {
|
||||
buffer_read = fread((char*)&buffer[0], 1, SHA_BUFFER_SIZE, app);
|
||||
fileread += buffer_read;
|
||||
|
||||
swiSHA1Update(&ctx, buffer, buffer_read);
|
||||
|
||||
printProgressBar((float)fileread / (float)filesize);
|
||||
}
|
||||
while (buffer_read == SHA_BUFFER_SIZE);
|
||||
|
||||
clearProgressBar();
|
||||
consoleSelect(&bottomScreen);
|
||||
|
||||
swiSHA1Final(buffer, &ctx);
|
||||
|
||||
//Store SHA1 sum
|
||||
memcpy((tmd + 0x1F4), buffer, SHA_DIGEST_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
int maketmd(char* input, char* tmdPath)
|
||||
{
|
||||
iprintf("MakeTMD for DSiWare Homebrew\n");
|
||||
iprintf("by Przemyslaw Skryjomski\n\t(Tuxality)\n");
|
||||
|
||||
if (input == NULL || tmdPath == NULL)
|
||||
{
|
||||
iprintf("\x1B[33m"); //yellow
|
||||
iprintf("\nUsage: %s file.app <file.tmd>\n", "maketmd");
|
||||
iprintf("\x1B[47m"); //white
|
||||
return 1;
|
||||
}
|
||||
|
||||
// APP file (input)
|
||||
FILE* app = fopen(input, "rb");
|
||||
|
||||
if (!app)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Error at opening %s for reading.\n", input);
|
||||
iprintf("\x1B[47m"); //white
|
||||
return 1;
|
||||
}
|
||||
|
||||
// TMD file (output)
|
||||
FILE* tmd = fopen(tmdPath, "wb");
|
||||
|
||||
if (!tmd)
|
||||
{
|
||||
fclose(app);
|
||||
iprintf("\x1B[31m"); //white
|
||||
iprintf("Error at opening %s for writing.\n", tmdPath);
|
||||
iprintf("\x1B[47m"); //white
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Allocate memory for TMD
|
||||
uint8_t* tmd_template = (uint8_t*)malloc(sizeof(uint8_t) * TMD_SIZE);
|
||||
memset(tmd_template, 0, sizeof(uint8_t) * TMD_SIZE); // zeroed
|
||||
|
||||
// Prepare TMD template then write to file
|
||||
tmd_create(tmd_template, app);
|
||||
fwrite((const char*)(&tmd_template[0]), TMD_SIZE, 1, tmd);
|
||||
|
||||
// Free allocated memory for TMD
|
||||
free(tmd_template);
|
||||
|
||||
// This is done in dtor, but we additionally flush tmd.
|
||||
fclose(app);
|
||||
fclose(tmd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
#ifndef MAKETMD_H
|
||||
#define MAKETMD_H
|
||||
|
||||
#include "main.h"
|
||||
#include "storage.h"
|
||||
|
||||
int maketmd(char* input, char* tmdPath);
|
||||
|
||||
#endif
|
||||
@ -1,6 +1,9 @@
|
||||
#include "menu.h"
|
||||
#include "main.h"
|
||||
|
||||
#define sign(X) ( ((X) > 0) - ((X) < 0) )
|
||||
#define repeat(X) for (int _I_ = 0; _I_ < (X); _I_++)
|
||||
|
||||
Menu* newMenu()
|
||||
{
|
||||
Menu* m = (Menu*)malloc(sizeof(Menu));
|
||||
|
||||
@ -264,7 +264,6 @@ bool nandio_lock_writing()
|
||||
|
||||
bool nandio_unlock_writing()
|
||||
{
|
||||
if (writingLocked && randomConfirmBox("Writing to NAND is locked!\nIf you're sure you understand\nthe risk, input the sequence\nbelow."))
|
||||
writingLocked = false;
|
||||
|
||||
return !writingLocked;
|
||||
|
||||
296
arm9/src/rom.c
296
arm9/src/rom.c
@ -1,296 +0,0 @@
|
||||
#include "rom.h"
|
||||
#include "main.h"
|
||||
#include "storage.h"
|
||||
#include <dirent.h>
|
||||
#include <nds.h>
|
||||
#include <malloc.h>
|
||||
#include <stdio.h>
|
||||
|
||||
tDSiHeader* getRomHeader(char const* fpath)
|
||||
{
|
||||
if (!fpath) return NULL;
|
||||
|
||||
tDSiHeader* h = NULL;
|
||||
FILE* f = fopen(fpath, "rb");
|
||||
|
||||
if (f)
|
||||
{
|
||||
h = (tDSiHeader*)malloc(sizeof(tDSiHeader));
|
||||
|
||||
if (h)
|
||||
{
|
||||
if (romIsCia(fpath))
|
||||
fseek(f, 0x3900, SEEK_SET);
|
||||
else
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
fread(h, sizeof(tDSiHeader), 1, f);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
tNDSBanner* getRomBanner(char const* fpath)
|
||||
{
|
||||
if (!fpath) return NULL;
|
||||
|
||||
tDSiHeader* h = getRomHeader(fpath);
|
||||
tNDSBanner* b = NULL;
|
||||
|
||||
if (h)
|
||||
{
|
||||
FILE* f = fopen(fpath, "rb");
|
||||
|
||||
if (f)
|
||||
{
|
||||
b = (tNDSBanner*)malloc(sizeof(tNDSBanner));
|
||||
|
||||
if (b)
|
||||
{
|
||||
if (romIsCia(fpath))
|
||||
fseek(f, 0x3900, SEEK_SET);
|
||||
else
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
fseek(f, h->ndshdr.bannerOffset, SEEK_CUR);
|
||||
fread(b, sizeof(tNDSBanner), 1, f);
|
||||
}
|
||||
}
|
||||
|
||||
free(h);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
bool getGameTitle(tNDSBanner* b, char* out, bool full)
|
||||
{
|
||||
if (!b) return false;
|
||||
if (!out) return false;
|
||||
|
||||
//get system language
|
||||
int lang = PersonalData->language;
|
||||
|
||||
//not japanese or chinese
|
||||
if (lang == 0 || lang == 6)
|
||||
lang = 1;
|
||||
|
||||
//read title
|
||||
u16 c;
|
||||
for (int i = 0; i < 128; i++)
|
||||
{
|
||||
c = b->titles[lang][i];
|
||||
|
||||
//remove accents
|
||||
if (c == 0x00F3)
|
||||
c = 'o';
|
||||
|
||||
if (c == 0x00E1)
|
||||
c = 'a';
|
||||
|
||||
out[i] = (char)c;
|
||||
|
||||
if (!full && out[i] == '\n')
|
||||
{
|
||||
out[i] = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
out[128] = '\0';
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getGameTitlePath(char const* fpath, char* out, bool full)
|
||||
{
|
||||
if (!fpath) return false;
|
||||
if (!out) return false;
|
||||
|
||||
tNDSBanner* b = getRomBanner(fpath);
|
||||
bool result = getGameTitle(b, out, full);
|
||||
|
||||
free(b);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool getRomLabel(tDSiHeader* h, char* out)
|
||||
{
|
||||
if (!h) return false;
|
||||
if (!out) return false;
|
||||
|
||||
sprintf(out, "%.12s", h->ndshdr.gameTitle);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getRomCode(tDSiHeader* h, char* out)
|
||||
{
|
||||
if (!h) return false;
|
||||
if (!out) return false;
|
||||
|
||||
sprintf(out, "%.4s", h->ndshdr.gameCode);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void printRomInfo(char const* fpath)
|
||||
{
|
||||
clearScreen(&topScreen);
|
||||
|
||||
if (!fpath) return;
|
||||
|
||||
tDSiHeader* h = getRomHeader(fpath);
|
||||
tNDSBanner* b = getRomBanner(fpath);
|
||||
|
||||
if (!isDsiHeader(h))
|
||||
{
|
||||
iprintf("Could not read dsi header.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!b)
|
||||
{
|
||||
iprintf("Could not read banner.\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
//proper title
|
||||
{
|
||||
char gameTitle[128+1];
|
||||
getGameTitle(b, gameTitle, true);
|
||||
|
||||
iprintf("%s\n\n", gameTitle);
|
||||
}
|
||||
|
||||
//file size
|
||||
{
|
||||
iprintf("Size: ");
|
||||
unsigned long long romSize = getRomSize(fpath);
|
||||
printBytes(romSize);
|
||||
//size in blocks, rounded up
|
||||
iprintf(" (%lld blocks)\n", ((romSize / BYTES_PER_BLOCK) * BYTES_PER_BLOCK + BYTES_PER_BLOCK) / BYTES_PER_BLOCK);
|
||||
}
|
||||
|
||||
iprintf("Label: %.12s\n", h->ndshdr.gameTitle);
|
||||
iprintf("Game Code: %.4s\n", h->ndshdr.gameCode);
|
||||
|
||||
//system type
|
||||
{
|
||||
iprintf("Unit Code: ");
|
||||
|
||||
switch (h->ndshdr.unitCode)
|
||||
{
|
||||
case 0: iprintf("NDS"); break;
|
||||
case 2: iprintf("NDS+DSi"); break;
|
||||
case 3: iprintf("DSi"); break;
|
||||
default: iprintf("unknown");
|
||||
}
|
||||
|
||||
iprintf("\n");
|
||||
}
|
||||
|
||||
//application type
|
||||
{
|
||||
iprintf("Program Type: ");
|
||||
|
||||
switch (h->ndshdr.reserved1[7])
|
||||
{
|
||||
case 0x3: iprintf("Normal"); break;
|
||||
case 0xB: iprintf("Sys"); break;
|
||||
case 0xF: iprintf("Debug/Sys"); break;
|
||||
default: iprintf("unknown");
|
||||
}
|
||||
|
||||
iprintf("\n");
|
||||
}
|
||||
|
||||
//DSi title ids
|
||||
{
|
||||
if (h->tid_high == 0x00030004 ||
|
||||
h->tid_high == 0x00030005 ||
|
||||
h->tid_high == 0x00030011 || // TID for software in TWL SDK
|
||||
h->tid_high == 0x00030015 ||
|
||||
h->tid_high == 0x00030017 ||
|
||||
h->tid_high == 0x00030000)
|
||||
{
|
||||
iprintf("Title ID: %08x %08x\n", (unsigned int)h->tid_high, (unsigned int)h->tid_low);
|
||||
}
|
||||
}
|
||||
|
||||
//print full file path
|
||||
iprintf("\n%s\n", fpath);
|
||||
|
||||
//print extra files
|
||||
int extensionPos = strrchr(fpath, '.') - fpath;
|
||||
char temp[PATH_MAX];
|
||||
strcpy(temp, fpath);
|
||||
strcpy(temp + extensionPos, ".tmd");
|
||||
//DSi TMDs are 520, TMDs from NUS are 2,312. If 2,312 we can simply trim it to 520
|
||||
int tmdSize = getFileSizePath(temp);
|
||||
if (access(temp, F_OK) == 0)
|
||||
printf("\t\x1B[%om%s\n\x1B[47m", (tmdSize == 520 || tmdSize == 2312) ? 047 : 041, strrchr(temp, '/') + 1);
|
||||
|
||||
strcpy(temp + extensionPos, ".pub");
|
||||
if (access(temp, F_OK) == 0)
|
||||
printf("\t\x1B[%om%s\n\x1B[47m", (getFileSizePath(temp) == h->public_sav_size) ? 047 : 041, strrchr(temp, '/') + 1);
|
||||
|
||||
strcpy(temp + extensionPos, ".prv");
|
||||
if (access(temp, F_OK) == 0)
|
||||
printf("\t\x1B[%om%s\n\x1B[47m", (getFileSizePath(temp) == h->private_sav_size) ? 047 : 041, strrchr(temp, '/') + 1);
|
||||
|
||||
strcpy(temp + extensionPos, ".bnr");
|
||||
if (access(temp, F_OK) == 0)
|
||||
printf("\t\x1B[%om%s\n\x1B[47m", (getFileSizePath(temp) == 0x4000) ? 047 : 041, strrchr(temp, '/') + 1);
|
||||
}
|
||||
}
|
||||
|
||||
free(b);
|
||||
free(h);
|
||||
}
|
||||
|
||||
unsigned long long getRomSize(char const* fpath)
|
||||
{
|
||||
if (!fpath) return 0;
|
||||
|
||||
unsigned long long size = 0;
|
||||
FILE* f = fopen(fpath, "rb");
|
||||
|
||||
if (f)
|
||||
{
|
||||
//cia
|
||||
if (romIsCia(fpath))
|
||||
{
|
||||
unsigned char bytes[4] = { 0 };
|
||||
fseek(f, 0x38D0, SEEK_SET);
|
||||
fread(bytes, 4, 1, f);
|
||||
size = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3];
|
||||
}
|
||||
else
|
||||
{
|
||||
fseek(f, 0, SEEK_END);
|
||||
size = ftell(f);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return size;
|
||||
}
|
||||
|
||||
bool romIsCia(char const* fpath)
|
||||
{
|
||||
if (!fpath) return false;
|
||||
return (strstr(fpath, ".cia") != NULL || strstr(fpath, ".CIA") != NULL);
|
||||
}
|
||||
|
||||
bool isDsiHeader(tDSiHeader* h)
|
||||
{
|
||||
if (!h) return false;
|
||||
|
||||
u16 crc16 = swiCRC16(0xFFFF, h, 0x15E);
|
||||
|
||||
return h->ndshdr.headerCRC16 == crc16;
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
#ifndef ROM_H
|
||||
#define ROM_H
|
||||
|
||||
#include <nds/ndstypes.h>
|
||||
#include <nds/memory.h>
|
||||
|
||||
tDSiHeader* getRomHeader(char const* fpath);
|
||||
tNDSBanner* getRomBanner(char const* fpath);
|
||||
|
||||
bool getGameTitle(tNDSBanner* b, char* out, bool full);
|
||||
bool getGameTitlePath(char const* fpath, char* out, bool full);
|
||||
|
||||
bool getRomLabel(tDSiHeader* h, char* out);
|
||||
bool getRomCode(tDSiHeader* h, char* out);
|
||||
|
||||
void printRomInfo(char const* fpath);
|
||||
|
||||
unsigned long long getRomSize(char const* fpath);
|
||||
|
||||
bool romIsCia(char const* fpath);
|
||||
bool isDsiHeader(tDSiHeader* h);
|
||||
|
||||
#endif
|
||||
@ -1,93 +0,0 @@
|
||||
#include "sav.h"
|
||||
#include <string.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#define align(v, a) (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v))
|
||||
|
||||
bool initFatHeader(FILE* f)
|
||||
{
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
//get size
|
||||
fseek(f, 0, SEEK_END);
|
||||
u32 size = ftell(f);
|
||||
|
||||
//based on GodMode9
|
||||
//https://github.com/d0k3/GodMode9/blob/d8d43c14f3317423c677b1c7e0987bcb9bbd7299/arm9/source/game/nds.c#L47-L105
|
||||
const u16 sectorSize = 0x200;
|
||||
|
||||
//fit maximum sectors for the size
|
||||
const u16 maxSectors = size / sectorSize;
|
||||
u16 sectorCount = 1;
|
||||
u16 secPerTrk = 1;
|
||||
u16 numHeads = 1;
|
||||
u16 sectorCountNext = 0;
|
||||
while (sectorCountNext <= maxSectors)
|
||||
{
|
||||
sectorCountNext = secPerTrk * (numHeads + 1) * (numHeads + 1);
|
||||
if (sectorCountNext <= maxSectors)
|
||||
{
|
||||
numHeads++;
|
||||
sectorCount = sectorCountNext;
|
||||
|
||||
secPerTrk++;
|
||||
sectorCountNext = secPerTrk * numHeads * numHeads;
|
||||
if (sectorCountNext <= maxSectors)
|
||||
{
|
||||
sectorCount = sectorCountNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
sectorCountNext = (secPerTrk + 1) * numHeads * numHeads;
|
||||
if (sectorCountNext <= maxSectors)
|
||||
{
|
||||
secPerTrk++;
|
||||
sectorCount = sectorCountNext;
|
||||
}
|
||||
|
||||
u8 secPerCluster = (sectorCount > (8 << 10)) ? 8 : (sectorCount > (1 << 10) ? 4 : 1);
|
||||
|
||||
u16 rootEntryCount = size < 0x8C000 ? 0x20 : 0x200;
|
||||
|
||||
u16 totalClusters = align(sectorCount, secPerCluster) / secPerCluster;
|
||||
u32 fatBytes = (align(totalClusters, 2) / 2) * 3; // 2 sectors -> 3 byte
|
||||
u16 fatSize = align(fatBytes, sectorSize) / sectorSize;
|
||||
|
||||
|
||||
FATHeader* h = (FATHeader*)malloc(sizeof(FATHeader));
|
||||
|
||||
h->BS_JmpBoot[0] = 0xE9;
|
||||
h->BS_JmpBoot[1] = 0;
|
||||
h->BS_JmpBoot[2] = 0;
|
||||
|
||||
memcpy(h->BS_OEMName, "MSWIN4.1", 8);
|
||||
|
||||
h->BPB_BytesPerSec = sectorSize;
|
||||
h->BPB_SecPerClus = secPerCluster;
|
||||
h->BPB_RsvdSecCnt = 0x0001;
|
||||
h->BPB_NumFATs = 0x02;
|
||||
h->BPB_RootEntCnt = rootEntryCount;
|
||||
h->BPB_TotSec16 = sectorCount;
|
||||
h->BPB_Media = 0xF8; // "hard drive"
|
||||
h->BPB_FATSz16 = fatSize;
|
||||
h->BPB_SecPerTrk = secPerTrk;
|
||||
h->BPB_NumHeads = numHeads;
|
||||
h->BPB_HiddSec = 0x00000000;
|
||||
h->BPB_TotSec32 = 0x00000000;
|
||||
h->BS_DrvNum = 0x05;
|
||||
h->BS_Reserved1 = 0x00;
|
||||
h->BS_BootSig = 0x29;
|
||||
h->BS_VolID = 0x12345678;
|
||||
memcpy(h->BS_VolLab, "VOLUMELABEL", 11);
|
||||
memcpy(h->BS_FilSysType,"FAT12 ", 8);
|
||||
memset(h->BS_BootCode, 0, sizeof(h->BS_BootCode));
|
||||
h->BS_BootSign = 0xAA55;
|
||||
|
||||
fseek(f, 0, SEEK_SET);
|
||||
fwrite(h, sizeof(FATHeader), 1, f);
|
||||
|
||||
free(h);
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -1,38 +0,0 @@
|
||||
#ifndef SAV_H
|
||||
#define SAV_H
|
||||
|
||||
#include <nds/ndstypes.h>
|
||||
#include <stdio.h>
|
||||
//http://elm-chan.org/docs/fat_e.html
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct
|
||||
{
|
||||
u8 BS_JmpBoot[3]; //0x0000
|
||||
u8 BS_OEMName[8]; //0x0003
|
||||
u16 BPB_BytesPerSec; //0x000B
|
||||
u8 BPB_SecPerClus; //0x000D
|
||||
u16 BPB_RsvdSecCnt; //0x000E
|
||||
u8 BPB_NumFATs;
|
||||
u16 BPB_RootEntCnt;
|
||||
u16 BPB_TotSec16;
|
||||
u8 BPB_Media;
|
||||
u16 BPB_FATSz16;
|
||||
u16 BPB_SecPerTrk;
|
||||
u16 BPB_NumHeads;
|
||||
u32 BPB_HiddSec;
|
||||
u32 BPB_TotSec32;
|
||||
u8 BS_DrvNum;
|
||||
u8 BS_Reserved1;
|
||||
u8 BS_BootSig;
|
||||
u32 BS_VolID;
|
||||
u8 BS_VolLab[11];
|
||||
u8 BS_FilSysType[8];
|
||||
u8 BS_BootCode[448];
|
||||
u16 BS_BootSign;
|
||||
} FATHeader;
|
||||
#pragma pack(push, 0)
|
||||
|
||||
bool initFatHeader(FILE* f);
|
||||
|
||||
#endif
|
||||
@ -6,26 +6,10 @@
|
||||
|
||||
#define TITLE_LIMIT 39
|
||||
|
||||
//printing
|
||||
void printBytes(unsigned long long bytes)
|
||||
{
|
||||
if (bytes < 1024)
|
||||
iprintf("%dB", (unsigned int)bytes);
|
||||
|
||||
else if (bytes < 1024 * 1024)
|
||||
printf("%.2fKB", (float)bytes / 1024.f);
|
||||
|
||||
else if (bytes < 1024 * 1024 * 1024)
|
||||
printf("%.2fMB", (float)bytes / 1024.f / 1024.f);
|
||||
|
||||
else
|
||||
printf("%.2fGB", (float)bytes / 1024.f / 1024.f / 1024.f);
|
||||
}
|
||||
|
||||
//progress bar
|
||||
static int lastBars = 0;
|
||||
|
||||
void printProgressBar(float percent)
|
||||
static void printProgressBar(float percent)
|
||||
{
|
||||
if (percent < 0.f) percent = 0.f;
|
||||
if (percent > 1.f) percent = 1.f;
|
||||
@ -59,7 +43,7 @@ void printProgressBar(float percent)
|
||||
}
|
||||
}
|
||||
|
||||
void clearProgressBar()
|
||||
static void clearProgressBar()
|
||||
{
|
||||
lastBars = 0;
|
||||
consoleSelect(&topScreen);
|
||||
@ -69,14 +53,7 @@ void clearProgressBar()
|
||||
//files
|
||||
bool fileExists(char const* path)
|
||||
{
|
||||
if (!path) return false;
|
||||
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
fclose(f);
|
||||
return true;
|
||||
return access(path, F_OK) == 0;
|
||||
}
|
||||
|
||||
int copyFile(char const* src, char const* dst)
|
||||
@ -175,25 +152,6 @@ unsigned long long getFileSizePath(char const* path)
|
||||
return size;
|
||||
}
|
||||
|
||||
bool padFile(char const* path, int size)
|
||||
{
|
||||
if (!path) return false;
|
||||
|
||||
FILE* f = fopen(path, "ab");
|
||||
if (!f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < size; i++)
|
||||
fputc('\0', f);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool toggleFileReadOnly(const char* path, bool readOnly)
|
||||
{
|
||||
int fatAttributes = FAT_getAttr(path);
|
||||
@ -217,236 +175,6 @@ bool writeToFile(FILE* fd, const char* buffer, size_t size)
|
||||
return toWrite == 0;
|
||||
}
|
||||
|
||||
//directories
|
||||
bool dirExists(char const* path)
|
||||
{
|
||||
if (!path) return false;
|
||||
|
||||
DIR* dir = opendir(path);
|
||||
|
||||
if (!dir)
|
||||
return false;
|
||||
|
||||
closedir(dir);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool copyDir(char const* src, char const* dst)
|
||||
{
|
||||
if (!src || !dst) return false;
|
||||
|
||||
// iprintf("copyDir\n%s\n%s\n\n", src, dst);
|
||||
|
||||
bool result = true;
|
||||
|
||||
DIR* dir = opendir(src);
|
||||
struct dirent* ent;
|
||||
|
||||
if (!dir)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
while ( (ent = readdir(dir)) )
|
||||
{
|
||||
if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0)
|
||||
continue;
|
||||
|
||||
if (ent->d_type == DT_DIR)
|
||||
{
|
||||
char* dsrc = (char*)malloc(strlen(src) + strlen(ent->d_name) + 4);
|
||||
sprintf(dsrc, "%s/%s", src, ent->d_name);
|
||||
|
||||
char* ddst = (char*)malloc(strlen(dst) + strlen(ent->d_name) + 4);
|
||||
sprintf(ddst, "%s/%s", dst, ent->d_name);
|
||||
|
||||
mkdir(ddst, 0777);
|
||||
if (!copyDir(dsrc, ddst))
|
||||
result = false;
|
||||
|
||||
free(ddst);
|
||||
free(dsrc);
|
||||
}
|
||||
else
|
||||
{
|
||||
char* fsrc = (char*)malloc(strlen(src) + strlen(ent->d_name) + 4);
|
||||
sprintf(fsrc, "%s/%s", src, ent->d_name);
|
||||
|
||||
char* fdst = (char*)malloc(strlen(dst) + strlen(ent->d_name) + 4);
|
||||
sprintf(fdst, "%s/%s", dst, ent->d_name);
|
||||
|
||||
// iprintf("%s\n%s\n\n", fsrc, fdst);
|
||||
iprintf("%s -> \n%s...", fsrc, fdst);
|
||||
|
||||
int ret = copyFile(fsrc, fdst);
|
||||
|
||||
if (ret != 0)
|
||||
{
|
||||
iprintf("\x1B[31m"); //red
|
||||
iprintf("Fail\n");
|
||||
iprintf("\x1B[33m"); //yellow
|
||||
|
||||
iprintf("%s\n", strerror(errno));
|
||||
/*
|
||||
switch (ret)
|
||||
{
|
||||
case 1:
|
||||
iprintf("Empty input path.\n");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
iprintf("Empty output path.\n");
|
||||
break;
|
||||
|
||||
case 3:
|
||||
iprintf("Error opening input file.\n");
|
||||
break;
|
||||
|
||||
case 4:
|
||||
iprintf("Error opening output file.\n");
|
||||
break;
|
||||
}
|
||||
*/
|
||||
iprintf("\x1B[47m"); //white
|
||||
result = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
iprintf("\x1B[42m"); //green
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m"); //white
|
||||
}
|
||||
|
||||
free(fdst);
|
||||
free(fsrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool deleteDir(char const* path)
|
||||
{
|
||||
if (!path) return false;
|
||||
|
||||
if (strcmp("/", path) == 0)
|
||||
{
|
||||
//oh fuck no
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = true;
|
||||
|
||||
DIR* dir = opendir(path);
|
||||
struct dirent* ent;
|
||||
|
||||
if (!dir)
|
||||
{
|
||||
result = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
while ( (ent = readdir(dir)) )
|
||||
{
|
||||
if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0)
|
||||
continue;
|
||||
|
||||
if (ent->d_type == DT_DIR)
|
||||
{
|
||||
//Delete directory
|
||||
char subpath[512];
|
||||
sprintf(subpath, "%s/%s", path, ent->d_name);
|
||||
|
||||
if (!deleteDir(subpath))
|
||||
result = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Delete file
|
||||
char fpath[512];
|
||||
sprintf(fpath, "%s/%s", path, ent->d_name);
|
||||
|
||||
iprintf("%s...", fpath);
|
||||
if (remove(fpath) != 0)
|
||||
{
|
||||
iprintf("\x1B[31m");
|
||||
iprintf("Fail\n");
|
||||
iprintf("\x1B[47m");
|
||||
result = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
iprintf("\x1B[42m");
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
iprintf("%s...", path);
|
||||
if (remove(path) != 0)
|
||||
{
|
||||
iprintf("\x1B[31m");
|
||||
iprintf("Fail\n");
|
||||
iprintf("\x1B[47m");
|
||||
result = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
iprintf("\x1B[42m");
|
||||
iprintf("Done\n");
|
||||
iprintf("\x1B[47m");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
unsigned long long getDirSize(const char* path, u32 blockSize)
|
||||
{
|
||||
if (!path) return 0;
|
||||
|
||||
unsigned long long size = 0;
|
||||
DIR* dir = opendir(path);
|
||||
struct dirent* ent;
|
||||
|
||||
if (dir)
|
||||
{
|
||||
while ((ent = readdir(dir)))
|
||||
{
|
||||
if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0)
|
||||
continue;
|
||||
|
||||
if (ent->d_type == DT_DIR)
|
||||
{
|
||||
char fullpath[512];
|
||||
sprintf(fullpath, "%s/%s", path, ent->d_name);
|
||||
|
||||
size += getDirSize(fullpath, blockSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
char fullpath[260];
|
||||
sprintf(fullpath, "%s/%s", path, ent->d_name);
|
||||
|
||||
size += getFileSizePath(fullpath);
|
||||
|
||||
// If we've specified a block size, round up to it
|
||||
if ((size % blockSize) != 0)
|
||||
size += blockSize - (size % blockSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return size;
|
||||
}
|
||||
|
||||
bool safeCreateDir(const char* path)
|
||||
{
|
||||
if (((mkdir(path, 0777) == 0) || errno == EEXIST))
|
||||
@ -458,142 +186,3 @@ bool safeCreateDir(const char* path)
|
||||
messageBox(errorStr);
|
||||
return false;
|
||||
}
|
||||
|
||||
//home menu
|
||||
int getMenuSlots()
|
||||
{
|
||||
//Assume the home menu has a hard limit on slots
|
||||
//Find a better way to do this
|
||||
return TITLE_LIMIT;
|
||||
}
|
||||
|
||||
int getMenuSlotsFree()
|
||||
{
|
||||
//Get number of open menu slots by subtracting the number of directories in the title folders
|
||||
//Find a better way to do this
|
||||
const int NUM_OF_DIRS = 4;
|
||||
const char* dirs[] = {
|
||||
"00030004",
|
||||
"00030005",
|
||||
"00030015",
|
||||
"00030017"
|
||||
};
|
||||
|
||||
int freeSlots = getMenuSlots();
|
||||
|
||||
DIR* dir;
|
||||
struct dirent* ent;
|
||||
|
||||
for (int i = 0; i < NUM_OF_DIRS; i++)
|
||||
{
|
||||
char path[256];
|
||||
sprintf(path, "%s:/title/%s", sdnandMode ? "sd" : "nand", dirs[i]);
|
||||
|
||||
dir = opendir(path);
|
||||
|
||||
if (dir)
|
||||
{
|
||||
while ( (ent = readdir(dir)) != NULL )
|
||||
{
|
||||
if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0)
|
||||
continue;
|
||||
|
||||
if (ent->d_type == DT_DIR)
|
||||
freeSlots -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
return freeSlots;
|
||||
}
|
||||
|
||||
//SD card
|
||||
bool sdIsInserted()
|
||||
{
|
||||
//Find a better way to do this.
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned long long getSDCardSize()
|
||||
{
|
||||
if (sdIsInserted())
|
||||
{
|
||||
struct statvfs st;
|
||||
if (statvfs("sd:/", &st) == 0)
|
||||
return st.f_bsize * st.f_blocks;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned long long getSDCardFree()
|
||||
{
|
||||
if (sdIsInserted())
|
||||
{
|
||||
struct statvfs st;
|
||||
if (statvfs("sd:/", &st) == 0)
|
||||
return st.f_bsize * st.f_bavail;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//internal storage
|
||||
unsigned long long getDsiSize()
|
||||
{
|
||||
//The DSi has 256MB of internal storage. Some is unavailable and used by other things.
|
||||
//An empty DSi reads 1024 open blocks
|
||||
return 1024 * BYTES_PER_BLOCK;
|
||||
}
|
||||
|
||||
unsigned long long getDsiFree()
|
||||
{
|
||||
u32 blockSize = getDsiClusterSize();
|
||||
|
||||
//Get free space by subtracting file sizes in nand folders
|
||||
unsigned long long size = getDsiSize();
|
||||
unsigned long long appSize = getDirSize(sdnandMode ? "sd:/title/00030004" : "nand:/title/00030004", blockSize);
|
||||
|
||||
//subtract, but don't go under 0
|
||||
if (appSize > size)
|
||||
{
|
||||
size = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
size -= appSize;
|
||||
}
|
||||
|
||||
unsigned long long realFree = getDsiRealFree();
|
||||
|
||||
return (realFree < size) ? realFree : size;
|
||||
}
|
||||
|
||||
unsigned long long getDsiRealSize()
|
||||
{
|
||||
struct statvfs st;
|
||||
if (statvfs(sdnandMode ? "sd:/" : "nand:/", &st) == 0)
|
||||
return st.f_bsize * st.f_blocks;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned long long getDsiRealFree()
|
||||
{
|
||||
struct statvfs st;
|
||||
if (statvfs(sdnandMode ? "sd:/" : "nand:/", &st) == 0)
|
||||
return st.f_bsize * st.f_bavail;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 getDsiClusterSize()
|
||||
{
|
||||
struct statvfs st;
|
||||
if (statvfs(sdnandMode ? "sd:/" : "nand:/", &st) == 0)
|
||||
return st.f_bsize;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -4,50 +4,16 @@
|
||||
#include <nds/ndstypes.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#define BACKUP_PATH "sd:/_nds/ntm/backup"
|
||||
#define BYTES_PER_BLOCK (1024*128)
|
||||
|
||||
//printing
|
||||
void printBytes(unsigned long long bytes);
|
||||
|
||||
//progress bar
|
||||
void printProgressBar(float percent);
|
||||
void clearProgressBar();
|
||||
|
||||
//Files
|
||||
bool fileExists(char const* path);
|
||||
int copyFile(char const* src, char const* dst);
|
||||
int copyFilePart(char const* src, u32 offset, u32 size, char const* dst);
|
||||
unsigned long long getFileSize(FILE* f);
|
||||
unsigned long long getFileSizePath(char const* path);
|
||||
bool padFile(char const* path, int size);
|
||||
bool toggleFileReadOnly(const char* path, bool readOnly);
|
||||
bool writeToFile(FILE* fd, const char* buffer, size_t size);
|
||||
|
||||
//Directories
|
||||
bool dirExists(char const* path);
|
||||
bool copyDir(char const* src, char const* dst);
|
||||
bool deleteDir(char const* path);
|
||||
unsigned long long getDirSize(char const* path, u32 blockSize);
|
||||
bool safeCreateDir(const char* path);
|
||||
|
||||
//home menu
|
||||
int getMenuSlots();
|
||||
int getMenuSlotsFree();
|
||||
#define getMenuSlotsUsed() (getMenuSlots() - getMenuSlotsFree())
|
||||
|
||||
//SD card
|
||||
bool sdIsInserted();
|
||||
unsigned long long getSDCardSize();
|
||||
unsigned long long getSDCardFree();
|
||||
#define getSDCardUsedSpace() (getSDCardSize() - getSDCardFree())
|
||||
|
||||
//internal storage
|
||||
unsigned long long getDsiSize();
|
||||
unsigned long long getDsiFree();
|
||||
unsigned long long getDsiRealSize();
|
||||
unsigned long long getDsiRealFree();
|
||||
u32 getDsiClusterSize();
|
||||
#define getDsiUsed() (getDSIStorageSize() - getDSIStorageFree())
|
||||
|
||||
#endif
|
||||
@ -1,75 +0,0 @@
|
||||
#include "main.h"
|
||||
#include "message.h"
|
||||
#include "storage.h"
|
||||
|
||||
void testMenu()
|
||||
{
|
||||
//top screen
|
||||
clearScreen(&topScreen);
|
||||
iprintf("Storage Check Test\n\n");
|
||||
|
||||
//bottom screen
|
||||
clearScreen(&bottomScreen);
|
||||
|
||||
unsigned int free = 0;
|
||||
unsigned int size = 0;
|
||||
|
||||
//home menu slots
|
||||
{
|
||||
iprintf("Free Home Menu Slots:\n");
|
||||
|
||||
free = getMenuSlotsFree();
|
||||
iprintf("\t%d / ", free);
|
||||
|
||||
size = getMenuSlots();
|
||||
iprintf("%d\n", size);
|
||||
}
|
||||
|
||||
//dsi menu
|
||||
{
|
||||
iprintf("\nFree DSi Menu Space:\n\t");
|
||||
|
||||
free = getDsiFree();
|
||||
printBytes(free);
|
||||
iprintf(" / ");
|
||||
|
||||
size = getDsiSize();
|
||||
printBytes(size);
|
||||
iprintf("\n");
|
||||
|
||||
iprintf("\t%d / %d blocks\n", free / BYTES_PER_BLOCK, size / BYTES_PER_BLOCK);
|
||||
}
|
||||
|
||||
//nand
|
||||
if (!sdnandMode)
|
||||
{
|
||||
iprintf("\nFree NAND Space:\n\t");
|
||||
|
||||
free = getDsiRealFree();
|
||||
printBytes(free);
|
||||
iprintf(" / ");
|
||||
|
||||
size = getDsiRealSize();
|
||||
printBytes(size);
|
||||
iprintf("\n");
|
||||
}
|
||||
|
||||
//SD Card
|
||||
{
|
||||
iprintf("\nFree SD Space:\n\t");
|
||||
|
||||
unsigned long long sdfree = getSDCardFree();
|
||||
printBytes(sdfree);
|
||||
iprintf(" / ");
|
||||
|
||||
unsigned long long sdsize = getSDCardSize();
|
||||
printBytes(sdsize);
|
||||
iprintf("\n");
|
||||
|
||||
printf("\t%d / %d blocks\n", (unsigned int)(sdfree / BYTES_PER_BLOCK), (unsigned int)(sdsize / BYTES_PER_BLOCK));
|
||||
}
|
||||
|
||||
//end
|
||||
iprintf("\nBack - [B]\n");
|
||||
keyWait(KEY_B);
|
||||
}
|
||||
@ -1,523 +0,0 @@
|
||||
#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_READ_ONLY,
|
||||
TITLE_MENU_BACK
|
||||
};
|
||||
|
||||
static bool readOnly = false;
|
||||
|
||||
static void generateList(Menu* m);
|
||||
static void printItem(Menu* m);
|
||||
static int subMenu();
|
||||
static void backup(Menu* m);
|
||||
static bool delete(Menu* m);
|
||||
static void toggleReadOnly(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)
|
||||
{
|
||||
readOnly = FAT_getAttr(m->items[m->cursor].value) & ATTR_READONLY;
|
||||
|
||||
switch (subMenu())
|
||||
{
|
||||
case TITLE_MENU_BACKUP:
|
||||
backup(m);
|
||||
break;
|
||||
|
||||
case TITLE_MENU_DELETE:
|
||||
{
|
||||
if (delete(m))
|
||||
{
|
||||
resetMenu(m);
|
||||
generateList(m);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case TITLE_MENU_READ_ONLY:
|
||||
toggleReadOnly(m);
|
||||
break;
|
||||
}
|
||||
|
||||
printMenu(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
freeMenu(m);
|
||||
}
|
||||
|
||||
static void generateList(Menu* m)
|
||||
{
|
||||
if (!m) return;
|
||||
|
||||
const int NUM_OF_DIRS = 4;
|
||||
const char* dirs[] = {
|
||||
"00030004",
|
||||
"00030005",
|
||||
"00030015",
|
||||
"00030017"
|
||||
};
|
||||
|
||||
const char* blacklist[4][6] = {
|
||||
{ // 00030004
|
||||
NULL //nothing blacklisted
|
||||
},
|
||||
{ // 00030005
|
||||
"484e44", // DS Download Play
|
||||
"484e45", // PictoChat
|
||||
"484e49", // Nintendo DSi Camera
|
||||
"484e4a", // Nintendo Zone
|
||||
"484e4b", // Nintendo DSi Sound
|
||||
NULL
|
||||
},
|
||||
{ // 00030015
|
||||
"484e42", // System Settings
|
||||
"484e46", // Nintendo DSi Shop
|
||||
NULL
|
||||
},
|
||||
{ // 00030017
|
||||
"484e41", // Launcher
|
||||
NULL
|
||||
}
|
||||
};
|
||||
|
||||
//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)
|
||||
{
|
||||
//if the region check somehow failed blacklist all-non DSiWare
|
||||
if (region == 0 && i > 0) continue;
|
||||
|
||||
bool blacklisted = false;
|
||||
for (int j = 0; blacklist[i][j] != NULL; j++)
|
||||
{
|
||||
char titleId[9];
|
||||
sprintf(titleId, "%s%02x", blacklist[i][j], region);
|
||||
if (strcmp(titleId, ent->d_name) == 0)
|
||||
blacklisted = true;
|
||||
|
||||
// also blacklist specific all-region titles
|
||||
if ((strcmp("484e4441", ent->d_name) == 0) || // Download Play
|
||||
(strcmp("484e4541", ent->d_name) == 0) || // PictoChat
|
||||
(strcmp("34544e41", ent->d_name) == 0)) // TwlNmenu
|
||||
blacklisted = true;
|
||||
}
|
||||
if (blacklisted) 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, readOnly ? "Mark not read-only" : "Mark read-only", 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);
|
||||
if (access(path, F_OK) == 0)
|
||||
{
|
||||
//get app version
|
||||
FILE *tmd = fopen(path, "rb");
|
||||
if (tmd)
|
||||
{
|
||||
u8 appVersion[4];
|
||||
fseek(tmd, 0x1E4, SEEK_SET);
|
||||
fread(&appVersion, 1, 4, tmd);
|
||||
fclose(tmd);
|
||||
|
||||
iprintf("%s -> \n%s...\n", path, dstpath);
|
||||
copyFile(path, dstpath);
|
||||
|
||||
//app
|
||||
sprintf(path, "%s/content/%02x%02x%02x%02x.app", srcpath, appVersion[0], appVersion[1], appVersion[2], appVersion[3]);
|
||||
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;
|
||||
}
|
||||
|
||||
static void toggleReadOnly(Menu* m)
|
||||
{
|
||||
if (!m) return;
|
||||
|
||||
tDSiHeader* h = getRomHeader(m->items[m->cursor].value);
|
||||
|
||||
char path[256];
|
||||
char srcpath[30];
|
||||
sprintf(srcpath, "%s:/title/%08lx/%08lx", sdnandMode ? "sd" : "nand", h->tid_high, h->tid_low);
|
||||
|
||||
if (!sdnandMode && !nandio_unlock_writing()) return;
|
||||
|
||||
//app
|
||||
strcpy(path, m->items[m->cursor].value);
|
||||
if (access(path, F_OK) == 0)
|
||||
FAT_setAttr(path, FAT_getAttr(path) ^ ATTR_READONLY);
|
||||
|
||||
//tmd
|
||||
sprintf(path, "%s/content/title.tmd", srcpath);
|
||||
if (access(path, F_OK) == 0)
|
||||
FAT_setAttr(path, FAT_getAttr(path) ^ ATTR_READONLY);
|
||||
|
||||
//public save
|
||||
sprintf(path, "%s/data/public.sav", srcpath);
|
||||
if (access(path, F_OK) == 0)
|
||||
FAT_setAttr(path, FAT_getAttr(path) ^ ATTR_READONLY);
|
||||
|
||||
//private save
|
||||
sprintf(path, "%s/data/private.sav", srcpath);
|
||||
if (access(path, F_OK) == 0)
|
||||
FAT_setAttr(path, FAT_getAttr(path) ^ ATTR_READONLY);
|
||||
|
||||
//banner save
|
||||
sprintf(path, "%s/data/banner.sav", srcpath);
|
||||
if (access(path, F_OK) == 0)
|
||||
FAT_setAttr(path, FAT_getAttr(path) ^ ATTR_READONLY);
|
||||
|
||||
if (!sdnandMode)
|
||||
nandio_lock_writing();
|
||||
|
||||
free(h);
|
||||
|
||||
messageBox("Title's read-only status\nsuccesfully toggled.");
|
||||
}
|
||||
@ -7,6 +7,7 @@
|
||||
|
||||
static char unlaunchInstallerBuffer[0x30000];
|
||||
static const char* hnaaTmdPath = "nand:/title/00030017/484e4141/content/title.tmd";
|
||||
static const char* hnaaBackupTmdPath = "nand:/title/00030017/484e4141/content/title.tmd.bak";
|
||||
|
||||
bool isLauncherTmdPatched(const char* path)
|
||||
{
|
||||
@ -110,16 +111,14 @@ static bool patchMainTmd(const char* path)
|
||||
|
||||
static bool restoreProtoTmd(const char* path)
|
||||
{
|
||||
bool hnaaBackupExists = false;
|
||||
hnaaBackupExists = (access("nand:/title/00030017/484e4141/content/title.tmd.bak", F_OK) == 0);
|
||||
if (!hnaaBackupExists)
|
||||
if (!fileExists(hnaaBackupTmdPath))
|
||||
{
|
||||
messageBox("\x1B[31mError:\x1B[33m No original tmd found!\nCan't uninstall unlaunch.\n");
|
||||
return false;
|
||||
}
|
||||
remove(path);
|
||||
copyFile("nand:/title/00030017/484e4141/content/title.tmd.bak", path);
|
||||
remove("nand:/title/00030017/484e4141/content/title.tmd.bak");
|
||||
rename(hnaaBackupTmdPath, path);
|
||||
toggleFileReadOnly(path, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -170,7 +169,7 @@ static bool installUnlaunchRetailConsole(const char* retailLauncherTmdPath)
|
||||
}
|
||||
|
||||
// We have to remove write protect otherwise reinstalling will fail.
|
||||
if (access(hnaaTmdPath, F_OK) == 0 && !toggleFileReadOnly(hnaaTmdPath, false)) {
|
||||
if (fileExists(hnaaTmdPath) && !toggleFileReadOnly(hnaaTmdPath, false)) {
|
||||
messageBox("\x1B[31mError:\x1B[33m Can't remove launcher tmd write protect\n");
|
||||
return false;
|
||||
}
|
||||
@ -240,15 +239,15 @@ static bool installUnlaunchProtoConsole(void)
|
||||
// Likely factory rejects that never had production firmware flashed.
|
||||
|
||||
// We have to remove write protect otherwise reinstalling will fail.
|
||||
if (access(hnaaTmdPath, F_OK) == 0 && !toggleFileReadOnly(hnaaTmdPath, false)) {
|
||||
if (fileExists(hnaaTmdPath) && !toggleFileReadOnly(hnaaTmdPath, false)) {
|
||||
messageBox("\x1B[31mError:\x1B[33m Can't remove launcher tmd write protect\n");
|
||||
return false;
|
||||
}
|
||||
bool hnaaBackupExists = (access("nand:/title/00030017/484e4141/content/title.tmd.bak", F_OK) == 0);
|
||||
bool hnaaBackupExists = fileExists(hnaaBackupTmdPath);
|
||||
// Back up the TMD since we'll be writing to it directly.
|
||||
if (!hnaaBackupExists)
|
||||
{
|
||||
rename(hnaaTmdPath, "nand:/title/00030017/484e4141/content/title.tmd.bak");
|
||||
rename(hnaaTmdPath, hnaaBackupTmdPath);
|
||||
// Mark backup tmd as readonly, just to be sure
|
||||
toggleFileReadOnly("nand:/title/00030017/484e4141/content/title.tmd.bak", true);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user