Badge dump overhaul
This commit is contained in:
@@ -37,6 +37,6 @@
|
||||
#define BADGE_MNG_SIZE 0xD4A8
|
||||
|
||||
Result install_badges(void);
|
||||
Result dump_badge_extdata(void);
|
||||
Result extract_badges(void);
|
||||
|
||||
#endif
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
167
source/badges.c
167
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))
|
||||
|
||||
@@ -3,6 +3,66 @@
|
||||
|
||||
#include <png.h>
|
||||
|
||||
// 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;
|
||||
|
||||
@@ -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++)
|
||||
|
||||
23
source/fs.c
23
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)))
|
||||
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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user