add ability to dump all your official themes
icon and name get extracted from the dlc data requires libctru from commit 5f13628dac75206f0c97d29a7427ce8284d910f1 or older (added the am commands necessary to find the dlc data) Makefile changed to reflect the macro change in unreleased libctru
This commit is contained in:
2
Makefile
2
Makefile
@@ -89,7 +89,7 @@ CFLAGS := -g -Wall -Wextra -O2 -mword-relocations \
|
||||
-ffunction-sections \
|
||||
$(ARCH)
|
||||
|
||||
CFLAGS += $(INCLUDE) -DARM11 -D_3DS -D_GNU_SOURCE -DVERSION="\"$(VERSION)\"" -DUSER_AGENT="\"$(APP_TITLE)/$(VERSION)\"" -DAPP_TITLE="\"$(APP_TITLE)\""
|
||||
CFLAGS += $(INCLUDE) -D__3DS__ -D_GNU_SOURCE -DVERSION="\"$(VERSION)\"" -DUSER_AGENT="\"$(APP_TITLE)/$(VERSION)\"" -DAPP_TITLE="\"$(APP_TITLE)\""
|
||||
ifneq ($(strip $(CITRA_MODE)),)
|
||||
CFLAGS += -DCITRA_MODE
|
||||
endif
|
||||
|
||||
@@ -56,6 +56,7 @@ typedef enum {
|
||||
INSTALL_LOADING_REMOTE_BGM,
|
||||
|
||||
INSTALL_DUMPING_THEME,
|
||||
INSTALL_DUMPING_ALL_THEMES,
|
||||
|
||||
INSTALL_NONE,
|
||||
} InstallType;
|
||||
@@ -84,6 +85,7 @@ typedef enum {
|
||||
TEXT_INSTALL_LOADING_REMOTE_BGM,
|
||||
|
||||
TEXT_INSTALL_DUMPING_THEME,
|
||||
TEXT_INSTALL_DUMPING_ALL_THEMES,
|
||||
|
||||
// Other text
|
||||
TEXT_VERSION,
|
||||
|
||||
@@ -29,6 +29,8 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#define ILLEGAL_CHARS "><\"?;:/\\+,.|[=]"
|
||||
|
||||
extern FS_Archive ArchiveSD;
|
||||
extern FS_Archive ArchiveHomeExt;
|
||||
extern FS_Archive ArchiveThemeExt;
|
||||
|
||||
@@ -128,11 +128,11 @@ Instructions_s extra_instructions[3] = {
|
||||
},
|
||||
{
|
||||
"\uE07B Browse ThemePlaza",
|
||||
"\uE07C Dump Current Theme"
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"\uE004 Sorting menu",
|
||||
NULL
|
||||
"\uE005 Dumping menu"
|
||||
},
|
||||
{
|
||||
"Exit",
|
||||
@@ -140,6 +140,27 @@ Instructions_s extra_instructions[3] = {
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
.info_line = "Release \uE002 to cancel or hold \uE006 and release \uE002 to dump",
|
||||
.instructions = {
|
||||
{
|
||||
"\uE079 Dump Current Theme",
|
||||
"\uE07A Dump All Themes"
|
||||
},
|
||||
{
|
||||
NULL,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
NULL,
|
||||
NULL
|
||||
},
|
||||
{
|
||||
"Exit",
|
||||
NULL
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -75,7 +75,8 @@ Result bgm_install(Entry_s theme);
|
||||
|
||||
Result shuffle_install(Entry_List_s themes);
|
||||
|
||||
Result dump_theme(void);
|
||||
Result dump_current_theme(void);
|
||||
Result dump_all_themes(void);
|
||||
|
||||
void themes_check_installed(void * void_arg);
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ void init_screens(void)
|
||||
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_PREVIEW], staticBuf, "Downloading preview, please wait...");
|
||||
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_BGM], staticBuf, "Downloading BGM, please wait...");
|
||||
C2D_TextParse(&text[TEXT_INSTALL_DUMPING_THEME], staticBuf, "Dumping theme, please wait...");
|
||||
C2D_TextParse(&text[TEXT_INSTALL_DUMPING_ALL_THEMES], staticBuf, "Dumping official themes, please wait...");
|
||||
|
||||
for(int i = 0; i < TEXT_AMOUNT; i++)
|
||||
C2D_TextOptimize(&text[i]);
|
||||
|
||||
@@ -353,7 +353,6 @@ void remake_file(FS_Path path, FS_Archive archive, u32 size)
|
||||
free(buf);
|
||||
}
|
||||
|
||||
#define ILLEGAL_CHARS "><\"?;:/\\+,.|[=]"
|
||||
static SwkbdCallbackResult fat32filter(void *user, const char **ppMessage, const char *text, size_t textlen)
|
||||
{
|
||||
(void)textlen;
|
||||
|
||||
@@ -401,8 +401,8 @@ int main(void)
|
||||
{
|
||||
if(key_l)
|
||||
index = 0;
|
||||
// else if(key_r) // uncomment when we use the right menu. we don't for now
|
||||
// index = 2;
|
||||
else if(key_r) // uncomment when we use the right menu. we don't for now
|
||||
index = 2;
|
||||
}
|
||||
instructions = extra_instructions[index];
|
||||
}
|
||||
@@ -648,13 +648,6 @@ int main(void)
|
||||
{
|
||||
load_icons_first(current_list, false);
|
||||
}
|
||||
else if((kDown | kHeld) & KEY_DRIGHT)
|
||||
{
|
||||
draw_install(INSTALL_DUMPING_THEME);
|
||||
Result res = dump_theme();
|
||||
if (R_FAILED(res)) DEBUG("Dump theme result: %lx\n", res);
|
||||
else load_lists(lists);
|
||||
}
|
||||
}
|
||||
else if(key_l)
|
||||
{
|
||||
@@ -677,6 +670,23 @@ int main(void)
|
||||
load_icons_first(current_list, false);
|
||||
}
|
||||
}
|
||||
else if(key_r)
|
||||
{
|
||||
if(((kDown | kHeld)) & KEY_DUP)
|
||||
{
|
||||
draw_install(INSTALL_DUMPING_THEME);
|
||||
Result res = dump_current_theme();
|
||||
if (R_FAILED(res)) DEBUG("Dump theme result: %lx\n", res);
|
||||
else load_lists(lists);
|
||||
}
|
||||
else if(((kDown | kHeld)) & KEY_DDOWN)
|
||||
{
|
||||
draw_install(INSTALL_DUMPING_ALL_THEMES);
|
||||
Result res = dump_all_themes();
|
||||
if (R_FAILED(res)) DEBUG("Dump all themes result: %lx\n", res);
|
||||
else load_lists(lists);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
240
source/themes.c
240
source/themes.c
@@ -328,7 +328,7 @@ dir_name_callback(void *data, const char ** ppMessage, const char * text, size_t
|
||||
return SWKBD_CALLBACK_OK;
|
||||
}
|
||||
|
||||
Result dump_theme(void)
|
||||
Result dump_current_theme(void)
|
||||
{
|
||||
const int max_chars = 255;
|
||||
char * output_dir = calloc(max_chars + 1, sizeof(char));
|
||||
@@ -415,6 +415,244 @@ Result dump_theme(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result dump_all_themes(void)
|
||||
{
|
||||
const u32 high_id = 0x0004008c;
|
||||
u32 low_id = 0;
|
||||
u8 regionCode, language;
|
||||
Result res = CFGU_SecureInfoGetRegion(®ionCode);
|
||||
if(R_FAILED(res))
|
||||
return res;
|
||||
|
||||
res = CFGU_GetSystemLanguage(&language);
|
||||
if(R_FAILED(res))
|
||||
return res;
|
||||
|
||||
switch(regionCode)
|
||||
{
|
||||
case CFG_REGION_JPN:
|
||||
low_id = 0x00008200;
|
||||
break;
|
||||
case CFG_REGION_USA:
|
||||
low_id = 0x00008f00;
|
||||
break;
|
||||
case CFG_REGION_EUR:
|
||||
low_id = 0x00009800;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* region_arr[4] = {
|
||||
"JPN",
|
||||
"USA",
|
||||
"EUR",
|
||||
"AUS",
|
||||
};
|
||||
|
||||
const char* language_arr[12] = {
|
||||
"jp",
|
||||
"en",
|
||||
"fr",
|
||||
"de",
|
||||
"it",
|
||||
"es",
|
||||
"zh",
|
||||
"ko",
|
||||
"nl",
|
||||
"pt",
|
||||
"ru",
|
||||
"tw",
|
||||
};
|
||||
|
||||
res = amAppInit();
|
||||
if(R_FAILED(res))
|
||||
return res;
|
||||
|
||||
Icon_s* smdh_data = calloc(1, sizeof(Icon_s));
|
||||
smdh_data->_padding1[0] = 0x53; // SMDH magic
|
||||
smdh_data->_padding1[1] = 0x4d;
|
||||
smdh_data->_padding1[2] = 0x44;
|
||||
smdh_data->_padding1[3] = 0x48;
|
||||
|
||||
utf8_to_utf16(smdh_data->author, (u8*)"Nintendo", 0x40);
|
||||
utf8_to_utf16(smdh_data->desc, (u8*)"Official theme. For personal use only. Do not redistribute.", 0x80);
|
||||
|
||||
for(u32 dlc_index = 0; dlc_index <= 0xFF; ++dlc_index)
|
||||
{
|
||||
const u64 titleId = ((u64)high_id << 32) | (low_id | dlc_index);
|
||||
|
||||
u32 count = 0;
|
||||
res = AMAPP_GetDLCContentInfoCount(&count, MEDIATYPE_SD, titleId);
|
||||
if(res == (Result)0xd8a083fa)
|
||||
{
|
||||
res = 0;
|
||||
break;
|
||||
}
|
||||
else if(R_FAILED(res))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
AM_ContentInfo* contentInfos = calloc(count, sizeof(AM_ContentInfo));
|
||||
u32 readcount = 0;
|
||||
res = AMAPP_ListDLCContentInfos(&readcount, MEDIATYPE_SD, titleId, count, 0, contentInfos);
|
||||
if(R_FAILED(res))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
u32 archivePath[4] = {low_id | dlc_index, high_id, MEDIATYPE_SD, 0};
|
||||
FS_Path ncch_path;
|
||||
ncch_path.type = PATH_BINARY;
|
||||
ncch_path.size = 0x10;
|
||||
ncch_path.data = archivePath;
|
||||
|
||||
FS_Archive ncch_archive;
|
||||
res = FSUSER_OpenArchive(&ncch_archive, ARCHIVE_SAVEDATA_AND_CONTENT, ncch_path);
|
||||
if(R_FAILED(res))
|
||||
{
|
||||
free(contentInfos);
|
||||
break;
|
||||
}
|
||||
|
||||
u32 metadataPath[5] = {0, 0, 0, 0, 0};
|
||||
FS_Path metadata_path;
|
||||
metadata_path.type = PATH_BINARY;
|
||||
metadata_path.size = 0x14;
|
||||
metadata_path.data = metadataPath;
|
||||
|
||||
Handle metadata_fh;
|
||||
res = FSUSER_OpenFile(&metadata_fh, ncch_archive, metadata_path, FS_OPEN_READ, 0);
|
||||
if(R_FAILED(res))
|
||||
{
|
||||
FSUSER_CloseArchive(ncch_archive);
|
||||
free(contentInfos);
|
||||
break;
|
||||
}
|
||||
|
||||
res = romfsMountFromFile(metadata_fh, 0, "meta");
|
||||
if(R_FAILED(res))
|
||||
{
|
||||
FSFILE_Close(metadata_fh);
|
||||
FSUSER_CloseArchive(ncch_archive);
|
||||
free(contentInfos);
|
||||
break;
|
||||
}
|
||||
|
||||
char contentinfoarchive_path[40] = {0};
|
||||
sprintf(contentinfoarchive_path, "meta:/ContentInfoArchive_%s_%s.bin", region_arr[regionCode], language_arr[language]);
|
||||
|
||||
FILE* fh = fopen(contentinfoarchive_path, "rb");
|
||||
|
||||
for(u32 i = 0; i < readcount; ++i)
|
||||
{
|
||||
if(i == 0) continue;
|
||||
AM_ContentInfo* content = &contentInfos[i];
|
||||
if((content->flags & (AM_CONTENT_DOWNLOADED | AM_CONTENT_OWNED)) == (AM_CONTENT_DOWNLOADED | AM_CONTENT_OWNED))
|
||||
{
|
||||
long off = 0x8 + 0xC8 * i;
|
||||
fseek(fh, off, SEEK_SET);
|
||||
char content_data[0xc8] = {0};
|
||||
fread(content_data, 1, 0xc8, fh);
|
||||
u32 extra_index = 0;
|
||||
memcpy(&extra_index, content_data + 0xC0, 4);
|
||||
|
||||
metadataPath[1] = content->index;
|
||||
|
||||
Handle theme_fh;
|
||||
res = FSUSER_OpenFile(&theme_fh, ncch_archive, metadata_path, FS_OPEN_READ, 0);
|
||||
if(R_FAILED(res))
|
||||
{
|
||||
DEBUG("theme open romfs error: %08lx\n", res);
|
||||
fclose(fh);
|
||||
free(contentInfos);
|
||||
romfsUnmount("meta");
|
||||
FSUSER_CloseArchive(ncch_archive);
|
||||
free(contentInfos);
|
||||
break;
|
||||
}
|
||||
|
||||
romfsMountFromFile(theme_fh, 0, "theme");
|
||||
|
||||
char themename[0x41] = {0};
|
||||
memcpy(themename, content_data, 0x40);
|
||||
char * illegal_char = themename;
|
||||
while ((illegal_char = strpbrk(illegal_char, ILLEGAL_CHARS)))
|
||||
{
|
||||
*illegal_char = '-';
|
||||
}
|
||||
|
||||
char path[0x107] = { 0 };
|
||||
sprintf(path, "/Themes/Dump-%02lx-%ld-%s", dlc_index, extra_index, themename);
|
||||
DEBUG("theme folder to create: %s\n", path);
|
||||
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, path), FS_ATTRIBUTE_DIRECTORY);
|
||||
|
||||
memset(smdh_data->name, 0, sizeof(smdh_data->name));
|
||||
utf8_to_utf16(smdh_data->name, (u8*)(content_data + 0), 0x40);
|
||||
|
||||
FILE* theme_file = fopen("theme:/body_LZ.bin", "rb");
|
||||
if(theme_file)
|
||||
{
|
||||
fseek(theme_file, 0, SEEK_END);
|
||||
long theme_size = ftell(theme_file);
|
||||
fseek(theme_file, 0, SEEK_CUR);
|
||||
char* theme_data = malloc(theme_size);
|
||||
fread(theme_data, 1, theme_size, theme_file);
|
||||
fclose(theme_file);
|
||||
|
||||
char themepath[0x107] = {0};
|
||||
sprintf(themepath, "%s/body_LZ.bin", path);
|
||||
remake_file(fsMakePath(PATH_ASCII, themepath), ArchiveSD, theme_size);
|
||||
buf_to_file(theme_size, fsMakePath(PATH_ASCII, themepath), ArchiveSD, theme_data);
|
||||
free(theme_data);
|
||||
}
|
||||
|
||||
FILE* bgm_file = fopen("theme:/bgm.bcstm", "rb");
|
||||
if(bgm_file)
|
||||
{
|
||||
fseek(bgm_file, 0, SEEK_END);
|
||||
long bgm_size = ftell(bgm_file);
|
||||
fseek(bgm_file, 0, SEEK_CUR);
|
||||
char* bgm_data = malloc(bgm_size);
|
||||
fread(bgm_data, 1, bgm_size, bgm_file);
|
||||
fclose(bgm_file);
|
||||
|
||||
char bgmpath[0x107] = {0};
|
||||
sprintf(bgmpath, "%s/bgm.bcstm", path);
|
||||
remake_file(fsMakePath(PATH_ASCII, bgmpath), ArchiveSD, bgm_size);
|
||||
buf_to_file(bgm_size, fsMakePath(PATH_ASCII, bgmpath), ArchiveSD, bgm_data);
|
||||
free(bgm_data);
|
||||
}
|
||||
|
||||
romfsUnmount("theme");
|
||||
char icondatapath[0x107] = {0};
|
||||
sprintf(icondatapath, "meta:/icons/%ld.icn", extra_index);
|
||||
FILE* iconfile = fopen(icondatapath, "rb");
|
||||
fread(smdh_data->big_icon, 1, sizeof(smdh_data->big_icon), iconfile);
|
||||
fclose(iconfile);
|
||||
|
||||
strcat(path, "/info.smdh");
|
||||
remake_file(fsMakePath(PATH_ASCII, path), ArchiveSD, 0x36c0);
|
||||
buf_to_file(0x36c0, fsMakePath(PATH_ASCII, path), ArchiveSD, (char*)smdh_data);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fh);
|
||||
fh = NULL;
|
||||
free(contentInfos);
|
||||
contentInfos = NULL;
|
||||
|
||||
romfsUnmount("meta");
|
||||
// don't need to close the file opened for the metadata, romfsUnmount took ownership
|
||||
FSUSER_CloseArchive(ncch_archive);
|
||||
}
|
||||
|
||||
free(smdh_data);
|
||||
amExit();
|
||||
return res;
|
||||
}
|
||||
|
||||
void themes_check_installed(void * void_arg)
|
||||
{
|
||||
Thread_Arg_s * arg = (Thread_Arg_s *)void_arg;
|
||||
|
||||
Reference in New Issue
Block a user