diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..af7775c --- /dev/null +++ b/Makefile @@ -0,0 +1,140 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +include $(DEVKITARM)/ds_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +# MAXMOD_SOUNDBANK contains a directory of music and sound effect files +#--------------------------------------------------------------------------------- +TARGET := $(shell basename $(CURDIR)) +BUILD := build +SOURCES := src +DATA := data +INCLUDES := include + +GAME_TITLE := TMFH +GAME_SUBTITLE1 := Title Manager for HiyaCFW +GAME_SUBTITLE2 := JeffRuLz + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -mthumb -mthumb-interwork -march=armv5te -mtune=arm946e-s + +CFLAGS := -g -Wall -O2\ + -fomit-frame-pointer\ + -ffast-math \ + $(ARCH) + +CFLAGS += $(INCLUDE) -DARM9 +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project (order is important) +#--------------------------------------------------------------------------------- +LIBS := -lfat -lnds9 + + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(LIBNDS) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +icons := $(wildcard *.bmp) + +ifneq (,$(findstring $(TARGET).bmp,$(icons))) + export GAME_ICON := $(CURDIR)/$(TARGET).bmp +else + ifneq (,$(findstring icon.bmp,$(icons))) + export GAME_ICON := $(CURDIR)/icon.bmp + endif +endif + +.PHONY: $(BUILD) clean + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds + +#--------------------------------------------------------------------------------- +else + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +$(OUTPUT).nds : $(OUTPUT).elf +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + $(bin2o) + +-include $(DEPSDIR)/*.d + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/src/installmenu.c b/src/installmenu.c new file mode 100644 index 0000000..8904e6e --- /dev/null +++ b/src/installmenu.c @@ -0,0 +1,642 @@ +#include "menus.h" +#include "storage.h" +#include "maketmd.h" +#include + +static int cursor = 0; +static int scrolly = 0; +static int numberOfTitles = 0; + +static void moveCursor(int dir); + +static void printList(); +static void printFileInfoNum(int num); + +static int getNumberOfTitles(); +//static void getGameTitle(char* title, FILE* f); +static int getFile(char* dest, int num, int fullpath); + +static void subMenu(); +static void install(char* fpath); + +void installMenu() +{ + cursor = 0; + scrolly = 0; + numberOfTitles = getNumberOfTitles(); + + consoleSelect(&topScreen); + consoleClear(); + + consoleSelect(&bottomScreen); + consoleClear(); + + //No titles error + if (numberOfTitles == 0) + { + iprintf("No files found.\n"); + iprintf("Place .nds(dsi) or .app files in %s\n", ROM_PATH); + iprintf("\nBack - B\n"); + + keyWait(KEY_B | KEY_A | KEY_START); + return; + } + + //Print data + consoleSelect(&topScreen); + printFileInfoNum(cursor); + + consoleSelect(&bottomScreen); + printList(); + + while (1) + { + swiWaitForVBlank(); + scanKeys(); + + int thisCursor = cursor; + int thisscrolly = scrolly; + + //Clear cursor + consoleSelect(&bottomScreen); + iprintf("\x1b[%d;0H ", cursor - scrolly); + + //Move cursor + if (keysDown() & KEY_DOWN) + moveCursor(1); + + if (keysDown() & KEY_UP) + moveCursor(-1); + + if (keysDown() & KEY_RIGHT) + { + repeat (10) + moveCursor(1); + } + + if (keysDown() & KEY_LEFT) + { + repeat (10) + moveCursor(-1); + } + + //Refresh screens + if (thisCursor != cursor) + { + consoleSelect(&topScreen); + consoleClear(); + + printFileInfoNum(cursor); + } + + if (thisscrolly != scrolly) + { + consoleSelect(&bottomScreen); + consoleClear(); + + printList(); + } + + //Print cursor + consoleSelect(&bottomScreen); + iprintf("\x1b[%d;0H>", cursor - scrolly); + + // + if (keysDown() & KEY_B) + break; + else if (keysDown() & KEY_A) + { + subMenu(); + + consoleSelect(&topScreen); + printFileInfoNum(cursor); + + consoleSelect(&bottomScreen); + printList(); + } + } +} + +void moveCursor(int dir) +{ + cursor += sign(dir); + + if (cursor < 0) + cursor = 0; + + if (cursor >= numberOfTitles - 1) + cursor = numberOfTitles - 1; + + if (cursor - scrolly >= 23) + scrolly += 1; + + if (cursor - scrolly < 0) + scrolly -= 1; +} + +void printList() +{ + consoleClear(); + + for (int i = scrolly; i < scrolly + 23; i++) + { + char str[256]; + if (getFile(str, i, 0) == 1) + iprintf(" %.30s\n", str); + } + + //Scroll arrows + if (scrolly > 0) + iprintf("\x1b[0;31H^"); + + if (scrolly < numberOfTitles - 23) + iprintf("\x1b[22;31Hv"); +} + +void printFileInfoNum(int num) +{ + consoleClear(); + + char path[256]; + if (getFile(path, num, 1) == 1) + printFileInfo(path); +} + +int getNumberOfTitles() +{ + DIR* dir; + struct dirent* ent; + int count = 0; + + dir = opendir(ROM_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) + { + if (strstr(ent->d_name, ".nds") != NULL || strstr(ent->d_name, ".app") != NULL) + count++; + } + } + } + + closedir(dir); + return count; +} +/* +void getGameTitle(char* title, FILE* f) +{ + tDSiHeader header; + tNDSBanner banner; + + fseek(f, 0, SEEK_SET); + fread(&header, sizeof(tDSiHeader), 1, f); + fseek(f, header.ndshdr.bannerOffset, SEEK_SET); + fread(&banner, sizeof(tNDSBanner), 1, f); + +// iprintf("\t%s\n", header.ndshdr.gameTitle); +// iprintf("\t%s\n", (char*)banner.titles[0]); + + int line = 0; + for (int i = 0; i < 64; i++) + { + char c = banner.titles[0][i]; + + if (c == '\n') + { + if (line == 0) + { + title[i] = ' '; + line = 1; + } + else + { + title[i] = '\0'; + break; + } + } + else + title[i] = c; + } + + title[64] = '\0'; +} +*/ + +int getFile(char* dest, int num, int fullpath) +{ + DIR* dir; + struct dirent* ent; + int result = 0; + + dir = opendir(ROM_PATH); + + if (dir) + { + int count = 0; + + while ( (ent = readdir(dir)) != NULL ) + { + if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0) + continue; + + if (ent->d_type != DT_DIR) + { + if(strstr(ent->d_name, ".nds") != NULL || strstr(ent->d_name, ".app") != NULL) + { + if (count < num) + { + count++; + continue; + } + else + { + if (fullpath == 0) + sprintf(dest, "%s", ent->d_name); + else + sprintf(dest, "%s%s", ROM_PATH, ent->d_name); + + result = 1; + break; + } + } + } + } + } + + closedir(dir); + return result; +} + +// +static int subCursor = 0; + +enum { + INSTALL_MENU_INSTALL, + INSTALL_MENU_DELETE, + INSTALL_MENU_BACK +}; + +void subMenu() +{ + bool printMenu = true; + subCursor = 0; + + while (1) + { + swiWaitForVBlank(); + scanKeys(); + + if (printMenu == true) + { + printMenu = false; + + consoleSelect(&bottomScreen); + consoleClear(); + + iprintf("\tInstall\n"); + iprintf("\tDelete\n"); + iprintf("\tBack - B\n"); + } + + //Clear cursor + iprintf("\x1b[%d;0H ", subCursor); + + //Move cursor + if (keysDown() & KEY_DOWN) + { + if (subCursor < INSTALL_MENU_BACK) + subCursor++; + } + + if (keysDown() & KEY_UP) + { + if (subCursor > 0) + subCursor--; + } + + //Reprint cursor + iprintf("\x1b[%d;0H>", subCursor); + + // + if (keysDown() & KEY_B) + break; + + else if (keysDown() & KEY_A) + { + if (subCursor == INSTALL_MENU_INSTALL) + { + char fpath[256]; + getFile(fpath, cursor, 1); + + char msg[512+1]; + msg[512] = '\0'; + sprintf(msg, "Are you sure you want to install\n%s\n", fpath); + + if (choiceBox(msg) == YES) + install(fpath); + + break; + } + + else if (subCursor == INSTALL_MENU_DELETE) + { + char fpath[256]; + getFile(fpath, cursor, 1); + + char msg[512+1]; + msg[512] = '\0'; + sprintf(msg, "Are you sure you want to delete\n%s\n", fpath); + + if (choiceBox(msg) == YES) + { + if (remove(fpath) != 0) + messageBox("File could not be deleted."); + + else + messageBox("File deleted."); + + //Reset + cursor = 0; + scrolly = 0; + numberOfTitles = getNumberOfTitles(); + + break; + } + else + { + printMenu = true; + } + } + + else if (subCursor == INSTALL_MENU_BACK) + { + break; + } + } + } +} + +void install(char* fpath) +{ + consoleSelect(&bottomScreen); + consoleClear(); + + iprintf("Installing %s\n", fpath); swiWaitForVBlank(); + + FILE* f = fopen(fpath, "rb"); + + if (!f) + { + iprintf("Error: could not open file.\nPress B to exit.\n"); + keyWait(KEY_A | KEY_B); + } + else + { + //Load header + tDSiHeader header; + tNDSBanner banner; + + { + fseek(f, 0, SEEK_SET); + fread(&header, sizeof(tDSiHeader), 1, f); + fseek(f, header.ndshdr.bannerOffset, SEEK_SET); + fread(&banner, sizeof(tNDSBanner), 1, f); + } + + //Print file size + int fileSize = -1; + + { + iprintf("File Size: "); swiWaitForVBlank(); + + fileSize = getFileSize(f); + + printBytes(fileSize); + printf("\n"); + } + + //Do not want file opened anymore + fclose(f); + + //SD card check + { + iprintf("Enough room on SD card?..."); swiWaitForVBlank(); + + if (getSDCardFree() < fileSize) + { + iprintf("No\n"); + goto error; + } + + iprintf("Yes\n"); + } + + //DSi storage check + { + iprintf("Enough room on DSi?..."); swiWaitForVBlank(); + + if (getDsiFree() < fileSize) + { + iprintf("No\n"); swiWaitForVBlank(); + goto error; + } + + iprintf("Yes\n"); swiWaitForVBlank(); + } + + //Menu slot check + { + iprintf("Open DSi menu slot?..."); swiWaitForVBlank(); + + if (getMenuSlotsFree() <= 0) + { + iprintf("No\n"); swiWaitForVBlank(); + goto error; + } + + iprintf("Yes\n"); swiWaitForVBlank(); + } + + //Create title directory + char titleID[8+1]; + char dirPath[256]; + + { + sprintf(titleID, "%08x", (unsigned int)header.tid_low); + sprintf(dirPath, "/title/%08x/%s", (unsigned int)header.tid_high, titleID); + + //iprintf("Creating dir\n%s\n", dirPath); swiWaitForVBlank(); + } + + //Check if title is already installed + { + DIR* dir = opendir(dirPath); + + if (dir) + { + closedir(dir); + + iprintf("Title %s is already used.\nInstall anyway?\n", titleID); + iprintf("Yes - A\nNo - B\n"); + + while (1) + { + swiWaitForVBlank(); + scanKeys(); + + if (keysDown() & KEY_A) + break; + + if (keysDown() & KEY_B) + goto complete; + } + } + } + + // + mkdir(dirPath, 0777); + + //Content folder + { + char contentPath[256]; + sprintf(contentPath, "%s/content", dirPath); + + //iprintf("Creating dir\n%s\n", contentPath); swiWaitForVBlank(); + mkdir(contentPath, 0777); + + //Create 0000000.app + //Does 00000000 always work? + { + char appPath[256]; + sprintf(appPath, "%s/00000000.app", contentPath); + + iprintf("Creating 00000000.app..."); swiWaitForVBlank(); + + if (copyFile(fpath, appPath) == 0) + { + iprintf("Failed\n"); + goto error; + } + + iprintf("Done\n"); + + //Make TMD + { + char tmdPath[256]; + sprintf(tmdPath, "%s/title.tmd", contentPath); + + if (maketmd(appPath, tmdPath) != 0) + goto error; + } + } + } + + //Data folder + { + char dataPath[256]; + sprintf(dataPath, "%s/data", dirPath); + + //iprintf("Creating dir\n%s\n", dataPath); swiWaitForVBlank(); + mkdir(dataPath, 0777); + + //If needed, create public.sav + if (header.public_sav_size > 0) + { + char publicPath[512]; + sprintf(publicPath, "%s/public.sav", dataPath); + + iprintf("Creating public.sav..."); swiWaitForVBlank(); + + FILE* file = fopen(publicPath, "wb"); + + if (!file) + iprintf("Failed\n"); + + else + { + char num = 0; + + repeat (header.public_sav_size) + fwrite(&num, 1, 1, f); + + iprintf("Done\n"); + } + + fclose(file); + } + + //If needed, create private.sav + if (header.private_sav_size > 0) + { + char privatePath[512]; + sprintf(privatePath, "%s/private.sav", dataPath); + + iprintf("Creating private.sav..."); swiWaitForVBlank(); + + FILE* file = fopen(privatePath, "wb"); + + if (!file) + iprintf("Failed\n"); + + else + { + char num = 0; + + repeat (header.private_sav_size) + fwrite(&num, 1, 1, f); + + iprintf("Done\n"); + } + + fclose(file); + } + + //If needed, create banner.sav + if (header.appflags & 0x4) + { + char bannerPath[512]; + sprintf(bannerPath, "%s/banner.sav", dataPath); + + iprintf("Creating banner.sav..."); swiWaitForVBlank(); + + FILE* file = fopen(bannerPath, "wb"); + + if (!file) + iprintf("Failed\n"); + + else + { + char num = 0; + + repeat (1024*16) //Is banner.sav always 16kb? + fwrite(&num, 1, 1, f); + + iprintf("Done\n"); + } + + fclose(file); + } + } + + iprintf("\nInstallation complete.\nPress B to exit.\n"); + keyWait(KEY_A | KEY_B); + } + +complete: + fclose(f); + return; + +error: + fclose(f); + + iprintf("\nInstallation failed.\nPress B to exit.\n"); + keyWait(KEY_A | KEY_B); + + return; +} \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..517304b --- /dev/null +++ b/src/main.c @@ -0,0 +1,133 @@ +#include "main.h" +#include "menus.h" + +#define VERSION "0.4" + +enum { + MAIN_MENU_INSTALL, + MAIN_MENU_TITLES, +// MAIN_MENU_RESTORE, + MAIN_MENU_TEST, + MAIN_MENU_EXIT +}; + +static int mainMenu(); + +int main(int argc, char **argv) +{ + videoSetMode(MODE_0_2D); + videoSetModeSub(MODE_0_2D); + + vramSetBankA(VRAM_A_MAIN_BG); + vramSetBankC(VRAM_C_SUB_BG); + + consoleInit(&topScreen, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, true, true); + consoleInit(&bottomScreen, 3, BgType_Text4bpp, BgSize_T_256x256, 31, 0, false, true); + + if (!fatInitDefault()) + { + consoleSelect(&bottomScreen); + consoleClear(); + + iprintf("fatInitDefault...Failed\n"); + iprintf("\nPress B to exit.\n"); + + keyWait(KEY_B | KEY_A | KEY_START); + } + else + { + bool programEnd = false; + + while (!programEnd) + { + switch (mainMenu()) + { + case MAIN_MENU_INSTALL: + installMenu(); + break; + + case MAIN_MENU_TITLES: + titleMenu(); + break; + + case MAIN_MENU_TEST: + testMenu(); + break; + + case MAIN_MENU_EXIT: + programEnd = true; + break; + } + } + } + + return 0; +} + +static int cursor = 0; + +int mainMenu() +{ + consoleSelect(&topScreen); + consoleClear(); + + iprintf("\tTitle Manager for HiyaCFW\n"); + iprintf("\nversion %s\n", VERSION); + iprintf("\x1b[23;0HJeff - 2018"); + + consoleSelect(&bottomScreen); + consoleClear(); + + iprintf("\tInstall\n"); + iprintf("\tTitles\n"); +// iprintf("\tRestore\n"); + iprintf("\tTest\n"); + iprintf("\tExit"); + + while (1) + { + swiWaitForVBlank(); + scanKeys(); + + //Clear cursor + iprintf("\x1b[%d;0H ", cursor); + + if (keysDown() & KEY_DOWN) + { + if ( (cursor += 1) > MAIN_MENU_EXIT ) + cursor = 0; + } + + if (keysDown() & KEY_RIGHT) + { + repeat (10) + { + if ( (cursor += 1) > MAIN_MENU_EXIT ) + cursor = 0; + } + } + + if (keysDown() & KEY_UP) + { + if ( (cursor -= 1) < 0 ) + cursor = MAIN_MENU_EXIT; + } + + if (keysDown() & KEY_LEFT) + { + repeat (10) + { + if ( (cursor -= 1) < 0 ) + cursor = MAIN_MENU_EXIT; + } + } + + //print cursor + iprintf("\x1b[%d;0H>", cursor); + + if (keysDown() & KEY_A) + break; + } + + return cursor; +} \ No newline at end of file diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..a2d90ee --- /dev/null +++ b/src/main.h @@ -0,0 +1,15 @@ +#ifndef MAIN_H +#define MAIN_H + +#include +#include +#include + +PrintConsole topScreen; +PrintConsole bottomScreen; + +#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 \ No newline at end of file diff --git a/src/maketmd.c b/src/maketmd.c new file mode 100644 index 0000000..9f2d7ba --- /dev/null +++ b/src/maketmd.c @@ -0,0 +1,173 @@ +/*--------------------------------------------------------------------------------- + +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 +#include +#include +#include +#include +#include + +//#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) + { + fseek(app, 0, SEEK_END); + uint32_t size = ftell(app); + size = __bswap32(size); + + // 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); + + swiSHA1Update(&ctx, buffer, buffer_read); + } while(buffer_read == SHA_BUFFER_SIZE); + + 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("\nUsage: %s file.app \n", "maketmd"); + return 1; + } + + // APP file (input) + FILE* app = fopen(input, "rb"); + + if(!app) { + iprintf("Error at opening %s for reading.\n", input); + return 1; + } + + // TMD file (output) + FILE* tmd = fopen(tmdPath, "wb"); + + if (!tmd) + { + fclose(app); + iprintf("Error at opening %s for writing.\n", tmdPath); + 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; +} \ No newline at end of file diff --git a/src/maketmd.h b/src/maketmd.h new file mode 100644 index 0000000..b81e675 --- /dev/null +++ b/src/maketmd.h @@ -0,0 +1,6 @@ +#ifndef MAKETMD_H +#define MAKETMD_H + +int maketmd(char* input, char* tmdPath); + +#endif \ No newline at end of file diff --git a/src/menus.c b/src/menus.c new file mode 100644 index 0000000..d954b48 --- /dev/null +++ b/src/menus.c @@ -0,0 +1,72 @@ +#include "menus.h" + +void keyWait(u32 key) +{ + while (1) + { + swiWaitForVBlank(); + scanKeys(); + + if (keysDown() & key) + break; + } +} + +int choiceBox(char* message) +{ + const int choiceRow = 10; + int cursor = 0; + + consoleSelect(&bottomScreen); + consoleClear(); + + iprintf("%s\n", message); + iprintf("\x1b[%d;0H\tYes\n\tNo\n", choiceRow); + + while (1) + { + swiWaitForVBlank(); + scanKeys(); + + //Clear cursor + iprintf("\x1b[%d;0H ", choiceRow + cursor); + + if (keysDown() & (KEY_UP | KEY_DOWN)) + cursor = !cursor; + + //Print cursor + iprintf("\x1b[%d;0H>", choiceRow + cursor); + + if (keysDown() & (KEY_A | KEY_START)) + break; + + if (keysDown() & KEY_B) + { + cursor = 1; + break; + } + } + + scanKeys(); + return (cursor == 0)? YES: NO; +} + +void messageBox(char* message) +{ + consoleSelect(&bottomScreen); + consoleClear(); + + iprintf("%s\n", message); + iprintf("\nOkay - A\n"); + + while (1) + { + swiWaitForVBlank(); + scanKeys(); + + if (keysDown() & (KEY_A | KEY_START)) + break; + } + + scanKeys(); +} \ No newline at end of file diff --git a/src/menus.h b/src/menus.h new file mode 100644 index 0000000..303d6e8 --- /dev/null +++ b/src/menus.h @@ -0,0 +1,19 @@ +#ifndef MENUS_H +#define MENUS_H + +#include "main.h" + +//Text box choices +#define YES 1 +#define NO 0 + +void titleMenu(); +void installMenu(); +void testMenu(); + +void keyWait(u32 key); + +int choiceBox(char* message); +void messageBox(char* message); + +#endif \ No newline at end of file diff --git a/src/storage.c b/src/storage.c new file mode 100644 index 0000000..63d66a6 --- /dev/null +++ b/src/storage.c @@ -0,0 +1,493 @@ +#include "storage.h" +#include "main.h" +#include +#include +#include +//#include + +#define TITLE_LIMIT 39 + +//Printing +void printBytes(int bytes) +{ + if (abs(bytes) < 1024) + iprintf("%dB", bytes); + + else if (abs(bytes) < 1024 * 1024) + printf("%.2fKB", (float)bytes / 1024); + + else if (abs(bytes) < 1024 * 1024 * 1024) + printf("%.2fMB", (float)bytes / 1024 / 1024); + + else + printf("%.2fGB", (float)bytes / 1024 / 1024 / 1024); +} + +void printFileInfo(const char* path) +{ + tDSiHeader header; + tNDSBanner banner; + + FILE* f = fopen(path, "rb"); + + if (f) + { + if (fread(&header, sizeof(tDSiHeader), 1, f) != 1) + iprintf("Could not read dsi header.\n"); + + else + { + fseek(f, header.ndshdr.bannerOffset, SEEK_SET); + + if (fread(&banner, sizeof(tNDSBanner), 1, f) != 1) + iprintf("Could not read banner.\n"); + + else + { + //Proper title + { + char gameTitle[128+1]; + gameTitle[128] = '\0'; + + //Convert 2 byte characters to 1 byte + for (int i = 0; i < 128; i++) + gameTitle[i] = (char)banner.titles[1][i]; + + iprintf("%s\n\n", gameTitle); + } + + //File size + { + iprintf("Size: "); + printBytes(getFileSize(f)); + iprintf("\n"); + } + + iprintf("Label: %.12s\n", header.ndshdr.gameTitle); + iprintf("Game Code: %.4s\n", header.ndshdr.gameCode); + + //System type + { + iprintf("Unit Code: "); + + switch (header.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 (header.ndshdr.reserved1[7]) + { + case 0x3: iprintf("Normal"); break; + case 0xB: iprintf("Sys"); break; + case 0xF: iprintf("Debug/Sys"); break; + default: iprintf("unknown"); + } + + iprintf("\n"); + } + + iprintf("Title ID: %08x %08x\n", (unsigned int)header.tid_high, + (unsigned int)header.tid_low); + + //Print full file path + iprintf("\n%s\n", path); + } + } + } + + fclose(f); +} + +//Progress bar +void printProgressBar(int progress, int total) +{ + iprintf("\x1b[23;0H["); + iprintf("\x1b[23;31H]"); + + float bar = ((float)progress / (float)total) * 30.f; + + for (int i = 0; i < bar; i++) + iprintf("\x1b[23;%dH|", 1 + i); +} + +void clearProgressBar() +{ + iprintf("\x1b[23;0H "); +} + +//Files +int copyFile(const char* in, char* out) +{ + int result = 0; + + if (in == NULL || out == NULL) + return 0; + + FILE* fin = fopen(in, "rb"); + FILE* fout = fopen(out, "wb"); + + if (fin == NULL || fout == NULL) + { + fclose(fin); + fclose(fout); + + return 0; + } + else + { + consoleSelect(&topScreen); + + int fileSize = getFileSize(fin); + int totalBytesRead = 0; + int progressTimer = 100; + + const int buffSize = 1024*32; //Arbitrary. A value too large freezes the system. + unsigned char* buffer = (unsigned char*)malloc(sizeof(unsigned char) * buffSize); + + while (1) + { + int bytesRead = fread(buffer, 1, buffSize, fin); + fwrite(buffer, 1, bytesRead, fout); + + totalBytesRead += bytesRead; + + //Re-print progress bar every so often, but not every time + if ((progressTimer += 1) >= 25) + { + progressTimer = 0; + printProgressBar(totalBytesRead, fileSize); + } + + if (feof(fin)) + { + result = 1; + break; + } + + if (ferror(fin)) + { + result = 0; + break; + } + } + + free(buffer); + + clearProgressBar(); + consoleSelect(&bottomScreen); + } + + fclose(fin); + fclose(fout); + + return result; +} + +int getFileSize(FILE* f) +{ + if (!f) + return 0; + + fseek(f, 0, SEEK_END); + int size = ftell(f); + fseek(f, 0, SEEK_SET); + + return size; +} + +int getFileSizePath(const char* path) +{ + if (path == NULL) + return -1; + + FILE* f = fopen(path, "rb"); + int size = getFileSize(f); + fclose(f); + + return size; +} + +//Directories +int dirExists(const char* path) +{ + if (path == NULL) + return 0; + + DIR* dir = opendir(path); + + if (dir) + { + closedir(dir); + return 1; + } + + closedir(dir); + return 0; +} + +/* Incomplete +int copyDir(char* in, char* out) +{ + if (in == NULL || out == NULL) + return 0; + + DIR* dir; + struct dirent *ent; + + dir = opendir(in); + + if (!dir) + { + closedir(dir); + return 0; + } + else + { + mkdir(out, 0777); + + while ( (ent = readdir(dir)) != NULL ) + { + if(strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0) + continue; + + if (ent->d_type == DT_DIR) + { + char inPath[512]; + char outPath[512]; + + sprintf(inPath, "%s%s/", in, ent->d_name); + sprintf(outPath, "%s%s/", out, ent->d_name); + + copyDir(inPath, outPath); + } + else + { + char inPath[512]; + char outPath[512]; + + sprintf(inPath, "%s%s", in, ent->d_name); + sprintf(outPath, "%s%s", out, ent->d_name); + + copyFile(inPath, outPath); + } + } + } + + closedir(dir); + return 1; +} +*/ + +int deleteDir(const char* path) +{ + if (strcmp("/", path) == 0) + { + //Oh fuck no + return 0; + } + + DIR* dir; + struct dirent *ent; + + dir = opendir(path); + + if (!dir) + { + closedir(dir); + return 0; + } + else + { + while ( (ent = readdir(dir)) != NULL ) + { + 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); + + deleteDir(subpath); + } + else + { + //Delete file + char fpath[512]; + sprintf(fpath, "%s%s", path, ent->d_name); + + remove(fpath); + } + } + } + + closedir(dir); + rmdir(path); + + return 1; +} + +int getDirSize(const char* path) +{ + if (path == NULL) + return 0; + + int size = 0; + + DIR* dir; + struct dirent *ent; + + 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) + { + char fullpath[512]; + sprintf(fullpath, "%s%s/", path, ent->d_name); + + size += getDirSize(fullpath); + } + else + { + char fullpath[256]; + sprintf(fullpath, "%s%s", path, ent->d_name); + + size += getFileSizePath(fullpath); + } + } + } + + closedir(dir); + return size; +} + + +//Home menu +int getMenuSlots() +{ + //Assume the home menu has a hard limit on slots + //Find a better way to do this + return TITLE_LIMIT; +} + +#define NUM_OF_DIRECTORIES 3 +static const char* directories[] = { + "00030004", + "00030005", + "00030015" +}; + +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 + + int freeSlots = TITLE_LIMIT; + + DIR* dir; + struct dirent* ent; + + for (int i = 0; i < NUM_OF_DIRECTORIES; i++) + { + char path[256]; + sprintf(path, "/title/%s", directories[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 +int sdIsInserted() +{ +// Undefined reference +// return sdmmc_cardinserted(); + + return 1; +} + +int getSDCardSize() +{ + if (sdIsInserted()) + { + struct statvfs st; + if (statvfs("/", &st) == 0) + return st.f_bsize * st.f_blocks; + } + + return 0; +} + +int getSDCardFree() +{ + if (sdIsInserted()) + { + struct statvfs st; + if (statvfs("/", &st) == 0) + return st.f_bsize * st.f_bavail; + } + + return 0; +} + +//Internal storage +int getDsiSize() +{ + //The DSi has 256MB of internal storage. Some is unavailable and used by other things. + //Find a better way to do this +// return 240 * 1024 * 1024; + return 248 * 1024 * 1024; +} + +int getDsiFree() +{ + //Get free space by subtracting file sizes in emulated nand folders + //Find a better way to do this + int size = getDsiSize(); + + size -= getDirSize("/sys/"); + size -= getDirSize("/title/"); + size -= getDirSize("/ticket/"); + size -= getDirSize("/shared1/"); + size -= getDirSize("/shared2/"); + size -= getDirSize("/import/"); + size -= getDirSize("/tmp/"); + size -= getDirSize("/progress/"); + + size -= getDirSize("/photo/"); + size -= getDirSize("/private/"); + + return size; +} \ No newline at end of file diff --git a/src/storage.h b/src/storage.h new file mode 100644 index 0000000..11c627a --- /dev/null +++ b/src/storage.h @@ -0,0 +1,47 @@ +#ifndef STORAGE_H +#define STORAGE_H + +#include + +#define BACKUP_PATH "/titlebackup/" +#define ROM_PATH "/dsi/" + +#define BYTES_PER_BLOCK (1024*128) + +//Printing +void printBytes(int bytes); +void printFileInfo(const char* path); + +//Progress bar +void printProgressBar(int progress, int total); +void clearProgressBar(); + +//Files +int copyFile(const char* in, char* out); +int getFileSize(FILE* f); +int getFileSizePath(const char* path); + +//Directories +int dirExists(const char* path); +//int copyDir(const char* in, char* out); +int deleteDir(const char* path); +int getDirSize(const char* path); + +//Home menu +int getMenuSlots(); +int getMenuSlotsFree(); +#define getMenuSlotsUsed() (getMenuSlots() - getMenuSlotsFree()) + +//SD Card +int sdIsInserted(); + +int getSDCardSize(); +int getSDCardFree(); +#define getSDCardUsedSpace() (getSDCardSize() - getSDCardFree()) + +//Internal storage +int getDsiSize(); +int getDsiFree(); +#define getDsiUsed() (getDSIStorageSize() - getDSIStorageFree()) + +#endif \ No newline at end of file diff --git a/src/testmenu.c b/src/testmenu.c new file mode 100644 index 0000000..a844b5b --- /dev/null +++ b/src/testmenu.c @@ -0,0 +1,63 @@ +#include "menus.h" +#include "storage.h" + +void testMenu() +{ + consoleSelect(&topScreen); + consoleClear(); + + iprintf("Storage Check Test\n\n"); + iprintf("Verify these are accurate and\nreport if they're not.\n"); + + consoleSelect(&bottomScreen); + consoleClear(); + + int free = -1; + int size = -1; + + //Home menu slots + { + iprintf("Free Home Menu Slots:\n"); swiWaitForVBlank(); + + free = getMenuSlotsFree(); + iprintf("\t%d / ", free); swiWaitForVBlank(); + + size = getMenuSlots(); + iprintf("%d\n", size); swiWaitForVBlank(); + } + + //SD Card + { + iprintf("\nFree SD Space:\n\t"); swiWaitForVBlank(); + + free = getSDCardFree(); + printBytes(free); + iprintf(" / "); swiWaitForVBlank(); + + size = getSDCardSize(); + printBytes(size); + iprintf("\n"); swiWaitForVBlank(); + + printf("\t%.0f / %.0f blocks\n", (float)free / BYTES_PER_BLOCK, (float)size / BYTES_PER_BLOCK); + } + + //Emunand + { + iprintf("\nFree DSi Space:\n\t"); swiWaitForVBlank(); + + free = getDsiFree(); + printBytes(free); + iprintf(" / "); swiWaitForVBlank(); + + size = getDsiSize(); + printBytes(size); + iprintf("\n"); swiWaitForVBlank(); + + printf("\t%.0f / %.0f blocks\n", (float)free / BYTES_PER_BLOCK, (float)size / BYTES_PER_BLOCK); + } + + // + iprintf("\nBack - B\n"); + + keyWait(KEY_B); +} \ No newline at end of file diff --git a/src/titlemenu.c b/src/titlemenu.c new file mode 100644 index 0000000..8f2731d --- /dev/null +++ b/src/titlemenu.c @@ -0,0 +1,497 @@ +#include "menus.h" +#include "storage.h" +#include + +#define NUM_OF_DIRECTORIES 3 +static const char* directories[] = { + "00030004", + "00030005", + "00030015" +}; + +static int cursor = 0; +static int scrolly = 0; +static int numberOfTitles = 0; + +static void moveCursor(int dir); + +static void printList(); +static void printTitleInfo(int num); + +static int getNumberOfTitles(); +static int getTitle(int num, char* title, char* path); + +static void subMenu(); + +void titleMenu() +{ + cursor = 0; + scrolly = 0; + numberOfTitles = getNumberOfTitles(); + + consoleSelect(&topScreen); + consoleClear(); + + consoleSelect(&bottomScreen); + consoleClear(); + + //No titles error + if (numberOfTitles <= 0) + { + iprintf("No titles found.\n"); + iprintf("Back - B\n"); + + keyWait(KEY_B | KEY_A | KEY_START); + return; + } + + //Print data + consoleSelect(&topScreen); + printTitleInfo(cursor); + + consoleSelect(&bottomScreen); + printList(); + + while (1) + { + swiWaitForVBlank(); + scanKeys(); + + int thisScrolly = scrolly; + int thisCursor = cursor; + + //Clear cursor + consoleSelect(&bottomScreen); + iprintf("\x1b[%d;0H ", cursor - scrolly); + + //Move cursor + if (keysDown() & KEY_DOWN) + moveCursor(1); + + if (keysDown() & KEY_UP) + moveCursor(-1); + + if (keysDown() & KEY_RIGHT) + { + repeat (10) + moveCursor(1); + } + + if (keysDown() & KEY_LEFT) + { + repeat (10) + moveCursor(-1); + } + + //Re-print list + if (thisCursor != cursor) + { + consoleSelect(&topScreen); + printTitleInfo(cursor); + } + + if (thisScrolly != scrolly) + { + consoleSelect(&bottomScreen); + printList(); + } + + //Print cursor + consoleSelect(&bottomScreen); + iprintf("\x1b[%d;0H>", cursor - scrolly); + + // + if (keysDown() & KEY_B) + break; + else if (keysDown() & KEY_A) + { + subMenu(); + + consoleSelect(&topScreen); + printTitleInfo(cursor); + + consoleSelect(&bottomScreen); + printList(); + } + } +} + +void moveCursor(int dir) +{ + cursor += sign(dir); + + if (cursor < 0) + cursor = 0; + + if (cursor >= numberOfTitles - 1) + cursor = numberOfTitles - 1; + + if (cursor - scrolly >= 23) + scrolly += 1; + + if (cursor - scrolly < 0) + scrolly -= 1; +} + +void printList() +{ + consoleClear(); + + for (int i = scrolly; i < scrolly + 23; i++) + { + char title[256]; + if (getTitle(i, title, NULL) == 1) + iprintf(" %.30s\n", title); + } + + //Scroll arrows + if (scrolly > 0) + iprintf("\x1b[0;31H^"); + + if (scrolly < numberOfTitles - 23) + iprintf("\x1b[22;31Hv"); +} + +void printTitleInfo(int num) +{ + consoleClear(); + + char path[256]; + if (getTitle(num, NULL, path) == 1) + printFileInfo(path); +} + +int getNumberOfTitles() +{ + int count = 0; + + //Scan choice title directories + for (int i = 0; i < NUM_OF_DIRECTORIES; i++) + { + DIR* dir; + struct dirent* ent; + + char dirPath[256]; + sprintf(dirPath, "/title/%s", directories[i]); + + dir = opendir(dirPath); + + 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) + { + //Search for an .app file + char contentPath[384]; + sprintf(contentPath, "%s/%s/content", dirPath, ent->d_name); + + DIR* subdir; + struct dirent* subent; + + subdir = opendir(contentPath); + + if (subdir) + { + while ( (subent = readdir(subdir)) != NULL ) + { + if (strcmp(".", subent->d_name) == 0 || strcmp("..", subent->d_name) == 0) + continue; + + //Found a title + if (strstr(subent->d_name, ".app") != NULL) + count++; + } + } + + closedir(subdir); + } + } + } + + closedir(dir); + } + + return count; +} + +int getTitle(int num, char* title, char* path) +{ + int result = 0; + int count = 0; + + //Scan choice title directories + for (int i = 0; i < NUM_OF_DIRECTORIES && result == 0; i++) + { + DIR* dir; + struct dirent* ent; + + char dirPath[256]; + sprintf(dirPath, "/title/%s", directories[i]); + + dir = opendir(dirPath); + + if (dir) + { + while ( (ent = readdir(dir)) != NULL && result == 0) + { + if (strcmp(".", ent->d_name) == 0 || strcmp("..", ent->d_name) == 0) + continue; + + if (ent->d_type == DT_DIR) + { + //Scan content folder + char contentPath[384]; + sprintf(contentPath, "%s/%s/content", dirPath, ent->d_name); + + DIR* subdir; + struct dirent* subent; + + subdir = opendir(contentPath); + + if (subdir) + { + while ( (subent = readdir(subdir)) != NULL && result == 0) + { + if (strcmp(".", subent->d_name) == 0 || strcmp("..", subent->d_name) == 0) + continue; + + if (subent->d_type != DT_DIR) + { + if (strstr(subent->d_name, ".app") != NULL) + { + if (count < num) + count++; + + else + { + //Found requested title + char filepath[384]; + sprintf(filepath, "%s/%s", contentPath, subent->d_name); + + //Output title + if (title != NULL) + { + FILE* f = fopen(filepath, "rb"); + + if (!f) + { + sprintf(title, " "); + } + else + { + tNDSHeader header; + tNDSBanner banner; + + fread(&header, sizeof(tNDSHeader), 1, f); + fseek(f, header.bannerOffset, SEEK_SET); + fread(&banner, sizeof(tNDSBanner), 1, f); + + char tstr[128+1]; + tstr[128] = '\0'; + + for (int i = 0; i < 128; i++) + { + char c = banner.titles[1][i]; + + if (c == '\n') + c = ' '; + + tstr[i] = c; + } + + sprintf(title, "%s", tstr); + } + + fclose(f); + } + + //Output path + if (path != NULL) + { + sprintf(path, "%s", filepath); + } + + //Exit this mess + result = 1; + break; + } + } + } + } + } + + closedir(subdir); + } + } + } + + closedir(dir); + } + + return result; +} + +// +static int subCursor = 0; + +enum { +// TITLE_MENU_BACKUP, + TITLE_MENU_DUMP, + TITLE_MENU_DELETE, + TITLE_MENU_BACK +}; + +void subMenu() +{ + subCursor = 0; + + consoleSelect(&bottomScreen); + consoleClear(); + +// iprintf("\tBackup\n"); + iprintf("\tDump\n"); + iprintf("\tDelete\n"); + iprintf("\tBack\n"); + + while (1) + { + swiWaitForVBlank(); + scanKeys(); + + //Clear cursor + iprintf("\x1b[%d;0H ", subCursor); + + //Move cursor + if (keysDown() & KEY_DOWN) + { + if (subCursor < TITLE_MENU_BACK) + subCursor += 1; + } + + if (keysDown() & KEY_UP) + { + if (subCursor > 0) + subCursor -= 1; + } + + //Print cursor + iprintf("\x1b[%d;0H>", subCursor); + + if (keysDown() & KEY_A) + { + char title[256]; + char path[256]; + getTitle(cursor, title, path); + + //Only get first line of title + for (int i = 0; i < 256; i++) + { + if (title[i] == '\n') + { + title[i] = '\0'; + break; + } + } + +/* // + if (subCursor == TITLE_MENU_BACKUP) + { + char dir[256]; + sprintf(dir, "%.24s", path); + + char msg[512]; + sprintf(msg, "Are you sure you want to backup\n%s", dir); + + if (choiceBox(msg) == YES) + { + if (getDirSize(dir) > getSDCardFree()) + messageBox("Error, not enough space on SD card.\nTitle backup failed."); + + else + { + if (copyDir(dir, BACKUP_PATH) == 1) + messageBox("Title was backed up."); + else + messageBox("Title backup failed."); + } + } + + break; + } +*/ + if (subCursor == TITLE_MENU_DUMP) + { + char fpath[256]; + if (getTitle(cursor, NULL, fpath) == 1) + { + FILE* f = fopen(fpath, "rb"); + + if (!f) + messageBox("Can not dump title.\n"); + + else + { + tNDSHeader header; + tNDSBanner banner; + + fread(&header, sizeof(tNDSHeader), 1, f); + fseek(f, header.bannerOffset, SEEK_SET); + fread(&banner, sizeof(tNDSBanner), 1, f); + fclose(f); + + char outpath[256]; + sprintf(outpath, "%s%.12s - %.4s.nds", ROM_PATH, header.gameTitle, header.gameCode); + + char msg[512]; + sprintf(msg, "Dump title to\n%s\n", outpath); + + if (choiceBox(msg) == YES) + { + if (copyFile(fpath, outpath) == 1) + messageBox("Title saved.\n"); + else + messageBox("Title dump failed.\n"); + } + } + } + + break; + } + + else if (subCursor == TITLE_MENU_DELETE) + { + char msg[512]; + sprintf(msg, "Are you sure you want to delete\n%s", title); + + if (choiceBox(msg) == YES) + { + char dirPath[256]; + sprintf(dirPath, "%.25s", path); + + if (deleteDir(dirPath) == 1) + messageBox("Title deleted.\n"); + else + messageBox("Title could not be deleted.\n"); + + //Reset main menu + cursor = 0; + scrolly = 0; + numberOfTitles = getNumberOfTitles(); + } + + break; + } + + else if (subCursor == TITLE_MENU_BACK) + { + break; + } + } + else if (keysDown() & KEY_B) + break; + } +} \ No newline at end of file