diff --git a/include/badges.h b/include/badges.h new file mode 100644 index 0000000..14c8907 --- /dev/null +++ b/include/badges.h @@ -0,0 +1,42 @@ +/* +* This file is part of Anemone3DS +* Copyright (C) 2016-2024 Contributors in CONTRIBUTORS.md +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#ifndef BADGES_H +#define BADGES_H + +#include "common.h" +#include "fs.h" +#include "conversion.h" +#include "unicode.h" + +#define MAX_BADGE 1000 +#define BADGE_DATA_SIZE 0xF4DF80 +#define BADGE_MNG_SIZE 0xD4A8 + +Result install_badges(void); +Result dump_badge_extdata(void); + +#endif \ No newline at end of file diff --git a/include/conversion.h b/include/conversion.h index bc5d23f..15c69c0 100644 --- a/include/conversion.h +++ b/include/conversion.h @@ -5,5 +5,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); #endif diff --git a/include/draw.h b/include/draw.h index 562845b..2f88d4f 100644 --- a/include/draw.h +++ b/include/draw.h @@ -57,6 +57,7 @@ typedef enum InstallType_e { INSTALL_DUMPING_THEME, INSTALL_DUMPING_ALL_THEMES, + INSTALL_BADGES, INSTALL_NONE, } InstallType; @@ -86,6 +87,7 @@ typedef enum { TEXT_INSTALL_DUMPING_THEME, TEXT_INSTALL_DUMPING_ALL_THEMES, + TEXT_INSTALL_BADGES, // Other text TEXT_VERSION, diff --git a/include/fs.h b/include/fs.h index a57e71e..6ed9a55 100644 --- a/include/fs.h +++ b/include/fs.h @@ -28,12 +28,14 @@ #define FS_H #include "common.h" +#include "badges.h" #define ILLEGAL_CHARS "><\"?;:/\\+,.|[=]*" extern FS_Archive ArchiveSD; extern FS_Archive ArchiveHomeExt; extern FS_Archive ArchiveThemeExt; +extern FS_Archive ArchiveBadgeExt; typedef struct { u32 enable : 1; diff --git a/include/ui_strings.h b/include/ui_strings.h index 27446ee..204a96b 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 *install_badges; float start_pos; const char *shuffle; } Draw_Strings_s; @@ -161,6 +162,10 @@ typedef struct { const char *done; } Themes_Strings_s; +typedef struct { + const char *extdata_locked; +} Badge_Strings_s; + typedef struct { Instructions_s normal_instructions[MODE_AMOUNT]; Instructions_s install_instructions; @@ -175,6 +180,7 @@ typedef struct { Instructions_s remote_extra_instructions; Splashes_Strings_s splashes; Themes_Strings_s themes; + Badge_Strings_s badges; } Language_s; typedef enum { diff --git a/source/badges.c b/source/badges.c new file mode 100644 index 0000000..bf14536 --- /dev/null +++ b/source/badges.c @@ -0,0 +1,427 @@ +/* +* This file is part of Anemone3DS +* Copyright (C) 2016-2024 Contributors in CONTRIBUTORS.md +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* Additional Terms 7.b and 7.c of GPLv3 apply to this file: +* * Requiring preservation of specified reasonable legal notices or +* author attributions in that material or in the Appropriate Legal +* Notices displayed by works containing it. +* * Prohibiting misrepresentation of the origin of that material, +* or requiring that modified versions of such material be marked in +* reasonable ways as different from the original version. +*/ + +#include "badges.h" +#include "draw.h" +#include "ui_strings.h" + +static Handle actHandle; +char *badgeDataBuffer; +char *badgeMngBuffer; +u16 *rgb_buf_64x64; +u16 *rgb_buf_32x32; +u8 *alpha_buf_64x64; +u8 *alpha_buf_32x32; + +Result actInit(void) +{ + return srvGetServiceHandle(&actHandle, "act:u"); +} + +Result actExit(void) +{ + return svcCloseHandle(actHandle); +} + +Result ACTU_Initialize(u32 sdkVersion, u32 memSize, Handle handle) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + + cmdbuf[0] = 0x00010084; + cmdbuf[1] = sdkVersion; + cmdbuf[2] = memSize; + cmdbuf[3] = 0x20; + cmdbuf[4] = 0x0; + cmdbuf[5] = 0x0; + cmdbuf[6] = handle; + + if ((ret = svcSendSyncRequest(actHandle)) != 0) return ret; + + return (Result) cmdbuf[1]; +} + +Result ACTU_GetAccountDataBlock(u32 slot, u32 size, u32 blockId, u32 *output) +{ + Result ret = 0; + u32 *cmdbuf = getThreadCommandBuffer(); + cmdbuf[0] = 0x000600C2; + cmdbuf[1] = slot; + cmdbuf[2] = size; + cmdbuf[3] = blockId; + cmdbuf[4] = (size << 4) | 12; + cmdbuf[5] = (u32) output; + + if ((ret = svcSendSyncRequest(actHandle)) != 0) return ret; + + return (Result) cmdbuf[1]; +} + + +void remove_exten(u16 *filename) +{ + for (int i = 0; i < strulen(filename, 0x8A); ++i) + { + if (filename[i] == '.') + { + filename[i] = 0x00; + return; + } + } +} + +u64 getShortcut(char *filename) +{ + u64 shortcut = 0xFFFFFFFFFFFFFFFF; + char *p1 = strchr(filename, '.'); + + if (p1 == NULL) return shortcut; + ++p1; + + char *p2 = strchr(p1, '.'); + if (p2 == NULL) return shortcut; + if (p2-p1 != 8) return shortcut; + + unsigned int lowpath; + if (sscanf(p1, "%08x", &lowpath) != 1) return shortcut; + + shortcut = 0x0004001000000000 + lowpath; + DEBUG("Shortcut %llu found for %s\n", shortcut, filename); + return shortcut; +} + +int install_badge_png(FS_Path badge_path, FS_DirectoryEntry badge_file, int *badge_count, int set_id) +{ + Result res; + char *file_buf = NULL; + res = file_to_buf(badge_path, ArchiveSD, &file_buf); + if (res != badge_file.fileSize) + { + return -1; + } + int badges_in_image = pngToRGB565(file_buf, badge_file.fileSize, rgb_buf_64x64, alpha_buf_64x64, rgb_buf_32x32, alpha_buf_32x32, false); + + char utf8_name[512] = {0}; + utf16_to_utf8((u8 *) utf8_name, badge_file.name, 0x8A); + u64 shortcut = getShortcut(utf8_name); + + for (int badge = 0; badge < badges_in_image; ++badge) + { + remove_exten(badge_file.name); + for (int j = 0; j < 16; ++j) // Copy name for all 16 languages + { + memcpy(badgeDataBuffer + 0x35E80 + *badge_count * 16 * 0x8A + j * 0x8A, badge_file.name, 0x8A); + } + memcpy(badgeDataBuffer + 0x318F80 + *badge_count * 0x2800, rgb_buf_64x64 + badge * 64 * 64, 64 * 64 * 2); + memcpy(badgeDataBuffer + 0x31AF80 + *badge_count * 0x2800, alpha_buf_64x64 + badge * 64 * 64/2, 64 * 64/2); + memcpy(badgeDataBuffer + 0xCDCF80 + *badge_count * 0xA00, rgb_buf_32x32 + badge * 32 * 32, 32 * 32 * 2); + memcpy(badgeDataBuffer + 0xCDD780 + *badge_count * 0xA00, alpha_buf_32x32 + badge * 32 * 32/2, 32 * 32/2); + + int badge_id = *badge_count + 1; + memcpy(badgeMngBuffer + 0x3E8 + *badge_count * 0x28 + 0x4, &badge_id, 4); + memcpy(badgeMngBuffer + 0x3E8 + *badge_count * 0x28 + 0x8, &set_id, 4); + memcpy(badgeMngBuffer + 0x3E8 + *badge_count*0x28 + 0xC, badge_count, 2); + badgeMngBuffer[0x3E8 + *badge_count*0x28 + 0x12] = 255; // Quantity Low + badgeMngBuffer[0x3E8 + *badge_count*0x28 + 0x13] = 255; // Quantity High + + memcpy(badgeMngBuffer + 0x3E8 + *badge_count*0x28 + 0x18, &shortcut, 8); + memcpy(badgeMngBuffer + 0x3E8 + *badge_count*0x28 + 0x20, &shortcut, 8); // u64 shortcut[2], not sure what second is for + + badgeMngBuffer[0x358 + *badge_count/8] |= 0 << (*badge_count % 8); // enabled badges bitfield + + *badge_count += 1; + } + return badges_in_image; +} + +int install_badge_dir(FS_DirectoryEntry set_dir, int *badge_count, int set_id) +{ + Result res; + Handle folder; + int start_idx = *badge_count; + char *icon_buf = NULL; + int icon_size = 0; + + FS_DirectoryEntry *badge_files = calloc(1024, sizeof(FS_DirectoryEntry)); + u16 path[512] = {0}; + u16 set_icon[17] = {0}; + utf8_to_utf16(set_icon, (u8 *) "_seticon.png", 16); + struacat(path, "/Badges/"); + strucat(path, set_dir.name); + res = FSUSER_OpenDirectory(&folder, ArchiveSD, fsMakePath(PATH_UTF16, path)); + if (R_FAILED(res)) + { + return -1; + } + u32 entries_read; + res = FSDIR_Read(folder, &entries_read, 1024, badge_files); + int badges_in_set = 0; + for (int i = 0; i < entries_read && *badge_count < 1000; ++i) + { + if (!strcmp(badge_files[i].shortExt, "PNG")) + { + memset(path, 0, 512 * sizeof(u16)); + struacat(path, "/Badges/"); + strucat(path, set_dir.name); + struacat(path, "/"); + strucat(path, badge_files[i].name); + if (!memcmp(set_icon, badge_files[i].name, 16)) + { + DEBUG("Found set icon\n"); + icon_size = file_to_buf(fsMakePath(PATH_UTF16, path), ArchiveSD, &icon_buf); + continue; + } + badges_in_set += install_badge_png(fsMakePath(PATH_UTF16, path), badge_files[i], badge_count, set_id); + } else if (!strcmp(badge_files[i].shortExt, "ZIP")) + { + badges_in_set += 1; + // Zip install + } + } + + int set_index = set_id - 1; + u32 total_count = 0xFFFF * badges_in_set; + for (int i = 0; i < 16; ++i) + { + strucat((u16 *) (badgeDataBuffer + set_index * 0x8A0 + i * 0x8A), set_dir.name); + } + badgeMngBuffer[0x3D8 + set_index/8] |= 0 << (set_index % 8); + + memset(badgeMngBuffer + 0xA028 + set_index * 0x30, 0xFF, 8); + badgeMngBuffer[0xA028 + 0xC + set_index * 0x30] = 0x10; // bytes 13 and 14 are 0x2710 + badgeMngBuffer[0xA028 + 0xD + set_index * 0x30] = 0x27; + memcpy(badgeMngBuffer + 0xA028 + 0x10 + set_index * 0x30, &set_id, 4); + memcpy(badgeMngBuffer + 0xA028 + 0x14 + set_index * 0x30, &set_index, 4); + memset(badgeMngBuffer + 0xA028 + 0x18 + set_index * 0x30, 0xFF, 4); + memcpy(badgeMngBuffer + 0xA028 + 0x1C + set_index * 0x30, &badges_in_set, 4); + memcpy(badgeMngBuffer + 0xA028 + 0x20 + set_index * 0x30, &total_count, 4); + memcpy(badgeMngBuffer + 0xA028 + 0x24 + set_index * 0x30, &start_idx, 4); + + int icon = pngToRGB565(icon_buf, icon_size, rgb_buf_64x64, alpha_buf_64x64, rgb_buf_32x32, alpha_buf_32x32, true); + + if (icon == 0) + { + DEBUG("Falling back on default icon\n"); + if (icon_buf) free(icon_buf); + FILE *fp = fopen("romfs:/hb_set.png", "rb"); + fseek(fp, 0L, SEEK_END); + icon_size = ftell(fp); + icon_buf = malloc(icon_size); + fseek(fp, 0L, SEEK_SET); + fread(icon_buf, 1, icon_size, fp); + fclose(fp); + pngToRGB565(icon_buf, icon_size, rgb_buf_64x64, alpha_buf_64x64, rgb_buf_32x32, alpha_buf_32x32, true); + } + + free(icon_buf); + memcpy(badgeDataBuffer + 0x250F80 + set_index * 0x2000, rgb_buf_64x64, 64 * 64 * 2); + badgeMngBuffer[0x3D8 + set_index/8] |= 0 << (set_index % 8); + free(badge_files); + return badges_in_set; +} + +Result install_badges(void) +{ + Handle handle = 0; + Handle folder = 0; + Result res = 0; + + res = actInit(); + if (R_FAILED(res)) + { + DEBUG("actInit() failed!\n"); + return res; + } + + res = ACTU_Initialize(0xB0502C8, 0, 0); + if (R_FAILED(res)) + { + DEBUG("ACTU_Initialize failed! %08lx\n", res); + return res; + } + + u32 nnidNum = 0xFFFFFFFF; + res = ACTU_GetAccountDataBlock(0xFE, 4, 12, &nnidNum); + if (R_FAILED(res)) + { + DEBUG("ACTU_GetAccountDataBlock failed! %08lx\n", res); + return res; + } + + u64 badgeDataSize = 0xF4DF80; + u64 badgeMngSize = 0xD4A8; + + badgeMngBuffer = NULL; + badgeDataBuffer = NULL; + rgb_buf_64x64 = NULL; + rgb_buf_32x32 = NULL; + alpha_buf_64x64 = NULL; + alpha_buf_32x32 = NULL; + + FS_DirectoryEntry *badge_files = calloc(1024, sizeof(FS_DirectoryEntry)); + res = FSUSER_OpenDirectory(&folder, ArchiveSD, fsMakePath(PATH_ASCII, "/Badges/")); + if (R_FAILED(res)) + { + DEBUG("Failed to open folder: %lx\n", res); + goto end; + } + + u32 entries_read; + res = FSDIR_Read(folder, &entries_read, 1024, badge_files); + DEBUG("%lu files found\n", entries_read); + rgb_buf_64x64 = malloc(12*6*64*64*2); //12x6 badges in sheet max, 64x64 pixel badges, 2 bytes per RGB data + alpha_buf_64x64 = malloc(12*6*64*64/2); //Same thing, but 2 pixels of alpha data per byte + rgb_buf_32x32 = malloc(12*6*32*32*2); //Same thing, but 32x32 + alpha_buf_32x32 = malloc(12*6*32*32/2); + badgeDataBuffer = calloc(1, badgeDataSize); + badgeMngBuffer = calloc(1, badgeMngSize); + int badge_count = 0; + int set_count = 0; + int default_set = 0; + int default_set_count = 0; + int default_idx = 0; + for (int i = 0; i < entries_read && badge_count < 1000; ++i) + { + if (!strcmp(badge_files[i].shortExt, "PNG")) + { + if (default_set == 0) + { + set_count += 1; + default_set = set_count; + default_idx = badge_count; + } + u16 path[0x512] = {0}; + struacat(path, "/Badges/"); + strucat(path, badge_files[i].name); + default_set_count += install_badge_png(fsMakePath(PATH_UTF16, path), badge_files[i], &badge_count, default_set); + } else if (!strcmp(badge_files[i].shortExt, "ZIP")) + { + if (default_set == 0) + { + set_count += 1; + default_set = set_count; + } + // Zip install + } else if (badge_files[i].attributes & FS_ATTRIBUTE_DIRECTORY) + { + set_count += 1; + install_badge_dir(badge_files[i], &badge_count, set_count); + } + } + + if (default_set != 0) + { + int default_index = default_set - 1; + u32 total_count = 0xFFFF * default_set_count; + for (int i = 0; i < 16; ++i) + { + u16 name[0x8A/2] = {0}; + utf8_to_utf16(name, (u8 *) "Other Badges", 0x8A); + memcpy(badgeDataBuffer + default_index * 0x8A0 + i * 0x8A, &name, 0x8A); + } + badgeMngBuffer[0x3D8 + default_index/8] |= 0 << (default_index % 8); + + memset(badgeMngBuffer + 0xA028 + default_index * 0x30, 0xFF, 8); + badgeMngBuffer[0xA028 + 0xC + default_index * 0x30] = 0x10; // bytes 13 and 14 are 0x2710 + badgeMngBuffer[0xA028 + 0xD + default_index * 0x30] = 0x27; + memcpy(badgeMngBuffer + 0xA028 + 0x10 + default_index * 0x30, &default_set, 4); + memcpy(badgeMngBuffer + 0xA028 + 0x14 + default_index * 0x30, &default_index, 4); + memset(badgeMngBuffer + 0xA028 + 0x18 + default_index * 0x30, 0xFF, 4); + memcpy(badgeMngBuffer + 0xA028 + 0x1C + default_index * 0x30, &default_set_count, 4); + memcpy(badgeMngBuffer + 0xA028 + 0x20 + default_index * 0x30, &total_count, 4); + memcpy(badgeMngBuffer + 0xA028 + 0x24 + default_index * 0x30, &default_idx, 4); + + FILE *fp = fopen("romfs:/anemone_set.png", "rb"); + fseek(fp, 0L, SEEK_END); + ssize_t size = ftell(fp); + char *icon_buf = malloc(size); + fseek(fp, 0L, SEEK_SET); + fread(icon_buf, 1, size, fp); + fclose(fp); + pngToRGB565(icon_buf, size, rgb_buf_64x64, alpha_buf_64x64, rgb_buf_32x32, alpha_buf_32x32, true); + free(icon_buf); + memcpy(badgeDataBuffer + 0x250F80 + default_index * 0x2000, rgb_buf_64x64, 64 * 64 * 2); + } + + res = buf_to_file(badgeDataSize, fsMakePath(PATH_ASCII, "/BadgeData.dat"), ArchiveBadgeExt, badgeDataBuffer); + if (res) + { + DEBUG("Error writing badge data! %lx\n", res); + throw_error(language.badges.extdata_locked, ERROR_LEVEL_WARNING); + goto end; + } + + u32 total_badges = 0xFFFF * badge_count; // Quantity * unique badges? + + badgeMngBuffer[0x4] = set_count; // badge set count + memcpy(badgeMngBuffer + 0x8, &badge_count, 4); + badgeMngBuffer[0x10] = 0xFF; // Selected Set - 0xFFFFFFFF is all badges + badgeMngBuffer[0x11] = 0xFF; + badgeMngBuffer[0x12] = 0xFF; + badgeMngBuffer[0x13] = 0xFF; + memcpy(badgeMngBuffer + 0x18, &total_badges, 4); + memcpy(badgeMngBuffer + 0x1C, &nnidNum, 4); + + for (int i = set_count; i < 100; ++i) // default values for remaining sets + { + memset(badgeMngBuffer + 0xA028 + 0x30 * i, 0xFF, 0x8); + memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0x8, 0x00, 0x4); + badgeMngBuffer[0xA028 + 0x30 * i + 0xC] = 0x10; + badgeMngBuffer[0xA028 + 0x30 * i + 0xD] = 0x27; + memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0xE, 0x0, 0x2); + memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0x10, 0xFF, 0xC); + memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0x1C, 0x00, 0x8); + memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0x24, 0xFF, 0x4); + memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0x28, 0x00, 0x8); + } + + res = FSUSER_OpenFile(&handle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), FS_OPEN_READ, 0); + if (res == 0) + { + FSFILE_Read(handle, NULL, 0xB2E8, badgeMngBuffer+0xB2E8, 360 * 0x18); + FSFILE_Close(handle); + } + + res = buf_to_file(badgeMngSize, fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), ArchiveBadgeExt, badgeMngBuffer); + if (res) + { + DEBUG("Error writing badge manage data! %lx\n", res); + throw_error(language.badges.extdata_locked, ERROR_LEVEL_WARNING); + goto end; + } + + end: + if (rgb_buf_64x64) free(rgb_buf_64x64); + if (alpha_buf_64x64) free(alpha_buf_64x64); + if (rgb_buf_32x32) free(rgb_buf_32x32); + if (alpha_buf_32x32) free(alpha_buf_32x32); + if (handle) FSFILE_Close(handle); + if (folder) FSDIR_Close(folder); + if (badgeDataBuffer) free(badgeDataBuffer); + if (badgeMngBuffer) free(badgeMngBuffer); + if (badge_files) free(badge_files); + return res; +} \ No newline at end of file diff --git a/source/conversion.c b/source/conversion.c index 517ec50..94f0167 100644 --- a/source/conversion.c +++ b/source/conversion.c @@ -3,6 +3,135 @@ #include +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; + if (fileSize < 8 || png_sig_cmp((png_bytep) png_buf, 0, 8)) + { + return 0; + } + + u32 *buf = (u32 *) png_buf; + FILE *fp = fmemopen(buf, fileSize, "rb"); + png_bytep *row_pointers = NULL; + + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + png_infop info = png_create_info_struct(png); + + png_init_io(png, fp); + png_read_info(png, info); + + u32 width = png_get_image_width(png, info); + u32 height = png_get_image_height(png, info); + png_byte color_type = png_get_color_type(png, info); + png_byte bit_depth = png_get_bit_depth(png, info); + + if (set_icon && (width != 48 || height != 48)) + { + DEBUG("Invalid set icon?\n"); + return 0; + } + if (!set_icon && (width < 64 || height < 64 || width % 64 != 0 || height % 64 != 0 || width > 12 * 64 || height > 6 * 64)) + { + DEBUG("Invalid png found...\n"); + return 0; + } + + if (bit_depth == 16) + png_set_strip_16(png); + + if (color_type == PNG_COLOR_TYPE_PALETTE) + png_set_palette_to_rgb(png); + + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) + png_set_expand_gray_1_2_4_to_8(png); + + if (png_get_valid(png, info, PNG_INFO_tRNS)) + png_set_tRNS_to_alpha(png); + + if (color_type == PNG_COLOR_TYPE_RGB || + color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_PALETTE) + png_set_filler(png, 0xFF, PNG_FILLER_AFTER); + + if (color_type == PNG_COLOR_TYPE_GRAY || + color_type == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + png_read_update_info(png, info); + + u32 x, y, r, g, b, a; + + memset(alpha_buf_64x64, 0, 12*6*64*64/2); + memset(alpha_buf_32x32, 0, 12*6*32*32/2); + + row_pointers = malloc(sizeof(png_bytep) * height); + for (y = 0; y < height; y++) + { + row_pointers[y] = (png_byte *) malloc(png_get_rowbytes(png, info)); + } + + png_read_image(png, row_pointers); + + png_destroy_read_struct(&png, &info, NULL); + + if (fp) fclose(fp); + for (y = 0; y < height; ++y) + { + png_bytep row = row_pointers[y]; + for (x = 0; x < width; ++x) + { + png_bytep px = &(row[x * 4]); + r = px[0] >> 3; + g = px[1] >> 2; + b = px[2] >> 3; + a = px[3] >> 4; + + // rgb565 conversion code adapted from GYTB + int rgb565_index = 8*64*((y/8)%8) | 64*((x/8)%8) | 32*((y/4)%2) | 16*((x/4)%2) | 8*((y/2)%2) | 4*((x/2)%2) | 2*(y%2) | (x%2); + rgb565_index |= 64*64*(height/64)*(x/64) + 64*64*(y/64); // account for multiple badges from 1 image + rgb_buf_64x64[rgb565_index] = (r << 11) | (g << 5) | b; + alpha_buf_64x64[rgb565_index / 2] |= a << (4 * (x % 2)); + } + } + + for (y = 0; y < height; y += 2) + { + png_bytep row = row_pointers[y]; + png_bytep nextrow = row_pointers[y+1]; + for (x = 0; x < width; x += 2) + { + png_bytep px1 = &(row[x * 4]); + png_bytep px2 = &(nextrow[x * 4]); + png_bytep px3 = &(row[(x + 1) * 4]); + png_bytep px4 = &(nextrow[(x + 1) * 4]); + r = (px1[0] + px2[0] + px3[0] + px4[0]) >> 5; + g = (px1[1] + px2[1] + px3[1] + px4[1]) >> 4; + b = (px1[2] + px2[2] + px3[2] + px4[2]) >> 5; + a = (px1[3] + px2[3] + px3[3] + px4[3]) >> 6; + int x2 = x/2; + int y2 = y/2; + + int rgb565_index = 4*64*((y2/8)%4) | 64*((x2/8)%4) | 32*((y2/4)%2) | 16*((x2/4)%2) | 8*((y2/2)%2) | 4*((x2/2)%2) | 2*(y2%2) | (x2%2); + rgb565_index |= 32*32*(height/64)*(x/64) + 32*32*(y/64); + + rgb_buf_32x32[rgb565_index] = (r << 11) | (g << 5) | b; + alpha_buf_32x32[rgb565_index / 2] |= a << (4 * (x2%2)); + } + } + + for (y = 0; y < height; y++) + { + free(row_pointers[y]); + } + + free(row_pointers); + if (!set_icon) + return (height/64)*(width/64); + else + return (height/48)*(width/48); +} + static void rotate_agbr_counterclockwise(char ** bufp, size_t size, size_t width) { uint32_t * buf = (uint32_t*)*bufp; diff --git a/source/draw.c b/source/draw.c index c6c0234..88e7e17 100644 --- a/source/draw.c +++ b/source/draw.c @@ -128,6 +128,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_BADGES], staticBuf, language.draw.install_badges); for(int i = 0; i < TEXT_AMOUNT; i++) C2D_TextOptimize(&text[i]); diff --git a/source/fs.c b/source/fs.c index 6149d06..81622a2 100644 --- a/source/fs.c +++ b/source/fs.c @@ -37,6 +37,34 @@ FS_Archive ArchiveSD; FS_Archive ArchiveHomeExt; FS_Archive ArchiveThemeExt; +FS_Archive ArchiveBadgeExt; + +Result createExtSaveData(u32 extdataID) +{ + u8 null_smdh[0x36C0] = {0}; + Handle *handle = fsGetSessionHandle(); + + u32 *cmdbuf = getThreadCommandBuffer(); + + u32 directory_limit = 1000; + u32 file_limit = 1000; + + cmdbuf[0] = 0x08300182; + cmdbuf[1] = MEDIATYPE_SD; + cmdbuf[2] = extdataID; + cmdbuf[3] = 0; + cmdbuf[4] = 0x36C0; + cmdbuf[5] = directory_limit; + cmdbuf[6] = file_limit; + cmdbuf[7] = (0x36C0 << 4) | 0xA; + cmdbuf[8] = (u32)&null_smdh; + + Result ret = 0; + if ((ret = svcSendSyncRequest(*handle))) + return ret; + + return cmdbuf[1]; +} Result open_archives(void) { @@ -49,6 +77,7 @@ Result open_archives(void) FS_Path home; FS_Path theme; + FS_Path badge; CFGU_SecureInfoGetRegion(®ionCode); switch(regionCode) @@ -78,6 +107,7 @@ Result open_archives(void) FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Themes"), FS_ATTRIBUTE_DIRECTORY); FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Splashes"), FS_ATTRIBUTE_DIRECTORY); + FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Badges"), FS_ATTRIBUTE_DIRECTORY); 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); @@ -94,6 +124,24 @@ Result open_archives(void) theme.data = themePath; if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveThemeExt, ARCHIVE_EXTDATA, theme))) return res; + u32 badgePath[3] = {MEDIATYPE_SD, 0x000014d1, 0}; + badge.type = PATH_BINARY; + badge.size = 0xC; + badge.data = badgePath; + if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveBadgeExt, ARCHIVE_EXTDATA, badge))) + { + if (R_SUMMARY(res) == RS_NOTFOUND) + { + createExtSaveData(0x000014d1); + FSUSER_OpenArchive(&ArchiveBadgeExt, ARCHIVE_EXTDATA, badge); + remake_file(fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), ArchiveBadgeExt, BADGE_MNG_SIZE); + remake_file(fsMakePath(PATH_ASCII, "/BadgeData.dat"), ArchiveBadgeExt, BADGE_DATA_SIZE); + } else + { + return res; + } + } + Handle test_handle; if(R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveThemeExt, fsMakePath(PATH_ASCII, "/ThemeManage.bin"), FS_OPEN_READ, 0))) return res; FSFILE_Close(test_handle); diff --git a/source/main.c b/source/main.c index cfe1553..cc8d136 100644 --- a/source/main.c +++ b/source/main.c @@ -33,6 +33,7 @@ #include "music.h" #include "remote.h" #include "ui_strings.h" +#include "badges.h" #include bool quit = false; @@ -828,6 +829,11 @@ int main(void) draw_mode = DRAW_MODE_LIST; extra_index = 1; } + else if (kDown & KEY_DRIGHT) + { + draw_install(INSTALL_BADGES); + install_badges(); + } else if (kDown & KEY_R) { extra_index = 2; diff --git a/source/ui_strings.c b/source/ui_strings.c index a4a67e4..6f8f425 100644 --- a/source/ui_strings.c +++ b/source/ui_strings.c @@ -129,7 +129,7 @@ const Language_s language_english = { }, { "\uE07B Browse ThemePlaza", - NULL + "\uE07C Install Badges" }, { "\uE004 Sorting menu", @@ -210,6 +210,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...", + .install_badges = "Installing badges, please wait...", .shuffle = "Shuffle: %i/10", }, .fs = @@ -356,6 +357,10 @@ const Language_s language_english = { .name_folder = "Name of output folder", .cancel = "Cancel", .done = "Done" + }, + .badges = + { + .extdata_locked = "Ext Data Locked\nTry restarting Anemone3DS, or using\nthe CIA version instead." } }; @@ -542,6 +547,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...", + .install_badges = "Installing badges, please wait...", .shuffle = "Aléatoire: %i/10", }, .fs = @@ -688,6 +694,10 @@ const Language_s language_french = { .name_folder = "Nom du dossier de destination", .cancel = "Annuler", .done = "OK" + }, + .badges = + { + .extdata_locked = "Ext Data Locked\nTry restarting Anemone3DS, or using\nthe CIA version instead." } }; @@ -874,6 +884,7 @@ const Language_s language_portuguese = { .download_bgm = "Baixando BGM, aguarde...", .dump_single = "Exportando tema, aguarde...", .dump_all_official = "Exportando temas oficiais, aguarde...", + .install_badges = "Installing badges, please wait...", .shuffle = "Shuffle: %i/10", }, .fs = @@ -934,7 +945,7 @@ const Language_s language_portuguese = { .http418 = "HTTP 418 I'm a teapot\nContate o administrador do site.", .http426 = "HTTP 426 Upgrade Required\nO 3DS não consegue acessar este servidor.\nContate o administrador do site.", .http451 = "HTTP 451 Unavailable for Legal Reasons\nAlguma entidade está impedindo\no acesso ao servidor host por razões legais.", - .http500 = "HTTP 500 Internal Server Error\Contate o administrador do site.", + .http500 = "HTTP 500 Internal Server Error\nContate o administrador do site.", .http502 = "HTTP 502 Bad Gateway\nContate o administrador do site.", .http503 = "HTTP 503 Service Unavailable\nContate o administrador do site.", .http504 = "HTTP 504 Gateway Timeout\nContate o administrador do site.", @@ -1020,6 +1031,10 @@ const Language_s language_portuguese = { .name_folder = "Nome da pasta de saída", .cancel = "Cancelar", .done = "Pronto" + }, + .badges = + { + .extdata_locked = "Ext Data Locked\nTry restarting Anemone3DS, or using\nthe CIA version instead." } };