diff --git a/include/common.h b/include/common.h index 8b6a749..b7a468f 100644 --- a/include/common.h +++ b/include/common.h @@ -55,6 +55,10 @@ enum TextureID { TEXTURE_FONT_RESERVED = 0, // used by pp2d for the font TEXTURE_ARROW, TEXTURE_SHUFFLE, + TEXTURE_INSTALLED, + TEXTURE_PREVIEW_ICON, + TEXTURE_DOWNLOAD, + TEXTURE_RELOAD, TEXTURE_BATTERY_0, TEXTURE_BATTERY_1, TEXTURE_BATTERY_2, diff --git a/include/draw.h b/include/draw.h index 96ceac7..e394ea8 100644 --- a/include/draw.h +++ b/include/draw.h @@ -32,6 +32,10 @@ #include "colors.h" typedef enum { + INSTALL_LOADING_THEMES, + INSTALL_LOADING_SPLASHES, + INSTALL_LOADING_ICONS, + INSTALL_SPLASH, INSTALL_SPLASH_DELETE, @@ -42,6 +46,8 @@ typedef enum { INSTALL_DOWNLOAD, INSTALL_ENTRY_DELETE, + + INSTALL_NONE, } InstallType; typedef enum { @@ -76,13 +82,12 @@ void init_screens(void); void exit_screens(void); void throw_error(char* error, ErrorLevel level); -bool draw_confirm(const char* conf_msg, Entry_List_s* list, EntryMode current_mode); +bool draw_confirm(const char* conf_msg, Entry_List_s* list); void draw_preview(int preview_offset); void draw_install(InstallType type); -void draw_instructions(Instructions_s instructions); -void draw_interface(Entry_List_s* list, EntryMode current_mode); +void draw_interface(Entry_List_s* list, Instructions_s instructions); #endif \ No newline at end of file diff --git a/include/instructions.h b/include/instructions.h index 39bae9a..53a1f3f 100644 --- a/include/instructions.h +++ b/include/instructions.h @@ -39,7 +39,7 @@ Instructions_s normal_instructions[MODE_AMOUNT] = { L"\uE001 Queue shuffle theme" }, { - NULL, + L"\uE002 Reload broken icons", L"\uE003 Preview theme" }, { @@ -60,7 +60,7 @@ Instructions_s normal_instructions[MODE_AMOUNT] = { L"\uE001 Delete installed splash" }, { - NULL, + L"\uE002 Reload broken icons", L"\uE003 Preview splash" }, { diff --git a/include/loading.h b/include/loading.h index 82b0b54..e50dd4f 100644 --- a/include/loading.h +++ b/include/loading.h @@ -29,6 +29,14 @@ #include "common.h" +enum ICON_IDS_OFFSET { + ICONS_ABOVE = 0, + ICONS_VISIBLE, + ICONS_UNDER, + + ICONS_OFFSET_AMOUNT, +}; + typedef struct { u8 _padding1[4 + 2 + 2]; @@ -48,29 +56,43 @@ typedef struct { u16 author[0x41]; u32 placeholder_color; - ssize_t icon_id; u16 path[0x106]; bool is_zip; bool in_shuffle; + bool installed; } Entry_s; typedef struct { Entry_s * entries; int entries_count; - ssize_t icon_id_start; + ssize_t texture_id_offset; + ssize_t icons_ids[ICONS_OFFSET_AMOUNT][ENTRIES_PER_SCREEN]; + ssize_t assoc_entry_ids[ICONS_OFFSET_AMOUNT][ENTRIES_PER_SCREEN]; + int previous_scroll; int scroll; + + int previous_selected; int selected_entry; int shuffle_count; + + EntryMode mode; } Entry_List_s; +typedef struct { + void ** thread_arg; + volatile bool run_thread; +} Thread_Arg_s; + void delete_entry(Entry_s entry); -Result load_entries(const char * loading_path, Entry_List_s * list); +Result load_entries(const char * loading_path, Entry_List_s * list, EntryMode mode); bool load_preview(Entry_List_s list, int * preview_offset); +void load_icons_first(Entry_List_s * current_list, bool silent); +void load_icons_thread(void * void_arg); u32 load_data(char * filename, Entry_s entry, char ** buf); #endif \ No newline at end of file diff --git a/include/splashes.h b/include/splashes.h index 6c63b8d..d16cfc4 100644 --- a/include/splashes.h +++ b/include/splashes.h @@ -33,4 +33,6 @@ void splash_delete(void); void splash_install(Entry_s splash); +void splash_check_installed(void * void_arg); + #endif \ No newline at end of file diff --git a/include/themes.h b/include/themes.h index c2f65f0..5e56d6d 100644 --- a/include/themes.h +++ b/include/themes.h @@ -75,4 +75,6 @@ Result bgm_install(Entry_s theme); Result shuffle_install(Entry_List_s themes); +void themes_check_installed(void * void_arg); + #endif \ No newline at end of file diff --git a/romfs/download.png b/romfs/download.png new file mode 100644 index 0000000..b79749b Binary files /dev/null and b/romfs/download.png differ diff --git a/romfs/installed.png b/romfs/installed.png new file mode 100644 index 0000000..fdb2134 Binary files /dev/null and b/romfs/installed.png differ diff --git a/romfs/preview.png b/romfs/preview.png new file mode 100644 index 0000000..25d3294 Binary files /dev/null and b/romfs/preview.png differ diff --git a/romfs/reload.png b/romfs/reload.png new file mode 100644 index 0000000..23c6ca9 Binary files /dev/null and b/romfs/reload.png differ diff --git a/romfs/shuffle.png b/romfs/shuffle.png index 2b12839..cd789bf 100644 Binary files a/romfs/shuffle.png and b/romfs/shuffle.png differ diff --git a/source/draw.c b/source/draw.c index 405a54a..02f2d3d 100644 --- a/source/draw.c +++ b/source/draw.c @@ -41,6 +41,10 @@ void init_screens(void) pp2d_load_texture_png(TEXTURE_ARROW, "romfs:/arrow.png"); pp2d_load_texture_png(TEXTURE_SHUFFLE, "romfs:/shuffle.png"); + pp2d_load_texture_png(TEXTURE_INSTALLED, "romfs:/installed.png"); + pp2d_load_texture_png(TEXTURE_RELOAD, "romfs:/reload.png"); + pp2d_load_texture_png(TEXTURE_PREVIEW_ICON, "romfs:/preview.png"); + pp2d_load_texture_png(TEXTURE_DOWNLOAD, "romfs:/download.png"); pp2d_load_texture_png(TEXTURE_BATTERY_0, "romfs:/battery0.png"); pp2d_load_texture_png(TEXTURE_BATTERY_1, "romfs:/battery1.png"); pp2d_load_texture_png(TEXTURE_BATTERY_2, "romfs:/battery2.png"); @@ -145,11 +149,12 @@ void throw_error(char* error, ErrorLevel level) } } -bool draw_confirm(const char* conf_msg, Entry_List_s* list, EntryMode current_mode) +bool draw_confirm(const char* conf_msg, Entry_List_s* list) { while(aptMainLoop()) { - draw_interface(list, current_mode); + Instructions_s instructions = {0}; + draw_interface(list, instructions); pp2d_draw_on(GFX_TOP, GFX_LEFT); draw_text_center(GFX_TOP, BUTTONS_Y_LINE_1, 0.7, 0.7, COLOR_YELLOW, conf_msg); pp2d_draw_wtext_center(GFX_TOP, BUTTONS_Y_LINE_3, 0.6, 0.6, COLOR_WHITE, L"\uE000 Yes \uE001 No"); @@ -177,6 +182,15 @@ void draw_install(InstallType type) draw_base_interface(); switch(type) { + case INSTALL_LOADING_THEMES: + pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Loading themes, please wait..."); + break; + case INSTALL_LOADING_SPLASHES: + pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Loading splashes, please wait..."); + break; + case INSTALL_LOADING_ICONS: + pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Loading icons, please wait..."); + break; case INSTALL_SINGLE: pp2d_draw_text_center(GFX_TOP, 120, 0.8, 0.8, COLOR_WHITE, "Installing a single theme..."); break; @@ -207,7 +221,7 @@ void draw_install(InstallType type) pp2d_end_draw(); } -void draw_instructions(Instructions_s instructions) +static void draw_instructions(Instructions_s instructions) { pp2d_draw_on(GFX_TOP, GFX_LEFT); @@ -243,9 +257,10 @@ void draw_instructions(Instructions_s instructions) } } -void draw_interface(Entry_List_s* list, EntryMode current_mode) +void draw_interface(Entry_List_s* list, Instructions_s instructions) { draw_base_interface(); + EntryMode current_mode = list->mode; const char* mode_string[MODE_AMOUNT] = { "Theme mode", @@ -267,10 +282,16 @@ void draw_interface(Entry_List_s* list, EntryMode current_mode) "Or \uE004 to switch to themes", }; pp2d_draw_text_center(GFX_TOP, 140, 0.7, 0.7, COLOR_YELLOW, mode_switch_string[current_mode]); - pp2d_draw_text_center(GFX_TOP, 170, 0.7, 0.7, COLOR_YELLOW, "Or \uE045 to quit"); + pp2d_draw_text_center(GFX_TOP, 170, 0.7, 0.7, COLOR_YELLOW, "Or to quit"); + pp2d_texture_select(TEXTURE_START_BUTTON, 162, 173); + pp2d_texture_blend(COLOR_YELLOW); + pp2d_texture_scale(1.25, 1.4); + pp2d_texture_draw(); return; } + draw_instructions(instructions); + int selected_entry = list->selected_entry; Entry_s current_entry = list->entries[selected_entry]; @@ -292,26 +313,18 @@ void draw_interface(Entry_List_s* list, EntryMode current_mode) switch(current_mode) { case MODE_THEMES: - pp2d_draw_textf(7, 3, 0.6, 0.6, list->shuffle_count <= 10 ? COLOR_WHITE : COLOR_RED, "Selected: %i/10", list->shuffle_count); + pp2d_draw_textf(7, 3, 0.6, 0.6, list->shuffle_count <= 10 ? COLOR_WHITE : COLOR_RED, "Shuffle: %i/10", list->shuffle_count); + pp2d_draw_texture_blend(TEXTURE_SHUFFLE, 320-120, 0, COLOR_WHITE); break; default: break; } - // Scroll the menu up or down if the selected theme is out of its bounds - //---------------------------------------------------------------- - for(int i = 0; i < list->entries_count; i++) { - if(list->entries_count <= ENTRIES_PER_SCREEN) break; + pp2d_draw_texture_blend(TEXTURE_RELOAD, 320-96, 0, COLOR_WHITE); + pp2d_draw_texture_blend(TEXTURE_PREVIEW_ICON, 320-72, 0, COLOR_WHITE); + pp2d_draw_texture_blend(TEXTURE_DOWNLOAD, 320-48, 0, COLOR_WHITE); + pp2d_draw_textf(320-24+2.5, -3, 1, 1, COLOR_WHITE, "%c", *mode_string[!list->mode]); - if(list->scroll > list->selected_entry) - list->scroll--; - - if((i < list->selected_entry) && \ - ((list->selected_entry - list->scroll) >= ENTRIES_PER_SCREEN) && \ - (list->scroll != (i - ENTRIES_PER_SCREEN))) - list->scroll++; - } - //---------------------------------------------------------------- // Show arrows if there are themes out of bounds //---------------------------------------------------------------- @@ -339,11 +352,33 @@ void draw_interface(Entry_List_s* list, EntryMode current_mode) } pp2d_draw_wtext(54, 40 + vertical_offset, 0.55, 0.55, font_color, name); if(!current_entry.placeholder_color) - pp2d_draw_texture(current_entry.icon_id, 0, 24 + vertical_offset); + { + ssize_t id = 0; + if(list->entries_count > ICONS_OFFSET_AMOUNT*ENTRIES_PER_SCREEN) + id = list->icons_ids[ICONS_VISIBLE][i - list->scroll]; + else + id = ((size_t *)list->icons_ids)[i]; + pp2d_draw_texture(id, 0, 24 + vertical_offset); + } else pp2d_draw_rectangle(0, 24 + vertical_offset, 48, 48, current_entry.placeholder_color); if(current_entry.in_shuffle) - pp2d_draw_texture_blend(TEXTURE_SHUFFLE, 280, 32 + vertical_offset, font_color); + pp2d_draw_texture_blend(TEXTURE_SHUFFLE, 320-24-4, 24 + vertical_offset, font_color); + if(current_entry.installed) + pp2d_draw_texture_blend(TEXTURE_INSTALLED, 320-24-4, 24 + 22 + vertical_offset, font_color); } + + char entries_count_str[0x20] = {0}; + sprintf(entries_count_str, "/%i", list->entries_count); + float x = 316; + x -= pp2d_get_text_width(entries_count_str, 0.6, 0.6); + pp2d_draw_text(x, 219, 0.6, 0.6, COLOR_WHITE, entries_count_str); + + char selected_entry_str[0x20] = {0}; + sprintf(selected_entry_str, "%i", selected_entry + 1); + x -= pp2d_get_text_width(selected_entry_str, 0.6, 0.6); + pp2d_draw_text(x, 219, 0.6, 0.6, COLOR_WHITE, selected_entry_str); + + pp2d_draw_text(176, 219, 0.6, 0.6, COLOR_WHITE, list->entries_count < 1000 ? "Selected:" : "Sel.:"); } diff --git a/source/fs.c b/source/fs.c index 3360d34..8a890c8 100644 --- a/source/fs.c +++ b/source/fs.c @@ -111,8 +111,11 @@ u32 file_to_buf(FS_Path path, FS_Archive archive, char** buf) u64 size; FSFILE_GetSize(file, &size); - *buf = calloc(1, size); - FSFILE_Read(file, NULL, 0, *buf, size); + if(size != 0) + { + *buf = calloc(1, size); + FSFILE_Read(file, NULL, 0, *buf, size); + } FSFILE_Close(file); return (u32)size; } diff --git a/source/loading.c b/source/loading.c index d3873fb..f8d2db8 100644 --- a/source/loading.c +++ b/source/loading.c @@ -54,10 +54,8 @@ u32 load_data(char * filename, Entry_s entry, char ** buf) } } -static void parse_smdh(Entry_s * entry, const ssize_t textureID, const u16 * fallback_name) +static void parse_smdh(Entry_s * entry, const u16 * fallback_name) { - pp2d_free_texture(textureID); - char *info_buffer = NULL; u64 size = load_data("/info.smdh", *entry, &info_buffer); Icon_s * smdh = (Icon_s *)info_buffer; @@ -75,6 +73,17 @@ static void parse_smdh(Entry_s * entry, const ssize_t textureID, const u16 * fal memcpy(entry->name, smdh->name, 0x40*sizeof(u16)); memcpy(entry->desc, smdh->desc, 0x80*sizeof(u16)); memcpy(entry->author, smdh->author, 0x40*sizeof(u16)); +} + +static void load_smdh_icon(Entry_s entry, const ssize_t textureID) +{ + pp2d_free_texture(textureID); + + char *info_buffer = NULL; + u64 size = load_data("/info.smdh", entry, &info_buffer); + if(!size) return; + + Icon_s * smdh = (Icon_s *)info_buffer; const u32 width = 48, height = 48; u32 *image = malloc(width*height*sizeof(u32)); @@ -93,8 +102,6 @@ static void parse_smdh(Entry_s * entry, const ssize_t textureID, const u16 * fal free(info_buffer); pp2d_load_texture_memory(textureID, (u8*)image, (u32)width, (u32)height); free(image); - - entry->icon_id = textureID; } static int compare_entries(const void * a, const void * b) @@ -110,7 +117,7 @@ static void sort_list(Entry_List_s * list) qsort(list->entries, list->entries_count, sizeof(Entry_s), compare_entries); //alphabet sort } -Result load_entries(const char * loading_path, Entry_List_s * list) +Result load_entries(const char * loading_path, Entry_List_s * list, EntryMode mode) { Handle dir_handle; Result res = FSUSER_OpenDirectory(&dir_handle, ArchiveSD, fsMakePath(PATH_ASCII, loading_path)); @@ -130,9 +137,16 @@ Result load_entries(const char * loading_path, Entry_List_s * list) continue; list->entries_count++; - list->entries = realloc(list->entries, list->entries_count * sizeof(Entry_s)); - if(list->entries == NULL) + Entry_s * new_list = realloc(list->entries, list->entries_count * sizeof(Entry_s)); + if(new_list == NULL) + { + free(list->entries); + list->entries = NULL; + res = -1; break; + } + else + list->entries = new_list; Entry_s * current_entry = &(list->entries[list->entries_count-1]); memset(current_entry, 0, sizeof(Entry_s)); @@ -142,27 +156,219 @@ Result load_entries(const char * loading_path, Entry_List_s * list) current_entry->is_zip = !strcmp(dir_entry.shortExt, "ZIP"); - ssize_t iconID = list->icon_id_start + list->entries_count - 1; - DEBUG("id: %u\n", iconID); - parse_smdh(current_entry, iconID, dir_entry.name); + parse_smdh(current_entry, dir_entry.name); } FSDIR_Close(dir_handle); sort_list(list); + list->mode = mode; return res; } -static u16 previous_path[0x106] = {0}; +static void small_load(Entry_List_s * current_list) +{ + DEBUG("small load\n"); + ssize_t * icons_ids = (ssize_t *)current_list->icons_ids; + ssize_t * assoc_entry_ids = (ssize_t *)current_list->assoc_entry_ids; + ssize_t id = current_list->texture_id_offset; + for(int i = 0; i < current_list->entries_count; i++, id++) + { + Entry_s current_entry = current_list->entries[i]; + load_smdh_icon(current_entry, id); + icons_ids[i] = id; + assoc_entry_ids[i] = i; + } +} + +static void first_load(Entry_List_s * current_list) +{ + DEBUG("first load\n"); + + ssize_t * above_icons_ids = current_list->icons_ids[ICONS_ABOVE]; + ssize_t * visible_icons_ids = current_list->icons_ids[ICONS_VISIBLE]; + ssize_t * under_icons_ids = current_list->icons_ids[ICONS_UNDER]; + + ssize_t * above_assoc_ids = current_list->assoc_entry_ids[ICONS_ABOVE]; + ssize_t * visible_assoc_ids = current_list->assoc_entry_ids[ICONS_VISIBLE]; + ssize_t * under_assoc_ids = current_list->assoc_entry_ids[ICONS_UNDER]; + + ssize_t id = current_list->texture_id_offset; + int starti = current_list->scroll; + + memset(visible_icons_ids, 0, ENTRIES_PER_SCREEN*sizeof(ssize_t)); + for(int i = starti; i < starti+ENTRIES_PER_SCREEN; i++, id++) + { + if(i >= current_list->entries_count) break; + + Entry_s current_entry = current_list->entries[i]; + load_smdh_icon(current_entry, id); + visible_icons_ids[i-starti] = id; + visible_assoc_ids[i-starti] = i; + } + + memset(above_icons_ids, 0, ENTRIES_PER_SCREEN*sizeof(ssize_t)); + starti -= ENTRIES_PER_SCREEN; + for(int i = starti; i < starti+ENTRIES_PER_SCREEN; i++, id++) + { + if(i >= current_list->entries_count) break; + int used_i = i; + if(i < 0) + used_i = current_list->entries_count + i; + + Entry_s current_entry = current_list->entries[used_i]; + load_smdh_icon(current_entry, id); + above_icons_ids[i-starti] = id; + above_assoc_ids[i-starti] = used_i; + } + + memset(under_icons_ids, 0, ENTRIES_PER_SCREEN*sizeof(ssize_t)); + starti += ENTRIES_PER_SCREEN*2; + for(int i = starti; i < starti+ENTRIES_PER_SCREEN; i++, id++) + { + int used_i = i; + if(i >= current_list->entries_count) + used_i = i - current_list->entries_count; + + Entry_s current_entry = current_list->entries[used_i]; + load_smdh_icon(current_entry, id); + under_icons_ids[i-starti] = id; + under_assoc_ids[i-starti] = used_i; + } +} + +void load_icons_first(Entry_List_s * current_list, bool silent) +{ + if(current_list == NULL || current_list->entries == NULL) return; + + if(!silent) + draw_install(INSTALL_LOADING_ICONS); + + if(current_list->entries_count <= ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT) + small_load(current_list); // if the list is one that doesnt need swapping, load everything at once + else + first_load(current_list); +} + +static void reverse(ssize_t a[], int sz) { + int i, j; + for (i = 0, j = sz; i < j; i++, j--) { + ssize_t tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } +} + +static void rotate(ssize_t array[], int size, int amt) { + if (amt < 0) + amt = size + amt; + reverse(array, size-amt-1); + reverse(array+size-amt, amt-1); + reverse(array, size-1); +} + +static void load_icons(Entry_List_s * current_list) +{ + if(current_list == NULL || current_list->entries == NULL) + return; + + if(current_list->entries_count <= ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT || current_list->previous_scroll == current_list->scroll) + return; // return if the list is one that doesnt need swapping, or if nothing changed + + #define SIGN(x) (x > 0 ? 1 : ((x < 0) ? -1 : 0)) + + int delta = current_list->scroll - current_list->previous_scroll; + if(abs(delta) >= current_list->entries_count - ENTRIES_PER_SCREEN*2) + delta = -SIGN(delta) * (current_list->entries_count - abs(delta)); + + int starti = current_list->scroll; + int endi = starti + abs(delta); + + if(delta < 0) + { + endi -= abs(delta) + 1; + starti += abs(delta) - 1; + } + + int ctr = 0; + Entry_s ** entries = calloc(abs(delta), sizeof(Entry_s *)); + ssize_t * ids = calloc(abs(delta), sizeof(ssize_t)); + + #define FIRST(arr) arr[0] + #define LAST(arr) arr[ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT - 1] + + ssize_t * icons_ids = (ssize_t *)current_list->icons_ids; + ssize_t * assoc_entry_ids = (ssize_t *)current_list->assoc_entry_ids; + + for(int i = starti; i != endi; i++, ctr++) + { + ssize_t id = 0; + int offset = i; + ssize_t * assoc = NULL; + + rotate(icons_ids, 3*ENTRIES_PER_SCREEN, -1*SIGN(delta)); + + if(delta > 0) + { + id = LAST(icons_ids); + assoc = &LAST(assoc_entry_ids); + offset += ENTRIES_PER_SCREEN*2 - delta; + } + else + { + id = FIRST(icons_ids); + assoc = &FIRST(assoc_entry_ids); + offset -= ENTRIES_PER_SCREEN; + i -= 2; //i-- twice to counter the i++, needed only for this case + } + + if(offset < 0) + offset += current_list->entries_count; + if(offset >= current_list->entries_count) + offset -= current_list->entries_count; + + entries[ctr] = ¤t_list->entries[offset]; + ids[ctr] = id; + *assoc = id; + } + + #undef FIRST + #undef LAST + #undef SIGN + + svcSleepThread(1e6); + for(int i = 0; i < abs(delta); i++) + load_smdh_icon(*entries[i], ids[i]); +} + +static bool loading_icons = false; +void load_icons_thread(void * void_arg) +{ + Thread_Arg_s * arg = (Thread_Arg_s *)void_arg; + Handle update_request = *(Handle *)arg->thread_arg[1]; + do + { + svcWaitSynchronization(update_request, U64_MAX); + svcClearEvent(update_request); + if(loading_icons) continue; + loading_icons = true; + volatile Entry_List_s * current_list = *(volatile Entry_List_s **)arg->thread_arg[0]; + load_icons((Entry_List_s *)current_list); + loading_icons = false; + } + while(arg->run_thread); +} + +static u16 previous_path_preview[0x106] = {0}; bool load_preview(Entry_List_s list, int * preview_offset) { if(list.entries == NULL) return false; Entry_s entry = list.entries[list.selected_entry]; - if(!memcmp(&previous_path, &entry.path, 0x106*sizeof(u16))) return true; + if(!memcmp(&previous_path_preview, &entry.path, 0x106*sizeof(u16))) return true; char *preview_buffer = NULL; u64 size = load_data("/preview.png", entry, &preview_buffer); @@ -178,7 +384,7 @@ bool load_preview(Entry_List_s list, int * preview_offset) u8 * image = NULL; unsigned int width = 0, height = 0; - if((lodepng_decode32(&image, &width, &height, (u8*)preview_buffer, size)) == 0) // no error + if((lodepng_decode32(&image, &width, &height, (u8*)preview_buffer, size)) == 0) // no error { for(u32 i = 0; i < width; i++) { @@ -190,7 +396,7 @@ bool load_preview(Entry_List_s list, int * preview_offset) } // mark the new preview as loaded for optimisation - memcpy(&previous_path, &entry.path, 0x106*sizeof(u16)); + memcpy(&previous_path_preview, &entry.path, 0x106*sizeof(u16)); // free the previously loaded preview. wont do anything if there wasnt one pp2d_free_texture(TEXTURE_PREVIEW); diff --git a/source/main.c b/source/main.c index 5490ad9..38c2cc5 100644 --- a/source/main.c +++ b/source/main.c @@ -37,6 +37,17 @@ #define FASTSCROLL_WAIT 1e8 static bool homebrew = false; +static bool installed_themes = false; + +static Thread iconLoadingThread = {0}; +static Thread_Arg_s iconLoadingThread_arg = {0}; +static Handle update_icons_handle; + +static Thread installCheckThreads[MODE_AMOUNT] = {0}; +static Thread_Arg_s installCheckThreads_arg[MODE_AMOUNT] = {0}; + +static Entry_List_s lists[MODE_AMOUNT] = {0}; + int __stacksize__ = 64 * 1024; Result archive_result; @@ -45,7 +56,7 @@ const char * main_paths[MODE_AMOUNT] = { "/Splashes/", }; -void init_services(void) +static void init_services(void) { consoleDebugInit(debugDevice_SVC); cfguInit(); @@ -61,7 +72,7 @@ void init_services(void) } } -void exit_services(void) +static void exit_services(void) { close_archives(); cfguExit(); @@ -70,56 +81,221 @@ void exit_services(void) acExit(); } +static void stop_install_check(void) +{ + for(int i = 0; i < MODE_AMOUNT; i++) + { + if(installCheckThreads_arg[i].run_thread) + { + installCheckThreads_arg[i].run_thread = false; + threadJoin(installCheckThreads[i], U64_MAX); + } + } +} + +static void exit_thread(void) +{ + if(iconLoadingThread_arg.run_thread) + { + DEBUG("exiting thread\n"); + iconLoadingThread_arg.run_thread = false; + svcSignalEvent(update_icons_handle); + threadJoin(iconLoadingThread, U64_MAX); + threadFree(iconLoadingThread); + } +} + void exit_function(void) { + stop_install_check(); + for(int i = 0; i < MODE_AMOUNT; i++) + { + Entry_List_s * current_list = &lists[i]; + free(current_list->entries); + current_list->entries = NULL; + } + + exit_thread(); + svcCloseHandle(update_icons_handle); exit_screens(); exit_services(); - if(homebrew) + if(installed_themes) { - APT_HardwareResetAsync(); - } - else - { - srvPublishToSubscriber(0x202, 0); + if(homebrew) + { + APT_HardwareResetAsync(); + } + else + { + srvPublishToSubscriber(0x202, 0); + } } } -void change_selected(Entry_List_s * list, int change_value) +static SwkbdCallbackResult jump_menu_callback(void* entries_count, const char** ppMessage, const char* text, size_t textlen) { + int typed_value = atoi(text); + if(typed_value > *(int*)entries_count) + { + *ppMessage = "The new position has to be\nsmaller or equal to the\nnumber of entries!"; + return SWKBD_CALLBACK_CONTINUE; + } + else if(typed_value == 0) + { + *ppMessage = "The new position has to\nbe positive!"; + return SWKBD_CALLBACK_CONTINUE; + } + return SWKBD_CALLBACK_OK; +} + +static void jump_menu(Entry_List_s * list) +{ + if(list == NULL) return; + + char numbuf[64] = {0}; + + SwkbdState swkbd; + + sprintf(numbuf, "%i", list->entries_count); + int max_chars = strlen(numbuf); + swkbdInit(&swkbd, SWKBD_TYPE_NUMPAD, 2, max_chars); + + sprintf(numbuf, "%i", list->selected_entry); + swkbdSetInitialText(&swkbd, numbuf); + + sprintf(numbuf, "Where do you want to jump to?\nMay cause icons to reload."); + swkbdSetHintText(&swkbd, numbuf); + + swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false); + swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Jump", true); + swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, 0, max_chars); + swkbdSetFilterCallback(&swkbd, jump_menu_callback, &list->entries_count); + + memset(numbuf, 0, sizeof(numbuf)); + SwkbdButton button = swkbdInputText(&swkbd, numbuf, sizeof(numbuf)); + if(button == SWKBD_BUTTON_CONFIRM) + { + list->selected_entry = atoi(numbuf) - 1; + list->scroll = list->selected_entry; + if(list->scroll >= list->entries_count - ENTRIES_PER_SCREEN) + list->scroll = list->entries_count - ENTRIES_PER_SCREEN - 1; + } +} + +static void handle_scrolling(Entry_List_s * list) +{ + // Scroll the menu up or down if the selected theme is out of its bounds + //---------------------------------------------------------------- + if(list->entries_count > ENTRIES_PER_SCREEN) + { + if(list->entries_count > ENTRIES_PER_SCREEN*2 && list->previous_scroll < ENTRIES_PER_SCREEN && list->selected_entry >= list->entries_count - ENTRIES_PER_SCREEN) + { + list->scroll = list->entries_count - ENTRIES_PER_SCREEN; + } + else if(list->entries_count > ENTRIES_PER_SCREEN*2 && list->selected_entry < ENTRIES_PER_SCREEN && list->previous_selected >= list->entries_count - ENTRIES_PER_SCREEN) + { + list->scroll = 0; + } + else if(list->selected_entry == list->previous_selected+1 && list->selected_entry == list->scroll+ENTRIES_PER_SCREEN) + { + list->scroll++; + } + else if(list->selected_entry == list->previous_selected-1 && list->selected_entry == list->scroll-1) + { + list->scroll--; + } + else if(list->selected_entry == list->previous_selected+ENTRIES_PER_SCREEN || list->selected_entry >= list->scroll + ENTRIES_PER_SCREEN) + { + list->scroll += ENTRIES_PER_SCREEN; + } + else if(list->selected_entry == list->previous_selected-ENTRIES_PER_SCREEN || list->selected_entry < list->scroll) + { + list->scroll -= ENTRIES_PER_SCREEN; + } + + if(list->scroll < 0) + list->scroll = 0; + if(list->scroll > list->entries_count - ENTRIES_PER_SCREEN) + list->scroll = list->entries_count - ENTRIES_PER_SCREEN; + } + //---------------------------------------------------------------- +} + +static void change_selected(Entry_List_s * list, int change_value) +{ + if(abs(change_value) >= list->entries_count) return; + list->selected_entry += change_value; - if(change_value < 0 && list->selected_entry < 0) - list->selected_entry = list->entries_count - 1; - else - list->selected_entry %= list->entries_count; + if(list->selected_entry < 0) + list->selected_entry += list->entries_count; + list->selected_entry %= list->entries_count; } -void load_lists(Entry_List_s * lists) +static void start_thread(void) { - DEBUG("origin: %u\n", TEXTURE_ICON); + if(iconLoadingThread_arg.run_thread) + { + DEBUG("starting thread\n"); + iconLoadingThread = threadCreate(load_icons_thread, &iconLoadingThread_arg, __stacksize__, 0x38, -2, false); + } +} - ssize_t last_icon_id = TEXTURE_ICON; +static void load_lists(Entry_List_s * lists) +{ + ssize_t texture_id_offset = TEXTURE_ICON; + + stop_install_check(); + exit_thread(); for(int i = 0; i < MODE_AMOUNT; i++) { + InstallType loading_screen = INSTALL_NONE; + if(i == MODE_THEMES) + loading_screen = INSTALL_LOADING_THEMES; + else if(i == MODE_SPLASHES) + loading_screen = INSTALL_LOADING_SPLASHES; + + draw_install(loading_screen); + Entry_List_s * current_list = &lists[i]; - last_icon_id += current_list->entries_count; free(current_list->entries); memset(current_list, 0, sizeof(Entry_List_s)); + Result res = load_entries(main_paths[i], current_list, i); + if(R_SUCCEEDED(res)) + { + if(current_list->entries_count > ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT) + iconLoadingThread_arg.run_thread = true; + + DEBUG("total: %i\n", current_list->entries_count); + + current_list->texture_id_offset = texture_id_offset; + load_icons_first(current_list, false); + + texture_id_offset += ENTRIES_PER_SCREEN*ICONS_OFFSET_AMOUNT; + + void (*install_check_function)(void*) = NULL; + if(i == MODE_THEMES) + install_check_function = themes_check_installed; + else if(i == MODE_SPLASHES) + install_check_function = splash_check_installed; + + Thread_Arg_s * current_arg = &installCheckThreads_arg[i]; + current_arg->run_thread = true; + current_arg->thread_arg = (void**)current_list; + + installCheckThreads[i] = threadCreate(install_check_function, current_arg, __stacksize__, 0x3f, -2, true); + svcSleepThread(1e8); + } } - pp2d_free_texture(last_icon_id); + start_thread(); +} - DEBUG("max: %u\n", last_icon_id); - - ssize_t icon_id_start = TEXTURE_ICON; - for(int i = 0; i < MODE_AMOUNT; i++) - { - Entry_List_s * current_list = &lists[i]; - current_list->icon_id_start = icon_id_start; - load_entries(main_paths[i], current_list); - icon_id_start += current_list->entries_count; - } - - DEBUG("end: %u\n", icon_id_start); +static void toggle_shuffle(Entry_List_s * list) +{ + Entry_s * current_entry = &list->entries[list->selected_entry]; + if(current_entry->in_shuffle) list->shuffle_count--; + else list->shuffle_count++; + current_entry->in_shuffle = !current_entry->in_shuffle; } int main(void) @@ -128,7 +304,16 @@ int main(void) init_services(); init_screens(); - Entry_List_s lists[MODE_AMOUNT] = {0}; + svcCreateEvent(&update_icons_handle, RESET_ONESHOT); + + static Entry_List_s * current_list = NULL; + void * iconLoadingThread_args_void[] = { + ¤t_list, + &update_icons_handle, + }; + iconLoadingThread_arg.thread_arg = iconLoadingThread_args_void; + iconLoadingThread_arg.run_thread = false; + load_lists(lists); EntryMode current_mode = MODE_THEMES; @@ -154,51 +339,70 @@ int main(void) u32 kHeld = hidKeysHeld(); u32 kUp = hidKeysUp(); - Entry_List_s * current_list = &lists[current_mode]; + current_list = &lists[current_mode]; + + Instructions_s instructions = normal_instructions[current_mode]; + if(install_mode) + instructions = install_instructions; if(qr_mode) take_picture(); else if(preview_mode) draw_preview(preview_offset); else { - draw_interface(current_list, current_mode); - if(install_mode) - draw_instructions(install_instructions); - else - draw_instructions(normal_instructions[current_mode]); + handle_scrolling(current_list); + svcSignalEvent(update_icons_handle); + svcSleepThread(1e6); + + current_list->previous_scroll = current_list->scroll; + current_list->previous_selected = current_list->selected_entry; + + draw_interface(current_list, instructions); + svcSleepThread(1e7); } pp2d_end_draw(); if(kDown & KEY_START) break; - if(!install_mode) { if(!preview_mode && !qr_mode && kDown & KEY_L) //toggle between splashes and themes { + switch_mode: current_mode++; current_mode %= MODE_AMOUNT; continue; } else if(!preview_mode && kDown & KEY_R) //toggle QR mode { - u32 out; - ACU_GetWifiStatus(&out); - if(out) + toggle_qr: + if(R_SUCCEEDED(camInit())) { - qr_mode = !qr_mode; - if(qr_mode) - init_qr(); + camExit(); + u32 out; + ACU_GetWifiStatus(&out); + if(out) + { + qr_mode = !qr_mode; + if(qr_mode) + init_qr(); + else + exit_qr(); + } else - exit_qr(); + { + throw_error("Please connect to Wi-Fi before scanning QRs", ERROR_LEVEL_WARNING); + } } else { - throw_error("Please connect to Wi-Fi before scanning QR", ERROR_LEVEL_WARNING); + throw_error("Your camera seems to have a problem, unable to scan QRs.", ERROR_LEVEL_WARNING); } + continue; } else if(!qr_mode && kDown & KEY_Y) //toggle preview mode { + toggle_preview: if(!preview_mode) preview_mode = load_preview(*current_list, &preview_offset); else @@ -217,6 +421,17 @@ int main(void) load_lists(lists); continue; } + else if(qr_mode && kDown & KEY_B) + { + exit_qr(); + qr_mode = false; + continue; + } + else if(preview_mode && kDown & (KEY_B | KEY_TOUCH)) + { + preview_mode = false; + continue; + } } if(qr_mode || preview_mode || current_list->entries == NULL) @@ -234,17 +449,50 @@ int main(void) if((kDown | kHeld) & KEY_DLEFT) { draw_install(INSTALL_BGM); - bgm_install(*current_entry); + if(R_SUCCEEDED(bgm_install(*current_entry))) + { + for(int i = 0; i < current_list->entries_count; i++) + { + Entry_s * theme = ¤t_list->entries[i]; + if(theme == current_entry) + theme->installed = true; + else + theme->installed = false; + } + installed_themes = true; + } } else if((kDown | kHeld) & KEY_DUP) { draw_install(INSTALL_SINGLE); theme_install(*current_entry); + { + for(int i = 0; i < current_list->entries_count; i++) + { + Entry_s * theme = ¤t_list->entries[i]; + if(theme == current_entry) + theme->installed = true; + else + theme->installed = false; + } + installed_themes = true; + } } else if((kDown | kHeld) & KEY_DRIGHT) { draw_install(INSTALL_NO_BGM); no_bgm_install(*current_entry); + { + for(int i = 0; i < current_list->entries_count; i++) + { + Entry_s * theme = ¤t_list->entries[i]; + if(theme == current_entry) + theme->installed = true; + else + theme->installed = false; + } + installed_themes = true; + } } else if((kDown | kHeld) & KEY_DDOWN) { @@ -254,14 +502,28 @@ int main(void) } else if(current_list->shuffle_count == 0) { - throw_error("You dont have any themes selected.", ERROR_LEVEL_WARNING); + throw_error("You don't have any themes selected.", ERROR_LEVEL_WARNING); } else { draw_install(INSTALL_SHUFFLE); Result res = shuffle_install(*current_list); if(R_FAILED(res)) DEBUG("shuffle install result: %lx\n", res); - else current_list->shuffle_count = 0; + else + { + for(int i = 0; i < current_list->entries_count; i++) + { + Entry_s * theme = ¤t_list->entries[i]; + if(theme->in_shuffle) + { + theme->in_shuffle = false; + theme->installed = true; + } + else theme->installed = false; + } + current_list->shuffle_count = 0; + installed_themes = true; + } } } } @@ -290,12 +552,10 @@ int main(void) switch(current_mode) { case MODE_THEMES: - if(current_entry->in_shuffle) current_list->shuffle_count--; - else current_list->shuffle_count++; - current_entry->in_shuffle = !current_entry->in_shuffle; + toggle_shuffle(current_list); break; case MODE_SPLASHES: - if(draw_confirm("Are you sure you would like to delete\nthe installed splash?", current_list, current_mode)) + if(draw_confirm("Are you sure you would like to delete\nthe installed splash?", current_list)) { draw_install(INSTALL_SPLASH_DELETE); splash_delete(); @@ -310,8 +570,10 @@ int main(void) switch(current_mode) { case MODE_THEMES: + load_icons_first(current_list, false); break; case MODE_SPLASHES: + load_icons_first(current_list, false); break; default: break; @@ -319,7 +581,7 @@ int main(void) } else if(kDown & KEY_SELECT) { - if(draw_confirm("Are you sure you would like to delete this?", current_list, current_mode)) + if(draw_confirm("Are you sure you would like to delete this?", current_list)) { draw_install(INSTALL_ENTRY_DELETE); delete_entry(*current_entry); @@ -340,10 +602,12 @@ int main(void) else if(kDown & KEY_LEFT) { change_selected(current_list, -ENTRIES_PER_SCREEN); + load_icons_first(current_list, true); } else if(kDown & KEY_RIGHT) { change_selected(current_list, ENTRIES_PER_SCREEN); + load_icons_first(current_list, true); } // Fast scroll using circle pad @@ -367,6 +631,81 @@ int main(void) change_selected(current_list, ENTRIES_PER_SCREEN); svcSleepThread(FASTSCROLL_WAIT); } + + // Movement using the touchscreen + if((kDown | kHeld) & KEY_TOUCH) + { + touchPosition touch = {0}; + hidTouchRead(&touch); + + u16 x = touch.px; + u16 y = touch.py; + + u16 arrowStartX = 152; + u16 arrowEndX = arrowStartX+16; + + #define BETWEEN(min, x, max) (min < x && x < max) + + if(kDown & KEY_TOUCH) + { + if(y < 24) + { + if(BETWEEN(arrowStartX, x, arrowEndX) && current_list->scroll > 0) + { + change_selected(current_list, -ENTRIES_PER_SCREEN); + load_icons_first(current_list, true); + } + else if(BETWEEN(320-24, x, 320)) + { + goto switch_mode; + } + else if(BETWEEN(320-48, x, 320-24)) + { + goto toggle_qr; + } + else if(BETWEEN(320-72, x, 320-48)) + { + goto toggle_preview; + } + else if(BETWEEN(320-96, x, 320-72)) + { + load_icons_first(current_list, false); + } + else if(BETWEEN(320-120, x, 320-96) && current_mode == MODE_THEMES) + { + toggle_shuffle(current_list); + } + } + else if(y >= 216) + { + if(BETWEEN(arrowStartX, x, arrowEndX) && current_list->scroll < current_list->entries_count - ENTRIES_PER_SCREEN) + { + change_selected(current_list, ENTRIES_PER_SCREEN); + load_icons_first(current_list, true); + } + else if(BETWEEN(176, x, 320)) + { + jump_menu(current_list); + } + } + } + else + { + if(BETWEEN(24, y, 216)) + { + for(int i = 0; i < ENTRIES_PER_SCREEN; i++) + { + u16 miny = 24 + 48*i; + u16 maxy = miny + 48; + if(BETWEEN(miny, y, maxy) && current_list->scroll + i < current_list->entries_count) + { + current_list->selected_entry = current_list->scroll + i; + break; + } + } + } + } + } } exit_function(); diff --git a/source/splashes.c b/source/splashes.c index 0782287..f8cc3f6 100644 --- a/source/splashes.c +++ b/source/splashes.c @@ -28,7 +28,7 @@ #include "fs.h" #include "draw.h" -void splash_delete(void) +void splash_delete(void) { remove("/luma/splash.bin"); remove("/luma/splashbottom.bin"); @@ -56,4 +56,62 @@ void splash_install(Entry_s splash) throw_error("WARNING: Splashes are disabled in Luma Config", ERROR_LEVEL_WARNING); } } +} + +void splash_check_installed(void * void_arg) +{ + Thread_Arg_s * arg = (Thread_Arg_s *)void_arg; + Entry_List_s * list = (Entry_List_s *)arg->thread_arg; + if(list == NULL || list->entries == NULL) return; + + #ifndef CITRA_MODE + char * top_buf = NULL; + u32 top_size = file_to_buf(fsMakePath(PATH_ASCII, "/luma/splash.bin"), ArchiveSD, &top_buf); + char * bottom_buf = NULL; + u32 bottom_size = file_to_buf(fsMakePath(PATH_ASCII, "/luma/splashbottom.bin"), ArchiveSD, &bottom_buf); + + if(!top_size && !bottom_size) + { + free(top_buf); + free(bottom_buf); + return; + } + + #define HASH_SIZE_BYTES 256/8 + u8 top_hash[HASH_SIZE_BYTES] = {0}; + FSUSER_UpdateSha256Context(top_buf, top_size, top_hash); + free(top_buf); + top_buf = NULL; + u8 bottom_hash[HASH_SIZE_BYTES] = {0}; + FSUSER_UpdateSha256Context(bottom_buf, bottom_size, bottom_hash); + free(bottom_buf); + bottom_buf = NULL; + + for(int i = 0; i < list->entries_count && arg->run_thread; i++) + { + Entry_s * splash = &list->entries[i]; + top_size = load_data("/splash.bin", *splash, &top_buf); + bottom_size = load_data("/splashbottom.bin", *splash, &bottom_buf); + + if(!top_size && !bottom_size) + { + continue; + } + + u8 splash_top_hash[HASH_SIZE_BYTES] = {0}; + FSUSER_UpdateSha256Context(top_buf, top_size, splash_top_hash); + free(top_buf); + top_buf = NULL; + u8 splash_bottom_hash[HASH_SIZE_BYTES] = {0}; + FSUSER_UpdateSha256Context(bottom_buf, bottom_size, splash_bottom_hash); + free(bottom_buf); + bottom_buf = NULL; + + if(!memcmp(splash_bottom_hash, bottom_hash, HASH_SIZE_BYTES) && !memcmp(splash_top_hash, top_hash, HASH_SIZE_BYTES)) + { + splash->installed = true; + break; + } + } + #endif } \ No newline at end of file diff --git a/source/themes.c b/source/themes.c index d846d45..b95495e 100644 --- a/source/themes.c +++ b/source/themes.c @@ -44,6 +44,12 @@ static Result install_theme_internal(Entry_List_s themes, int installmode) if(installmode & THEME_INSTALL_SHUFFLE) { + if(themes.shuffle_count == 0) + { + DEBUG("no themes selected for shuffle\n"); + return MAKERESULT(RL_USAGE, RS_INVALIDARG, RM_COMMON, RD_INVALID_SELECTION); + } + if(themes.shuffle_count > MAX_SHUFFLE_THEMES) { DEBUG("too many themes selected for shuffle\n"); @@ -99,7 +105,6 @@ static Result install_theme_internal(Entry_List_s themes, int installmode) free(music); } - current_theme->in_shuffle = false; shuffle_count++; } } @@ -142,7 +147,6 @@ static Result install_theme_internal(Entry_List_s themes, int installmode) if(installmode & THEME_INSTALL_BGM) { music_size = load_data("/bgm.bcstm", current_theme, &music); - if(music_size > BGM_MAX_SIZE) { free(music); @@ -152,6 +156,7 @@ static Result install_theme_internal(Entry_List_s themes, int installmode) res = buf_to_file(music_size, "/BgmCache.bin", ArchiveThemeExt, music); free(music); + if(R_FAILED(res)) return res; } } @@ -221,7 +226,7 @@ static Result install_theme_internal(Entry_List_s themes, int installmode) return 0; } -Result theme_install(Entry_s theme) +inline Result theme_install(Entry_s theme) { Entry_List_s list = {0}; list.entries_count = 1; @@ -230,7 +235,7 @@ Result theme_install(Entry_s theme) return install_theme_internal(list, THEME_INSTALL_BODY | THEME_INSTALL_BGM); } -Result bgm_install(Entry_s theme) +inline Result bgm_install(Entry_s theme) { Entry_List_s list = {0}; list.entries_count = 1; @@ -239,7 +244,7 @@ Result bgm_install(Entry_s theme) return install_theme_internal(list, THEME_INSTALL_BGM); } -Result no_bgm_install(Entry_s theme) +inline Result no_bgm_install(Entry_s theme) { Entry_List_s list = {0}; list.entries_count = 1; @@ -248,7 +253,84 @@ Result no_bgm_install(Entry_s theme) return install_theme_internal(list, THEME_INSTALL_BODY); } -Result shuffle_install(Entry_List_s themes) +inline Result shuffle_install(Entry_List_s themes) { return install_theme_internal(themes, THEME_INSTALL_SHUFFLE | THEME_INSTALL_BODY | THEME_INSTALL_BGM); } + +void themes_check_installed(void * void_arg) +{ + Thread_Arg_s * arg = (Thread_Arg_s *)void_arg; + Entry_List_s * list = (Entry_List_s *)arg->thread_arg; + if(list == NULL || list->entries == NULL) return; + + #ifndef CITRA_MODE + char* savedata_buf = NULL; + u32 savedata_size = file_to_buf(fsMakePath(PATH_ASCII, "/SaveData.dat"), ArchiveHomeExt, &savedata_buf); + if(!savedata_size) return; + SaveData_dat_s* savedata = (SaveData_dat_s*)savedata_buf; + bool shuffle = savedata->shuffle; + free(savedata_buf); + + #define HASH_SIZE_BYTES 256/8 + u8 body_hash[MAX_SHUFFLE_THEMES][HASH_SIZE_BYTES]; + memset(body_hash, 0, MAX_SHUFFLE_THEMES*HASH_SIZE_BYTES); + + char* thememanage_buf = NULL; + u32 theme_manage_size = file_to_buf(fsMakePath(PATH_ASCII, "/ThemeManage.bin"), ArchiveThemeExt, &thememanage_buf); + if(!theme_manage_size) return; + ThemeManage_bin_s * theme_manage = (ThemeManage_bin_s *)thememanage_buf; + + u32 single_body_size = theme_manage->body_size; + u32 shuffle_body_sizes[MAX_SHUFFLE_THEMES] = {0}; + memcpy(shuffle_body_sizes, theme_manage->shuffle_body_sizes, sizeof(u32)*MAX_SHUFFLE_THEMES); + free(thememanage_buf); + + if(shuffle) + { + char * body_buf = NULL; + u32 body_cache_size = file_to_buf(fsMakePath(PATH_ASCII, "/BodyCache_rd.bin"), ArchiveThemeExt, &body_buf); + if(!body_cache_size) return; + + for(int i = 0; i < MAX_SHUFFLE_THEMES; i++) + { + FSUSER_UpdateSha256Context(body_buf + BODY_CACHE_SIZE*i, shuffle_body_sizes[i], body_hash[i]); + } + + free(body_buf); + } + else + { + char * body_buf = NULL; + u32 body_size = file_to_buf(fsMakePath(PATH_ASCII, "/BodyCache.bin"), ArchiveThemeExt, &body_buf); + if(!body_size) return; + + u8 * hash = body_hash[0]; + FSUSER_UpdateSha256Context(body_buf, single_body_size, hash); + free(body_buf); + } + + int total_installed = 0; + for(int i = 0; i < list->entries_count && total_installed < MAX_SHUFFLE_THEMES && arg->run_thread; i++) + { + Entry_s * theme = &list->entries[i]; + char * theme_body = NULL; + u32 theme_body_size = load_data("/body_LZ.bin", *theme, &theme_body); + if(!theme_body_size) return; + + u8 theme_body_hash[HASH_SIZE_BYTES]; + FSUSER_UpdateSha256Context(theme_body, theme_body_size, theme_body_hash); + free(theme_body); + + for(int j = 0; j < MAX_SHUFFLE_THEMES; j++) + { + if(!memcmp(body_hash[j], theme_body_hash, HASH_SIZE_BYTES)) + { + theme->installed = true; + total_installed++; + if(!shuffle) break; //only need to check the first if the installed theme inst shuffle + } + } + } + #endif +}