diff --git a/Makefile b/Makefile index 20fef4e..a29754a 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,7 @@ ICON := resources/icon.png #--------------------------------------------------------------------------------- ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=softfp -CFLAGS := -g -Wall -O3 -mword-relocations \ +CFLAGS := -g -Wall -Wno-strict-aliasing -O3 -mword-relocations \ -fomit-frame-pointer -ffast-math \ $(ARCH) @@ -155,7 +155,7 @@ banner.bnr: $(TOPDIR)/resources/banner.png $(TOPDIR)/resources/audio.wav @echo "built ... banner" icon.icn: $(TOPDIR)/resources/icon.png - $(BANNERTOOL) makesmdh -s "$(APP_TITLE)" -l "$(APP_DESCRIPTION)" -p "$(APP_AUTHOR)" -i $(TOPDIR)/resources/icon.png -o icon.icn + $(BANNERTOOL) makesmdh -s "$(APP_TITLE)" -l "$(APP_TITLE)" -p "$(APP_AUTHOR)" -i $(TOPDIR)/resources/icon.png -o icon.icn @echo "built ... icon" stripped.elf: $(OUTPUT).elf diff --git a/source/common.c b/source/common.cpp similarity index 61% rename from source/common.c rename to source/common.cpp index f4d1b98..f46e74b 100644 --- a/source/common.c +++ b/source/common.cpp @@ -3,10 +3,16 @@ #include #include #include +#include + +#include +#include +#include +#include #include <3ds.h> -#include "common.h" +#include "common.hpp" static unsigned char asciiData[128][8] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, @@ -155,56 +161,16 @@ PAD_KEY buttonMap[13] = { KEY_TOUCH }; -const int defaultBufferSize = 100; -static char defaultBuffer[100]; - -char* sdprintf(const char* format, ...) { - va_list args; - va_start(args, format); - char* ret = vsdprintf(format, args); - va_end(args); - return ret; -} - -char* vsdprintf(const char* format, va_list args) { - va_list copy; - va_copy(copy, args); - - char* ret = NULL; - int len = vsnprintf(defaultBuffer, defaultBufferSize, format, args); - if(len >= defaultBufferSize) { - char* buffer = (char*) malloc(len * sizeof(char)); - vsnprintf(buffer, (size_t) len, format, copy); - ret = buffer; - } else { - char* buffer = (char*) malloc(defaultBufferSize * sizeof(char)); - strcpy(buffer, defaultBuffer); - ret = buffer; - } - - va_end(copy); - return ret; -} - u8* fb = NULL; u16 fbWidth = 0; u16 fbHeight = 0; -bool screen_begin_draw() { +bool screen_begin_draw(Screen screen) { if(fb != NULL) { return false; } - fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &fbWidth, &fbHeight); - return true; -} - -bool screen_begin_draw_info() { - if(fb != NULL) { - return false; - } - - fb = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &fbWidth, &fbHeight); + fb = gfxGetFramebuffer(screen == TOP_SCREEN ? GFX_TOP : GFX_BOTTOM, GFX_LEFT, &fbWidth, &fbHeight); return true; } @@ -236,11 +202,7 @@ int screen_get_width() { return fbHeight; } - int width = 0; - screen_begin_draw(); - width = fbHeight; - screen_end_draw(); - return width; + return 0; } int screen_get_height() { @@ -249,16 +211,12 @@ int screen_get_height() { return fbWidth; } - int height = 0; - screen_begin_draw(); - height = fbWidth; - screen_end_draw(); - return height; + return 0; } void screen_take_screenshot() { u32 imageSize = 400 * 480 * 3; - u8* temp = (u8*) malloc(0x36 + imageSize); + u8 temp[0x36 + imageSize]; memset(temp, 0, 0x36 + imageSize); *(u16*) &temp[0x0] = 0x4D42; @@ -293,11 +251,10 @@ void screen_take_screenshot() { } char file[256]; - snprintf(file, 256, "sdmc:/wo3ds_screenshot_%08d.bmp", (int) (svcGetSystemTick() / 446872)); + snprintf(file, 256, "sdmc:/screenshot_%08d.bmp", (int) (svcGetSystemTick() / 446872)); int fd = open(file, O_WRONLY | O_CREAT | O_SYNC); write(fd, temp, 0x36 + imageSize); close(fd); - free(temp); } int screen_get_index(int x, int y) { @@ -361,11 +318,11 @@ void screen_fill(int x, int y, int width, int height, u8 r, u8 g, u8 b) { } } -int screen_get_str_width(const char* str) { - return strlen(str) * 8; +int screen_get_str_width(std::string str) { + return str.length() * 8; } -int screen_get_str_height(const char* str) { +int screen_get_str_height(std::string str) { return 8; } @@ -385,16 +342,16 @@ void screen_draw_char(char c, int x, int y, u8 r, u8 g, u8 b) { } } -void screen_draw_string(const char* string, int x, int y, u8 r, u8 g, u8 b) { +void screen_draw_string(std::string str, int x, int y, u8 r, u8 g, u8 b) { if(fb == NULL) { return; } - int len = (int) strlen(string); + int len = str.length(); int cx = x; int cy = y; for(int i = 0; i < len; i++) { - char c = string[i]; + char c = str[i]; if(c == '\n') { cx = x; cy += 8; @@ -409,18 +366,242 @@ void screen_clear(u8 r, u8 g, u8 b) { screen_fill(0, 0, screen_get_width(), screen_get_height(), r, g, b); } -void screen_clear_all() { - for(int i = 0; i < 2; i++) { - screen_begin_draw(); - screen_clear(0, 0, 0); - screen_end_draw(); +typedef struct { + std::string id; + std::string name; + std::vector details; +} SelectableElement; - screen_begin_draw_info(); - screen_clear(0, 0, 0); - screen_end_draw(); +SelectionResult ui_select(std::vector elements, SelectableElement* selected, bool enableBack, std::function onLoop) { + u32 cursor = 0; + u32 scroll = 0; - screen_swap_buffers(); - } + u32 selectionScroll = 0; + u64 selectionScrollEndTime = 0; + + while(platform_is_running()) { + input_poll(); + if(input_is_pressed(BUTTON_A)) { + *selected = elements.at(cursor); + return SELECTED; + } + + if(enableBack && input_is_pressed(BUTTON_B)) { + return BACK; + } + + if(input_is_pressed(BUTTON_DOWN) && cursor < elements.size() - 1) { + cursor++; + if(cursor >= scroll + 20) { + scroll++; + } + + selectionScroll = 0; + selectionScrollEndTime = 0; + } + + if(input_is_pressed(BUTTON_UP) && cursor > 0) { + cursor--; + if(cursor < scroll) { + scroll--; + } + + selectionScroll = 0; + selectionScrollEndTime = 0; + } + + screen_begin_draw(BOTTOM_SCREEN); + screen_clear(0, 0, 0); + + u32 screenWidth = (u32) screen_get_width(); + for(std::vector::iterator it = elements.begin() + scroll; it != elements.begin() + scroll + 20 && it != elements.end(); it++) { + SelectableElement element = *it; + u32 index = (u32) (it - elements.begin()); + u8 color = 255; + int offset = 0; + if(index == cursor) { + color = 0; + screen_fill(0, (int) (index - scroll) * 12, (int) screenWidth, screen_get_str_height(element.name), 255, 255, 255); + u32 width = (u32) screen_get_str_width(element.name); + if(width > screenWidth) { + if(selectionScroll + screenWidth >= width) { + if(selectionScrollEndTime == 0) { + selectionScrollEndTime = platform_get_time(); + } else if(platform_get_time() - selectionScrollEndTime >= 4000) { + selectionScroll = 0; + selectionScrollEndTime = 0; + } + } else { + selectionScroll++; + } + } + + offset = -selectionScroll; + } + + screen_draw_string(element.name, offset, (int) (index - scroll) * 12, color, color, color); + } + + screen_end_draw(); + + screen_begin_draw(TOP_SCREEN); + screen_clear(0, 0, 0); + + SelectableElement currSelected = elements.at(cursor); + if(currSelected.details.size() != 0) { + for(std::vector::iterator it = currSelected.details.begin(); it != currSelected.details.end(); it++) { + std::string detail = *it; + u32 index = (u32) (it - currSelected.details.begin()); + screen_draw_string(detail, 0, (int) index * 12, 255, 255, 255); + } + } + + bool result = onLoop(); + + screen_end_draw(); + screen_swap_buffers(); + if(result) { + return MANUAL_BREAK; + } + } + + return APP_CLOSING; +} + +bool ui_is_directory(std::string path) { + DIR *dir = opendir(path.c_str()); + if(!dir) { + return false; + } + + closedir(dir); + return true; +} + +struct ui_alphabetize { + inline bool operator() (SelectableElement a, SelectableElement b) { + return a.name.compare(b.name) < 0; + } +}; + +std::vector ui_get_dir_elements(std::string directory, std::string extension) { + std::vector elements; + elements.push_back({".", "."}); + elements.push_back({"..", ".."}); + + DIR *dir = opendir(directory.c_str()); + if(dir != NULL) { + while(true) { + struct dirent *ent = readdir(dir); + if(ent == NULL) { + break; + } + + std::string dirName = std::string(ent->d_name); + std::string path = directory + "/" + dirName; + if(ui_is_directory(path)) { + elements.push_back({path, dirName}); + } else { + std::string::size_type dotPos = path.rfind('.'); + if(dotPos != std::string::npos && path.substr(dotPos + 1).compare(extension) == 0) { + struct stat st; + stat(path.c_str(), &st); + + std::vector info; + std::stringstream stream; + stream << "File Size: " << st.st_size << " bytes (" << std::fixed << std::setprecision(2) << st.st_size / 1024.0f / 1024.0f << "MB)"; + info.push_back(stream.str()); + elements.push_back({path, dirName, info}); + } + } + } + + closedir(dir); + } + + std::sort(elements.begin(), elements.end(), ui_alphabetize()); + return elements; +} + +bool ui_select_file(std::string rootDirectory, std::string extension, std::string* selectedFile, std::function onLoop) { + std::stack directoryStack; + std::string currDirectory = rootDirectory; + while(platform_is_running()) { + SelectableElement selected; + std::vector contents = ui_get_dir_elements(currDirectory, extension); + SelectionResult result = ui_select(contents, &selected, !directoryStack.empty(), onLoop); + if(result == APP_CLOSING || result == MANUAL_BREAK) { + break; + } else if(result == BACK) { + currDirectory = directoryStack.top(); + directoryStack.pop(); + } else if(result == SELECTED) { + if(selected.name.compare(".") == 0) { + continue; + } else if(selected.name.compare("..") == 0) { + if(directoryStack.empty()) { + continue; + } + + currDirectory = directoryStack.top(); + directoryStack.pop(); + } else { + if(ui_is_directory(selected.id)) { + directoryStack.push(currDirectory); + currDirectory = selected.id; + } else { + *selectedFile = selected.id; + return true; + } + } + } + } + + return false; +} + +bool ui_select_app(MediaType mediaType, App* selectedApp, std::function onLoop) { + std::vector apps = app_list(mediaType); + std::vector elements; + for(std::vector::iterator it = apps.begin(); it != apps.end(); it++) { + App app = *it; + + std::stringstream titleId; + titleId << std::setfill('0') << std::setw(16) << std::hex << app.titleId; + + std::stringstream uniqueId; + uniqueId << std::setfill('0') << std::setw(8) << std::hex << app.uniqueId; + + std::vector details; + details.push_back("Title ID: " + titleId.str()); + details.push_back("Unique ID: " + uniqueId.str()); + details.push_back("Product Code: " + std::string(app.productCode)); + details.push_back("Platform: " + app_get_platform_name(app.platform)); + details.push_back("Category: " + app_get_category_name(app.category)); + + elements.push_back({titleId.str(), app.productCode, details}); + } + + if(elements.size() == 0) { + elements.push_back({"None", "None"}); + } + + std::sort(elements.begin(), elements.end(), ui_alphabetize()); + + SelectableElement selected; + SelectionResult result = ui_select(elements, &selected, false, onLoop); + if(result != APP_CLOSING && result != MANUAL_BREAK && selected.id.compare("None") != 0) { + for(std::vector::iterator it = apps.begin(); it != apps.end(); it++) { + App app = *it; + if(app.titleId == (u64) strtoll(selected.id.c_str(), NULL, 16)) { + *selectedApp = app; + } + } + + return true; + } + + return false; } void input_poll() { @@ -509,7 +690,7 @@ AppCategory app_category_from_id(u16 id) { return APP; } -const char* app_get_platform_name(AppPlatform platform) { +std::string app_get_platform_name(AppPlatform platform) { switch(platform) { case WII: return "Wii"; @@ -524,7 +705,7 @@ const char* app_get_platform_name(AppPlatform platform) { } } -const char* app_get_category_name(AppCategory category) { +std::string app_get_category_name(AppCategory category) { switch(category) { case APP: return "App"; @@ -541,35 +722,23 @@ const char* app_get_category_name(AppCategory category) { } } -App* app_list(MediaType mediaType, u32* count) { +std::vector app_list(MediaType mediaType) { + std::vector titles; if(!am_prepare()) { - return NULL; + return titles; } u32 titleCount; if(AM_GetTitleCount(app_mediatype_to_byte(mediaType), &titleCount) != 0) { - if(count != NULL) { - *count = 0; - } - - return (App*) malloc(0); - } - - if(count != NULL) { - *count = titleCount; + return titles; } u64 titleIds[titleCount]; if(AM_GetTitleList(app_mediatype_to_byte(mediaType), titleCount, titleIds) != 0) { - if(count != NULL) { - *count = 0; - } - - return (App*) malloc(0); + return titles; } - App* titles = (App*) malloc(titleCount * sizeof(App)); - for(int i = 0; i < titleCount; i++) { + for(u32 i = 0; i < titleCount; i++) { u64 titleId = titleIds[i]; App app; app.titleId = titleId; @@ -583,18 +752,18 @@ App* app_list(MediaType mediaType, u32* count) { app.platform = app_platform_from_id(((u16*) &titleId)[3]); app.category = app_category_from_id(((u16*) &titleId)[2]); - titles[i] = app; + titles.push_back(app); } return titles; } -bool app_install(MediaType mediaType, const char* path, bool (*onProgress)(int progress)) { +bool app_install(MediaType mediaType, std::string path, std::function onProgress) { if(!am_prepare()) { return false; } - FILE* fd = fopen(path, "r"); + FILE* fd = fopen(path.c_str(), "r"); if(!fd) { return false; } @@ -615,7 +784,7 @@ bool app_install(MediaType mediaType, const char* path, bool (*onProgress)(int p FSFILE_SetSize(ciaHandle, size); u32 bufSize = 1024 * 256; // 256KB - void* buf = malloc(bufSize); + void* buf = malloc(bufSize); bool cancelled = false; for(u64 pos = 0; pos < size; pos += bufSize) { if(onProgress != NULL && !onProgress((int) ((pos / (float) size) * 100))) { @@ -628,7 +797,7 @@ bool app_install(MediaType mediaType, const char* path, bool (*onProgress)(int p FSFILE_Write(ciaHandle, NULL, pos, buf, bytesRead, FS_WRITE_NOFLUSH); } - free(buf); + free(buf); fclose(fd); if(cancelled) { @@ -640,27 +809,27 @@ bool app_install(MediaType mediaType, const char* path, bool (*onProgress)(int p } Result res = AM_FinishCiaInstall(app_mediatype_to_byte(mediaType), &ciaHandle); - if(res != 0 && res != 0xC8A044DC) { // Happens when already installed, but seems to have succeeded anyway... + if(res != 0 && (u32) res != 0xC8A044DC) { // Happens when already installed, but seems to have succeeded anyway... return false; } return true; } -bool app_delete(MediaType mediaType, App app) { +bool app_delete(App app) { if(!am_prepare()) { return false; } - return AM_DeleteAppTitle(app_mediatype_to_byte(mediaType), app.titleId) == 0; + return AM_DeleteAppTitle(app_mediatype_to_byte(app.mediaType), app.titleId) == 0; } -bool app_launch(MediaType mediaType, App app) { +bool app_launch(App app) { if(!ns_prepare()) { return false; } - return NS_RebootToTitle(mediaType, app.titleId) == 0; + return NS_RebootToTitle(app.mediaType, app.titleId) == 0; } u64 fs_get_free_space(MediaType mediaType) { @@ -720,15 +889,18 @@ void platform_delay(int ms) { svcSleepThread(ms * 1000000); } -void platform_print(const char* str) { - svcOutputDebugString(str, strlen(str)); -} - void platform_printf(const char* format, ...) { va_list args; va_start(args, format); - char* str = vsdprintf(format, args); - va_end(args); - platform_print(str); - free(str); + int len = vsnprintf(NULL, 0, format, args); + va_end(args); + + char str[len + 1]; + + va_list args2; + va_start(args2, format); + vsnprintf(str, (size_t) len + 1, format, args2); + va_end(args2); + + svcOutputDebugString(str, strlen(str)); } \ No newline at end of file diff --git a/source/common.h b/source/common.hpp similarity index 61% rename from source/common.h rename to source/common.hpp index 72ad727..f453f4a 100644 --- a/source/common.h +++ b/source/common.hpp @@ -1,14 +1,14 @@ #ifndef __COMMON_H__ #define __COMMON_H__ -#ifdef __cplusplus -extern "C" { -#endif - #include #include #include +#include +#include +#include + #define printf platform_printf typedef uint8_t u8; @@ -21,13 +21,25 @@ typedef int16_t s16; typedef int32_t s32; typedef int64_t s64; -typedef struct _color { +typedef struct { u8 r; u8 g; u8 b; } Color; -typedef enum _button { +typedef enum { + TOP_SCREEN, + BOTTOM_SCREEN +} Screen; + +typedef enum { + SELECTED, + BACK, + APP_CLOSING, + MANUAL_BREAK +} SelectionResult; + +typedef enum { BUTTON_A, BUTTON_B, BUTTON_X, @@ -43,17 +55,17 @@ typedef enum _button { BUTTON_TOUCH } Button; -typedef struct _touch { +typedef struct { int x; int y; } Touch; -typedef enum _media_type { +typedef enum { NAND, SD } MediaType; -typedef enum _app_platform { +typedef enum { WII, DSI, THREEDS, @@ -62,7 +74,7 @@ typedef enum _app_platform { } AppPlatform; // TODO: verify categories. -typedef enum _app_category { +typedef enum { APP, DLC, PATCH, @@ -70,7 +82,7 @@ typedef enum _app_category { TWL } AppCategory; -typedef struct _app { +typedef struct { u64 titleId; u32 uniqueId; char productCode[16]; @@ -79,11 +91,7 @@ typedef struct _app { AppCategory category; } App; -char* sdprintf(const char* format, ...); -char* vsdprintf(const char* format, va_list args); - -bool screen_begin_draw(); -bool screen_begin_draw_info(); +bool screen_begin_draw(Screen screen); bool screen_end_draw(); void screen_swap_buffers_quick(); void screen_swap_buffers(); @@ -92,11 +100,13 @@ int screen_get_width(); int screen_get_height(); void screen_draw(int x, int y, u8 r, u8 g, u8 b); void screen_fill(int x, int y, int width, int height, u8 r, u8 g, u8 b); -int screen_get_str_width(const char* str); -int screen_get_str_height(const char* str); -void screen_draw_string(const char* string, int x, int y, u8 r, u8 g, u8 b); +int screen_get_str_width(std::string str); +int screen_get_str_height(std::string str); +void screen_draw_string(std::string str, int x, int y, u8 r, u8 g, u8 b); void screen_clear(u8 r, u8 g, u8 b); -void screen_clear_all(); + +bool ui_select_file(std::string rootDirectory, std::string extension, std::string* selectedFile, std::function onLoop); +bool ui_select_app(MediaType mediaType, App* selectedApp, std::function onLoop); void input_poll(); bool input_is_released(Button button); @@ -104,12 +114,12 @@ bool input_is_pressed(Button button); bool input_is_held(Button button); Touch input_get_touch(); -const char* app_get_platform_name(AppPlatform platform); -const char* app_get_category_name(AppCategory category); -App* app_list(MediaType mediaType, u32* count); -bool app_install(MediaType mediaType, const char* path, bool (*onProgress)(int progress)); -bool app_delete(MediaType mediaType, App app); -bool app_launch(MediaType mediaType, App app); +std::string app_get_platform_name(AppPlatform platform); +std::string app_get_category_name(AppCategory category); +std::vector app_list(MediaType mediaType); +bool app_install(MediaType mediaType, std::string path, std::function onProgress); +bool app_delete(App app); +bool app_launch(App app); u64 fs_get_free_space(MediaType mediaType); @@ -118,11 +128,6 @@ void platform_cleanup(); bool platform_is_running(); u64 platform_get_time(); void platform_delay(int ms); -void platform_print(const char* str); void platform_printf(const char* format, ...); -#ifdef __cplusplus -} -#endif - #endif \ No newline at end of file diff --git a/source/main.cpp b/source/main.cpp index a4a343d..63f76cb 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,7 +1,96 @@ -#include +#include "common.hpp" -#include "common.h" -#include "ui.h" +#include +#include + +typedef enum { + INSTALL, + DELETE +} Mode; + +bool ui_display_install_progress(int progress) { + std::stringstream stream; + stream << "Installing: ["; + int progressBars = progress / 4; + for(int i = 0; i < 25; i++) { + if(i < progressBars) { + stream << '|'; + } else { + stream << ' '; + } + } + + std::ios state(NULL); + state.copyfmt(stream); + stream << "] " << std::setfill('0') << std::setw(3) << progress; + stream.copyfmt(state); + stream << "%"; + + std::string msg = stream.str(); + std::string cancel = "Press B to cancel."; + + screen_begin_draw(TOP_SCREEN); + screen_clear(0, 0, 0); + screen_draw_string(msg, (screen_get_width() - screen_get_str_width(msg)) / 2, (screen_get_height() - screen_get_str_height(msg)) / 2 - screen_get_str_height(msg), 255, 255, 255); + screen_draw_string(cancel, (screen_get_width() - screen_get_str_width(cancel)) / 2, (screen_get_height() - screen_get_str_height(cancel)) / 2 + screen_get_str_height(cancel), 255, 255, 255); + screen_end_draw(); + screen_swap_buffers_quick();; + + input_poll(); + return !input_is_pressed(BUTTON_B); +} + +void ui_display_deleting() { + std::string msg = "Deleting title..."; + screen_begin_draw(TOP_SCREEN); + screen_clear(0, 0, 0); + screen_draw_string(msg, (screen_get_width() - screen_get_str_width(msg)) / 2, (screen_get_height() - screen_get_str_height(msg)) / 2, 255, 255, 255); + screen_end_draw(); + screen_swap_buffers(); +} + +void ui_display_result(bool install, bool state) { + std::string msg = install ? (state ? "Install succeeded! Press start." : "Install failed! Press start.") : (state ? "Delete succeeded! Press start." : "Delete failed! Press start."); + while(platform_is_running()) { + input_poll(); + if(input_is_pressed(BUTTON_START)) { + break; + } + + screen_begin_draw(TOP_SCREEN); + screen_clear(0, 0, 0); + screen_draw_string(msg, (screen_get_width() - screen_get_str_width(msg)) / 2, (screen_get_height() - screen_get_str_height(msg)) / 2, 255, 255, 255); + screen_end_draw(); + screen_swap_buffers(); + } +} + +bool ui_prompt_operation(Mode mode, std::string name) { + std::stringstream stream; + stream << (mode == INSTALL ? "Install" : "Delete") << " the selected title?"; + std::string msg = stream.str(); + std::string prompt = "Press A to confirm, B to cancel."; + while(platform_is_running()) { + input_poll(); + if(input_is_pressed(BUTTON_A)) { + return true; + } + + if(input_is_pressed(BUTTON_B)) { + return false; + } + + screen_begin_draw(TOP_SCREEN); + screen_clear(0, 0, 0); + screen_draw_string(msg, (screen_get_width() - screen_get_str_width(msg)) / 2, (screen_get_height() - screen_get_str_height(msg)) / 2 - screen_get_str_height(msg), 255, 255, 255); + screen_draw_string(name, (screen_get_width() - screen_get_str_width(name)) / 2, (screen_get_height() - screen_get_str_height(name)) / 2, 255, 255, 255); + screen_draw_string(prompt, (screen_get_width() - screen_get_str_width(prompt)) / 2, (screen_get_height() - screen_get_str_height(prompt)) / 2 + screen_get_str_height(prompt), 255, 255, 255); + screen_end_draw(); + screen_swap_buffers(); + } + + return false; +} int main(int argc, char **argv) { if(!platform_init()) { @@ -10,41 +99,68 @@ int main(int argc, char **argv) { MediaType destination = SD; Mode mode = INSTALL; + u64 freeSpace = fs_get_free_space(destination); + auto onLoop = [&]() { + bool breakLoop = false; + if(input_is_pressed(BUTTON_L)) { + if(destination == SD) { + destination = NAND; + } else { + destination = SD; + } + + freeSpace = fs_get_free_space(destination); + if(mode == DELETE) { + breakLoop = true; + } + } + + if(input_is_pressed(BUTTON_R)) { + if(mode == INSTALL) { + mode = DELETE; + } else { + mode = INSTALL; + } + + breakLoop = true; + } + + std::stringstream stream; + stream << "Free Space: " << freeSpace << " bytes (" << std::fixed << std::setprecision(2) << freeSpace / 1024.0f / 1024.0f << "MB)"; + + std::string space = stream.str(); + std::string status = std::string("Destination: ") + (destination == NAND ? "NAND" : "SD") + ", Mode: " + (mode == INSTALL ? "Install" : "Delete"); + std::string msg = "L - Switch Destination, R - Switch Mode"; + + screen_draw_string(space, (screen_get_width() - screen_get_str_width(space)) / 2, screen_get_height() - 4 - screen_get_str_height(msg) - screen_get_str_height(status) - screen_get_str_height(space), 255, 255, 255); + screen_draw_string(status, (screen_get_width() - screen_get_str_width(status)) / 2, screen_get_height() - 4 - screen_get_str_height(msg) - screen_get_str_height(status), 255, 255, 255); + screen_draw_string(msg, (screen_get_width() - screen_get_str_width(msg)) / 2, screen_get_height() - 4 - screen_get_str_height(msg), 255, 255, 255); + + return breakLoop; + }; + while(platform_is_running()) { - char* targetInstall = NULL; + std::string targetInstall; App targetDelete; - UIResult result; + bool obtained = false; if(mode == INSTALL) { - result = uiSelectFile(&targetInstall, "sdmc:", "cia", &destination, &mode); - } else { - result = uiSelectTitle(&targetDelete, &destination, &mode); + obtained = ui_select_file("sdmc:", "cia", &targetInstall, onLoop); + } else if(mode == DELETE) { + obtained = ui_select_app(destination, &targetDelete, onLoop); } - if(result == EXIT_APP) { - break; - } else if(result == SWITCH_MODE) { - if(mode == INSTALL) { - mode = DELETE; - } else { - mode = INSTALL; - } - } else { - if(mode == INSTALL) { - if(uiPromptOperation(mode)) { - uiDisplayResult(true, app_install(destination, targetInstall, &uiDisplayInstallProgress)); - } - - free(targetInstall); - } else { - char* str = sdprintf("%08lx - %s, %s, %s", targetDelete.uniqueId, targetDelete.productCode, app_get_platform_name(targetDelete.platform), app_get_category_name(targetDelete.category)); - if(uiPromptOperation(mode)) { - uiDisplayDeleting(); - uiDisplayResult(false, app_delete(destination, targetDelete)); - } - - free(str); - } - } + if(obtained) { + if(mode == INSTALL) { + if(ui_prompt_operation(mode, targetInstall)) { + ui_display_result(true, app_install(destination, targetInstall, &ui_display_install_progress)); + } + } else if(mode == DELETE) { + if(ui_prompt_operation(mode, targetDelete.productCode)) { + ui_display_deleting(); + ui_display_result(false, app_delete(targetDelete)); + } + } + } } platform_cleanup(); diff --git a/source/ui.cpp b/source/ui.cpp deleted file mode 100644 index 71b499b..0000000 --- a/source/ui.cpp +++ /dev/null @@ -1,409 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "ui.h" -#include "common.h" - -struct ui_alphabetize { - inline bool operator() (char* a, char* b) { - return strcasecmp(a, b) < 0; - } -}; - -bool uiIsDirectory(char* path) { - DIR *dir = opendir(path); - if(!dir) { - return false; - } - - closedir(dir); - return true; -} - -std::vector* uiGetDirectoryContents(const char* directory, const char* extensionFilter) { - std::vector* contents = new std::vector(); - char slash[strlen(directory) + 2]; - snprintf(slash, sizeof(slash), "%s/", directory); - DIR *dir = opendir(slash); - if(dir != NULL) { - while(true) { - struct dirent *ent = readdir(dir); - if(ent == NULL) { - break; - } - - char path[strlen(directory) + strlen(ent->d_name) + 2]; - snprintf(path, strlen(directory) + strlen(ent->d_name) + 2, "%s/%s", directory, ent->d_name); - if(uiIsDirectory(path)) { - contents->push_back(strdup(ent->d_name)); - } else { - const char *dot = strrchr(path, '.'); - if(dot && dot != path && strcmp(dot + 1, extensionFilter) == 0) { - contents->push_back(strdup(ent->d_name)); - } - } - } - - closedir(dir); - contents->push_back(strdup(".")); - contents->push_back(strdup("..")); - std::sort(contents->begin(), contents->end(), ui_alphabetize()); - } else { - return NULL; - } - - return contents; -} - -std::vector* uiTitlesToVector(App* apps, u32 count) { - std::vector* contents = new std::vector(); - if(count == 0) { - contents->push_back(strdup("None")); - } else { - for(u32 title = 0; title < count; title++) { - contents->push_back(sdprintf("%08lx - %s, %s, %s", apps[title].uniqueId, apps[title].productCode, app_get_platform_name(apps[title].platform), app_get_category_name(apps[title].category))); - } - } - - std::sort(contents->begin(), contents->end(), ui_alphabetize()); - return contents; -} - -void uiFreeVectorContents(std::vector* contents) { - for(std::vector::iterator it = contents->begin(); it != contents->end(); it++) { - free((char*) *it); - } -} - -UIResult uiDisplaySelector(char** selected, std::vector* contents, const char* currDir, MediaType destination, Mode mode) { - const char* selectCia = mode == INSTALL ? "Select a CIA to install." : "Select a CIA to delete."; - const char* pressL = "Press L to switch destinations."; - const char* pressR = "Press R to switch between installing and deleting."; - const char* destString = destination == NAND ? "Destination: NAND" : "Destination: SD"; - const char* modeString = mode == INSTALL ? "Mode: Install" : "Mode: Delete"; - - char* freeSpace = sdprintf("Free space: %llu bytes", fs_get_free_space(destination)); - char* requiredSpace = NULL; - - unsigned int cursor = 0; - unsigned int scroll = 0; - int horizScroll = 0; - u64 horizEndTime = 0; - UIResult result = SUCCESS; - while(platform_is_running()) { - input_poll(); - if(input_is_pressed(BUTTON_A)) { - *selected = contents->at(cursor); - result = SUCCESS; - break; - } - - if(input_is_pressed(BUTTON_B)) { - result = BACK; - break; - } - - if(input_is_pressed(BUTTON_L)) { - result = SWITCH_DEST; - break; - } - - if(input_is_pressed(BUTTON_R)) { - result = SWITCH_MODE; - break; - } - - bool cursorChanged = false; - if(input_is_pressed(BUTTON_DOWN) && cursor < contents->size() - 1) { - cursor++; - int diff = cursor - scroll; - if(diff >= 20) { - scroll++; - } - - horizScroll = 0; - horizEndTime = 0; - cursorChanged = true; - } - - if(input_is_pressed(BUTTON_UP) && cursor > 0) { - cursor--; - int diff = cursor - scroll; - if(diff < 0) { - scroll--; - } - - horizScroll = 0; - horizEndTime = 0; - cursorChanged = true; - } - - if(cursorChanged && currDir != NULL) { - char* currSelection = contents->at(cursor); - char* path = sdprintf("%s/%s", currDir, currSelection); - if(strcmp(currSelection, ".") != 0 && strcmp(currSelection, "..") != 0 && !uiIsDirectory(path)) { - struct stat st; - stat(path, &st); - requiredSpace = sdprintf("Required space: %lu bytes", st.st_size); - } else if(requiredSpace != NULL) { - free(requiredSpace); - requiredSpace = NULL; - } - - free(path); - } - - screen_begin_draw(); - screen_clear(0, 0, 0); - - int screenWidth = screen_get_width(); - int i = 0; - for(std::vector::iterator it = contents->begin() + scroll; it != contents->end(); it++) { - u8 color = 255; - int offset = 0; - if(i + scroll == cursor) { - screen_fill(0, i * 12, screenWidth, 8, 255, 255, 255); - color = 0; - int width = strlen(*it) * 8; - if(width > screenWidth) { - if(-horizScroll + screenWidth >= width) { - if(horizEndTime == 0) { - horizEndTime = platform_get_time(); - } else if(platform_get_time() - horizEndTime >= 4000) { - horizScroll = 0; - horizEndTime = 0; - } - } else { - horizScroll -= 1; - } - } - - offset = horizScroll; - } - - screen_draw_string(*it, offset, i * 12, color, color, color); - i++; - if(i >= 20) { - break; - } - } - - screen_end_draw(); - - screen_begin_draw_info(); - screen_clear(0, 0, 0); - - screen_draw_string(selectCia, (screen_get_width() - screen_get_str_width(selectCia)) / 2, (screen_get_height() - screen_get_str_height(pressL)) / 2 - screen_get_str_height(selectCia), 255, 255, 255); - screen_draw_string(pressL, (screen_get_width() - screen_get_str_width(pressL)) / 2, (screen_get_height() - screen_get_str_height(pressL)) / 2, 255, 255, 255); - screen_draw_string(pressR, (screen_get_width() - screen_get_str_width(pressR)) / 2, (screen_get_height() - screen_get_str_height(pressL)) / 2 + screen_get_str_height(pressL), 255, 255, 255); - screen_draw_string(freeSpace, (screen_get_width() - screen_get_str_width(freeSpace)) / 2, (screen_get_height() - screen_get_str_height(pressL)) / 2 + screen_get_str_height(pressL) * 4, 255, 255, 255); - if(requiredSpace != NULL) { - screen_draw_string(requiredSpace, (screen_get_width() - screen_get_str_width(requiredSpace)) / 2, (screen_get_height() - screen_get_str_height(pressL)) / 2 + screen_get_str_height(pressL) * 5, 255, 255, 255); - } - - screen_draw_string(destString, 0, screen_get_height() - screen_get_str_height(destString), 255, 255, 255); - screen_draw_string(modeString, screen_get_width() - screen_get_str_width(modeString), screen_get_height() - screen_get_str_height(modeString), 255, 255, 255); - screen_end_draw(); - - screen_swap_buffers(); - } - - free(freeSpace); - if(requiredSpace != NULL) { - free(requiredSpace); - } - - if(!platform_is_running()) { - result = EXIT_APP; - } - - return result; -} - -UIResult uiSelectFile(char** selected, const char* directory, const char* extension, MediaType* destination, Mode* mode) { - std::vector* contents = uiGetDirectoryContents(directory, extension); - UIResult result; - while(true) { - char* selectedEntry = NULL; - UIResult res = uiDisplaySelector(&selectedEntry, contents, directory, *destination, *mode); - if(res == SWITCH_DEST) { - if(*destination == NAND) { - *destination = SD; - } else { - *destination = NAND; - } - - continue; - } else if(res == BACK || (selectedEntry != NULL && strcmp(selectedEntry, "..") == 0)) { - if(strcmp(directory, "sdmc:") != 0) { - result = BACK; - break; - } else { - continue; - } - } else if(res != SUCCESS) { - result = res; - break; - } - - if(strcmp(selectedEntry, ".") == 0) { - continue; - } - - char* path = sdprintf("%s/%s", directory, selectedEntry); - if(uiIsDirectory(path)) { - char *select; - UIResult dirRes = uiSelectFile(&select, path, extension, destination, mode); - free(path); - if(dirRes == BACK) { - continue; - } - - result = dirRes; - *selected = select; - break; - } else { - result = SUCCESS; - *selected = path; - break; - } - } - - uiFreeVectorContents(contents); - delete(contents); - return result; -} - -UIResult uiSelectTitle(App* selected, MediaType* destination, Mode* mode) { - u32 appCount; - App* apps = app_list(*destination, &appCount); - std::vector* contents = uiTitlesToVector(apps, appCount); - UIResult result; - while(true) { - char* selectedEntry = NULL; - UIResult res = uiDisplaySelector(&selectedEntry, contents, NULL, *destination, *mode); - if(selectedEntry != NULL && strcmp(selectedEntry, "None") == 0) { - continue; - } - - if(res == BACK) { - continue; - } else if(res == SWITCH_DEST) { - if(*destination == NAND) { - *destination = SD; - } else { - *destination = NAND; - } - - uiFreeVectorContents(contents); - delete(contents); - free(apps); - apps = app_list(*destination, &appCount); - contents = uiTitlesToVector(apps, appCount); - continue; - } else if(res != SUCCESS) { - result = res; - break; - } - - for(u32 i = 0; i < appCount; i++) { - char* data = sdprintf("%08lx - %s, %s, %s", apps[i].uniqueId, apps[i].productCode, app_get_platform_name(apps[i].platform), app_get_category_name(apps[i].category)); - if(strcmp(selectedEntry, data) == 0) { - *selected = apps[i]; - free(data); - break; - } - - free(data); - } - - if(selected == NULL) { - continue; - } - - result = SUCCESS; - break; - } - - uiFreeVectorContents(contents); - delete(contents); - free(apps); - return result; -} - -bool uiDisplayInstallProgress(int progress) { - char* msg = sdprintf("Installing: [ ] %03d%%", progress); - const char* cancel = "Press B to cancel."; - for(int pos = 13; pos < 13 + (progress / 4); pos++) { - msg[pos] = '|'; - } - - screen_begin_draw_info(); - screen_clear(0, 0, 0); - screen_draw_string(msg, (screen_get_width() - screen_get_str_width(msg)) / 2, (screen_get_height() - screen_get_str_height(msg)) / 2 - screen_get_str_height(msg), 255, 255, 255); - screen_draw_string(cancel, (screen_get_width() - screen_get_str_width(cancel)) / 2, (screen_get_height() - screen_get_str_height(cancel)) / 2 + screen_get_str_height(cancel), 255, 255, 255); - screen_end_draw(); - screen_swap_buffers_quick(); - - free(msg); - - input_poll(); - return !input_is_pressed(BUTTON_B); -} - -void uiDisplayDeleting() { - const char* msg = "Deleting title..."; - screen_begin_draw_info(); - screen_clear(0, 0, 0); - screen_draw_string(msg, (screen_get_width() - screen_get_str_width(msg)) / 2, (screen_get_height() - screen_get_str_height(msg)) / 2, 255, 255, 255); - screen_end_draw(); - screen_swap_buffers(); -} - -void uiDisplayResult(bool install, bool state) { - const char* msg = install ? (state ? "Install succeeded! Press start." : "Install failed! Press start.") : (state ? "Delete succeeded! Press start." : "Delete failed! Press start."); - while(platform_is_running()) { - input_poll(); - if(input_is_pressed(BUTTON_START)) { - break; - } - - screen_begin_draw_info(); - screen_clear(0, 0, 0); - screen_draw_string(msg, (screen_get_width() - screen_get_str_width(msg)) / 2, (screen_get_height() - screen_get_str_height(msg)) / 2, 255, 255, 255); - screen_end_draw(); - screen_swap_buffers(); - } -} - -bool uiPromptOperation(Mode mode) { - char* msg = sdprintf("%s the selected title?", mode == INSTALL ? "Install" : "Delete"); - const char* prompt = "Press A to confirm, B to cancel."; - while(platform_is_running()) { - input_poll(); - if(input_is_pressed(BUTTON_A)) { - free(msg); - return true; - } - - if(input_is_pressed(BUTTON_B)) { - free(msg); - return false; - } - - screen_begin_draw_info(); - screen_clear(0, 0, 0); - screen_draw_string(msg, (screen_get_width() - screen_get_str_width(msg)) / 2, (screen_get_height() - screen_get_str_height(msg)) / 2 - screen_get_str_height(msg), 255, 255, 255); - screen_draw_string(prompt, (screen_get_width() - screen_get_str_width(prompt)) / 2, (screen_get_height() - screen_get_str_height(prompt)) / 2 + screen_get_str_height(prompt), 255, 255, 255); - screen_end_draw(); - screen_swap_buffers(); - } - - free(msg); - return false; -} diff --git a/source/ui.h b/source/ui.h deleted file mode 100644 index c7f10d5..0000000 --- a/source/ui.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef __UI_H__ -#define __UI_H__ - -#include "common.h" - -typedef enum _ui_result { - SUCCESS, - BACK, - SWITCH_DEST, - SWITCH_MODE, - EXIT_APP -} UIResult; - -typedef enum _mode { - INSTALL, - DELETE -} Mode; - -UIResult uiSelectFile(char** selected, const char* directory, const char* extension, MediaType* destination, Mode* mode); -UIResult uiSelectTitle(App* selected, MediaType* destination, Mode* mode); -bool uiDisplayInstallProgress(int progress); -void uiDisplayDeleting(); -void uiDisplayResult(bool install, bool state); -bool uiPromptOperation(Mode mode); - -#endif diff --git a/tools/bannertool b/tools/bannertool index ea68a27..5e60fb0 100755 Binary files a/tools/bannertool and b/tools/bannertool differ diff --git a/tools/bannertool.exe b/tools/bannertool.exe index ea5a50a..008c20c 100755 Binary files a/tools/bannertool.exe and b/tools/bannertool.exe differ