diff --git a/source/draw.c b/source/draw.c index 14d8e4e..6e081d5 100644 --- a/source/draw.c +++ b/source/draw.c @@ -9,7 +9,6 @@ #include "font.h" #include "draw.h" -// #include "fs.h" #include "hid.h" void ClearScreen(u8* screen, int width, int color) @@ -22,7 +21,7 @@ void ClearScreen(u8* screen, int width, int color) } } -void ClearScreenFull(bool clear_top, bool clear_bottom, int color) +void ClearScreenF(bool clear_top, bool clear_bottom, int color) { if (clear_top) { ClearScreen(TOP_SCREEN0, SCREEN_WIDTH_TOP, color); @@ -34,6 +33,32 @@ void ClearScreenFull(bool clear_top, bool clear_bottom, int color) } } +void DrawRectangle(u8* screen, int x, int y, int width, int height, int color) +{ + for (int yy = 0; yy < height; yy++) { + int xDisplacement = (x * BYTES_PER_PIXEL * SCREEN_HEIGHT); + int yDisplacement = ((SCREEN_HEIGHT - (y + yy) - 1) * BYTES_PER_PIXEL); + u8* screenPos = screen + xDisplacement + yDisplacement; + for (int xx = width - 1; xx >= 0; xx--) { + *(screenPos + 0) = color >> 16; // B + *(screenPos + 1) = color >> 8; // G + *(screenPos + 2) = color & 0xFF; // R + screenPos += BYTES_PER_PIXEL * SCREEN_HEIGHT; + } + } +} + +void DrawRectangleF(bool use_top, int x, int y, int width, int height, int color) +{ + if (use_top) { + DrawRectangle(TOP_SCREEN0, x, y, width, height, color); + DrawRectangle(TOP_SCREEN1, x, y, width, height, color); + } else { + DrawRectangle(BOT_SCREEN0, x, y, width, height, color); + DrawRectangle(BOT_SCREEN1, x, y, width, height, color); + } +} + void DrawCharacter(u8* screen, int character, int x, int y, int color, int bgcolor) { for (int yy = 0; yy < 8; yy++) { @@ -83,19 +108,91 @@ void DrawStringF(bool use_top, int x, int y, int color, int bgcolor, const char } } -void ShowError(const char *format, ...) +u32 GetDrawStringHeight(const char* str) { + u32 height = 8; + for (char* lf = strchr(str, '\n'); (lf != NULL); lf = strchr(lf + 1, '\n')) + height += 10; + return height; +} + +u32 GetDrawStringWidth(char* str) { + u32 width = 0; + char* old_lf = str; + for (char* lf = strchr(str, '\n'); (lf != NULL); lf = strchr(lf + 1, '\n')) { + if ((lf - old_lf) > width) width = lf - old_lf; + old_lf = lf; + } + if (old_lf == str) + width = strnlen(str, 256); + width *= 8; + return width; +} + +void ResizeString(char* dest, const char* orig, int nsize, int tpos, bool align_right) { + int osize = strnlen(orig, 256); + if (nsize < osize) { + TruncateString(dest, orig, nsize, tpos); + } else if (!align_right) { + snprintf(dest, nsize + 1, "%-*.*s", nsize, nsize, orig); + } else { + snprintf(dest, nsize + 1, "%*.*s", nsize, nsize, orig); + } +} + +void TruncateString(char* dest, const char* orig, int nsize, int tpos) { + int osize = strnlen(orig, 256); + if (nsize < 0) { + return; + } if (nsize <= 3) { + snprintf(dest, nsize, orig); + } else if (nsize >= osize) { + snprintf(dest, nsize + 1, orig); + } else { + if (tpos + 3 > nsize) tpos = nsize - 3; + snprintf(dest, nsize + 1, "%-.*s...%-.*s", tpos, orig, nsize - (3 + tpos), orig + osize - (nsize - (3 + tpos))); + } +} + +void FormatBytes(char* str, u64 bytes) { // str should be 32 byte in size, just to be safe + const char* units[] = {" byte", "kB", "MB", "GB"}; + + if (bytes == (u64) -1) snprintf(str, 32, "INVALID"); + else if (bytes < 1024) snprintf(str, 32, "%llu%s", bytes, units[0]); + else { + u32 scale = 1; + u64 bytes100 = (bytes * 100) >> 10; + for(; (bytes100 >= 1024*100) && (scale < 3); scale++, bytes100 >>= 10); + snprintf(str, 32, "%llu.%llu%s", bytes100 / 100, (bytes100 % 100) / 10, units[scale]); + } +} + +bool ShowPrompt(bool ask, const char *format, ...) { - char str[128] = {}; // 128 should be more than enough + u32 str_width, str_height; + u32 x, y; + + char str[384] = {}; // 384 should be more than enough va_list va; va_start(va, format); - vsnprintf(str, 128, format, va); + vsnprintf(str, 384, format, va); va_end(va); - ClearScreenFull(true, false, COLOR_BLACK); - DrawStringF(true, 80, 80, COLOR_WHITE, COLOR_BLACK, str); + str_width = GetDrawStringWidth(str); + str_height = GetDrawStringHeight(str) + (2 * 10); + x = (str_width >= SCREEN_WIDTH_TOP) ? 0 : (SCREEN_WIDTH_TOP - str_width) / 2; + y = (str_height >= SCREEN_HEIGHT) ? 0 : (SCREEN_HEIGHT - str_height) / 2; - // InputWait(); + ClearScreenF(true, false, COLOR_STD_BG); + DrawStringF(true, x, y, COLOR_STD_FONT, COLOR_STD_BG, (ask) ? "%s\n\n( to continue, to cancel)" : "%s\n\n( to continue)", str); + + while (true) { + u32 pad_state = InputWait(); + if (pad_state & BUTTON_A) break; + else if (pad_state & BUTTON_B) return false; + } + + return true; } /*void ShowProgress(u64 current, u64 total) diff --git a/source/draw.h b/source/draw.h index d387c49..fc30ceb 100644 --- a/source/draw.h +++ b/source/draw.h @@ -15,9 +15,23 @@ #define COLOR_BLACK RGB(0x00, 0x00, 0x00) #define COLOR_WHITE RGB(0xFF, 0xFF, 0xFF) -#define COLOR_GREY RGB(0x7F, 0x7F, 0x7F) +#define COLOR_GREY RGB(0x80, 0x80, 0x80) + +#define COLOR_RED RGB(0xFF, 0x00, 0x00) +#define COLOR_GREEN RGB(0x00, 0xFF, 0x00) +#define COLOR_BLUE RGB(0xFF, 0x00, 0xFF) +#define COLOR_YELLOW RGB(0xFF, 0xFF, 0x00) +#define COLOR_CYAN RGB(0xFF, 0x00, 0xFF) + +#define COLOR_TINTEDBLUE RGB(0x60, 0x60, 0x80) +#define COLOR_TINTEDYELLOW RGB(0xD0, 0xD0, 0x60) +#define COLOR_TINTEDGREEN RGB(0x70, 0x80, 0x70) + #define COLOR_TRANSPARENT RGB(0xFF, 0x00, 0xEF) // otherwise known as 'super fuchsia' +#define COLOR_STD_BG COLOR_BLACK +#define COLOR_STD_FONT COLOR_WHITE + #ifdef EXEC_GATEWAY #define TOP_SCREEN0 (u8*)(*(u32*)((uint32_t)0x080FFFC0 + 4 * (*(u32*)0x080FFFD8 & 1))) #define BOT_SCREEN0 (u8*)(*(u32*)((uint32_t)0x080FFFD0 + 4 * (*(u32*)0x080FFFDC & 1))) @@ -33,11 +47,21 @@ #endif void ClearScreen(unsigned char *screen, int width, int color); -void ClearScreenFull(bool clear_top, bool clear_bottom, int color); +void ClearScreenF(bool clear_top, bool clear_bottom, int color); + +void DrawRectangle(u8* screen, int x, int y, int width, int height, int color); +void DrawRectangleF(bool use_top, int x, int y, int width, int height, int color); void DrawCharacter(unsigned char *screen, int character, int x, int y, int color, int bgcolor); void DrawString(unsigned char *screen, const char *str, int x, int y, int color, int bgcolor); void DrawStringF(bool use_top, int x, int y, int color, int bgcolor, const char *format, ...); -void ShowError(const char *format, ...); +u32 GetDrawStringHeight(const char* str); +u32 GetDrawStringWidth(char* str); + +void ResizeString(char* dest, const char* orig, int nsize, int tpos, bool align_right); +void TruncateString(char* dest, const char* orig, int nsize, int tpos); +void FormatBytes(char* str, u64 bytes); + +bool ShowPrompt(bool ask, const char *format, ...); void ShowProgress(u64 current, u64 total); diff --git a/source/fs.c b/source/fs.c index ec39cc7..a1628d9 100644 --- a/source/fs.c +++ b/source/fs.c @@ -18,18 +18,17 @@ bool InitFS() *(u32*)0x10000020 = 0; *(u32*)0x10000020 = 0x340; #endif - for (numfs = 0; numfs < 16; numfs++) { + for (numfs = 0; numfs < 7; numfs++) { char fsname[8]; snprintf(fsname, 8, "%lu:", numfs); int res = f_mount(fs + numfs, fsname, 1); if (res != FR_OK) { if (numfs >= 4) break; - ShowError("Initialising failed! (%lu/%s/%i)", numfs, fsname, res); + ShowPrompt(false, "Initialising failed! (%lu/%s/%i)", numfs, fsname, res); DeinitFS(); return false; } } - ShowError("Mounted: %i partitions", numfs); return true; } @@ -43,10 +42,6 @@ void DeinitFS() numfs = 0; } -bool FileExists(const char* path) { - return (f_stat(path, NULL) == FR_OK); -} - bool FileCreate(const char* path, u8* data, u32 size) { FIL file; UINT bytes_written = 0; @@ -72,7 +67,7 @@ void Screenshot() for (; n < 1000; n++) { snprintf(filename, 16, "0:/snap%03i.bmp", (int) n); - if (!FileExists(filename)) break; + if (f_stat(filename, NULL) == FR_OK) break; } if (n >= 1000) return; @@ -90,20 +85,18 @@ void Screenshot() bool GetRootDirContentsWorker(DirStruct* contents) { static const char* drvname[16] = { "SDCARD", - "SYSCTRN", "SYSTWLN", "SYSTWLP", - "EMU0CTRN", "EMU0TWLN", "EMU0TWLP", - "EMU1CTRN", "EMU1TWLN", "EMU1TWLP", - "EMU2CTRN", "EMU2TWLN", "EMU2TWLP", - "EMU3CTRN", "EMU3TWLN", "EMU3TWLP" + "SYSNAND CTRNAND", "SYSNAND TWLN", "SYSNAND TWLP", + "EMUNAND CTRNAND", "EMUNAND TWLN", "EMUNAND TWLP" }; for (u32 pdrv = 0; (pdrv < numfs) && (pdrv < MAX_ENTRIES); pdrv++) { memset(contents->entry[pdrv].path, 0x00, 16); snprintf(contents->entry[pdrv].path + 0, 4, "%lu:", pdrv); - snprintf(contents->entry[pdrv].path + 4, 16, "[%lu:] %s", pdrv, drvname[pdrv]); + snprintf(contents->entry[pdrv].path + 4, 32, "[%lu:] %s", pdrv, drvname[pdrv]); contents->entry[pdrv].name = contents->entry[pdrv].path + 4; - contents->entry[pdrv].size = 0; - contents->entry[pdrv].type = T_FAT_DIR; + contents->entry[pdrv].size = GetTotalSpace(contents->entry[pdrv].path); + contents->entry[pdrv].type = T_FAT_ROOT; + contents->entry[pdrv].marked = 0; } contents->n_entries = numfs; @@ -141,6 +134,7 @@ bool GetDirContentsWorker(DirStruct* contents, char* fpath, int fsize, bool recu entry->type = T_FAT_FILE; entry->size = fno.fsize; } + entry->marked = 0; contents->n_entries++; if (contents->n_entries >= MAX_ENTRIES) break; @@ -178,20 +172,32 @@ DirStruct* GetDirContents(const char* path) { #else return sectors * _MAX_SS; #endif -} +}*/ -uint64_t RemainingStorageSpace() +uint64_t GetFreeSpace(const char* path) { DWORD free_clusters; - FATFS *fs2; - FRESULT res = f_getfree("0:", &free_clusters, &fs2); - if (res) + FATFS *fs_ptr; + char fsname[4] = { '\0' }; + int fsnum = -1; + + strncpy(fsname, path, 2); + fsnum = *fsname - (int) '0'; + if ((fsnum < 0) || (fsnum >= 7) || (fsname[1] != ':')) + return -1; + if (f_getfree(fsname, &free_clusters, &fs_ptr) != FR_OK) return -1; - return ClustersToBytes(&fs, free_clusters); + return (uint64_t) free_clusters * fs[fsnum].csize * _MAX_SS; } -uint64_t TotalStorageSpace() +uint64_t GetTotalSpace(const char* path) { - return ClustersToBytes(&fs, fs.n_fatent - 2); -}*/ + int fsnum = -1; + + fsnum = *path - (int) '0'; + if ((fsnum < 0) || (fsnum >= 7) || (path[1] != ':')) + return -1; + + return (uint64_t) (fs[fsnum].n_fatent - 2) * fs[fsnum].csize * _MAX_SS; +} diff --git a/source/fs.h b/source/fs.h index f5411d0..0479413 100644 --- a/source/fs.h +++ b/source/fs.h @@ -3,8 +3,6 @@ #include "common.h" typedef enum { - T_NAND_BASE, // might not be needed - T_NONFAT_ROOT, // might not be needed T_FAT_ROOT, T_FAT_FILE, T_FAT_DIR @@ -15,8 +13,9 @@ typedef enum { typedef struct { char* name; // should point to the correct portion of the path char path[256]; - u32 size; + u64 size; EntryType type; + u8 marked; } DirEntry; typedef struct { @@ -27,9 +26,6 @@ typedef struct { bool InitFS(); void DeinitFS(); -/** Check if file exists **/ -bool FileExists(const char* path); - /** Create / overwrite file and write the provided data to it **/ bool FileCreate(const char* path, u8* data, u32 size); @@ -39,8 +35,8 @@ void Screenshot(); /** Get directory content under a given path **/ DirStruct* GetDirContents(const char* path); -/** Gets remaining space on SD card in bytes */ -uint64_t RemainingStorageSpace(); +/** Gets remaining space in filesystem in bytes */ +uint64_t GetFreeSpace(const char* path); -/** Gets total space on SD card in bytes */ -uint64_t TotalStorageSpace(); +/** Gets total spacein filesystem in bytes */ +uint64_t GetTotalSpace(const char* path); diff --git a/source/godmode.c b/source/godmode.c index 44dce96..7174aab 100644 --- a/source/godmode.c +++ b/source/godmode.c @@ -3,24 +3,72 @@ #include "hid.h" #include "fs.h" -void DrawDirContents(DirStruct* contents, u32* offset, u32 cursor) { +#define COLOR_TOP_BAR COLOR_WHITE +#define COLOR_MARKED COLOR_TINTEDYELLOW +#define COLOR_FILE COLOR_TINTEDGREEN +#define COLOR_DIR COLOR_TINTEDBLUE +#define COLOR_ROOT COLOR_GREY + +void DrawUserInterface(const char* curr_path, DirEntry* curr_entry) { + const u32 info_start = 16; + char bytestr0[32]; + char bytestr1[32]; + char tempstr[64]; + + // top bar - current path & free/total storage + DrawRectangleF(true, 0, 0, SCREEN_WIDTH_TOP, 12, COLOR_TOP_BAR); + if (strncmp(curr_path, "", 256) != 0) { + TruncateString(tempstr, curr_path, 30, 8); + DrawStringF(true, 2, 2, COLOR_STD_BG, COLOR_TOP_BAR, tempstr); + DrawStringF(true, 30 * 8 + 4, 2, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", "LOADING..."); + FormatBytes(bytestr0, GetFreeSpace(curr_path)); + FormatBytes(bytestr1, GetTotalSpace(curr_path)); + snprintf(tempstr, 64, "%s/%s", bytestr0, bytestr1); + DrawStringF(true, 30 * 8 + 4, 2, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", tempstr); + } else { + DrawStringF(true, 2, 2, COLOR_STD_BG, COLOR_TOP_BAR, "[root]"); + DrawStringF(true, 30 * 8 + 6, 2, COLOR_STD_BG, COLOR_TOP_BAR, "%19.19s", "GodMode9"); + } + + // left top - current file info + ResizeString(tempstr, curr_entry->name, 20, 8, false); + DrawStringF(true, 2, info_start, (curr_entry->marked) ? COLOR_MARKED : COLOR_STD_FONT, COLOR_STD_BG, "%s", tempstr); + if (curr_entry->type == T_FAT_DIR) { + ResizeString(tempstr, "(dir)", 20, 8, false); + DrawStringF(true, 4, info_start + 10, COLOR_DIR, COLOR_STD_BG, tempstr); + } else { + FormatBytes(bytestr0, curr_entry->size); + ResizeString(tempstr, bytestr0, 20, 8, false); + DrawStringF(true, 4, info_start + 10, COLOR_FILE, COLOR_STD_BG, tempstr); + } + + // bottom: inctruction block + char* instr = "GodMode 9 v0.0.1\n//<\x18\x19\x1A\x1B> - Navigation\n - Mark (multiple) file(s)\n - Make a Screenshot\n - Reboot / Power off"; + DrawStringF(true, (SCREEN_WIDTH_TOP - GetDrawStringWidth(instr)) / 2, SCREEN_HEIGHT - 2 - GetDrawStringHeight(instr), COLOR_STD_FONT, COLOR_STD_BG, instr); +} + +void DrawDirContents(DirStruct* contents, u32 cursor) { + static u32 offset_disp = 0; const int str_width = 40; + const u32 start_y = 2; const u32 stp_y = 12; const u32 pos_x = 0; - u32 pos_y = 2; + const u32 lines = (SCREEN_HEIGHT-start_y+stp_y-1) / stp_y; + u32 pos_y = start_y; + + if (offset_disp > cursor) offset_disp = cursor; + else if (offset_disp + lines <= cursor) offset_disp = cursor - lines + 1; for (u32 i = 0; pos_y < SCREEN_HEIGHT; i++) { char tempstr[str_width + 1]; - u32 offset_i = *offset + i; + u32 offset_i = offset_disp + i; + u32 color_bg = COLOR_STD_BG; u32 color_font; - u32 color_bg; if (offset_i < contents->n_entries) { if (cursor != offset_i) { - color_font = COLOR_GREY; - color_bg = COLOR_BLACK; + color_font = (contents->entry[offset_i].marked) ? COLOR_MARKED : (contents->entry[offset_i].type == T_FAT_DIR) ? COLOR_DIR : (contents->entry[offset_i].type == T_FAT_FILE) ? COLOR_FILE : COLOR_ROOT; } else { - color_font = COLOR_WHITE; - color_bg = COLOR_BLACK; + color_font = COLOR_STD_FONT; } snprintf(tempstr, str_width + 1, "%-*.*s", str_width, str_width, contents->entry[offset_i].name); } else { @@ -34,21 +82,19 @@ void DrawDirContents(DirStruct* contents, u32* offset, u32 cursor) { } u32 GodMode() { + static const u32 quick_stp = 20; u32 exit_mode = GODMODE_EXIT_REBOOT; char current_path[256] = { 0x00 }; DirStruct* contents; u32 cursor = 0; - u32 offset_disp = 0; - ClearScreenFull(true, true, COLOR_BLACK); - if (!InitFS()) { - InputWait(); - return exit_mode; - } + ClearScreenF(true, true, COLOR_BLACK); + if (!InitFS()) return exit_mode; contents = GetDirContents(""); while (true) { // this is the main loop - DrawDirContents(contents, &offset_disp, cursor); + DrawUserInterface(current_path, &contents->entry[cursor]); + DrawDirContents(contents, cursor); u32 pad_state = InputWait(); if (pad_state & BUTTON_DOWN) { cursor++; @@ -56,17 +102,26 @@ u32 GodMode() { cursor = contents->n_entries - 1; } else if ((pad_state & BUTTON_UP) && cursor) { cursor--; - } else if ((pad_state & BUTTON_A) && (contents->entry[cursor].type == T_FAT_DIR)) { + } else if (pad_state & BUTTON_RIGHT) { + cursor += quick_stp; + if (cursor >= contents->n_entries) + cursor = contents->n_entries - 1; + } else if (pad_state & BUTTON_LEFT) { + cursor = (cursor >= quick_stp) ? cursor - quick_stp : 0; + } else if ((pad_state & BUTTON_L1) && *current_path) { + contents->entry[cursor].marked ^= 0x1; + } else if ((pad_state & BUTTON_A) && (contents->entry[cursor].type != T_FAT_FILE)) { strncpy(current_path, contents->entry[cursor].path, 256); contents = GetDirContents(current_path); - cursor = offset_disp = 0; - ShowError(current_path); + cursor = 0; + ClearScreenF(true, true, COLOR_STD_BG); } else if (pad_state & BUTTON_B) { char* last_slash = strrchr(current_path, '/'); if (last_slash) *last_slash = '\0'; else *current_path = '\0'; contents = GetDirContents(current_path); - cursor = offset_disp = 0; + cursor = 0; + ClearScreenF(true, true, COLOR_STD_BG); } else if (pad_state & BUTTON_X) { Screenshot(); }