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."
}
};