16 Commits

Author SHA1 Message Date
Dylan G
dc21e4d24b Patched a memory leak (I think) (thanks DeltaV) 2022-05-22 15:40:48 +01:00
Dylan G
9a51667546 Handle the case where the server does not provide Content-Disposition by getting the filename from the URL.
Currently untested.
2022-05-22 15:33:20 +01:00
Dylan G
b81a9aaa4c Dropped HTTPS for the browser as ThemePlaza is retiring TLSv1.1 2022-05-22 15:23:36 +01:00
Dylan G
6ba1ef111e Merge pull request #262 from LiquidFenrir/better-dump
add ability to dump all your official themes
2022-02-26 16:02:55 +00:00
Dylan G
0500b24431 Merge pull request #260 from LiquidFenrir/patch-1
Make BUF_TO_READ larger to fix audio in some cases
2022-02-26 15:47:30 +00:00
Dylan G
83071d3734 Merge pull request #250 from LiquidFenrir/shuffle-fix
make shuffle work on consoles that never used it
2022-02-26 15:47:11 +00:00
LiquidFenrir
4e2bea53c1 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
2021-12-22 00:00:42 +01:00
Théo B
0da2594251 Make BUF_TO_READ larger to fix audio in some cases
If the preview .ogg has a samplerate > the BUF_TO_READ constant, there was an out of bounds write to the audio buffers which resulted in crackling.
Thus, upped it to 48000 (0x80-aligned, and pretty much the highest rate anything consumer goes). a bit big, but safe.
2021-09-26 12:10:25 +02:00
Dylan G
c5dc7448e4 Merge pull request #259 from LiquidFenrir/patch-1
Update CONTRIBUTORS.md
2021-08-08 19:05:37 +01:00
Théo B
999b764c26 Update CONTRIBUTORS.md
Might as well add my name there, since it's on the account as well now.
2021-08-08 19:27:36 +02:00
Dylan G
18cb5c616f Correctly handle cases where files already exist on the filesystem.
Also: patched a bug wherein the filename filter was acting up, transforming `file.zip` to `file-zip.zip`, for example.
2021-06-17 01:43:50 +01:00
Dylan G
3f2e4c03f3 Constness 2021-06-17 01:37:33 +01:00
Dylan G
163e12d38a Add issue templates (#249)
* Add issue templates

We've needed this for a while, due to bug reports that don't really mean anything.

* response to feedback
2021-06-14 13:04:33 -04:00
02c3e617ae Handle when content-disposition header is not present 2021-06-14 13:03:57 -04:00
LiquidFenrir
6161874d07 fix a shuffle weirdness (10 - N official ones)
from anemone, when setting a N theme shuffle, then a single theme you could only set 10 - N official themes on shuffle afterwards (and even then, it would fail and reset the theme stuff, leaving you able to set however many official themes on shuffle you wanted)
this skips the failing step
2021-04-03 21:55:52 +02:00
LiquidFenrir
cea9b8655a make shuffle work on consoles that never used it 2021-03-31 15:27:58 +02:00
16 changed files with 564 additions and 105 deletions

39
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,39 @@
---
name: Bug report
about: Found a bug? Report it here
title: ''
labels: ''
assignees: ''
---
**Description**
A clear and concise description of what the bug is.
**Steps to reproduce**
***If you cannot reproduce the bug, please describe what you were doing in as much detail as possible***
*If you were scanning using the QR scanner, ensure you include a link to the theme you were attempting to download.*
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
Add screenshots of any error screens you encountered. Bug reports without screenshots will take longer to solve, so we recommend you get some!
**Crash dumps**
If your bug causes a crash in Luma3DS, please upload any crash dumps generated. You can find these in `SD:/luma/dumps/armX` (`X = 9 or 11`) with the name Luma3DS's exception handlers give you.
**System information**
*System model* e.g. new3DS XL
*System firmware version* e.g. 11.14.0 (this can be found in the System Settings applet by default)
*Anemone3DS version* e.g. v2.2.0 (found in the bottom-left of Anemone3DS)
*Luma3DS version* e.g. v10.2.1 (found in the menu when you hold Select on boot)
**If you are not on the [latest version of Anemone3DS](https://github.com/astronautlevel2/Anemone3DS/releases/latest), your bug report will likely be closed. Ensure the bug occurs in the latest build!**

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,6 +1,6 @@
# Main Contributors # Main Contributors
* Alex Taber ([@astronautlevel2](https://github.com/astronautlevel2)) * Alex Taber ([@astronautlevel2](https://github.com/astronautlevel2))
* [@LiquidFenrir](https://github.com/LiquidFenrir) * Théo B. ([@LiquidFenrir](https://github.com/LiquidFenrir))
* Dawid Eckert ([@daedreth](https://github.com/daedreth)) * Dawid Eckert ([@daedreth](https://github.com/daedreth))
* Dylan G. ([@helloman892](https://github.com/helloman892)) * Dylan G. ([@helloman892](https://github.com/helloman892))
* Nils P. ([@ZetaDesigns](https://github.com/ZetaDesigns)) * Nils P. ([@ZetaDesigns](https://github.com/ZetaDesigns))

View File

@@ -89,7 +89,7 @@ CFLAGS := -g -Wall -Wextra -O2 -mword-relocations \
-ffunction-sections \ -ffunction-sections \
$(ARCH) $(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)),) ifneq ($(strip $(CITRA_MODE)),)
CFLAGS += -DCITRA_MODE CFLAGS += -DCITRA_MODE
endif endif

View File

@@ -56,6 +56,7 @@ typedef enum {
INSTALL_LOADING_REMOTE_BGM, INSTALL_LOADING_REMOTE_BGM,
INSTALL_DUMPING_THEME, INSTALL_DUMPING_THEME,
INSTALL_DUMPING_ALL_THEMES,
INSTALL_NONE, INSTALL_NONE,
} InstallType; } InstallType;
@@ -84,6 +85,7 @@ typedef enum {
TEXT_INSTALL_LOADING_REMOTE_BGM, TEXT_INSTALL_LOADING_REMOTE_BGM,
TEXT_INSTALL_DUMPING_THEME, TEXT_INSTALL_DUMPING_THEME,
TEXT_INSTALL_DUMPING_ALL_THEMES,
// Other text // Other text
TEXT_VERSION, TEXT_VERSION,
@@ -160,7 +162,7 @@ void start_frame(void);
void end_frame(void); void end_frame(void);
void set_screen(C3D_RenderTarget * screen); void set_screen(C3D_RenderTarget * screen);
void throw_error(char* error, ErrorLevel level); void throw_error(const char* error, ErrorLevel level);
bool draw_confirm(const char* conf_msg, Entry_List_s* list); bool draw_confirm(const char* conf_msg, Entry_List_s* list);
void draw_preview(C2D_Image preview, int preview_offset); void draw_preview(C2D_Image preview, int preview_offset);

View File

@@ -29,6 +29,8 @@
#include "common.h" #include "common.h"
#define ILLEGAL_CHARS "><\"?;:/\\+,.|[=]"
extern FS_Archive ArchiveSD; extern FS_Archive ArchiveSD;
extern FS_Archive ArchiveHomeExt; extern FS_Archive ArchiveHomeExt;
extern FS_Archive ArchiveThemeExt; extern FS_Archive ArchiveThemeExt;
@@ -44,5 +46,6 @@ u32 compress_lz_file_fast(FS_Path path, FS_Archive archive, char *in_buf, u32 si
Result buf_to_file(u32 size, FS_Path path, FS_Archive archive, char *buf); Result buf_to_file(u32 size, FS_Path path, FS_Archive archive, char *buf);
void remake_file(FS_Path path, FS_Archive archive, u32 size); void remake_file(FS_Path path, FS_Archive archive, u32 size);
void save_zip_to_sd(char * filename, u32 size, char * buf, EntryMode mode);
#endif #endif

View File

@@ -128,11 +128,11 @@ Instructions_s extra_instructions[3] = {
}, },
{ {
"\uE07B Browse ThemePlaza", "\uE07B Browse ThemePlaza",
"\uE07C Dump Current Theme" NULL
}, },
{ {
"\uE004 Sorting menu", "\uE004 Sorting menu",
NULL "\uE005 Dumping menu"
}, },
{ {
"Exit", "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 #endif

View File

@@ -34,7 +34,7 @@
#include <tremor/ivorbisfile.h> #include <tremor/ivorbisfile.h>
#include <tremor/ivorbiscodec.h> #include <tremor/ivorbiscodec.h>
#define BUF_TO_READ 40960 // How much data should be buffered at a time #define BUF_TO_READ 48000 // How much data should be buffered at a time
typedef struct { typedef struct {
OggVorbis_File vf; OggVorbis_File vf;
@@ -51,4 +51,4 @@ typedef struct {
void play_audio(audio_s *); void play_audio(audio_s *);
#endif #endif

View File

@@ -31,7 +31,7 @@
#include "draw.h" #include "draw.h"
#include <ctype.h> #include <ctype.h>
#define THEMEPLAZA_BASE_URL "https://themeplaza.art" #define THEMEPLAZA_BASE_URL "http://themeplaza.art"
#define THEMEPLAZA_API_URL "/api/anemone/v1" #define THEMEPLAZA_API_URL "/api/anemone/v1"
#define THEMEPLAZA_BASE_API_URL THEMEPLAZA_BASE_URL THEMEPLAZA_API_URL #define THEMEPLAZA_BASE_API_URL THEMEPLAZA_BASE_URL THEMEPLAZA_API_URL

View File

@@ -49,8 +49,9 @@ typedef struct {
u8 _padding1[0x13b8]; u8 _padding1[0x13b8];
ThemeEntry_s theme_entry; ThemeEntry_s theme_entry;
ThemeEntry_s shuffle_themes[MAX_SHUFFLE_THEMES]; ThemeEntry_s shuffle_themes[MAX_SHUFFLE_THEMES];
u8 _padding2[0xb]; u8 shuffle_seedA[0xb];
bool shuffle; u8 shuffle;
u8 shuffle_seedB[0xa];
} SaveData_dat_s; } SaveData_dat_s;
typedef struct { typedef struct {
@@ -75,7 +76,8 @@ Result bgm_install(Entry_s theme);
Result shuffle_install(Entry_List_s themes); 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); void themes_check_installed(void * void_arg);

View File

@@ -391,14 +391,7 @@ bool init_qr(void)
if(mode != MODE_AMOUNT) if(mode != MODE_AMOUNT)
{ {
char path_to_file[0x107] = {0}; save_zip_to_sd(filename, zip_size, zip_buf, mode);
sprintf(path_to_file, "%s%s", main_paths[mode], filename);
char * extension = strrchr(path_to_file, '.');
if (extension == NULL || strcmp(extension, ".zip"))
strcat(path_to_file, ".zip");
remake_file(fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_size);
buf_to_file(zip_size, fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_buf);
success = true; success = true;
} }
else else

View File

@@ -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_PREVIEW], staticBuf, "Downloading preview, please wait...");
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_BGM], staticBuf, "Downloading BGM, 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_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++) for(int i = 0; i < TEXT_AMOUNT; i++)
C2D_TextOptimize(&text[i]); C2D_TextOptimize(&text[i]);
@@ -275,7 +276,7 @@ void draw_base_interface(void)
set_screen(top); set_screen(top);
} }
void throw_error(char* error, ErrorLevel level) void throw_error(const char* error, ErrorLevel level)
{ {
Text bottom_text = TEXT_AMOUNT; Text bottom_text = TEXT_AMOUNT;
Color text_color = COLOR_WHITE; Color text_color = COLOR_WHITE;

View File

@@ -27,6 +27,7 @@
#include <strings.h> #include <strings.h>
#include "fs.h" #include "fs.h"
#include "draw.h"
#include "unicode.h" #include "unicode.h"
#include <archive.h> #include <archive.h>
@@ -351,3 +352,115 @@ void remake_file(FS_Path path, FS_Archive archive, u32 size)
buf_to_file(size, path, archive, buf); buf_to_file(size, path, archive, buf);
free(buf); free(buf);
} }
static SwkbdCallbackResult fat32filter(void *user, const char **ppMessage, const char *text, size_t textlen)
{
(void)textlen;
(void)user;
*ppMessage = "Input must not contain:\n" ILLEGAL_CHARS;
if(strpbrk(text, ILLEGAL_CHARS))
{
DEBUG("illegal filename: %s\n", text);
return SWKBD_CALLBACK_CONTINUE;
}
return SWKBD_CALLBACK_OK;
}
// assumes the input buffer is a ZIP. if it isn't, why are you calling this?
void save_zip_to_sd(char * filename, u32 size, char * buf, EntryMode mode)
{
static char path_to_file[32761]; // FAT32 paths can be quite long.
const int max_chars = 250;
char new_filename[max_chars + 5]; // .zip + \0
renamed:
sprintf(path_to_file, "%s%s", main_paths[mode], filename);
// filter out characters illegal in FAT32 filenames
char * curr_filename = strrchr(path_to_file, '/') + 1;
char * illegal_char = curr_filename;
while ((illegal_char = strpbrk(illegal_char, ILLEGAL_CHARS)))
{
DEBUG("Illegal char found in filename: %c\n", *illegal_char);
if (*illegal_char == '.')
{
// skip initial . (this is allowed)
if (illegal_char == curr_filename)
continue;
// skip extension delimiter
if (strpbrk(illegal_char + 1, ".") == NULL)
{
illegal_char++;
continue;
}
}
*illegal_char = '-';
}
// ensure the extension is .zip
char * extension = strrchr(path_to_file, '.');
if (extension == NULL || strcmp(extension, ".zip"))
strcat(path_to_file, ".zip");
DEBUG("path: %s\n", path_to_file);
FS_Path path = fsMakePath(PATH_ASCII, path_to_file);
// check if file already exists, and if it does, prompt the user
// to overwrite or change name (or exit)
Result res = FSUSER_CreateFile(ArchiveSD, path, 0, size);
if (R_FAILED(res))
{
if (res == (long)0xC82044BE)
{
DEBUG("File already exists\n");
SwkbdState swkbd;
swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 3, max_chars / 2);
swkbdSetHintText(&swkbd, "Choose a new filename or tap Overwrite");
swkbdSetFeatures(&swkbd, SWKBD_PREDICTIVE_INPUT | SWKBD_DARKEN_TOP_SCREEN);
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_MIDDLE, "Overwrite", false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Rename", true);
swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, SWKBD_FILTER_CALLBACK, -1);
swkbdSetFilterCallback(&swkbd, &fat32filter, NULL);
SwkbdButton button = swkbdInputText(&swkbd, new_filename, max_chars);
switch (button)
{
case SWKBD_BUTTON_RIGHT:
DEBUG("Renaming to %s\n", new_filename);
strcat(new_filename, ".zip");
filename = new_filename;
goto renamed;
case SWKBD_BUTTON_MIDDLE:
// we good
DEBUG("Overwriting %s\n", filename);
break;
case SWKBD_BUTTON_LEFT:
// do nothing
DEBUG("File rename cancelled\n");
return;
case SWKBD_BUTTON_NONE:
DEBUG("SWKBD broke wtf??? :- %x\n", swkbdGetResult(&swkbd));
return throw_error("???\nTry a USB keyboard", ERROR_LEVEL_WARNING);
}
}
else if (res == (long)0xC86044D2)
{
DEBUG("SD card is full\n");
return throw_error("SD card is full.\nDelete some themes to make space.", ERROR_LEVEL_WARNING);
}
else
{
DEBUG("error: %lx\n", res);
return throw_error("FS Error:\nGet a new SD card.", ERROR_LEVEL_ERROR);
}
}
DEBUG("Saving to SD: %s\n", path_to_file);
remake_file(path, ArchiveSD, size);
buf_to_file(size, path, ArchiveSD, buf);
}

View File

@@ -401,8 +401,8 @@ int main(void)
{ {
if(key_l) if(key_l)
index = 0; index = 0;
// else if(key_r) // uncomment when we use the right menu. we don't for now else if(key_r) // uncomment when we use the right menu. we don't for now
// index = 2; index = 2;
} }
instructions = extra_instructions[index]; instructions = extra_instructions[index];
} }
@@ -648,13 +648,6 @@ int main(void)
{ {
load_icons_first(current_list, false); 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) else if(key_l)
{ {
@@ -677,6 +670,23 @@ int main(void)
load_icons_first(current_list, false); 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; continue;
} }

View File

@@ -24,6 +24,8 @@
* reasonable ways as different from the original version. * reasonable ways as different from the original version.
*/ */
#include <ctype.h>
#include "remote.h" #include "remote.h"
#include "loading.h" #include "loading.h"
#include "fs.h" #include "fs.h"
@@ -358,17 +360,8 @@ static void download_remote_entry(Entry_s * entry, EntryMode mode)
} }
free(download_url); free(download_url);
char path_to_file[0x107] = { 0 }; save_zip_to_sd(filename, zip_size, zip_buf, mode);
sprintf(path_to_file, "%s%s", main_paths[mode], filename);
free(filename); free(filename);
char * extension = strrchr(path_to_file, '.');
if (extension == NULL || strcmp(extension, ".zip"))
strcat(path_to_file, ".zip");
DEBUG("Saving to SD: %s\n", path_to_file);
remake_file(fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_size);
buf_to_file(zip_size, fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_buf);
free(zip_buf); free(zip_buf);
} }
@@ -759,7 +752,6 @@ typedef enum ParseResult
SUCCESS, // 200/203 (203 indicates a successful request with a transformation applied by a proxy) SUCCESS, // 200/203 (203 indicates a successful request with a transformation applied by a proxy)
REDIRECT, // 301/302/307/308 REDIRECT, // 301/302/307/308
HTTPC_ERROR, HTTPC_ERROR,
ABORTED,
SERVER_IS_MISBEHAVING, SERVER_IS_MISBEHAVING,
SEE_OTHER = 303, // Theme Plaza returns these SEE_OTHER = 303, // Theme Plaza returns these
HTTP_UNAUTHORIZED = 401, HTTP_UNAUTHORIZED = 401,
@@ -778,18 +770,45 @@ typedef enum ParseResult
HTTP_GATEWAY_TIMEOUT = 504, HTTP_GATEWAY_TIMEOUT = 504,
} ParseResult; } ParseResult;
static SwkbdCallbackResult fat32filter(void *user, const char **ppMessage, const char *text, size_t textlen) // unescapes URL-encoded string non-destructively
static char * unescape(const char * url)
{ {
(void)textlen; char * unescaped = malloc(strlen(url) + 1);
(void)user; // source shamelessly ripped from https://stackoverflow.com/a/14530993/4073959
*ppMessage = "Input must not contain:\n><\"?;:/\\+,.|[=]"; char a;
if(strpbrk(text, "><\"?;:/\\+,.|[=]")) char b;
while (*url)
{ {
DEBUG("illegal filename: %s\n", text); if ((*url == '%') &&
return SWKBD_CALLBACK_CONTINUE; ((a = url[1]) && (b = url[2])) &&
(isxdigit(a) && isxdigit(b))) {
if (a >= 'a')
a -= 'a' - 'A';
if (a >= 'A')
a -= ('A' - 10);
else
a -= '0';
if (b >= 'a')
b -= 'a' - 'A';
if (b >= 'A')
b -= ('A' - 10);
else
b -= '0';
*unescaped++ = 16 * a + b;
url += 3;
}
else if (*url == '+')
{
*unescaped++ = ' ';
url++;
}
else
{
*unescaped++ = *url++;
}
} }
*unescaped++ = '\0';
return SWKBD_CALLBACK_OK; return unescaped;
} }
// the good paths for this function return SUCCESS, ABORTED, or REDIRECT; // the good paths for this function return SUCCESS, ABORTED, or REDIRECT;
@@ -852,64 +871,52 @@ static ParseResult parse_header(struct header * out, httpcContext * context, con
if (out->filename) if (out->filename)
{ {
bool present = 1;
out->result_code = httpcGetResponseHeader(context, "Content-Disposition", content_buf, 1024); out->result_code = httpcGetResponseHeader(context, "Content-Disposition", content_buf, 1024);
if (R_FAILED(out->result_code)) if (R_FAILED(out->result_code))
{ {
DEBUG("httpcGetResponseHeader\n"); if (out->result_code == (long)0xD8A0A028L)
return HTTPC_ERROR; present = 0;
else
{
DEBUG("httpcGetResponseHeader\n");
return HTTPC_ERROR;
}
} }
// content_buf: Content-Disposition: attachment; ... filename=<filename>;? ... // content_buf: Content-Disposition: attachment; ... filename=<filename>;? ...
char * filename = strstr(content_buf, "filename="); // filename=<filename>;? ... if (present)
if (!filename)
{ {
const int max_chars = 250; char * filename = strstr(content_buf, "filename="); // filename=<filename>;? ...
// needs to be heap allocated only because the call site is expected to free it // in the extreme fringe case that filename is missing:
*out->filename = malloc(max_chars + 5); // + .zip and the null term if (filename != NULL)
SwkbdState swkbd;
swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 2, max_chars / 2);
swkbdSetHintText(&swkbd, "Choose a filename");
swkbdSetFeatures(&swkbd, SWKBD_PREDICTIVE_INPUT | SWKBD_DARKEN_TOP_SCREEN);
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Download", true);
swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, SWKBD_FILTER_CALLBACK, -1);
swkbdSetFilterCallback(&swkbd, &fat32filter, NULL);
SwkbdButton button = swkbdInputText(&swkbd, *out->filename, max_chars);
if (button != SWKBD_BUTTON_CONFIRM)
{ {
out->result_code = swkbdGetResult(&swkbd); filename = strpbrk(filename, "=") + 1; // <filename>;?
return ABORTED; char * end = strpbrk(filename, ";");
if (end)
*end = '\0'; // <filename>
// safe to assume the filename is quoted
// (if it isn't, then we already have a null-terminated string <filename>)
if (filename[0] == '"')
{
filename[strlen(filename) - 1] = '\0';
filename++;
}
*out->filename = malloc(strlen(filename) + 1);
strcpy(*out->filename, filename);
}
else
{
*out->filename = NULL;
} }
strcat(*out->filename, ".zip");
return SUCCESS;
} }
else
filename = strpbrk(filename, "=") + 1; // <filename>;?
char * end = strpbrk(filename, ";");
if (end)
*end = '\0'; // <filename>
if (filename[0] == '"')
// safe to assume the filename is quoted
{ {
filename[strlen(filename) - 1] = '\0'; *out->filename = NULL;
filename++;
} }
char * illegal_char;
// filter out characters illegal in FAT32 filenames
while ((illegal_char = strpbrk(filename, "><\"?;:/\\+,.|[=]")))
*illegal_char = '-';
*out->filename = malloc(strlen(filename) + 1);
strcpy(*out->filename, filename);
} }
return SUCCESS; return SUCCESS;
} }
@@ -961,11 +968,6 @@ redirect: // goto here if we need to redirect
{ {
case SUCCESS: case SUCCESS:
break; break;
case ABORTED:
ret = httpcCloseContext(&context);
if(R_FAILED(ret))
return ret;
return MAKERESULT(RL_SUCCESS, RS_CANCELED, RM_APPLICATION, RD_CANCEL_REQUESTED);
case HTTPC_ERROR: case HTTPC_ERROR:
DEBUG("httpc error %lx\n", _header.result_code); DEBUG("httpc error %lx\n", _header.result_code);
snprintf(err_buf, ERROR_BUFFER_SIZE, "Error in HTTPC sysmodule - 0x%08lx.\nIf you are seeing this, please contact an\nAnemone developer on the Theme Plaza Discord.", _header.result_code); snprintf(err_buf, ERROR_BUFFER_SIZE, "Error in HTTPC sysmodule - 0x%08lx.\nIf you are seeing this, please contact an\nAnemone developer on the Theme Plaza Discord.", _header.result_code);
@@ -1130,6 +1132,15 @@ no_error:;
*buf = new_buf; *buf = new_buf;
DEBUG("size: %lu\n", *size); DEBUG("size: %lu\n", *size);
if (filename) { DEBUG("filename: %s\n", *filename); } if (filename) {
if (*filename == NULL)
{
// Content-Disposition extraction failed somehow
char * tmp = unescape(url);
*filename = strdup(basename(tmp));
free(tmp);
}
DEBUG("filename: %s\n", *filename);
}
return MAKERESULT(RL_SUCCESS, RS_SUCCESS, RM_APPLICATION, RD_SUCCESS); return MAKERESULT(RL_SUCCESS, RS_SUCCESS, RM_APPLICATION, RD_SUCCESS);
} }

View File

@@ -261,18 +261,24 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
SaveData_dat_s* savedata = (SaveData_dat_s*)savedata_buf; SaveData_dat_s* savedata = (SaveData_dat_s*)savedata_buf;
memset(&savedata->theme_entry, 0, sizeof(ThemeEntry_s)); memset(&savedata->theme_entry, 0, sizeof(ThemeEntry_s));
savedata->theme_entry.type = 3;
savedata->theme_entry.index = 0xff;
savedata->shuffle = (installmode & THEME_INSTALL_SHUFFLE); savedata->shuffle = (installmode & THEME_INSTALL_SHUFFLE) ? 1 : 0;
memset(savedata->shuffle_themes, 0, sizeof(ThemeEntry_s)*MAX_SHUFFLE_THEMES);
if(installmode & THEME_INSTALL_SHUFFLE) if(installmode & THEME_INSTALL_SHUFFLE)
{ {
memset(savedata->shuffle_themes, 0, sizeof(ThemeEntry_s)*MAX_SHUFFLE_THEMES);
for(int i = 0; i < themes.shuffle_count; i++) for(int i = 0; i < themes.shuffle_count; i++)
{ {
savedata->shuffle_themes[i].type = 3; savedata->shuffle_themes[i].type = 3;
savedata->shuffle_themes[i].index = i; savedata->shuffle_themes[i].index = i;
} }
const u8 shuffle_seed[0xB] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
memcpy(savedata->shuffle_seedA, shuffle_seed, 0xB);
memcpy(savedata->shuffle_seedB, shuffle_seed, 0xA);
}
else
{
savedata->theme_entry.type = 3;
savedata->theme_entry.index = 0xff;
} }
res = buf_to_file(savedata_size, fsMakePath(PATH_ASCII, "/SaveData.dat"), ArchiveHomeExt, savedata_buf); res = buf_to_file(savedata_size, fsMakePath(PATH_ASCII, "/SaveData.dat"), ArchiveHomeExt, savedata_buf);
@@ -328,7 +334,7 @@ dir_name_callback(void *data, const char ** ppMessage, const char * text, size_t
return SWKBD_CALLBACK_OK; return SWKBD_CALLBACK_OK;
} }
Result dump_theme(void) Result dump_current_theme(void)
{ {
const int max_chars = 255; const int max_chars = 255;
char * output_dir = calloc(max_chars + 1, sizeof(char)); char * output_dir = calloc(max_chars + 1, sizeof(char));
@@ -415,6 +421,244 @@ Result dump_theme(void)
return 0; return 0;
} }
Result dump_all_themes(void)
{
const u32 high_id = 0x0004008c;
u32 low_id = 0;
u8 regionCode, language;
Result res = CFGU_SecureInfoGetRegion(&regionCode);
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) void themes_check_installed(void * void_arg)
{ {
Thread_Arg_s * arg = (Thread_Arg_s *)void_arg; Thread_Arg_s * arg = (Thread_Arg_s *)void_arg;