diff --git a/include/badges.h b/include/badges.h index 14c8907..5563c81 100644 --- a/include/badges.h +++ b/include/badges.h @@ -37,6 +37,6 @@ #define BADGE_MNG_SIZE 0xD4A8 Result install_badges(void); -Result dump_badge_extdata(void); +Result extract_badges(void); #endif \ No newline at end of file diff --git a/include/conversion.h b/include/conversion.h index 15c69c0..059e00c 100644 --- a/include/conversion.h +++ b/include/conversion.h @@ -6,5 +6,6 @@ size_t bin_to_abgr(char ** bufp, size_t size); size_t png_to_abgr(char ** bufp, size_t size, u32 *height); int pngToRGB565(char *png_buf, u64 fileSize, u16 *rgb_buf_64x64, u8 *alpha_buf_64x64, u16 *rgb_buf_32x32, u8 *alpha_buf_32x32, bool set_icon); +int rgb565ToPngFile(char *filename, u16 *rgb_buf, u8 *alpha_buf, int width, int height); #endif diff --git a/include/draw.h b/include/draw.h index 95c0568..1627e63 100644 --- a/include/draw.h +++ b/include/draw.h @@ -58,6 +58,7 @@ typedef enum InstallType_e { INSTALL_DUMPING_THEME, INSTALL_DUMPING_ALL_THEMES, + INSTALL_DUMPING_BADGES, INSTALL_BADGES, INSTALL_NONE, @@ -89,6 +90,7 @@ typedef enum { TEXT_INSTALL_DUMPING_THEME, TEXT_INSTALL_DUMPING_ALL_THEMES, + TEXT_INSTALL_DUMPING_BADGES, TEXT_INSTALL_BADGES, // Other text diff --git a/include/ui_strings.h b/include/ui_strings.h index 9054e14..5f90505 100644 --- a/include/ui_strings.h +++ b/include/ui_strings.h @@ -79,6 +79,7 @@ typedef struct { const char *download_bgm; const char *dump_single; const char *dump_all_official; + const char *dump_badges; const char *install_badges; float start_pos; const char *shuffle; diff --git a/source/badges.c b/source/badges.c index 4aeb41c..9b0073b 100644 --- a/source/badges.c +++ b/source/badges.c @@ -264,7 +264,7 @@ int install_badge_dir(FS_DirectoryEntry set_dir, int *badge_count, int set_id) u32 total_count = 0xFFFF * badges_in_set; for (int i = 0; i < 16; ++i) { - FSFILE_Write(badgeDataHandle, NULL, set_index * 0x8A0 + i * 0x8A, set_dir.name, strulen(set_dir.name, 0x45) * 2, 0); + FSFILE_Write(badgeDataHandle, NULL, set_index * 0x8A0 + i * 0x8A, set_dir.name, strulen(set_dir.name, 0x45) * 2 + 2, 0); } badgeMngBuffer[0x3D8 + set_index/8] |= 0 << (set_index % 8); @@ -361,6 +361,163 @@ Result backup_badges(void) return 0; } +typedef struct set_node_s { + u32 set_id; + u32 set_index; + struct set_node_s *next; +} SetNode; + +u32 get_set_index(SetNode *head, u32 set_id) +{ + SetNode *cursor = head; + while (cursor != NULL) + { + if (cursor->set_id == set_id) + return cursor->set_index; + cursor = cursor->next; + } + return 0xFFFFFFFF; +} + +void free_list(SetNode *head) +{ + SetNode *cursor = head; + while (cursor) + { + SetNode *next = cursor->next; + free(cursor); + cursor = next; + } +} + +SetNode * extract_sets(char *badgeMngBuffer, Handle backupDataHandle) +{ + u32 setCount = *((u32 *) (badgeMngBuffer + 0x4)); + if (setCount == 0) return NULL; + + SetNode *head = calloc(1, sizeof(SetNode)); + SetNode *cursor = head; + + u16 *icon_rgb_buf = malloc(64 * 64 * 2); + u8 *icon_alpha_buf = malloc(64 * 64 * 0.5); + + for (u32 i = 0; i < setCount; ++i) + { + memcpy(&(cursor->set_id), &badgeMngBuffer[0xA028 + 0x30 * i + 0x10], 4); + memcpy(&(cursor->set_index), &badgeMngBuffer[0xA028 + 0x30 * i + 0x14], 4); + + if (cursor->set_id != 0xEFBE) // 0xEFBE is GYTB Set ID; GYTB doesn't properly create sets, so skip + { + u16 utf16SetName[0x46] = {0}; + FSFILE_Read(backupDataHandle, NULL, cursor->set_index * 16 * 0x8A, utf16SetName, 0x8A); + u16 set_path[256] = {0}; + struacat(set_path, "/3ds/" APP_TITLE "/BadgeBackups/"); + strucat(set_path, utf16SetName); + FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_UTF16, set_path), FS_ATTRIBUTE_DIRECTORY); + memset(icon_alpha_buf, 255, 64 * 64 * 0.5); + FSFILE_Read(backupDataHandle, NULL, 0x250F80 + cursor->set_index * 0x2000, icon_rgb_buf, 0x2000); + char filename[256] = {0}; + utf16_to_utf8((u8 *) filename, set_path, 256); + strcat(filename, "/_seticon.png"); + rgb565ToPngFile(filename, icon_rgb_buf, icon_alpha_buf, 48, 48); + } + + cursor->next = calloc(1, sizeof(SetNode)); + cursor = cursor->next; + } + + free(icon_rgb_buf); + free(icon_alpha_buf); + + return head; +} + +Result extract_badges(void) +{ + DEBUG("Dumping installed badges...\n"); + char *badgeMngBuffer = malloc(BADGE_MNG_SIZE); + u32 size = file_to_buf(fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), ArchiveBadgeExt, &badgeMngBuffer); + DEBUG("%lu bytes read\n", size); + + Result res = 0; + SetNode *head; + Handle backupDataHandle; + u16 *badge_rgb_buf = malloc(64 * 64 * 2); + u8 *badge_alpha_buf = malloc(64 * 64 * 0.5); + u32 badge_count = 0; + memcpy(&badge_count, badgeMngBuffer + 0x8, 4); + DEBUG("%lu badges found\n", badge_count); + if (badge_count > 0) + { + res = FSUSER_OpenFile(&backupDataHandle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeData.dat"), FS_OPEN_READ, 0); + if (R_FAILED(res)) + { + throw_error(language.badges.extdata_locked, ERROR_LEVEL_WARNING); + DEBUG("backupDataHandle open failed\n"); + return -1; + } + head = extract_sets(badgeMngBuffer, backupDataHandle); + } else { + return 0; + } + for (u32 i = 0; i < badge_count; ++i) + { + u32 badgeId; + memcpy(&badgeId, badgeMngBuffer + 0x3E8 + i * 0x28 + 0x4, 4); + u32 badgeSetId; + memcpy(&badgeSetId, badgeMngBuffer + 0x3E8 + i * 0x28 + 0x8, 4); + u16 badgeSubId; + memcpy(&badgeSubId, badgeMngBuffer + 0x3E8 + i * 0x28 + 0xE, 2); + u32 shortcut; + memcpy(&shortcut, badgeMngBuffer + 0x3E8 + i * 0x28 + 0x18, 4); + + char filename[512] = {0}; + + u16 utf16Name[0x46] = {0}; + FSFILE_Read(backupDataHandle, NULL, 0x35E80 + i * 16 * 0x8A, utf16Name, 0x8A); + char utf8Name[256] = {0}; + res = utf16_to_utf8((u8 *) utf8Name, utf16Name, 256); + + char dir[256] = "/3ds/" APP_TITLE "/BadgeBackups/"; + if (badgeSetId != 0xEFBE) // 0xEFBE is GYTB Set ID; GYTB doesn't properly create sets, so skip + { + u32 set_index = get_set_index(head, badgeSetId); + if (set_index == 0xFFFFFFFF) { + sprintf(dir, "/3ds" APP_TITLE "/BadgeBackups/Unknown Set"); + } else + { + u16 utf16SetName[0x46] = {0}; + FSFILE_Read(backupDataHandle, NULL, set_index * 16 * 0x8A, utf16SetName, 0x8A); + char utf8SetName[128] = {0}; + res = utf16_to_utf8((u8 *) utf8SetName, utf16SetName, 128); + DEBUG("UTF-8 Set Name: %s; ID: %lx\n", utf8SetName, badgeSetId); + sprintf(dir, "/3ds/" APP_TITLE "/BadgeBackups/%s", utf8SetName); + } + } + + if (shortcut == 0xFFFFFFFF) + { + sprintf(filename, "%s/%s.%lx.%x.png", dir, utf8Name, badgeId, badgeSubId); + } else + { + sprintf(filename, "%s/%s.%08lx.%lx.%x.png", dir, utf8Name, shortcut, badgeId, badgeSubId); + } + DEBUG("Dump filename: %s\n", filename); + + FSFILE_Read(backupDataHandle, NULL, 0x318F80 + i * 0x2800, badge_rgb_buf, 0x2000); + FSFILE_Read(backupDataHandle, NULL, 0x318F80 + i * 0x2800 + 0x2000, badge_alpha_buf, 0x800); + rgb565ToPngFile(filename, badge_rgb_buf, badge_alpha_buf, 64, 64); + draw_loading_bar(i + 1, badge_count, INSTALL_DUMPING_BADGES); + } + + free(badgeMngBuffer); + free(badge_rgb_buf); + free(badge_alpha_buf); + free_list(head); + + return res; +} + Result install_badges(void) { Handle handle = 0; @@ -368,14 +525,6 @@ Result install_badges(void) Result res = 0; draw_loading_bar(0, 1, INSTALL_BADGES); - DEBUG("Backing up badges\n"); - res = backup_badges(); - if (R_FAILED(res)) - { - DEBUG("Backup badges failed\n"); - return res; - } - DEBUG("Initializing ACT\n"); res = actInit(); if (R_FAILED(res)) diff --git a/source/conversion.c b/source/conversion.c index 94f0167..3d17e98 100644 --- a/source/conversion.c +++ b/source/conversion.c @@ -3,6 +3,66 @@ #include +// don't be fooled - this function always expects 64x64 input buffers. Width/height only +// control output resolution +int rgb565ToPngFile(char *filename, u16 *rgb_buf, u8 *alpha_buf, int width, int height) +{ + FILE *fp = fopen(filename, "wb"); + u8 *image = malloc(64 * 64 * 4); + if (!image) return -1; + + int i, x, y, r, g, b, a; + + for (i = 0; i < 64 * 64; ++i) + { + r = (rgb_buf[i] & 0xF800) >> 11; + g = (rgb_buf[i] & 0x07E0) >> 5; + b = (rgb_buf[i] & 0x001F); + a = (alpha_buf[i/2] >> (4*(i%2))) & 0x0F; + + r = round(r * 255.0 / 31.0); + g = round(g * 255.0 / 63.0); + b = round(b * 255.0 / 31.0); + a = a * 0x11; + x = 8*((i/64)%8) + (((i%64)&0x01) >> 0) + (((i%64)&0x04) >> 1) + (((i%64)&0x10) >> 2); + y = 8*(i/512) + (((i%64)&0x02) >> 1) + (((i%64)&0x08) >> 2) + (((i%64)&0x20) >> 3); + image[y * 64 * 4 + x * 4 + 0] = r; + image[y * 64 * 4 + x * 4 + 1] = g; + image[y * 64 * 4 + x * 4 + 2] = b; + image[y * 64 * 4 + x * 4 + 3] = a; + } + + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_infop info = png_create_info_struct(png); + setjmp(png_jmpbuf(png)); + png_init_io(png, fp); + png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(png, info); + png_bytep row = malloc(4 * width * sizeof(png_byte)); + for (y = 0; y < height; ++y) + { + for (x = 0; x < width; ++x) + { + row[x * 4 + 0] = image[(y * 64 + x) * 4 + 0]; + row[x * 4 + 1] = image[(y * 64 + x) * 4 + 1]; + row[x * 4 + 2] = image[(y * 64 + x) * 4 + 2]; + row[x * 4 + 3] = image[(y * 64 + x) * 4 + 3]; + } + + png_write_row(png, row); + } + + png_write_end(png, info); + png_free_data(png, info, PNG_FREE_ALL, -1); + png_destroy_write_struct(&png, NULL); + free(row); + free(image); + fflush(fp); + fclose(fp); + + return 0; +} + int pngToRGB565(char *png_buf, u64 fileSize, u16 *rgb_buf_64x64, u8 *alpha_buf_64x64, u16 *rgb_buf_32x32, u8 *alpha_buf_32x32, bool set_icon) { if (png_buf == NULL) return 0; diff --git a/source/draw.c b/source/draw.c index 34ab785..5d43edf 100644 --- a/source/draw.c +++ b/source/draw.c @@ -136,6 +136,7 @@ void init_screens(void) C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_BGM], staticBuf, language.draw.download_bgm); C2D_TextParse(&text[TEXT_INSTALL_DUMPING_THEME], staticBuf, language.draw.dump_single); C2D_TextParse(&text[TEXT_INSTALL_DUMPING_ALL_THEMES], staticBuf, language.draw.dump_all_official); + C2D_TextParse(&text[TEXT_INSTALL_DUMPING_BADGES], staticBuf, language.draw.dump_badges); C2D_TextParse(&text[TEXT_INSTALL_BADGES], staticBuf, language.draw.install_badges); for(int i = 0; i < TEXT_AMOUNT; i++) diff --git a/source/fs.c b/source/fs.c index 187b996..ad9025a 100644 --- a/source/fs.c +++ b/source/fs.c @@ -112,6 +112,8 @@ Result open_archives(void) FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds"), FS_ATTRIBUTE_DIRECTORY); FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE), FS_ATTRIBUTE_DIRECTORY); FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE "/cache"), FS_ATTRIBUTE_DIRECTORY); + FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE "/BadgeBackups"), FS_ATTRIBUTE_DIRECTORY); + FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE "/BadgeBackups/Unknown Set"), FS_ATTRIBUTE_DIRECTORY); u32 homeMenuPath[3] = {MEDIATYPE_SD, archive2, 0}; home.type = PATH_BINARY; @@ -158,16 +160,27 @@ Result open_badge_extdata() } } - if (R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "BadgeData.dat"), FS_OPEN_READ, 0))) + if (R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeData.dat"), FS_OPEN_READ, 0))) { + if (R_SUMMARY(res) == RS_NOTFOUND) + { FSUSER_CreateFile(ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeData.dat"), 0, BADGE_DATA_SIZE); FSUSER_OpenFile(&test_handle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeData.dat"), FS_OPEN_WRITE, 0); FSFILE_Flush(test_handle); + } + DEBUG("Error 0x%08ld opening BadgeData.dat, retrying\n", res); } FSFILE_Close(test_handle); - if(R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "BadgeMngFile.dat"), FS_OPEN_READ, 0))) - remake_file(fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), ArchiveBadgeExt, BADGE_MNG_SIZE); + if(R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), FS_OPEN_READ, 0))) + { + DEBUG("Error 0x%08ld opening BadgeMngFile.dat, retrying\n", res); + if (R_SUMMARY(res) == RS_NOTFOUND) + { + remake_file(fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), ArchiveBadgeExt, BADGE_MNG_SIZE); + } + } + FSFILE_Close(test_handle); if(R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveSD, fsMakePath(PATH_ASCII, "/Badges/ThemePlaza Badges/_seticon.png"), FS_OPEN_READ, 0))) { @@ -214,7 +227,11 @@ u32 file_to_buf(FS_Path path, FS_Archive archive, char ** buf) { Handle file; Result res = 0; - if (R_FAILED(res = FSUSER_OpenFile(&file, archive, path, FS_OPEN_READ, 0))) return 0; + if (R_FAILED(res = FSUSER_OpenFile(&file, archive, path, FS_OPEN_READ, 0))) + { + DEBUG("file_to_buf failed - 0x%08ld\n", res); + return 0; + } u64 size; FSFILE_GetSize(file, &size); diff --git a/source/main.c b/source/main.c index b6ba7a7..f0e89a7 100644 --- a/source/main.c +++ b/source/main.c @@ -906,7 +906,15 @@ int main(void) draw_mode = DRAW_MODE_LIST; extra_index = 1; } - else if(kDown &KEY_B) + else if(kDown & KEY_DLEFT) + { + draw_install(INSTALL_DUMPING_BADGES); + extract_badges(); + extra_mode = false; + draw_mode = DRAW_MODE_LIST; + extra_index = 1; + } + else if(kDown & KEY_B) { extra_index = 1; } diff --git a/source/ui_strings.c b/source/ui_strings.c index 46614c0..c423a01 100644 --- a/source/ui_strings.c +++ b/source/ui_strings.c @@ -149,7 +149,7 @@ const Language_s language_english = { "\uE07A Dump All Themes" }, { - NULL, + "\uE07B Dump Badges", NULL }, { @@ -213,6 +213,7 @@ const Language_s language_english = { .download_bgm = "Downloading BGM, please wait...", .dump_single = "Dumping theme, please wait...", .dump_all_official = "Dumping official themes, please wait...", + .dump_badges = "Dumping installed badges, please wait...", .install_badges = "Installing badges, please wait...", .shuffle = "Shuffle: %i/10", }, @@ -554,7 +555,7 @@ const Language_s language_spanish = { "\uE07A Volcar todos los temas" }, { - NULL, + "\uE07B Dump Badges", NULL }, { @@ -618,6 +619,7 @@ const Language_s language_spanish = { .download_bgm = "Descargando BGM, por favor espera...", .dump_single = "Volcando tema, por favor espera...", .dump_all_official = "Volcando temas oficiales, por favor espera...", + .dump_badges = "Dumping installed badges, please wait...", .install_badges = "Instalando insignias, por favor espera...", .shuffle = "Aleatorio: %i/10", }, @@ -960,7 +962,7 @@ const Language_s language_french = { "\uE07A Dump tous les thèmes" }, { - NULL, + "\uE07B Dump Badges", NULL }, { @@ -1024,6 +1026,7 @@ const Language_s language_french = { .download_bgm = "Téléchargement de la musique,\nveuillez patienter...", .dump_single = "Extraction du thème installé,\nveuillez patienter...", .dump_all_official = "Extraction des thèmes officiels,\nveuillez patienter...", + .dump_badges = "Dumping installed badges, please wait...", .install_badges = "Installation des badges,\nveuillez patienter...", .shuffle = "Aléatoire: %i/10", }, @@ -1365,7 +1368,7 @@ const Language_s language_portuguese = { "\uE07A Exportar Todos Temas" }, { - NULL, + "\uE07B Exportar Insígnias", NULL }, { @@ -1429,6 +1432,7 @@ const Language_s language_portuguese = { .download_bgm = "Baixando BGM, aguarde...", .dump_single = "Exportando tema, aguarde...", .dump_all_official = "Exportando temas oficiais, aguarde...", + .dump_badges = "Dumping installed badges, please wait...", .install_badges = "Instalando insígnias, aguarde...", .shuffle = "Shuffle: %i/10", },