31 Commits

Author SHA1 Message Date
Dylan G
adccc70cca Fixed useless 404 error when a remote preview has no BGM 2022-07-31 20:41:22 +01:00
Dylan G
b66ca12039 Merge pull request #272 from LiquidFenrir/slight-music-rework
make audio safer
2022-06-17 23:05:22 +01:00
LiquidFenrir
4a56a883fa make audio safer
- centralized stop function
- freeing the struct not from the thread while waiting on handle in it
- thread not detached
- maybe fixes hang on exit from HM in ndsp status check loop
2022-06-14 13:02:15 +02:00
Dylan G
9ebfe387a0 Merge pull request #270 from LiquidFenrir/exit-fixes
fix multiple crashes
2022-06-10 23:07:34 +01:00
LiquidFenrir
806d0033de fix multiple crashes
on exit:
- bad timing when the install checks threads run, could crash
- quitting through HOME when a bgm was previewed in the browser
on http get failure:
- the quit flag would be enabled, but the browser wouldn't honour it
2022-06-05 11:49:09 +02:00
Dylan G
4c053bb447 Merge pull request #269 from LiquidFenrir/patch-2
fix dumping official body and bgm, and double free
2022-06-03 20:46:14 +01:00
Théo B
1ed6c46644 fix dumping official body and bgm, and double free
fixes #268
2022-06-03 21:40:23 +02:00
Dylan G
e22d09ba54 Typo in README
a stray backslash found its way into the first line
2022-05-30 14:24:23 +01:00
Dylan G
a0c16a64ec Search strings now escaped properly (fixes #267) 2022-05-30 12:04:18 +01:00
Dylan G
a1e7ed9924 Added header-only URL encoding/decoding 2022-05-30 12:03:11 +01:00
Dylan G
7f7fdc010a Fixed faulty FS error when a filename contains a / 2022-05-30 10:43:12 +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
Dylan G
7745530764 Fixed 4720a499 (TP returns 303 currently, not 404) => handle 303 well(?); pushed some code around 2021-03-11 13:47:24 +00:00
Dylan G
0bab0f6700 Patched httpc error message to actually be readable on console; also provides Result code 2021-03-11 12:34:03 +00:00
Dylan G
38ab370cf1 Merge pull request #247 from KennLDN/patch-1
Fix broken link in CONTRIBUTORS.md
2021-02-08 13:40:16 +00:00
kenn
5cea6c8df4 Fix broken link in CONTRIBUTORS.md 2021-02-08 13:38:32 +00:00
Alex Taber
e4e0118c1a Prevent out of bounds scrolling in the browser 2021-01-01 23:51:51 -05:00
Dylan G
accdaaed2a Reduced code duplication 2020-12-31 21:28:10 +00:00
20 changed files with 743 additions and 197 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,10 +1,10 @@
# 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))
* Matt Kenny ([@8kenn](https://github.com/8kenn)) * Matt Kenny ([@KennLDN](https://github.com/KennLDN))
# Minor Contributors # Minor Contributors
* Nic ([@Wizzrobes](https://github.com/Wizzrobes)) * Nic ([@Wizzrobes](https://github.com/Wizzrobes))

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

@@ -1,6 +1,6 @@
![# Anemone3DS](https://github.com/astronautlevel2/Anemone3DS/blob/master/meta/banner.png) ![# Anemone3DS](https://github.com/astronautlevel2/Anemone3DS/blob/master/meta/banner.png)
A Theme and Splashscreen Manager for the Nintendo 3DS, written in C.\ A Theme and Splashscreen Manager for the Nintendo 3DS, written in C.
# Dependencies # Dependencies
* devkitARM, which can be installed following the instructions [here](https://devkitpro.org/wiki/Getting_Started). * devkitARM, which can be installed following the instructions [here](https://devkitpro.org/wiki/Getting_Started).

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;
@@ -46,9 +46,10 @@ typedef struct {
u32 filesize; u32 filesize;
volatile bool stop; volatile bool stop;
Handle finished; Thread playing_thread;
} audio_s; } audio_s;
void play_audio(audio_s *); void play_audio(audio_s *);
void stop_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);

88
include/urls.h Normal file
View File

@@ -0,0 +1,88 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2020 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 <http://www.gnu.org/licenses/>.
*
* 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.
*/
static char from_hex(char c)
{
return isdigit(c) ? c - '0' : tolower(c) - 'a' + 10;
}
static char to_hex(char code)
{
static char hex[] = "0123456789abcdef";
return hex[code & 15];
}
// ensure caller frees
char * url_escape(char * url)
{
char * ptr = url;
char * buf = malloc(3 * strlen(url) + 1);
char * ptr_buf = buf;
while (*ptr)
{
if (isalnum((int)*ptr) || strchr("-_.~", *ptr))
*ptr_buf++ = *ptr;
else if (*ptr == ' ')
*ptr_buf++ = '+';
else
{
*ptr_buf++ = '%';
*ptr_buf++ = to_hex(*ptr >> 4);
*ptr_buf++ = to_hex(*ptr & 15);
}
ptr++;
}
*ptr_buf = '\0';
return buf;
}
// ensure caller frees
char * url_unescape(char * url)
{
char * ptr = url;
char * buf = malloc(strlen(url) + 1);
char * ptr_buf = buf;
while (*ptr)
{
if (*ptr == '%')
{
if (ptr[1] && ptr[2])
{
*ptr_buf++ = from_hex(ptr[1]) << 4 | from_hex(ptr[2]);
ptr += 2;
}
}
else if (*ptr == '+')
*ptr_buf++ = ' ';
else
*ptr_buf++ = *ptr;
ptr++;
}
*ptr_buf = '\0';
return buf;
}

View File

@@ -340,7 +340,7 @@ bool init_qr(void)
free(zip_buf); free(zip_buf);
return false; return false;
} }
else if (R_DESCRIPTION(res) == RD_CANCEL_REQUESTED) else if (R_DESCRIPTION(res) == RD_NO_DATA || R_DESCRIPTION(res) == RD_CANCEL_REQUESTED)
{ {
free(filename); free(filename);
return true; return true;
@@ -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;
@@ -301,7 +302,7 @@ void throw_error(char* error, ErrorLevel level)
draw_base_interface(); draw_base_interface();
draw_text_center(GFX_TOP, 100, 0.5f, 0.6f, 0.6f, colors[text_color], error); draw_text_center(GFX_TOP, 100, 0.5f, 0.6f, 0.6f, colors[text_color], error);
draw_c2d_text_center(GFX_TOP, 150, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], &text[bottom_text]); draw_c2d_text_center(GFX_TOP, 170, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], &text[bottom_text]);
end_frame(); end_frame();
if(kDown & KEY_A) break; if(kDown & KEY_A) break;

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 = path_to_file + strlen(main_paths[mode]);
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

@@ -608,7 +608,6 @@ Result load_audio(Entry_s entry, audio_s *audio)
} }
audio->mix[0] = audio->mix[1] = 1.0f; // Determines volume for the 12 (?) different outputs. See http://smealum.github.io/ctrulib/channel_8h.html#a30eb26f1972cc3ec28370263796c0444 audio->mix[0] = audio->mix[1] = 1.0f; // Determines volume for the 12 (?) different outputs. See http://smealum.github.io/ctrulib/channel_8h.html#a30eb26f1972cc3ec28370263796c0444
svcCreateEvent(&audio->finished, RESET_STICKY);
ndspChnSetInterp(0, NDSP_INTERP_LINEAR); ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
ndspChnSetMix(0, audio->mix); // See mix comment above ndspChnSetMix(0, audio->mix); // See mix comment above

View File

@@ -37,7 +37,7 @@
bool quit = false; bool quit = false;
bool dspfirm = false; bool dspfirm = false;
audio_s * audio = NULL; static audio_s * audio = NULL;
static bool homebrew = false; static bool homebrew = false;
static bool installed_themes = false; static bool installed_themes = false;
@@ -109,6 +109,12 @@ static void stop_install_check(void)
{ {
installCheckThreads_arg[i].run_thread = false; installCheckThreads_arg[i].run_thread = false;
} }
for(int i = 0; i < MODE_AMOUNT; i++)
{
threadJoin(installCheckThreads[i], U64_MAX);
threadFree(installCheckThreads[i]);
installCheckThreads[i] = NULL;
}
} }
static void exit_thread(void) static void exit_thread(void)
@@ -121,6 +127,7 @@ static void exit_thread(void)
svcWaitSynchronization(update_icons_mutex, U64_MAX); svcWaitSynchronization(update_icons_mutex, U64_MAX);
threadJoin(iconLoadingThread, U64_MAX); threadJoin(iconLoadingThread, U64_MAX);
threadFree(iconLoadingThread); threadFree(iconLoadingThread);
iconLoadingThread = NULL;
} }
} }
@@ -138,6 +145,7 @@ static void free_icons(Entry_List_s * list)
free(list->icons[i]); free(list->icons[i]);
} }
free(list->icons); free(list->icons);
list->icons = NULL;
} }
void free_lists(void) void free_lists(void)
@@ -157,8 +165,7 @@ void exit_function(bool power_pressed)
{ {
if(audio) if(audio)
{ {
audio->stop = true; stop_audio(&audio);
svcWaitSynchronization(audio->finished, U64_MAX);
} }
free_lists(); free_lists();
svcCloseHandle(update_icons_mutex); svcCloseHandle(update_icons_mutex);
@@ -230,7 +237,7 @@ static void load_lists(Entry_List_s * lists)
if(install_check_function != NULL) if(install_check_function != NULL)
{ {
installCheckThreads[i] = threadCreate(install_check_function, current_arg, __stacksize__, 0x3f, -2, true); installCheckThreads[i] = threadCreate(install_check_function, current_arg, __stacksize__, 0x3f, -2, false);
svcSleepThread(1e8); svcSleepThread(1e8);
} }
} }
@@ -401,8 +408,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];
} }
@@ -504,9 +511,7 @@ int main(void)
preview_mode = false; preview_mode = false;
if(current_mode == MODE_THEMES && audio) if(current_mode == MODE_THEMES && audio)
{ {
audio->stop = true; stop_audio(&audio);
svcWaitSynchronization(audio->finished, U64_MAX);
audio = NULL;
} }
} }
continue; continue;
@@ -516,9 +521,7 @@ int main(void)
preview_mode = false; preview_mode = false;
if(current_mode == MODE_THEMES && audio) if(current_mode == MODE_THEMES && audio)
{ {
audio->stop = true; stop_audio(&audio);
svcWaitSynchronization(audio->finished, U64_MAX);
audio = NULL;
} }
continue; continue;
} }
@@ -648,13 +651,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 +673,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

@@ -69,17 +69,26 @@ void thread_audio(void* data) {
while(!audio->stop) { while(!audio->stop) {
update_audio(audio); update_audio(audio);
} }
free(audio->filebuf); ndspChnWaveBufClear(0);
ndspChnReset(0);
ov_clear(&audio->vf); ov_clear(&audio->vf);
free(audio->filebuf);
linearFree((void*)audio->wave_buf[0].data_vaddr); linearFree((void*)audio->wave_buf[0].data_vaddr);
linearFree((void*)audio->wave_buf[1].data_vaddr); linearFree((void*)audio->wave_buf[1].data_vaddr);
while (audio->wave_buf[0].status != NDSP_WBUF_DONE || audio->wave_buf[1].status != NDSP_WBUF_DONE) svcSleepThread(1e7);
svcSignalEvent(audio->finished);
svcSleepThread(1e8);
svcCloseHandle(audio->finished);
free(audio);
} }
void play_audio(audio_s *audio) { void play_audio(audio_s *audio) {
threadCreate(thread_audio, audio, 0x1000, 0x3F, 1, true); audio->playing_thread = threadCreate(thread_audio, audio, 0x1000, 0x3F, 1, false);
}
void stop_audio(audio_s** audio_ptr) {
audio_s* audio = *audio_ptr;
if(audio->playing_thread)
{
audio->stop = true;
threadJoin(audio->playing_thread, U64_MAX);
threadFree(audio->playing_thread);
}
free(audio);
*audio_ptr = NULL;
} }

View File

@@ -24,11 +24,18 @@
* 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"
#include "unicode.h" #include "unicode.h"
#include "music.h" #include "music.h"
#include "urls.h"
// forward declaration of special case used only here
// TODO: replace this travesty with a proper handler
static Result http_get_with_not_found_flag(const char * url, char ** filename, char ** buf, u32 * size, InstallType install_type, const char * acceptable_mime_types, bool not_found_is_error);
static Instructions_s browser_instructions[MODE_AMOUNT] = { static Instructions_s browser_instructions[MODE_AMOUNT] = {
{ {
@@ -250,7 +257,7 @@ static void load_remote_list(Entry_List_s * list, json_int_t page, EntryMode mod
json_decref(root); json_decref(root);
} }
else else
throw_error("Couldn't download ThemePlaza data.\nMake sure WiFi is on.", ERROR_LEVEL_WARNING); throw_error("Couldn't download Theme Plaza data.\nMake sure WiFi is on.", ERROR_LEVEL_WARNING);
free(page_json); free(page_json);
} }
@@ -324,10 +331,13 @@ static void load_remote_bgm(Entry_s * entry)
draw_install(INSTALL_LOADING_REMOTE_BGM); draw_install(INSTALL_LOADING_REMOTE_BGM);
Result res = http_get(bgm_url, NULL, &bgm_ogg, &bgm_size, INSTALL_LOADING_REMOTE_BGM, "application/ogg, audio/ogg"); Result res = http_get_with_not_found_flag(bgm_url, NULL, &bgm_ogg, &bgm_size, INSTALL_LOADING_REMOTE_BGM, "application/ogg, audio/ogg", false);
free(bgm_url); free(bgm_url);
if (R_FAILED(res)) if (R_FAILED(res))
return; return;
// if bgm doesn't exist on the server
if (R_SUMMARY(res) == RS_NOTFOUND && R_MODULE(res) == RM_FILE_SERVER)
return;
u16 path[0x107] = { 0 }; u16 path[0x107] = { 0 };
strucat(path, entry->path); strucat(path, entry->path);
@@ -358,17 +368,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);
} }
@@ -443,18 +444,11 @@ static void search_menu(Entry_List_s * list)
if (button == SWKBD_BUTTON_CONFIRM) if (button == SWKBD_BUTTON_CONFIRM)
{ {
free(list->tp_search); free(list->tp_search);
for (unsigned int i = 0; i < strlen(search); i++) list->tp_search = url_escape(search);
{ DEBUG("Search escaped: %s -> %s\n", search, list->tp_search);
if (search[i] == ' ')
search[i] = '+';
}
list->tp_search = search;
load_remote_list(list, 1, list->mode, false); load_remote_list(list, 1, list->mode, false);
} }
else
{
free(search); free(search);
}
} }
static void change_selected(Entry_List_s * list, int change_value) static void change_selected(Entry_List_s * list, int change_value)
@@ -469,6 +463,7 @@ static void change_selected(Entry_List_s * list, int change_value)
newval += list->entries_per_screen_h; newval += list->entries_per_screen_h;
if (newval / list->entries_per_screen_h != list->selected_entry / list->entries_per_screen_h) if (newval / list->entries_per_screen_h != list->selected_entry / list->entries_per_screen_h)
newval += list->entries_per_screen_h * (-change_value); newval += list->entries_per_screen_h * (-change_value);
newval %= list->entries_count;
} }
else else
{ {
@@ -495,7 +490,7 @@ bool themeplaza_browser(EntryMode mode)
bool extra_mode = false; bool extra_mode = false;
while (aptMainLoop()) while (aptMainLoop() && !quit)
{ {
if (current_list->entries == NULL) if (current_list->entries == NULL)
break; break;
@@ -523,12 +518,6 @@ bool themeplaza_browser(EntryMode mode)
exit: exit:
quit = true; quit = true;
downloaded = false; downloaded = false;
if (audio)
{
audio->stop = true;
svcWaitSynchronization(audio->finished, U64_MAX);
audio = NULL;
}
break; break;
} }
@@ -587,9 +576,7 @@ bool themeplaza_browser(EntryMode mode)
preview_mode = false; preview_mode = false;
if (mode == MODE_THEMES && audio != NULL) if (mode == MODE_THEMES && audio != NULL)
{ {
audio->stop = true; stop_audio(&audio);
svcWaitSynchronization(audio->finished, U64_MAX);
audio = NULL;
} }
} }
} }
@@ -600,9 +587,7 @@ bool themeplaza_browser(EntryMode mode)
preview_mode = false; preview_mode = false;
if (mode == MODE_THEMES && audio != NULL) if (mode == MODE_THEMES && audio != NULL)
{ {
audio->stop = true; stop_audio(&audio);
svcWaitSynchronization(audio->finished, U64_MAX);
audio = NULL;
} }
} }
else else
@@ -668,9 +653,7 @@ bool themeplaza_browser(EntryMode mode)
preview_mode = false; preview_mode = false;
if (mode == MODE_THEMES && audio) if (mode == MODE_THEMES && audio)
{ {
audio->stop = true; stop_audio(&audio);
svcWaitSynchronization(audio->finished, U64_MAX);
audio = NULL;
} }
continue; continue;
} }
@@ -737,6 +720,11 @@ bool themeplaza_browser(EntryMode mode)
} }
} }
if (audio)
{
stop_audio(&audio);
}
free_preview(preview); free_preview(preview);
free_icons(current_list); free_icons(current_list);
@@ -756,10 +744,10 @@ typedef struct header
typedef enum ParseResult 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/303/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
HTTP_UNAUTHORIZED = 401, HTTP_UNAUTHORIZED = 401,
HTTP_FORBIDDEN = 403, HTTP_FORBIDDEN = 403,
HTTP_NOT_FOUND = 404, HTTP_NOT_FOUND = 404,
@@ -776,7 +764,7 @@ 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) /*static SwkbdCallbackResult fat32filter(void *user, const char **ppMessage, const char *text, size_t textlen)
{ {
(void)textlen; (void)textlen;
(void)user; (void)user;
@@ -788,7 +776,7 @@ static SwkbdCallbackResult fat32filter(void *user, const char **ppMessage, const
} }
return SWKBD_CALLBACK_OK; return SWKBD_CALLBACK_OK;
} }*/
// the good paths for this function return SUCCESS, ABORTED, or REDIRECT; // the good paths for this function return SUCCESS, ABORTED, or REDIRECT;
// all other paths are failures // all other paths are failures
@@ -850,65 +838,53 @@ 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))
{
if (out->result_code == (long)0xD8A0A028L)
present = 0;
else
{ {
DEBUG("httpcGetResponseHeader\n"); DEBUG("httpcGetResponseHeader\n");
return HTTPC_ERROR; return HTTPC_ERROR;
} }
}
// content_buf: Content-Disposition: attachment; ... filename=<filename>;? ... // content_buf: Content-Disposition: attachment; ... filename=<filename>;? ...
if (present)
{
char * filename = strstr(content_buf, "filename="); // filename=<filename>;? ... char * filename = strstr(content_buf, "filename="); // filename=<filename>;? ...
if (!filename) // in the extreme fringe case that filename is missing:
if (filename != NULL)
{ {
const int max_chars = 250;
// needs to be heap allocated only because the call site is expected to free it
*out->filename = malloc(max_chars + 5); // + .zip and the null term
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);
return ABORTED;
}
strcat(*out->filename, ".zip");
return SUCCESS;
}
filename = strpbrk(filename, "=") + 1; // <filename>;? filename = strpbrk(filename, "=") + 1; // <filename>;?
char * end = strpbrk(filename, ";"); char * end = strpbrk(filename, ";");
if (end) if (end)
*end = '\0'; // <filename> *end = '\0'; // <filename>
if (filename[0] == '"')
// safe to assume the filename is quoted // 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[strlen(filename) - 1] = '\0';
filename++; filename++;
} }
char * illegal_char;
// filter out characters illegal in FAT32 filenames
while ((illegal_char = strpbrk(filename, "><\"?;:/\\+,.|[=]")))
*illegal_char = '-';
*out->filename = malloc(strlen(filename) + 1); *out->filename = malloc(strlen(filename) + 1);
strcpy(*out->filename, filename); strcpy(*out->filename, filename);
} }
else
{
*out->filename = NULL;
}
}
else
{
*out->filename = NULL;
}
}
return SUCCESS; return SUCCESS;
} }
@@ -918,6 +894,11 @@ static ParseResult parse_header(struct header * out, httpcContext * context, con
* call example: written = http_get("url", &filename, &buffer_to_download_to, &filesize, INSTALL_DOWNLOAD, "application/json"); * call example: written = http_get("url", &filename, &buffer_to_download_to, &filesize, INSTALL_DOWNLOAD, "application/json");
*/ */
Result http_get(const char * url, char ** filename, char ** buf, u32 * size, InstallType install_type, const char * acceptable_mime_types) Result http_get(const char * url, char ** filename, char ** buf, u32 * size, InstallType install_type, const char * acceptable_mime_types)
{
return http_get_with_not_found_flag(url, filename, buf, size, install_type, acceptable_mime_types, true);
}
static Result http_get_with_not_found_flag(const char * url, char ** filename, char ** buf, u32 * size, InstallType install_type, const char * acceptable_mime_types, bool not_found_is_error)
{ {
Result ret; Result ret;
httpcContext context; httpcContext context;
@@ -952,17 +933,32 @@ redirect: // goto here if we need to redirect
return ret; return ret;
} }
char err_buf[0x69]; #define ERROR_BUFFER_SIZE 0x80
char err_buf[ERROR_BUFFER_SIZE];
Result res;
ParseResult parse = parse_header(&_header, &context, acceptable_mime_types); ParseResult parse = parse_header(&_header, &context, acceptable_mime_types);
switch (parse) switch (parse)
{ {
case SUCCESS: case SUCCESS:
break; break;
case ABORTED: case HTTPC_ERROR:
ret = httpcCloseContext(&context); DEBUG("httpc error %lx\n", _header.result_code);
if(R_FAILED(ret)) 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);
return ret; throw_error(err_buf, ERROR_LEVEL_ERROR);
return MAKERESULT(RL_SUCCESS, RS_CANCELED, RM_APPLICATION, RD_CANCEL_REQUESTED); quit = true;
httpcCloseContext(&context);
return _header.result_code;
case SEE_OTHER:
if (strstr(url, THEMEPLAZA_BASE_URL))
{
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 303 See Other (Theme Plaza)\nHas this theme been approved?");
goto error;
}
else
{
snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 303 See Other\nDownload the resource directly\nor contact the site administrator.");
goto error;
}
case REDIRECT: case REDIRECT:
httpcGetResponseHeader(&context, "Location", redirect_url, 0x824); httpcGetResponseHeader(&context, "Location", redirect_url, 0x824);
httpcCloseContext(&context); httpcCloseContext(&context);
@@ -980,82 +976,85 @@ redirect: // goto here if we need to redirect
} }
DEBUG("HTTP Redirect: %s %s\n", new_url, *redirect_url == '/' ? "relative" : "absolute"); DEBUG("HTTP Redirect: %s %s\n", new_url, *redirect_url == '/' ? "relative" : "absolute");
goto redirect; goto redirect;
case HTTP_UNACCEPTABLE:
DEBUG("HTTP 406 Unacceptable; Accept: %s\n", acceptable_mime_types);
throw_error(ZIP_NOT_AVAILABLE, ERROR_LEVEL_WARNING);
return httpcCloseContext(&context);
case SERVER_IS_MISBEHAVING: case SERVER_IS_MISBEHAVING:
DEBUG("Server is misbehaving (provided resource with incorrect MIME)\n"); DEBUG("Server is misbehaving (provided resource with incorrect MIME)\n");
throw_error(ZIP_NOT_AVAILABLE, ERROR_LEVEL_WARNING); snprintf(err_buf, ERROR_BUFFER_SIZE, ZIP_NOT_AVAILABLE);
return httpcCloseContext(&context); goto error;
case HTTPC_ERROR:
DEBUG("httpc error\n");
throw_error("Error in HTTPC sysmodule.\nIf you are seeing this, please contact an Anemone developer\non the ThemePlaza Discord.", ERROR_LEVEL_ERROR);
quit = true;
httpcCloseContext(&context);
return _header.result_code;
case HTTP_NOT_FOUND: case HTTP_NOT_FOUND:
case HTTP_GONE: ; if (!not_found_is_error)
goto not_found_non_error;
[[fallthrough]];
case HTTP_GONE:
const char * http_error = parse == HTTP_NOT_FOUND ? "404 Not Found" : "410 Gone"; const char * http_error = parse == HTTP_NOT_FOUND ? "404 Not Found" : "410 Gone";
DEBUG("HTTP %s; URL: %s\n", http_error, url); DEBUG("HTTP %s; URL: %s\n", http_error, url);
if (strstr(url, THEMEPLAZA_BASE_URL) && parse == HTTP_NOT_FOUND) if (strstr(url, THEMEPLAZA_BASE_URL) && parse == HTTP_NOT_FOUND)
throw_error("HTTP 404 Not Found\nHas this theme been approved?", ERROR_LEVEL_WARNING); snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 404 Not Found\nHas this theme been approved?");
else else
{ snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP %s\nCheck that the URL is correct.", http_error);
snprintf(err_buf, 0x69, "HTTP %s\nCheck that the URL is correct.", http_error); goto error;
throw_error(err_buf, ERROR_LEVEL_WARNING); case HTTP_UNACCEPTABLE:
} DEBUG("HTTP 406 Unacceptable; Accept: %s\n", acceptable_mime_types);
return httpcCloseContext(&context); snprintf(err_buf, ERROR_BUFFER_SIZE, ZIP_NOT_AVAILABLE);
goto error;
case HTTP_UNAUTHORIZED: case HTTP_UNAUTHORIZED:
case HTTP_FORBIDDEN: case HTTP_FORBIDDEN:
case HTTP_PROXY_UNAUTHORIZED: case HTTP_PROXY_UNAUTHORIZED:
DEBUG("HTTP %u: device not authenticated\n", parse); DEBUG("HTTP %u: device not authenticated\n", parse);
snprintf(err_buf, 0x69, "HTTP %s\nContact the site administrator.", parse == HTTP_UNAUTHORIZED snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP %s\nContact the site administrator.", parse == HTTP_UNAUTHORIZED
? "401 Unauthorized" ? "401 Unauthorized"
: parse == HTTP_FORBIDDEN : parse == HTTP_FORBIDDEN
? "403 Forbidden" ? "403 Forbidden"
: "407 Proxy Authentication Required"); : "407 Proxy Authentication Required");
throw_error(err_buf, ERROR_LEVEL_WARNING); goto error;
return httpcCloseContext(&context);
case HTTP_URI_TOO_LONG: case HTTP_URI_TOO_LONG:
DEBUG("HTTP 414; URL is too long, maybe too many redirects?\n"); DEBUG("HTTP 414; URL is too long, maybe too many redirects?\n");
throw_error("HTTP 414 URI Too Long\nThe QR code points to a really long URL.\nDownload the file directly.", ERROR_LEVEL_WARNING); snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 414 URI Too Long\nThe QR code points to a really long URL.\nDownload the file directly.");
return httpcCloseContext(&context); goto error;
case HTTP_IM_A_TEAPOT: case HTTP_IM_A_TEAPOT:
DEBUG("HTTP 418 I'm a teapot\n"); DEBUG("HTTP 418 I'm a teapot\n");
throw_error("HTTP 418 I'm a teapot\nContact the site administrator.", ERROR_LEVEL_WARNING); snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 418 I'm a teapot\nContact the site administrator.");
return httpcCloseContext(&context); goto error;
case HTTP_UPGRADE_REQUIRED: case HTTP_UPGRADE_REQUIRED:
DEBUG("HTTP 426; HTTP/2 required\n"); DEBUG("HTTP 426; HTTP/2 required\n");
throw_error("HTTP 426 Upgrade Required\nThe 3DS does not support this website.\nContact the site administrator.", ERROR_LEVEL_WARNING); snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 426 Upgrade Required\nThe 3DS cannot connect to this server.\nContact the site administrator.");
return httpcCloseContext(&context); goto error;
case HTTP_LEGAL_REASONS: case HTTP_LEGAL_REASONS:
DEBUG("HTTP 451; URL: %s\n", url); DEBUG("HTTP 451; URL: %s\n", url);
throw_error("HTTP 451 Unavailable for Legal Reasons\nSome entity is preventing access\nto the host server for legal reasons.", ERROR_LEVEL_WARNING); snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 451 Unavailable for Legal Reasons\nSome entity is preventing access\nto the host server for legal reasons.");
return httpcCloseContext(&context); goto error;
case HTTP_INTERNAL_SERVER_ERROR: case HTTP_INTERNAL_SERVER_ERROR:
DEBUG("HTTP 500\n"); DEBUG("HTTP 500; URL: %s\n", url);
throw_error("HTTP 500 Internal Server Error\nContact the site administrator.", ERROR_LEVEL_WARNING); snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 500 Internal Server Error\nContact the site administrator.");
return httpcCloseContext(&context); goto error;
case HTTP_BAD_GATEWAY: case HTTP_BAD_GATEWAY:
DEBUG("HTTP 502\n"); DEBUG("HTTP 502; URL: %s\n", url);
throw_error("HTTP 502 Bad Gateway\nContact the site administrator.", ERROR_LEVEL_WARNING); snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 502 Bad Gateway\nContact the site administrator.");
return httpcCloseContext(&context); goto error;
case HTTP_SERVICE_UNAVAILABLE: case HTTP_SERVICE_UNAVAILABLE:
DEBUG("HTTP 503\n"); DEBUG("HTTP 503; URL: %s\n", url);
throw_error("HTTP 503 Service Unavailable\nContact the site administrator.", ERROR_LEVEL_WARNING); snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 503 Service Unavailable\nContact the site administrator.");
return httpcCloseContext(&context); goto error;
case HTTP_GATEWAY_TIMEOUT: case HTTP_GATEWAY_TIMEOUT:
DEBUG("HTTP 504\n"); DEBUG("HTTP 504; URL: %s\n", url);
throw_error("HTTP 504 Gateway Timeout\nContact the site administrator.", ERROR_LEVEL_WARNING); snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP 504 Gateway Timeout\nContact the site administrator.");
return httpcCloseContext(&context); goto error;
default: default:
DEBUG("HTTP %u\n", parse); DEBUG("HTTP %u; URL: %s\n", parse, url);
snprintf(err_buf, 0x69, "HTTP %u\nIf you believe this is unexpected, please\ncontact the site administrator.", parse); snprintf(err_buf, ERROR_BUFFER_SIZE, "HTTP %u\nIf you believe this is unexpected, please\ncontact the site administrator.", parse);
throw_error(err_buf, ERROR_LEVEL_WARNING); goto error;
return httpcCloseContext(&context);
} }
goto no_error;
error:
throw_error(err_buf, ERROR_LEVEL_WARNING);
res = httpcCloseContext(&context);
if (R_FAILED(res)) return res;
return MAKERESULT(RL_TEMPORARY, RS_CANCELED, RM_APPLICATION, RD_NO_DATA);
not_found_non_error:
res = httpcCloseContext(&context);
if (R_FAILED(res)) return res;
return MAKERESULT(RL_SUCCESS, RS_NOTFOUND, RM_FILE_SERVER, RD_NO_DATA);
no_error:;
u32 chunk_size; u32 chunk_size;
if (_header.file_size) if (_header.file_size)
// the only reason we chunk this at all is for the download bar; // the only reason we chunk this at all is for the download bar;
@@ -1115,4 +1114,4 @@ redirect: // goto here if we need to redirect
DEBUG("size: %lu\n", *size); DEBUG("size: %lu\n", *size);
if (filename) { DEBUG("filename: %s\n", *filename); } if (filename) { 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,243 @@ 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))
{
free(contentInfos);
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");
if(fh != NULL)
{
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);
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_SET);
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_SET);
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;