40 Commits

Author SHA1 Message Date
Alex Taber
fc697af002 Merge branch 'master' into 3.0.1-hotfix 2024-06-17 20:29:27 -04:00
0a23b20f6e Backup badges if no badges found 2024-06-17 18:57:26 -04:00
a8cc3ebfc3 Filter newline and carriage returns when dumping themes 2024-06-17 17:04:35 -04:00
b1454bf2a1 Fix debug statement 2024-06-17 14:38:42 -04:00
1a2efdc129 Remove illegal fat32 chars in badge dump 2024-06-17 14:24:57 -04:00
11113bbc07 Remove illegal fat32 chars in badge dump 2024-06-17 14:10:14 -04:00
7182fd9078 Beginning config support
Currently only supports changing 4 colors
2024-06-17 12:00:25 -04:00
e613a525a5 Properly close directories when installing badges 2024-06-17 10:52:10 -04:00
Alex Taber
a563683d32 Translation updates (#321)
Co-authored-by: Gato-kun <146145363+Gatokun@users.noreply.github.com>
Co-authored-by: arth <122994355+iveurne@users.noreply.github.com>
Co-authored-by: cooolgamer <64099608+cooolgamer@users.noreply.github.com>
2024-06-14 13:31:59 -04:00
0420b23fbf Add check for body_buf & properly free buffers
Also remove unused extdata dump method
2024-06-13 18:28:20 -04:00
c3e09adcb2 Badge dump overhaul 2024-06-12 18:04:33 -04:00
4669f13d05 Some changes to scroll algorithm 2024-06-12 11:04:52 -04:00
80dd00933b Properly zero files, badge/set index (unused?) 2024-06-11 17:01:05 -04:00
cooolgamer
bb18085413 UI Changes, moved hamburger menu (#317) 2024-06-11 10:52:16 -04:00
29233eadfb Progress bar more accurate 2024-06-10 11:27:09 -04:00
e40cd00754 Avoid loading 15 MB files into RAM ever 2024-06-10 10:48:34 -04:00
7abf989de8 Add progress bar for shuffle install 2024-06-08 12:29:35 -04:00
2ed3d8b34d Add progress bar for badge install 2024-06-08 10:43:35 -04:00
01e7f89a9a Cache last search and restore it upon failed search.
Closes #316
2024-06-08 00:54:23 -04:00
df4f6a8736 Add install_badges string to Spanish 2024-06-08 00:24:36 -04:00
4fdfad0c0a Properly follow channel info offsets 2024-06-07 23:15:10 -04:00
arth
74509d75db Portugese translation update (#314)
- Translated untranslated strings

- Fixed & shortened a few strings
2024-06-07 20:40:44 -04:00
Gato-kun
bb1e0ac872 Spanish
I really hope i did it right this time
2024-06-07 20:39:58 -04:00
cooolgamer
d426143eaf More French translations + Themes,Splashs,Badges cycling on TP using L&R (#313) 2024-06-07 10:04:59 -04:00
b2374deaf5 Better RAM Usage, proper null safety
Null safety added to some FS functions

Don't attempt to open multiple 15 MB files at once

Some strings changes so the themeplaza browser is better
2024-06-05 14:33:02 -04:00
d79a96fd75 Fix outdated english string 2024-06-04 22:46:45 -04:00
c2edc50750 Backup badge extdata before installing badges 2024-06-04 22:38:19 -04:00
8e876d027b Remove most compiler warnings 2024-06-04 17:43:27 -04:00
f8fba122ac Update README 2024-06-04 17:28:43 -04:00
e09c33f0e8 BCSTM Player 2024-06-04 17:17:16 -04:00
8f676eafae Fix preview regression introduced in df4c81c 2024-06-04 16:34:20 -04:00
77138530ed Update contributors 2024-06-03 15:46:03 -04:00
9860dec103 Ask on invalid zip if zip is badge file 2024-06-03 10:23:18 -04:00
1a01ba502f Add install badges from zip file 2024-06-03 00:58:07 -04:00
df4c81c96d Download SMDH files for badges
Thanks to ThemePlaza for providing these to make browser icons work
Also fixed badge previews by increasing tex size if the preview
image is larger than 512 pixel high
2024-06-02 20:51:39 -04:00
30833995d1 Add spanish translation
Also some minor badge changes

Co-authored-by: Tristanabs <76444200+Tristanabs@users.noreply.github.com>
2024-06-01 22:19:53 -04:00
b0db54e71a TP Badge Browser. Incomplete - see notes
TODO:

- Load icons for browser

- Fix previews (max tex size 512x512 and preview image 512x1024?
2024-05-31 12:05:18 -04:00
4d9cde01c4 Initial badges work
Additional cleanup probably necessary, some more feature work still in progress (zip files)
2024-05-30 15:45:57 -04:00
Zemogiter
8c42a23eeb Update README.md (#309)
Included lib-curl as a requirement for successful build.
2024-05-24 18:39:49 +01:00
arth
970b3c9749 Small fixes on Portuguese (#308) 2024-05-21 11:15:56 -04:00
32 changed files with 3191 additions and 257 deletions

View File

@@ -21,3 +21,4 @@
# Translation Contributors # Translation Contributors
* [@cooolgamer](https://github.com/cooolgamer/) for French * [@cooolgamer](https://github.com/cooolgamer/) for French
* Arth ([@iveurne](https://github.com/iveurne)) for Portugese * Arth ([@iveurne](https://github.com/iveurne)) for Portugese
* [@Tristanabs](https://github.com/Tristanabs) and Angelpro09_xd for Spanish

View File

@@ -90,7 +90,7 @@ CFLAGS := -g -Wall -Wextra -O2 -mword-relocations \
$(ARCH) $(ARCH)
CFLAGS += $(INCLUDE) -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)\""
CFLAGS += `arm-none-eabi-pkg-config --cflags-only-other vorbisidec libarchive jansson libpng` CFLAGS += `arm-none-eabi-pkg-config --cflags-only-other libcurl vorbisidec libarchive jansson libpng`
ifneq ($(strip $(CITRA_MODE)),) ifneq ($(strip $(CITRA_MODE)),)
CFLAGS += -DCITRA_MODE CFLAGS += -DCITRA_MODE
endif endif

View File

@@ -11,7 +11,7 @@ A Theme and Splashscreen Manager for the Nintendo 3DS, written in C.
First of all, make sure devkitARM is properly installed - `$DEVKITPRO` and `$DEVKITARM` should be set to `/opt/devkitpro` and `$DEVKITPRO/devkitARM`, respectively. First of all, make sure devkitARM is properly installed - `$DEVKITPRO` and `$DEVKITARM` should be set to `/opt/devkitpro` and `$DEVKITPRO/devkitARM`, respectively.
After that, open the directory you want to clone the repo into, and execute After that, open the directory you want to clone the repo into, and execute
`git clone https://github.com/astronautlevel2/Anemone3DS` (or any other cloning method). `git clone https://github.com/astronautlevel2/Anemone3DS` (or any other cloning method).
To install the prerequisite libraries, begin by ensuring devkitPro pacman (and the base install group, `3ds-dev`) is installed, and then install the dkP packages `3ds-jansson`, `3ds-libvorbisidec`, `3ds-libpng`, `3ds-lz4`, and `3ds-libarchive` using `[sudo] [dkp-]pacman -S <package-name>`. To install the prerequisite libraries, begin by ensuring devkitPro pacman (and the base install group, `3ds-dev`) is installed, and then install the dkP packages `3ds-jansson`, `3ds-libvorbisidec`, `3ds-libpng`, `3ds-lz4`, `3ds-libarchive` and `3ds-curl` using `[sudo] [dkp-]pacman -S <package-name>`.
After adding [makerom](https://github.com/profi200/Project_CTR) and [bannertool](https://github.com/Steveice10/buildtools) to your PATH, just enter your directory and run `make`. All built binaries will be in `/out/`. After adding [makerom](https://github.com/profi200/Project_CTR) and [bannertool](https://github.com/Steveice10/buildtools) to your PATH, just enter your directory and run `make`. All built binaries will be in `/out/`.
@@ -28,9 +28,11 @@ Special thanks go to these people who, whilst not directly contributing, helped
* [SteveIce10](https://github.com/SteveIce10), whose QR code in FBI was essential. * [SteveIce10](https://github.com/SteveIce10), whose QR code in FBI was essential.
* [BernardoGiordano](https://github.com/BernardoGiordano) for making pp2d, and being super responsive to feature requests and just general help. * [BernardoGiordano](https://github.com/BernardoGiordano) for making pp2d, and being super responsive to feature requests and just general help.
* [yellows8](https://github.com/yellows8) for his home menu extdump tool, which was invaluable in debugging. * [yellows8](https://github.com/yellows8) for his home menu extdump tool, which was invaluable in debugging.
* the folks on #dev of Nintendo Homebrew, who helped with unicode shenanigans (especially [Stary2001](https://github.com/Stary2001), [Fenrir](https://github.com/FenrirWolf), and DanielKO). * [MrCheeze](https://github.com/MrCheeze) and [AntiMach](https://github.com/AntiMach) whose GYTB and ABE code served as a reference on badge management
* the maintainers for all used libraries, including but not limited to ctrulib, zlib, citro3d, pp2d, and quirc. * [Tobid7](https://github.com/tobid7) whose BCSTM-Player project served as a reference on the BCSTM format
* all the people who helped keep me going and motivated me to work. This includes, but is definitely not limited to: * The folks on #dev of Nintendo Homebrew, who helped with unicode shenanigans (especially [Stary2001](https://github.com/Stary2001), [Fenrir](https://github.com/FenrirWolf), and DanielKO).
* The maintainers for all used libraries, including but not limited to ctrulib, libarchive, citro3d, citro2d, and quirc. An especially big thanks to the devkitPro team for maintaining a phenomenal toolchain.
* All the people who helped keep me going and motivated me to work. This includes, but is definitely not limited to:
+ The members of the [Nintendo Homebrew Discord](https://discord.gg/C29hYvh) + The members of the [Nintendo Homebrew Discord](https://discord.gg/C29hYvh)
+ The members of the __Secret Shack Service Discord__ + The members of the __Secret Shack Service Discord__

Binary file not shown.

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 606 B

BIN
assets/badge.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@@ -26,3 +26,4 @@ qr.png
bgm_only.png bgm_only.png
back.png back.png
dump.png dump.png
badge.png

42
include/badges.h Normal file
View File

@@ -0,0 +1,42 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2024 Contributors in CONTRIBUTORS.md
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <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.
*/
#ifndef BADGES_H
#define BADGES_H
#include "common.h"
#include "fs.h"
#include "conversion.h"
#include "unicode.h"
#define MAX_BADGE 1000
#define BADGE_DATA_SIZE 0xF4DF80
#define BADGE_MNG_SIZE 0xD4A8
Result install_badges(void);
Result extract_badges(void);
#endif

View File

@@ -28,6 +28,7 @@
#define COLORS_H #define COLORS_H
#include "common.h" #include "common.h"
#include "config.h"
typedef u32 Color; typedef u32 Color;

View File

@@ -71,6 +71,14 @@ typedef enum {
DRAW_MODE_AMOUNT, DRAW_MODE_AMOUNT,
} DrawMode; } DrawMode;
typedef enum {
REMOTE_MODE_THEMES = 0,
REMOTE_MODE_SPLASHES,
REMOTE_MODE_BADGES,
REMOTE_MODE_AMOUNT,
} RemoteMode;
extern const char * main_paths[MODE_AMOUNT]; extern const char * main_paths[MODE_AMOUNT];
extern const int entries_per_screen_v[MODE_AMOUNT]; extern const int entries_per_screen_v[MODE_AMOUNT];
extern const int entries_per_screen_h[MODE_AMOUNT]; extern const int entries_per_screen_h[MODE_AMOUNT];

45
include/config.h Normal file
View File

@@ -0,0 +1,45 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2024 Contributors in CONTRIBUTORS.md
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <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.
*/
#ifndef CONFIG_H
#define CONFIG_H
#include "common.h"
#include "fs.h"
#include <jansson.h>
typedef struct {
u32 background_color;
u32 accent_color;
u32 red_color;
u32 yellow_color;
} Config_s;
extern Config_s config;
void load_config(void);
#endif

View File

@@ -5,5 +5,7 @@
size_t bin_to_abgr(char ** bufp, size_t size); size_t bin_to_abgr(char ** bufp, size_t size);
size_t png_to_abgr(char ** bufp, size_t size, u32 *height); size_t png_to_abgr(char ** bufp, size_t size, u32 *height);
int pngToRGB565(char *png_buf, u64 fileSize, u16 *rgb_buf_64x64, u8 *alpha_buf_64x64, u16 *rgb_buf_32x32, u8 *alpha_buf_32x32, bool set_icon);
int rgb565ToPngFile(char *filename, u16 *rgb_buf, u8 *alpha_buf, int width, int height);
#endif #endif

View File

@@ -52,11 +52,14 @@ typedef enum InstallType_e {
INSTALL_LOADING_REMOTE_THEMES, INSTALL_LOADING_REMOTE_THEMES,
INSTALL_LOADING_REMOTE_SPLASHES, INSTALL_LOADING_REMOTE_SPLASHES,
INSTALL_LOADING_REMOTE_BADGES,
INSTALL_LOADING_REMOTE_PREVIEW, INSTALL_LOADING_REMOTE_PREVIEW,
INSTALL_LOADING_REMOTE_BGM, INSTALL_LOADING_REMOTE_BGM,
INSTALL_DUMPING_THEME, INSTALL_DUMPING_THEME,
INSTALL_DUMPING_ALL_THEMES, INSTALL_DUMPING_ALL_THEMES,
INSTALL_DUMPING_BADGES,
INSTALL_BADGES,
INSTALL_NONE, INSTALL_NONE,
} InstallType; } InstallType;
@@ -81,11 +84,14 @@ typedef enum {
TEXT_INSTALL_LOADING_REMOTE_THEMES, TEXT_INSTALL_LOADING_REMOTE_THEMES,
TEXT_INSTALL_LOADING_REMOTE_SPLASHES, TEXT_INSTALL_LOADING_REMOTE_SPLASHES,
TEXT_INSTALL_LOADING_REMOTE_BADGES,
TEXT_INSTALL_LOADING_REMOTE_PREVIEW, TEXT_INSTALL_LOADING_REMOTE_PREVIEW,
TEXT_INSTALL_LOADING_REMOTE_BGM, TEXT_INSTALL_LOADING_REMOTE_BGM,
TEXT_INSTALL_DUMPING_THEME, TEXT_INSTALL_DUMPING_THEME,
TEXT_INSTALL_DUMPING_ALL_THEMES, TEXT_INSTALL_DUMPING_ALL_THEMES,
TEXT_INSTALL_DUMPING_BADGES,
TEXT_INSTALL_BADGES,
// Other text // Other text
TEXT_VERSION, TEXT_VERSION,
@@ -109,6 +115,7 @@ typedef enum {
TEXT_THEMEPLAZA_THEME_MODE, TEXT_THEMEPLAZA_THEME_MODE,
TEXT_THEMEPLAZA_SPLASH_MODE, TEXT_THEMEPLAZA_SPLASH_MODE,
TEXT_THEMEPLAZA_BADGE_MODE,
TEXT_SEARCH, TEXT_SEARCH,
TEXT_PAGE, TEXT_PAGE,
@@ -165,7 +172,7 @@ void set_screen(C3D_RenderTarget * screen);
void throw_error(const char * error, ErrorLevel level); void throw_error(const char * error, ErrorLevel level);
bool draw_confirm(const char * conf_msg, Entry_List_s * list, DrawMode draw_mode); bool draw_confirm(const char * conf_msg, Entry_List_s * list, DrawMode draw_mode);
void draw_preview(C2D_Image preview, int preview_offset); void draw_preview(C2D_Image preview, int preview_offset, float preview_scale);
void draw_install(InstallType type); void draw_install(InstallType type);
void draw_loading_bar(u32 current, u32 max, InstallType type); void draw_loading_bar(u32 current, u32 max, InstallType type);
@@ -179,5 +186,6 @@ void draw_home(u64 start_time, u64 cur_time);
void draw_base_interface(void); void draw_base_interface(void);
void draw_grid_interface(Entry_List_s * list, Instructions_s instructions, int extra_mode); void draw_grid_interface(Entry_List_s * list, Instructions_s instructions, int extra_mode);
void draw_interface(Entry_List_s * list, Instructions_s instructions, DrawMode draw_mode); void draw_interface(Entry_List_s * list, Instructions_s instructions, DrawMode draw_mode);
bool draw_confirm_no_interface(const char *conf_msg);
#endif #endif

View File

@@ -28,12 +28,15 @@
#define FS_H #define FS_H
#include "common.h" #include "common.h"
#include "badges.h"
#include "config.h"
#define ILLEGAL_CHARS "><\"?;:/\\+,.|[=]*" #define ILLEGAL_CHARS "><\"?;:/\\+,.|[=]*\n\r"
extern FS_Archive ArchiveSD; extern FS_Archive ArchiveSD;
extern FS_Archive ArchiveHomeExt; extern FS_Archive ArchiveHomeExt;
extern FS_Archive ArchiveThemeExt; extern FS_Archive ArchiveThemeExt;
extern FS_Archive ArchiveBadgeExt;
typedef struct { typedef struct {
u32 enable : 1; u32 enable : 1;
@@ -52,7 +55,9 @@ typedef struct {
u32 coppa : 1; u32 coppa : 1;
} Parental_Restrictions_s; } Parental_Restrictions_s;
Result init_sd(void);
Result open_archives(void); Result open_archives(void);
Result open_badge_extdata(void);
Result close_archives(void); Result close_archives(void);
Result load_parental_controls(Parental_Restrictions_s *restrictions); Result load_parental_controls(Parental_Restrictions_s *restrictions);
@@ -63,7 +68,9 @@ u32 decompress_lz_file(FS_Path file_name, FS_Archive archive, char ** buf);
u32 compress_lz_file_fast(FS_Path path, FS_Archive archive, char * in_buf, u32 size); u32 compress_lz_file_fast(FS_Path path, FS_Archive archive, char * in_buf, u32 size);
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);
Result zero_handle_memeasy(Handle handle);
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); void save_zip_to_sd(char * filename, u32 size, char * buf, RemoteMode mode);
s16 for_each_file_zip(u16 *zip_path, u32 (*zip_iter_callback)(char *filebuf, u64 file_size, const char *name, void *userdata), void *userdata);
#endif #endif

View File

@@ -72,6 +72,7 @@ bool load_preview_from_buffer(char * row_pointers, u32 size, C2D_Image * preview
bool load_preview(const Entry_List_s * list, C2D_Image * preview_image, int * preview_offset); bool load_preview(const Entry_List_s * list, C2D_Image * preview_image, int * preview_offset);
void free_preview(C2D_Image preview_image); void free_preview(C2D_Image preview_image);
Result load_audio(const Entry_s *, audio_s *); Result load_audio(const Entry_s *, audio_s *);
Result load_audio_ogg(const Entry_s * entry, audio_ogg_s * audio);
void load_icons_first(Entry_List_s * current_list, bool silent); void load_icons_first(Entry_List_s * current_list, bool silent);
void handle_scrolling(Entry_List_s * list); void handle_scrolling(Entry_List_s * list);
void load_icons_thread(void * void_arg); void load_icons_thread(void * void_arg);

View File

@@ -34,7 +34,37 @@
#include <tremor/ivorbisfile.h> #include <tremor/ivorbisfile.h>
#include <tremor/ivorbiscodec.h> #include <tremor/ivorbiscodec.h>
#define BUF_TO_READ 48000 // How much data should be buffered at a time #define BUFFER_COUNT 8
#define BUF_TO_READ 48000 // How much data should be buffered at a time for ogg
typedef struct {
char *music_buf;
size_t music_size;
size_t cursor;
u32 last_time;
bool is_little_endian;
bool is_looping;
u32 info_offset;
u32 data_offset;
u8 channel_count;
u32 sample_rate;
u32 loop_start;
u32 loop_end;
u32 num_blocks;
u32 block_size;
u32 block_samples;
u32 last_block_samples;
u32 last_block_size;
u32 current_block;
unsigned short adpcm_coefs[2][16];
ndspWaveBuf wave_buf[2][BUFFER_COUNT];
ndspAdpcmData adpcm_data[2][2];
unsigned short channel[2];
unsigned int active_channels;
u8 *buffer_data[2][BUFFER_COUNT];
volatile bool stop;
Thread playing_thread;
} audio_s;
typedef struct { typedef struct {
OggVorbis_File vf; OggVorbis_File vf;
@@ -47,9 +77,12 @@ typedef struct {
volatile bool stop; volatile bool stop;
Thread playing_thread; Thread playing_thread;
} audio_s; } audio_ogg_s;
void play_audio(audio_s *); void play_audio(audio_s *);
void stop_audio(audio_s **); void stop_audio(audio_s **);
void play_audio_ogg(audio_ogg_s *);
void stop_audio_ogg(audio_ogg_s **);
#endif #endif

View File

@@ -39,6 +39,12 @@
#define THEMEPLAZA_JSON_PAGE_COUNT "pages" #define THEMEPLAZA_JSON_PAGE_COUNT "pages"
#define THEMEPLAZA_JSON_PAGE_IDS "items" #define THEMEPLAZA_JSON_PAGE_IDS "items"
#define THEMEPLAZA_QUERY_ENTRY_INFO THEMEPLAZA_BASE_API_URL "/query?item_id=%" JSON_INTEGER_FORMAT
#define THEMEPLAZA_JSON_TITLE "title"
#define THEMEPLAZA_JSON_AUTHOR "author"
#define THEMEPLAZA_JSON_DESC "description"
#define THEMEPLAZA_JSON_SUCCESS "success"
#define THEMEPLAZA_JSON_ERROR_MESSAGE "message" #define THEMEPLAZA_JSON_ERROR_MESSAGE "message"
#define THEMEPLAZA_JSON_ERROR_MESSAGE_NOT_FOUND "No items found" #define THEMEPLAZA_JSON_ERROR_MESSAGE_NOT_FOUND "No items found"
@@ -62,7 +68,7 @@ typedef struct {
char *mime_type; char *mime_type;
} curl_header; } curl_header;
bool themeplaza_browser(EntryMode mode); bool themeplaza_browser(RemoteMode mode);
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);
#endif #endif

View File

@@ -31,15 +31,13 @@
#include "draw.h" #include "draw.h"
#include "common.h" #include "common.h"
#define SPLASHES_STRINGS 2
#define THEMES_STRINGS 6
typedef struct { typedef struct {
const char *quit; const char *quit;
const char *thread_error; const char *thread_error;
const char *zip_not_theme_splash; const char *zip_not_theme_splash;
const char *file_not_zip; const char *file_not_zip;
const char *download_failed; const char *download_failed;
const char *badge_question;
} Camera_Strings_s; } Camera_Strings_s;
typedef struct { typedef struct {
@@ -56,6 +54,7 @@ typedef struct {
const char *sel; const char *sel;
const char *tp_theme_mode; const char *tp_theme_mode;
const char *tp_splash_mode; const char *tp_splash_mode;
const char *tp_badge_mode;
const char *search; const char *search;
const char *page; const char *page;
const char *err_quit; const char *err_quit;
@@ -75,10 +74,13 @@ typedef struct {
const char *delete_sd; const char *delete_sd;
const char *download_themes; const char *download_themes;
const char *download_splashes; const char *download_splashes;
const char *download_badges;
const char *download_preview; const char *download_preview;
const char *download_bgm; const char *download_bgm;
const char *dump_single; const char *dump_single;
const char *dump_all_official; const char *dump_all_official;
const char *dump_badges;
const char *install_badges;
float start_pos; float start_pos;
const char *shuffle; const char *shuffle;
} Draw_Strings_s; } Draw_Strings_s;
@@ -161,6 +163,10 @@ typedef struct {
const char *done; const char *done;
} Themes_Strings_s; } Themes_Strings_s;
typedef struct {
const char *extdata_locked;
} Badge_Strings_s;
typedef struct { typedef struct {
Instructions_s normal_instructions[MODE_AMOUNT]; Instructions_s normal_instructions[MODE_AMOUNT];
Instructions_s install_instructions; Instructions_s install_instructions;
@@ -171,10 +177,11 @@ typedef struct {
Loading_Strings_s loading; Loading_Strings_s loading;
Main_Strings_s main; Main_Strings_s main;
Remote_Strings_s remote; Remote_Strings_s remote;
Instructions_s remote_instructions[MODE_AMOUNT]; Instructions_s remote_instructions[REMOTE_MODE_AMOUNT];
Instructions_s remote_extra_instructions; Instructions_s remote_extra_instructions[3];
Splashes_Strings_s splashes; Splashes_Strings_s splashes;
Themes_Strings_s themes; Themes_Strings_s themes;
Badge_Strings_s badges;
} Language_s; } Language_s;
typedef enum { typedef enum {

View File

@@ -29,9 +29,10 @@
#include "common.h" #include "common.h"
ssize_t strulen(const u16 *, ssize_t); void replace_chars(u16 *input, char *remove, u16 with);
size_t strulen(const u16 *, ssize_t);
void struacat(u16 * input, const char * addition); void struacat(u16 * input, const char * addition);
void printu(u16 * input); void printu(u16 * input);
u16 * strucat(u16 * destination, const u16 * source); size_t strucat(u16 * destination, const u16 * source);
#endif #endif

798
source/badges.c Normal file
View File

@@ -0,0 +1,798 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2024 Contributors in CONTRIBUTORS.md
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <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.
*/
// Badge implementation adapted from GYTB by MrCheeze and ABE by AntiMach
// https://github.com/AntiMach/advanced-badge-editor
// https://github.com/MrCheeze/GYTB
#include "badges.h"
#include "draw.h"
#include "ui_strings.h"
static Handle actHandle;
Handle badgeDataHandle;
char *badgeMngBuffer;
u16 *rgb_buf_64x64;
u16 *rgb_buf_32x32;
u8 *alpha_buf_64x64;
u8 *alpha_buf_32x32;
u64 progress_finish;
u64 progress_status;
Result actInit(void)
{
return srvGetServiceHandle(&actHandle, "act:u");
}
Result actExit(void)
{
return svcCloseHandle(actHandle);
}
Result ACTU_Initialize(u32 sdkVersion, u32 memSize, Handle handle)
{
Result ret = 0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = 0x00010084;
cmdbuf[1] = sdkVersion;
cmdbuf[2] = memSize;
cmdbuf[3] = 0x20;
cmdbuf[4] = 0x0;
cmdbuf[5] = 0x0;
cmdbuf[6] = handle;
if ((ret = svcSendSyncRequest(actHandle)) != 0) return ret;
return (Result) cmdbuf[1];
}
Result ACTU_GetAccountDataBlock(u32 slot, u32 size, u32 blockId, u32 *output)
{
Result ret = 0;
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = 0x000600C2;
cmdbuf[1] = slot;
cmdbuf[2] = size;
cmdbuf[3] = blockId;
cmdbuf[4] = (size << 4) | 12;
cmdbuf[5] = (u32) output;
if ((ret = svcSendSyncRequest(actHandle)) != 0) return ret;
return (Result) cmdbuf[1];
}
void remove_exten(u16 *filename)
{
for (int i = 0; i < strulen(filename, 0x8A); ++i)
{
if (filename[i] == '.')
{
filename[i] = 0x00;
return;
}
}
}
u64 getShortcut(char *filename)
{
u64 shortcut = 0xFFFFFFFFFFFFFFFF;
char *p1 = strchr(filename, '.');
if (p1 == NULL) return shortcut;
++p1;
char *p2 = strchr(p1, '.');
if (p2 == NULL) return shortcut;
if (p2-p1 != 8) return shortcut;
unsigned int lowpath;
if (sscanf(p1, "%08x", &lowpath) != 1) return shortcut;
shortcut = 0x0004001000000000 + lowpath;
DEBUG("Shortcut %16llx found for %s\n", shortcut, filename);
return shortcut;
}
int install_badge_generic(char *file_buf, u64 file_size, u16 *name, int *badge_count, int set_id)
{
int badges_in_image = pngToRGB565(file_buf, file_size, rgb_buf_64x64, alpha_buf_64x64, rgb_buf_32x32, alpha_buf_32x32, false);
char utf8_name[512] = {0};
utf16_to_utf8((u8 *) utf8_name, name, 0x8A);
u64 shortcut = getShortcut(utf8_name);
int badges_installed = 0;
for (int badge = 0; badge < badges_in_image && *badge_count < 1000; ++badge)
{
remove_exten(name);
for (int j = 0; j < 16; ++j) // Copy name for all 16 languages
{
FSFILE_Write(badgeDataHandle, NULL, 0x35E80 + *badge_count * 16 * 0x8A + j * 0x8A, name, 0x8A, 0);
}
FSFILE_Write(badgeDataHandle, NULL, 0x318F80 + *badge_count * 0x2800, rgb_buf_64x64 + badge * 64 * 64, 64 * 64 * 2, 0);
FSFILE_Write(badgeDataHandle, NULL, 0x31AF80 + *badge_count * 0x2800, alpha_buf_64x64 + badge * 64 * 64/2, 64 * 64/2, 0);
FSFILE_Write(badgeDataHandle, NULL, 0xCDCF80 + *badge_count * 0xA00, rgb_buf_32x32 + badge * 32 * 32, 32 * 32 * 2, 0);
FSFILE_Write(badgeDataHandle, NULL, 0xCDD780 + *badge_count * 0xA00, alpha_buf_32x32 + badge * 32 * 32/2, 32 * 32/2, 0);
int badge_id = *badge_count + 1;
memcpy(badgeMngBuffer + 0x3E8 + *badge_count * 0x28 + 0x4, &badge_id, 4);
memcpy(badgeMngBuffer + 0x3E8 + *badge_count * 0x28 + 0x8, &set_id, 4);
memcpy(badgeMngBuffer + 0x3E8 + *badge_count*0x28 + 0xC, badge_count, 2);
badgeMngBuffer[0x3E8 + *badge_count*0x28 + 0x12] = 255; // Quantity Low
badgeMngBuffer[0x3E8 + *badge_count*0x28 + 0x13] = 255; // Quantity High
memcpy(badgeMngBuffer + 0x3E8 + *badge_count*0x28 + 0x18, &shortcut, 8);
memcpy(badgeMngBuffer + 0x3E8 + *badge_count*0x28 + 0x20, &shortcut, 8); // u64 shortcut[2], not sure what second is for
badgeMngBuffer[0x358 + *badge_count/8] |= 1 << (*badge_count % 8); // enabled badges bitfield
badges_installed++;
*badge_count += 1;
}
return badges_installed;
}
int install_badge_png(FS_Path badge_path, FS_DirectoryEntry badge_file, int *badge_count, int set_id)
{
u64 res;
char *file_buf = NULL;
res = file_to_buf(badge_path, ArchiveSD, &file_buf);
if (res != badge_file.fileSize)
{
return -1;
}
int badges = install_badge_generic(file_buf, badge_file.fileSize, badge_file.name, badge_count, set_id);
free(file_buf);
return badges;
}
typedef struct {
int *badge_count;
int set_id;
int installed;
} zip_userdata;
u32 zip_callback(char *file_buf, u64 file_size, const char *name, void *userdata)
{
progress_finish += 1;
zip_userdata *data = (zip_userdata *) userdata;
u16 *utf16_name = calloc(strlen(name), sizeof(u16));
utf8_to_utf16(utf16_name, (u8 *) name, strlen(name));
data->installed += install_badge_generic(file_buf, file_size, utf16_name, data->badge_count, data->set_id);
free(utf16_name);
progress_status += 1;
return 0;
}
int install_badge_zip(u16 *path, int *badge_count, int set_id)
{
zip_userdata data = {0};
data.set_id = set_id;
data.badge_count = badge_count;
for_each_file_zip(path, zip_callback, &data);
return data.installed;
}
int install_badge_dir(FS_DirectoryEntry set_dir, int *badge_count, int set_id)
{
Result res;
Handle folder;
int start_idx = *badge_count;
char *icon_buf = NULL;
int icon_size = 0;
FS_DirectoryEntry *badge_files = calloc(1024, sizeof(FS_DirectoryEntry));
u16 path[512] = {0};
u16 set_icon[17] = {0};
utf8_to_utf16(set_icon, (u8 *) "_seticon.png", 16);
struacat(path, "/Badges/");
strucat(path, set_dir.name);
res = FSUSER_OpenDirectory(&folder, ArchiveSD, fsMakePath(PATH_UTF16, path));
if (R_FAILED(res))
{
return -1;
}
u32 entries_read;
res = FSDIR_Read(folder, &entries_read, 1024, badge_files);
int badges_in_set = 0;
progress_finish += entries_read;
for (u32 i = 0; i < entries_read && *badge_count < 1000; ++i)
{
if (!strcmp(badge_files[i].shortExt, "PNG"))
{
memset(path, 0, 512 * sizeof(u16));
struacat(path, "/Badges/");
strucat(path, set_dir.name);
struacat(path, "/");
strucat(path, badge_files[i].name);
if (!memcmp(set_icon, badge_files[i].name, 16))
{
DEBUG("Found set icon for folder set %d\n", set_id);
icon_size = file_to_buf(fsMakePath(PATH_UTF16, path), ArchiveSD, &icon_buf);
continue;
}
badges_in_set += install_badge_png(fsMakePath(PATH_UTF16, path), badge_files[i], badge_count, set_id);
} else if (!strcmp(badge_files[i].shortExt, "ZIP"))
{
memset(path, 0, 512 * sizeof(u16));
struacat(path, "/Badges/");
strucat(path, set_dir.name);
struacat(path, "/");
strucat(path, badge_files[i].name);
badges_in_set += install_badge_zip(path, badge_count, set_id);
}
progress_status += 1;
draw_loading_bar(progress_status, progress_finish, INSTALL_BADGES);
}
if (!badges_in_set)
{
goto end;
}
int set_index = set_id - 1;
u32 total_count = 0xFFFF * badges_in_set;
for (int i = 0; i < 16; ++i)
{
FSFILE_Write(badgeDataHandle, NULL, set_index * 0x8A0 + i * 0x8A, set_dir.name, strulen(set_dir.name, 0x45) * 2 + 2, 0);
}
badgeMngBuffer[0x3D8 + set_index/8] |= 0 << (set_index % 8);
memset(badgeMngBuffer + 0xA028 + set_index * 0x30, 0xFF, 8);
badgeMngBuffer[0xA028 + 0xC + set_index * 0x30] = 0x10; // bytes 13 and 14 are 0x2710
badgeMngBuffer[0xA028 + 0xD + set_index * 0x30] = 0x27;
memcpy(badgeMngBuffer + 0xA028 + 0x10 + set_index * 0x30, &set_id, 4);
memcpy(badgeMngBuffer + 0xA028 + 0x14 + set_index * 0x30, &set_index, 4);
memset(badgeMngBuffer + 0xA028 + 0x18 + set_index * 0x30, 0xFF, 4);
memcpy(badgeMngBuffer + 0xA028 + 0x1C + set_index * 0x30, &badges_in_set, 4);
memcpy(badgeMngBuffer + 0xA028 + 0x20 + set_index * 0x30, &total_count, 4);
memcpy(badgeMngBuffer + 0xA028 + 0x24 + set_index * 0x30, &start_idx, 4);
int icon = pngToRGB565(icon_buf, icon_size, rgb_buf_64x64, alpha_buf_64x64, rgb_buf_32x32, alpha_buf_32x32, true);
if (icon == 0)
{
DEBUG("Falling back on default icon\n");
if (icon_buf) free(icon_buf);
FILE *fp = fopen("romfs:/hb_set.png", "rb");
fseek(fp, 0L, SEEK_END);
icon_size = ftell(fp);
icon_buf = malloc(icon_size);
fseek(fp, 0L, SEEK_SET);
fread(icon_buf, 1, icon_size, fp);
fclose(fp);
pngToRGB565(icon_buf, icon_size, rgb_buf_64x64, alpha_buf_64x64, rgb_buf_32x32, alpha_buf_32x32, true);
}
free(icon_buf);
FSFILE_Write(badgeDataHandle, NULL, 0x250F80 + set_index * 0x2000, rgb_buf_64x64, 64 * 64 * 2, 0);
badgeMngBuffer[0x3D8 + set_index/8] |= 0 << (set_index % 8);
end:
free(badge_files);
FSDIR_Close(folder);
return badges_in_set;
}
typedef struct set_node_s {
u32 set_id;
u32 set_index;
struct set_node_s *next;
} SetNode;
u32 get_set_index(SetNode *head, u32 set_id)
{
SetNode *cursor = head;
while (cursor != NULL)
{
if (cursor->set_id == set_id)
return cursor->set_index;
cursor = cursor->next;
}
return 0xFFFFFFFF;
}
void free_list(SetNode *head)
{
SetNode *cursor = head;
while (cursor)
{
SetNode *next = cursor->next;
free(cursor);
cursor = next;
}
}
SetNode * extract_sets(char *badgeMngBuffer, Handle backupDataHandle)
{
u32 setCount = *((u32 *) (badgeMngBuffer + 0x4));
if (setCount == 0) return NULL;
SetNode *head = calloc(1, sizeof(SetNode));
SetNode *cursor = head;
u16 *icon_rgb_buf = malloc(64 * 64 * 2);
u8 *icon_alpha_buf = malloc(64 * 64 * 0.5);
for (u32 i = 0; i < setCount; ++i)
{
memcpy(&(cursor->set_id), &badgeMngBuffer[0xA028 + 0x30 * i + 0x10], 4);
memcpy(&(cursor->set_index), &badgeMngBuffer[0xA028 + 0x30 * i + 0x14], 4);
if (cursor->set_id != 0xEFBE) // 0xEFBE is GYTB Set ID; GYTB doesn't properly create sets, so skip
{
DEBUG("Processing icon for set %lu at index %lu\n", cursor->set_id, cursor->set_index);
u16 utf16SetName[0x46] = {0};
FSFILE_Read(backupDataHandle, NULL, cursor->set_index * 16 * 0x8A, utf16SetName, 0x8A);
replace_chars(utf16SetName, ILLEGAL_CHARS, u'-');
u16 set_path[256] = {0};
struacat(set_path, "/3ds/" APP_TITLE "/BadgeBackups/");
size_t set_name_len = strucat(set_path, utf16SetName);
if (!set_name_len)
{
struacat(set_path, "Unknown Set");
}
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_UTF16, set_path), FS_ATTRIBUTE_DIRECTORY);
memset(icon_alpha_buf, 255, 64 * 64 * 0.5);
FSFILE_Read(backupDataHandle, NULL, 0x250F80 + cursor->set_index * 0x2000, icon_rgb_buf, 0x2000);
char filename[256] = {0};
utf16_to_utf8((u8 *) filename, set_path, 256);
strcat(filename, "/_seticon.png");
DEBUG("%s\n", filename);
rgb565ToPngFile(filename, icon_rgb_buf, icon_alpha_buf, 48, 48);
}
cursor->next = calloc(1, sizeof(SetNode));
cursor = cursor->next;
}
free(icon_rgb_buf);
free(icon_alpha_buf);
return head;
}
Result extract_badges(void)
{
DEBUG("Dumping installed badges...\n");
char *badgeMngBuffer = NULL;
u32 size = file_to_buf(fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), ArchiveBadgeExt, &badgeMngBuffer);
DEBUG("%lu bytes read\n", size);
Result res = 0;
SetNode *head;
Handle backupDataHandle;
u16 *badge_rgb_buf = malloc(64 * 64 * 2);
u8 *badge_alpha_buf = malloc(64 * 64 * 0.5);
u32 badge_count = 0;
memcpy(&badge_count, badgeMngBuffer + 0x8, 4);
DEBUG("%lu badges found\n", badge_count);
if (badge_count > 0)
{
res = FSUSER_OpenFile(&backupDataHandle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeData.dat"), FS_OPEN_READ, 0);
if (R_FAILED(res))
{
free(badgeMngBuffer);
free(badge_rgb_buf);
free(badge_alpha_buf);
char err_string[128] = {0};
sprintf(err_string, language.badges.extdata_locked, res);
throw_error(err_string, ERROR_LEVEL_WARNING);
DEBUG("backupDataHandle open failed\n");
return -1;
}
head = extract_sets(badgeMngBuffer, backupDataHandle);
} else {
free(badgeMngBuffer);
free(badge_rgb_buf);
free(badge_alpha_buf);
return 0;
}
for (u32 i = 0; i < badge_count; ++i)
{
u32 badgeId;
memcpy(&badgeId, badgeMngBuffer + 0x3E8 + i * 0x28 + 0x4, 4);
u32 badgeSetId;
memcpy(&badgeSetId, badgeMngBuffer + 0x3E8 + i * 0x28 + 0x8, 4);
u16 badgeSubId;
memcpy(&badgeSubId, badgeMngBuffer + 0x3E8 + i * 0x28 + 0xE, 2);
u32 shortcut;
memcpy(&shortcut, badgeMngBuffer + 0x3E8 + i * 0x28 + 0x18, 4);
char filename[512] = {0};
u16 utf16Name[0x46] = {0};
FSFILE_Read(backupDataHandle, NULL, 0x35E80 + i * 16 * 0x8A, utf16Name, 0x8A);
replace_chars(utf16Name, ILLEGAL_CHARS, u'-');
char utf8Name[256] = {0};
res = utf16_to_utf8((u8 *) utf8Name, utf16Name, 256);
char dir[256] = "/3ds/" APP_TITLE "/BadgeBackups/";
if (badgeSetId != 0xEFBE) // 0xEFBE is GYTB Set ID; GYTB doesn't properly create sets, so skip
{
u32 set_index = get_set_index(head, badgeSetId);
if (set_index == 0xFFFFFFFF) {
sprintf(dir, "/3ds" APP_TITLE "/BadgeBackups/Unknown Set");
} else
{
u16 utf16SetName[0x46] = {0};
FSFILE_Read(backupDataHandle, NULL, set_index * 16 * 0x8A, utf16SetName, 0x8A);
replace_chars(utf16SetName, ILLEGAL_CHARS, u'-');
char utf8SetName[128] = {0};
res = utf16_to_utf8((u8 *) utf8SetName, utf16SetName, 128);
if (!res)
strncpy(utf8SetName, "Unknown Set", 128);
DEBUG("UTF-8 Set Name: %s; ID: %lx\n", utf8SetName, badgeSetId);
sprintf(dir, "/3ds/" APP_TITLE "/BadgeBackups/%s", utf8SetName);
}
}
if (shortcut == 0xFFFFFFFF)
{
sprintf(filename, "%s/%s.%lx.%x.png", dir, utf8Name, badgeId, badgeSubId);
} else
{
sprintf(filename, "%s/%s.%08lx.%lx.%x.png", dir, utf8Name, shortcut, badgeId, badgeSubId);
}
DEBUG("Dump filename: %s\n", filename);
FSFILE_Read(backupDataHandle, NULL, 0x318F80 + i * 0x2800, badge_rgb_buf, 0x2000);
FSFILE_Read(backupDataHandle, NULL, 0x318F80 + i * 0x2800 + 0x2000, badge_alpha_buf, 0x800);
rgb565ToPngFile(filename, badge_rgb_buf, badge_alpha_buf, 64, 64);
draw_loading_bar(i + 1, badge_count, INSTALL_DUMPING_BADGES);
}
free(badgeMngBuffer);
free(badge_rgb_buf);
free(badge_alpha_buf);
free_list(head);
FSFILE_Close(backupDataHandle);
return res;
}
Result backup_badges_fast(void)
{
char *badgeMng = NULL;
DEBUG("writing badge data: making files...\n");
char mng_path[128] = "/3ds/" APP_TITLE "/BadgeMngFile.dat";
char data_path[128] = "/3ds/" APP_TITLE "/BadgeData.dat";
DEBUG("mng_path: %s, data_path: %s\n", mng_path, data_path);
Handle dataHandle = 0;
Handle sdHandle = 0;
DEBUG("loading existing badge mng file...\n");
u32 mngRead = file_to_buf(fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), ArchiveBadgeExt, &badgeMng);
DEBUG("loading existing badge data file\n");
Result res = FSUSER_OpenFile(&dataHandle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeData.dat"), FS_OPEN_READ, 0);
if (mngRead != BADGE_MNG_SIZE || R_FAILED(res))
{
char err_string[128] = {0};
sprintf(err_string, language.badges.extdata_locked, res);
throw_error(err_string, ERROR_LEVEL_WARNING);
if (badgeMng) free(badgeMng);
if (dataHandle) FSFILE_Close(dataHandle);
FSFILE_Close(sdHandle);
return -1;
}
remake_file(fsMakePath(PATH_ASCII, mng_path), ArchiveSD, BADGE_MNG_SIZE);
FSUSER_CreateFile(ArchiveSD, fsMakePath(PATH_ASCII, data_path), 0, BADGE_DATA_SIZE);
FSUSER_OpenFile(&sdHandle, ArchiveSD, fsMakePath(PATH_ASCII, data_path), FS_OPEN_WRITE, 0);
DEBUG("writing badge data: writing BadgeMngFile...\n");
res = buf_to_file(mngRead, fsMakePath(PATH_ASCII, mng_path), ArchiveSD, badgeMng);
if (R_FAILED(res))
{
DEBUG("Failed to write badgemngfile: 0x%08lx\n", res);
free(badgeMng);
FSFILE_Close(dataHandle);
FSFILE_Close(sdHandle);
return -1;
}
DEBUG("writing badge data: writing badgedata...\n");
char *buf = malloc(0x10000);
u64 size = BADGE_DATA_SIZE;
u64 cur = 0;
while (size > 0)
{
u32 read = 0;
res = FSFILE_Read(dataHandle, &read, cur, buf, min(0x10000, size));
res = FSFILE_Write(sdHandle, NULL, cur, buf, read, FS_WRITE_FLUSH);
size -= read;
cur += read;
}
free(badgeMng);
free(buf);
FSFILE_Close(dataHandle);
FSFILE_Close(sdHandle);
return 0;
}
Result install_badges(void)
{
Handle handle = 0;
Handle folder = 0;
Result res = 0;
draw_loading_bar(0, 1, INSTALL_BADGES);
{
char testpath[128] = "/3ds/" APP_TITLE "/BadgeData.dat";
if (R_FAILED(res = FSUSER_OpenFile(&handle, ArchiveSD, fsMakePath(PATH_ASCII, testpath), FS_OPEN_READ, 0)))
{
if (R_SUMMARY(res) == RS_NOTFOUND)
{
res = backup_badges_fast();
if (R_FAILED(res)) return res;
} else
{
DEBUG("????: 0x%08lx\n", res);
}
}
}
if (handle) FSFILE_Close(handle);
DEBUG("Initializing ACT\n");
res = actInit();
if (R_FAILED(res))
{
DEBUG("actInit() failed!\n");
return res;
}
DEBUG("Initializing ACTU\n");
res = ACTU_Initialize(0xB0502C8, 0, 0);
if (R_FAILED(res))
{
DEBUG("ACTU_Initialize failed! %08lx\n", res);
return res;
}
DEBUG("Getting NNID\n");
u32 nnidNum = 0xFFFFFFFF;
res = ACTU_GetAccountDataBlock(0xFE, 4, 12, &nnidNum);
if (R_FAILED(res))
{
DEBUG("ACTU_GetAccountDataBlock failed! %08lx\n", res);
return res;
}
DEBUG("NNID found: 0x%08lx\n", nnidNum);
badgeMngBuffer = NULL;
badgeDataHandle = 0;
rgb_buf_64x64 = NULL;
rgb_buf_32x32 = NULL;
alpha_buf_64x64 = NULL;
alpha_buf_32x32 = NULL;
DEBUG("Opening badge directory\n");
FS_DirectoryEntry *badge_files = calloc(1024, sizeof(FS_DirectoryEntry));
res = FSUSER_OpenDirectory(&folder, ArchiveSD, fsMakePath(PATH_ASCII, "/Badges/"));
if (R_FAILED(res))
{
DEBUG("Failed to open folder: %lx\n", res);
goto end;
}
u32 entries_read;
res = FSDIR_Read(folder, &entries_read, 1024, badge_files);
DEBUG("%lu files found\n", entries_read);
rgb_buf_64x64 = malloc(12*6*64*64*2); //12x6 badges in sheet max, 64x64 pixel badges, 2 bytes per RGB data
alpha_buf_64x64 = malloc(12*6*64*64/2); //Same thing, but 2 pixels of alpha data per byte
rgb_buf_32x32 = malloc(12*6*32*32*2); //Same thing, but 32x32
alpha_buf_32x32 = malloc(12*6*32*32/2);
res = FSUSER_OpenFile(&badgeDataHandle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeData.dat"), FS_OPEN_WRITE, 0);
badgeMngBuffer = calloc(1, BADGE_MNG_SIZE);
if (!rgb_buf_64x64)
{
DEBUG("rgb_buf_64x64 alloc failed\n");
goto end;
}
if (!alpha_buf_64x64)
{
DEBUG("alpha_buf_64x64 alloc failed\n");
goto end;
}
if (!rgb_buf_32x32)
{
DEBUG("rgb_buf_32x32 alloc failed\n");
goto end;
}
if (!alpha_buf_32x32)
{
DEBUG("alpha_buf_32x32 alloc failed\n");
goto end;
}
if (R_FAILED(res))
{
badgeDataHandle = 0;
char err_string[128] = {0};
sprintf(err_string, language.badges.extdata_locked, res);
throw_error(err_string, ERROR_LEVEL_WARNING);
DEBUG("badgeDataHandle open failed\n");
goto end;
}
if (!badgeMngBuffer)
{
DEBUG("badgeMngBuffer alloc failed\n");
goto end;
}
zero_handle_memeasy(badgeDataHandle);
int badge_count = 0;
int set_count = 0;
int default_set = 0;
int default_set_count = 0;
int default_idx = 0;
progress_finish = entries_read + 12;
progress_status = 12;
draw_loading_bar(progress_status, progress_finish, INSTALL_BADGES);
for (u32 i = 0; i < entries_read && badge_count < 1000; ++i)
{
if (!strcmp(badge_files[i].shortExt, "PNG"))
{
if (default_set == 0)
{
set_count += 1;
default_set = set_count;
default_idx = badge_count;
}
u16 path[0x512] = {0};
struacat(path, "/Badges/");
strucat(path, badge_files[i].name);
default_set_count += install_badge_png(fsMakePath(PATH_UTF16, path), badge_files[i], &badge_count, default_set);
} else if (!strcmp(badge_files[i].shortExt, "ZIP"))
{
if (default_set == 0)
{
set_count += 1;
default_set = set_count;
}
u16 path[0x512] = {0};
struacat(path, "/Badges/");
strucat(path, badge_files[i].name);
default_set_count += install_badge_zip(path, &badge_count, default_set);
} else if (badge_files[i].attributes & FS_ATTRIBUTE_DIRECTORY)
{
set_count += 1;
u32 count = install_badge_dir(badge_files[i], &badge_count, set_count);
if (count == 0)
set_count -= 1;
}
progress_status += 1;
draw_loading_bar(progress_status, progress_finish, INSTALL_BADGES);
}
DEBUG("Badges installed - doing metadata\n");
if (default_set != 0)
{
int default_index = default_set - 1;
u32 total_count = 0xFFFF * default_set_count;
for (int i = 0; i < 16; ++i)
{
u16 name[0x8A/2] = {0};
utf8_to_utf16(name, (u8 *) "Other Badges", 0x8A);
FSFILE_Write(badgeDataHandle, NULL, default_index * 0x8A0 + i * 0x8A, &name, strulen(name, 0x45) * 2, 0);
}
badgeMngBuffer[0x3D8 + default_index/8] |= 1 << (default_index % 8);
memset(badgeMngBuffer + 0xA028 + default_index * 0x30, 0xFF, 8);
badgeMngBuffer[0xA028 + 0xC + default_index * 0x30] = 0x10; // bytes 13 and 14 are 0x2710
badgeMngBuffer[0xA028 + 0xD + default_index * 0x30] = 0x27;
memcpy(badgeMngBuffer + 0xA028 + 0x10 + default_index * 0x30, &default_set, 4);
memcpy(badgeMngBuffer + 0xA028 + 0x14 + default_index * 0x30, &default_index, 4);
memset(badgeMngBuffer + 0xA028 + 0x18 + default_index * 0x30, 0xFF, 4);
memcpy(badgeMngBuffer + 0xA028 + 0x1C + default_index * 0x30, &default_set_count, 4);
memcpy(badgeMngBuffer + 0xA028 + 0x20 + default_index * 0x30, &total_count, 4);
memcpy(badgeMngBuffer + 0xA028 + 0x24 + default_index * 0x30, &default_idx, 4);
FILE *fp = fopen("romfs:/anemone_set.png", "rb");
fseek(fp, 0L, SEEK_END);
ssize_t size = ftell(fp);
char *icon_buf = malloc(size);
fseek(fp, 0L, SEEK_SET);
fread(icon_buf, 1, size, fp);
fclose(fp);
pngToRGB565(icon_buf, size, rgb_buf_64x64, alpha_buf_64x64, rgb_buf_32x32, alpha_buf_32x32, true);
free(icon_buf);
FSFILE_Write(badgeDataHandle, NULL, 0x250F80 + default_index * 0x2000, rgb_buf_64x64, 64 * 64 * 2, 0);
}
FSFILE_Flush(badgeDataHandle);
u32 total_badges = 0xFFFF * badge_count; // Quantity * unique badges?
badgeMngBuffer[0x4] = set_count; // badge set count
memcpy(badgeMngBuffer + 0x8, &badge_count, 4);
badgeMngBuffer[0x10] = 0xFF; // Selected Set - 0xFFFFFFFF is all badges
badgeMngBuffer[0x11] = 0xFF;
badgeMngBuffer[0x12] = 0xFF;
badgeMngBuffer[0x13] = 0xFF;
memcpy(badgeMngBuffer + 0x18, &total_badges, 4);
memcpy(badgeMngBuffer + 0x1C, &nnidNum, 4);
for (int i = set_count; i < 100; ++i) // default values for remaining sets
{
memset(badgeMngBuffer + 0xA028 + 0x30 * i, 0xFF, 0x8);
memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0x8, 0x00, 0x4);
badgeMngBuffer[0xA028 + 0x30 * i + 0xC] = 0x10;
badgeMngBuffer[0xA028 + 0x30 * i + 0xD] = 0x27;
memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0xE, 0x0, 0x2);
memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0x10, 0xFF, 0xC);
memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0x1C, 0x00, 0x8);
memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0x24, 0xFF, 0x4);
memset(badgeMngBuffer + 0xA028 + 0x30 * i + 0x28, 0x00, 0x8);
}
res = FSUSER_OpenFile(&handle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), FS_OPEN_READ, 0);
if (res == 0)
{
FSFILE_Read(handle, NULL, 0xB2E8, badgeMngBuffer+0xB2E8, 360 * 0x18);
FSFILE_Close(handle);
}
res = buf_to_file(BADGE_MNG_SIZE, fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), ArchiveBadgeExt, badgeMngBuffer);
if (res)
{
DEBUG("Error writing badge manage data! %lx\n", res);
char err_string[128] = {0};
sprintf(err_string, language.badges.extdata_locked, res);
throw_error(err_string, ERROR_LEVEL_WARNING);
goto end;
}
end:
actExit();
if (rgb_buf_64x64) free(rgb_buf_64x64);
if (alpha_buf_64x64) free(alpha_buf_64x64);
if (rgb_buf_32x32) free(rgb_buf_32x32);
if (alpha_buf_32x32) free(alpha_buf_32x32);
if (handle) FSFILE_Close(handle);
if (folder) FSDIR_Close(folder);
if (badgeDataHandle) FSFILE_Close(badgeDataHandle);
if (badgeMngBuffer) free(badgeMngBuffer);
if (badge_files) free(badge_files);
return res;
}

View File

@@ -360,13 +360,13 @@ bool init_qr(void)
if(r == ARCHIVE_OK) if(r == ARCHIVE_OK)
{ {
EntryMode mode = MODE_AMOUNT; RemoteMode mode = REMOTE_MODE_AMOUNT;
char * buf = NULL; char * buf = NULL;
do { do {
if(zip_memory_to_buf("body_LZ.bin", zip_buf, zip_size, &buf) != 0) if(zip_memory_to_buf("body_LZ.bin", zip_buf, zip_size, &buf) != 0)
{ {
mode = MODE_THEMES; mode = REMOTE_MODE_THEMES;
break; break;
} }
@@ -374,7 +374,7 @@ bool init_qr(void)
buf = NULL; buf = NULL;
if(zip_memory_to_buf("splash.bin", zip_buf, zip_size, &buf) != 0) if(zip_memory_to_buf("splash.bin", zip_buf, zip_size, &buf) != 0)
{ {
mode = MODE_SPLASHES; mode = REMOTE_MODE_SPLASHES;
break; break;
} }
@@ -382,7 +382,7 @@ bool init_qr(void)
buf = NULL; buf = NULL;
if(zip_memory_to_buf("splashbottom.bin", zip_buf, zip_size, &buf) != 0) if(zip_memory_to_buf("splashbottom.bin", zip_buf, zip_size, &buf) != 0)
{ {
mode = MODE_SPLASHES; mode = REMOTE_MODE_SPLASHES;
break; break;
} }
} }
@@ -391,16 +391,24 @@ bool init_qr(void)
free(buf); free(buf);
buf = NULL; buf = NULL;
if(mode != MODE_AMOUNT) if(mode != REMOTE_MODE_AMOUNT)
{ {
save_zip_to_sd(filename, zip_size, zip_buf, mode); save_zip_to_sd(filename, zip_size, zip_buf, mode);
success = true; success = true;
} }
else else
{
bool badge = draw_confirm_no_interface(language.camera.badge_question);
if (badge)
{
save_zip_to_sd(filename, zip_size, zip_buf, REMOTE_MODE_BADGES);
// don't set success since we don't need to reload lists for badge zips
} else
{ {
throw_error(language.camera.zip_not_theme_splash, ERROR_LEVEL_WARNING); throw_error(language.camera.zip_not_theme_splash, ERROR_LEVEL_WARNING);
} }
} }
}
else else
{ {
throw_error(language.camera.file_not_zip, ERROR_LEVEL_WARNING); throw_error(language.camera.file_not_zip, ERROR_LEVEL_WARNING);

View File

@@ -30,11 +30,11 @@ Color colors[COLOR_AMOUNT] = {0};
void init_colors(void) void init_colors(void)
{ {
colors[COLOR_BACKGROUND] = C2D_Color32(35, 28, 32, 255); //silver-y black colors[COLOR_BACKGROUND] = config.background_color;
colors[COLOR_ACCENT] = C2D_Color32(12, 58, 111, 255); colors[COLOR_ACCENT] = config.accent_color;
colors[COLOR_WHITE] = C2D_Color32(255, 255, 255, 255); colors[COLOR_WHITE] = C2D_Color32(255, 255, 255, 255);
colors[COLOR_CURSOR] = C2D_Color32(200, 200, 200, 255); colors[COLOR_CURSOR] = C2D_Color32(200, 200, 200, 255);
colors[COLOR_BLACK] = C2D_Color32(0, 0, 0, 255); colors[COLOR_BLACK] = C2D_Color32(0, 0, 0, 255);
colors[COLOR_RED] = C2D_Color32(229, 66, 66, 255); colors[COLOR_RED] = config.red_color;
colors[COLOR_YELLOW] = C2D_Color32(239, 220, 11, 255); colors[COLOR_YELLOW] = config.yellow_color;
} }

117
source/config.c Normal file
View File

@@ -0,0 +1,117 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2024 Contributors in CONTRIBUTORS.md
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <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.
*/
#include "config.h"
Config_s config;
void load_config(void)
{
memset(&config, 0, sizeof(Config_s));
char *json_buf = NULL;
u32 json_len = file_to_buf(fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE "/config.json"), ArchiveSD, &json_buf);
if (json_len)
{
json_error_t error;
json_t *root = json_loadb(json_buf, json_len, 0, &error);
if (root)
{
const char *key;
json_t *value;
json_object_foreach(root, key, value)
{
if (json_is_array(value) && !strcmp(key, "Accent Color") && json_array_size(value) == 4)
{
if (json_is_integer(json_array_get(value, 0)) && json_is_integer(json_array_get(value, 1))
&& json_is_integer(json_array_get(value, 2)) && json_is_integer(json_array_get(value, 3)))
{
u8 r = min(255, json_integer_value(json_array_get(value, 0)));
u8 g = min(255, json_integer_value(json_array_get(value, 1)));
u8 b = min(255, json_integer_value(json_array_get(value, 2)));
u8 a = min(255, json_integer_value(json_array_get(value, 3)));
config.accent_color = C2D_Color32(r, g, b, a);
}
}
else if (json_is_array(value) && !strcmp(key, "Background Color") && json_array_size(value) == 4)
{
if (json_is_integer(json_array_get(value, 0)) && json_is_integer(json_array_get(value, 1))
&& json_is_integer(json_array_get(value, 2)) && json_is_integer(json_array_get(value, 3)))
{
u8 r = min(255, json_integer_value(json_array_get(value, 0)));
u8 g = min(255, json_integer_value(json_array_get(value, 1)));
u8 b = min(255, json_integer_value(json_array_get(value, 2)));
u8 a = min(255, json_integer_value(json_array_get(value, 3)));
config.background_color = C2D_Color32(r, g, b, a);
}
}
else if (json_is_array(value) && !strcmp(key, "Red Color") && json_array_size(value) == 4)
{
if (json_is_integer(json_array_get(value, 0)) && json_is_integer(json_array_get(value, 1))
&& json_is_integer(json_array_get(value, 2)) && json_is_integer(json_array_get(value, 3)))
{
u8 r = min(255, json_integer_value(json_array_get(value, 0)));
u8 g = min(255, json_integer_value(json_array_get(value, 1)));
u8 b = min(255, json_integer_value(json_array_get(value, 2)));
u8 a = min(255, json_integer_value(json_array_get(value, 3)));
config.red_color = C2D_Color32(r, g, b, a);
}
}
else if (json_is_array(value) && !strcmp(key, "Yellow Color") && json_array_size(value) == 4)
{
if (json_is_integer(json_array_get(value, 0)) && json_is_integer(json_array_get(value, 1))
&& json_is_integer(json_array_get(value, 2)) && json_is_integer(json_array_get(value, 3)))
{
u8 r = min(255, json_integer_value(json_array_get(value, 0)));
u8 g = min(255, json_integer_value(json_array_get(value, 1)));
u8 b = min(255, json_integer_value(json_array_get(value, 2)));
u8 a = min(255, json_integer_value(json_array_get(value, 3)));
config.yellow_color = C2D_Color32(r, g, b, a);
}
}
}
} else
{
DEBUG(error.text);
}
}
if (config.accent_color == 0)
config.accent_color = C2D_Color32(12, 58, 111, 255);
if (config.background_color == 0)
config.background_color = C2D_Color32(35, 28, 32, 255); //silver-y black
if (config.red_color == 0)
config.red_color = C2D_Color32(229, 66, 66, 255);
if (config.yellow_color == 0)
config.yellow_color = C2D_Color32(239, 220, 11, 255);
if (json_buf) free(json_buf);
}

View File

@@ -3,6 +3,195 @@
#include <png.h> #include <png.h>
// don't be fooled - this function always expects 64x64 input buffers. Width/height only
// control output resolution
int rgb565ToPngFile(char *filename, u16 *rgb_buf, u8 *alpha_buf, int width, int height)
{
FILE *fp = fopen(filename, "wb");
u8 *image = malloc(64 * 64 * 4);
if (!image) return -1;
int i, x, y, r, g, b, a;
for (i = 0; i < 64 * 64; ++i)
{
r = (rgb_buf[i] & 0xF800) >> 11;
g = (rgb_buf[i] & 0x07E0) >> 5;
b = (rgb_buf[i] & 0x001F);
a = (alpha_buf[i/2] >> (4*(i%2))) & 0x0F;
r = round(r * 255.0 / 31.0);
g = round(g * 255.0 / 63.0);
b = round(b * 255.0 / 31.0);
a = a * 0x11;
x = 8*((i/64)%8) + (((i%64)&0x01) >> 0) + (((i%64)&0x04) >> 1) + (((i%64)&0x10) >> 2);
y = 8*(i/512) + (((i%64)&0x02) >> 1) + (((i%64)&0x08) >> 2) + (((i%64)&0x20) >> 3);
image[y * 64 * 4 + x * 4 + 0] = r;
image[y * 64 * 4 + x * 4 + 1] = g;
image[y * 64 * 4 + x * 4 + 2] = b;
image[y * 64 * 4 + x * 4 + 3] = a;
}
png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop info = png_create_info_struct(png);
setjmp(png_jmpbuf(png));
png_init_io(png, fp);
png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png, info);
png_bytep row = malloc(4 * width * sizeof(png_byte));
for (y = 0; y < height; ++y)
{
for (x = 0; x < width; ++x)
{
row[x * 4 + 0] = image[(y * 64 + x) * 4 + 0];
row[x * 4 + 1] = image[(y * 64 + x) * 4 + 1];
row[x * 4 + 2] = image[(y * 64 + x) * 4 + 2];
row[x * 4 + 3] = image[(y * 64 + x) * 4 + 3];
}
png_write_row(png, row);
}
png_write_end(png, info);
png_free_data(png, info, PNG_FREE_ALL, -1);
png_destroy_write_struct(&png, NULL);
free(row);
free(image);
fflush(fp);
fclose(fp);
return 0;
}
int pngToRGB565(char *png_buf, u64 fileSize, u16 *rgb_buf_64x64, u8 *alpha_buf_64x64, u16 *rgb_buf_32x32, u8 *alpha_buf_32x32, bool set_icon)
{
if (png_buf == NULL) return 0;
if (fileSize < 8 || png_sig_cmp((png_bytep) png_buf, 0, 8))
{
return 0;
}
u32 *buf = (u32 *) png_buf;
FILE *fp = fmemopen(buf, fileSize, "rb");
png_bytep *row_pointers = NULL;
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop info = png_create_info_struct(png);
png_init_io(png, fp);
png_read_info(png, info);
u32 width = png_get_image_width(png, info);
u32 height = png_get_image_height(png, info);
png_byte color_type = png_get_color_type(png, info);
png_byte bit_depth = png_get_bit_depth(png, info);
if (set_icon && (width != 48 || height != 48))
{
DEBUG("Invalid set icon?\n");
return 0;
}
if (!set_icon && (width < 64 || height < 64 || width % 64 != 0 || height % 64 != 0 || width > 12 * 64 || height > 6 * 64))
{
DEBUG("Invalid png found...\n");
return 0;
}
if (bit_depth == 16)
png_set_strip_16(png);
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png);
if (png_get_valid(png, info, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png);
if (color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png, 0xFF, PNG_FILLER_AFTER);
if (color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
png_read_update_info(png, info);
u32 x, y, r, g, b, a;
memset(alpha_buf_64x64, 0, 12*6*64*64/2);
memset(alpha_buf_32x32, 0, 12*6*32*32/2);
row_pointers = malloc(sizeof(png_bytep) * height);
for (y = 0; y < height; y++)
{
row_pointers[y] = (png_byte *) malloc(png_get_rowbytes(png, info));
}
png_read_image(png, row_pointers);
png_destroy_read_struct(&png, &info, NULL);
if (fp) fclose(fp);
for (y = 0; y < height; ++y)
{
png_bytep row = row_pointers[y];
for (x = 0; x < width; ++x)
{
png_bytep px = &(row[x * 4]);
r = px[0] >> 3;
g = px[1] >> 2;
b = px[2] >> 3;
a = px[3] >> 4;
// rgb565 conversion code adapted from GYTB
int rgb565_index = 8*64*((y/8)%8) | 64*((x/8)%8) | 32*((y/4)%2) | 16*((x/4)%2) | 8*((y/2)%2) | 4*((x/2)%2) | 2*(y%2) | (x%2);
rgb565_index |= 64*64*(height/64)*(x/64) + 64*64*(y/64); // account for multiple badges from 1 image
rgb_buf_64x64[rgb565_index] = (r << 11) | (g << 5) | b;
alpha_buf_64x64[rgb565_index / 2] |= a << (4 * (x % 2));
}
}
for (y = 0; y < height; y += 2)
{
png_bytep row = row_pointers[y];
png_bytep nextrow = row_pointers[y+1];
for (x = 0; x < width; x += 2)
{
png_bytep px1 = &(row[x * 4]);
png_bytep px2 = &(nextrow[x * 4]);
png_bytep px3 = &(row[(x + 1) * 4]);
png_bytep px4 = &(nextrow[(x + 1) * 4]);
r = (px1[0] + px2[0] + px3[0] + px4[0]) >> 5;
g = (px1[1] + px2[1] + px3[1] + px4[1]) >> 4;
b = (px1[2] + px2[2] + px3[2] + px4[2]) >> 5;
a = (px1[3] + px2[3] + px3[3] + px4[3]) >> 6;
int x2 = x/2;
int y2 = y/2;
int rgb565_index = 4*64*((y2/8)%4) | 64*((x2/8)%4) | 32*((y2/4)%2) | 16*((x2/4)%2) | 8*((y2/2)%2) | 4*((x2/2)%2) | 2*(y2%2) | (x2%2);
rgb565_index |= 32*32*(height/64)*(x/64) + 32*32*(y/64);
rgb_buf_32x32[rgb565_index] = (r << 11) | (g << 5) | b;
alpha_buf_32x32[rgb565_index / 2] |= a << (4 * (x2%2));
}
}
for (y = 0; y < height; y++)
{
free(row_pointers[y]);
}
free(row_pointers);
if (!set_icon)
return (height/64)*(width/64);
else
return (height/48)*(width/48);
}
static void rotate_agbr_counterclockwise(char ** bufp, size_t size, size_t width) static void rotate_agbr_counterclockwise(char ** bufp, size_t size, size_t width)
{ {
uint32_t * buf = (uint32_t*)*bufp; uint32_t * buf = (uint32_t*)*bufp;

View File

@@ -47,6 +47,12 @@ static const char * mode_switch_char[MODE_AMOUNT] = {
"T", "T",
}; };
static const char *remote_mode_switch_char[REMOTE_MODE_AMOUNT] = {
"T",
"S",
"B",
};
void init_screens(void) void init_screens(void)
{ {
init_colors(); init_colors();
@@ -97,6 +103,7 @@ void init_screens(void)
C2D_TextParse(&text[TEXT_THEMEPLAZA_THEME_MODE], staticBuf, language.draw.tp_theme_mode); C2D_TextParse(&text[TEXT_THEMEPLAZA_THEME_MODE], staticBuf, language.draw.tp_theme_mode);
C2D_TextParse(&text[TEXT_THEMEPLAZA_SPLASH_MODE], staticBuf, language.draw.tp_splash_mode); C2D_TextParse(&text[TEXT_THEMEPLAZA_SPLASH_MODE], staticBuf, language.draw.tp_splash_mode);
C2D_TextParse(&text[TEXT_THEMEPLAZA_BADGE_MODE], staticBuf, language.draw.tp_badge_mode);
C2D_TextParse(&text[TEXT_SEARCH], staticBuf, language.draw.search); C2D_TextParse(&text[TEXT_SEARCH], staticBuf, language.draw.search);
C2D_TextParse(&text[TEXT_PAGE], staticBuf, language.draw.page); C2D_TextParse(&text[TEXT_PAGE], staticBuf, language.draw.page);
@@ -124,10 +131,13 @@ void init_screens(void)
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_THEMES], staticBuf, language.draw.download_themes); C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_THEMES], staticBuf, language.draw.download_themes);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_SPLASHES], staticBuf, language.draw.download_splashes); C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_SPLASHES], staticBuf, language.draw.download_splashes);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_BADGES], staticBuf, language.draw.download_badges);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_PREVIEW], staticBuf, language.draw.download_preview); C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_PREVIEW], staticBuf, language.draw.download_preview);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_BGM], staticBuf, language.draw.download_bgm); C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_BGM], staticBuf, language.draw.download_bgm);
C2D_TextParse(&text[TEXT_INSTALL_DUMPING_THEME], staticBuf, language.draw.dump_single); C2D_TextParse(&text[TEXT_INSTALL_DUMPING_THEME], staticBuf, language.draw.dump_single);
C2D_TextParse(&text[TEXT_INSTALL_DUMPING_ALL_THEMES], staticBuf, language.draw.dump_all_official); C2D_TextParse(&text[TEXT_INSTALL_DUMPING_ALL_THEMES], staticBuf, language.draw.dump_all_official);
C2D_TextParse(&text[TEXT_INSTALL_DUMPING_BADGES], staticBuf, language.draw.dump_badges);
C2D_TextParse(&text[TEXT_INSTALL_BADGES], staticBuf, language.draw.install_badges);
for(int i = 0; i < TEXT_AMOUNT; i++) for(int i = 0; i < TEXT_AMOUNT; i++)
C2D_TextOptimize(&text[i]); C2D_TextOptimize(&text[i]);
@@ -339,13 +349,31 @@ bool draw_confirm(const char * conf_msg, Entry_List_s * list, DrawMode draw_mode
return false; return false;
} }
void draw_preview(C2D_Image preview, int preview_offset) bool draw_confirm_no_interface(const char *conf_msg)
{
while(aptMainLoop())
{
draw_base_interface();
draw_text_center(GFX_TOP, 100, 0.5f, 0.7f, 0.7f, colors[COLOR_YELLOW], conf_msg);
draw_c2d_text_center(GFX_TOP, 170, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], &text[TEXT_CONFIRM_YES_NO]);
end_frame();
hidScanInput();
u32 kDown = hidKeysDown();
if(kDown & KEY_A) return true;
if(kDown & KEY_B) return false;
}
return false;
}
void draw_preview(C2D_Image preview, int preview_offset, float preview_scale)
{ {
start_frame(); start_frame();
set_screen(top); set_screen(top);
C2D_DrawImageAt(preview, -preview_offset, 0, 0.5f, NULL, 1.0f, 1.0f); C2D_DrawImageAt(preview, -preview_offset, 0, 0.5f, NULL, preview_scale, preview_scale);
set_screen(bottom); set_screen(bottom);
C2D_DrawImageAt(preview, -(preview_offset+40), -240, 0.5f, NULL, 1.0f, 1.0f); C2D_DrawImageAt(preview, -(preview_offset+40), -240, 0.5f, NULL, preview_scale, preview_scale);
} }
static void draw_install_handler(InstallType type) static void draw_install_handler(InstallType type)
@@ -519,9 +547,10 @@ void draw_grid_interface(Entry_List_s * list, Instructions_s instructions, int e
draw_base_interface(); draw_base_interface();
EntryMode current_mode = list->mode; EntryMode current_mode = list->mode;
C2D_Text * mode_string[MODE_AMOUNT] = { C2D_Text * mode_string[REMOTE_MODE_AMOUNT] = {
&text[TEXT_THEMEPLAZA_THEME_MODE], &text[TEXT_THEMEPLAZA_THEME_MODE],
&text[TEXT_THEMEPLAZA_SPLASH_MODE], &text[TEXT_THEMEPLAZA_SPLASH_MODE],
&text[TEXT_THEMEPLAZA_BADGE_MODE],
}; };
draw_c2d_text_center(GFX_TOP, 4, 0.5f, 0.5f, 0.5f, colors[COLOR_WHITE], mode_string[current_mode]); draw_c2d_text_center(GFX_TOP, 4, 0.5f, 0.5f, 0.5f, colors[COLOR_WHITE], mode_string[current_mode]);
@@ -540,7 +569,7 @@ void draw_grid_interface(Entry_List_s * list, Instructions_s instructions, int e
draw_image(sprites_exit_idx, 320-72, 0); draw_image(sprites_exit_idx, 320-72, 0);
draw_image(sprites_preview_idx, 320-48, 0); draw_image(sprites_preview_idx, 320-48, 0);
draw_text(320-24+2.5, -3, 0.6, 1.0f, 0.9f, colors[COLOR_WHITE], mode_switch_char[!current_mode]); draw_text(320-24+2.5, -3, 0.6, 1.0f, 0.9f, colors[COLOR_WHITE], remote_mode_switch_char[current_mode]);
draw_image(sprites_arrow_left_idx, 3, 114); draw_image(sprites_arrow_left_idx, 3, 114);
draw_image(sprites_arrow_right_idx, 308, 114); draw_image(sprites_arrow_right_idx, 308, 114);
@@ -667,7 +696,7 @@ void draw_interface(Entry_List_s * list, Instructions_s instructions, DrawMode d
{ {
char * shuffle_count_string = NULL; char * shuffle_count_string = NULL;
asprintf(&shuffle_count_string, language.draw.shuffle, list->shuffle_count); asprintf(&shuffle_count_string, language.draw.shuffle, list->shuffle_count);
draw_text(7, 3, 0.6, 0.6, 0.6f, list->shuffle_count <= 10 && list->shuffle_count >= 2 ? colors[COLOR_WHITE] : colors[COLOR_RED], shuffle_count_string); draw_text(30, 3, 0.6, 0.6, 0.6f, list->shuffle_count <= 10 && list->shuffle_count >= 2 ? colors[COLOR_WHITE] : colors[COLOR_RED], shuffle_count_string);
free(shuffle_count_string); free(shuffle_count_string);
} }
@@ -678,10 +707,10 @@ void draw_interface(Entry_List_s * list, Instructions_s instructions, DrawMode d
draw_image(sprites_exit_idx, 320-72, 0); draw_image(sprites_exit_idx, 320-72, 0);
draw_image(sprites_preview_idx, 320-48, 0); draw_image(sprites_preview_idx, 320-48, 0);
draw_text(320-24+2.5, -3, 0.6, 1.0f, 0.9f, colors[COLOR_WHITE], mode_switch_char[!current_mode]); draw_text(320-24+2.5, -3, 0.6, 1.0f, 0.9f, colors[COLOR_WHITE], mode_switch_char[!current_mode]);
draw_image(sprites_menu_idx, 320-144, 0); draw_image(sprites_menu_idx, 2, 0);
if (current_mode == MODE_THEMES) if (current_mode == MODE_THEMES)
{ {
draw_image(sprites_shuffle_idx, 320-168, 0); draw_image(sprites_shuffle_idx, 320-144, 0);
} }
} }
else else
@@ -692,22 +721,23 @@ void draw_interface(Entry_List_s * list, Instructions_s instructions, DrawMode d
draw_image(sprites_shuffle_idx, 320-48, 0); draw_image(sprites_shuffle_idx, 320-48, 0);
draw_image(sprites_shuffle_no_bgm_idx, 320-72, 0); draw_image(sprites_shuffle_no_bgm_idx, 320-72, 0);
draw_image(sprites_bgm_only_idx, 320-96, 0); draw_image(sprites_bgm_only_idx, 320-96, 0);
draw_image(sprites_back_idx, 320-120, 0); draw_image(sprites_back_idx, 2, 0);
} else if (draw_mode == DRAW_MODE_EXTRA) } else if (draw_mode == DRAW_MODE_EXTRA)
{ {
draw_image(sprites_browse_idx, 320-24, 0); draw_image(sprites_browse_idx, 320-24, 0);
draw_image(sprites_dump_idx, 320-48, 0); draw_image(sprites_dump_idx, 320-48, 0);
draw_image(sprites_sort_idx, 320-72, 0); draw_image(sprites_sort_idx, 320-72, 0);
draw_image(sprites_back_idx, 320-96, 0); draw_image(sprites_badge_idx, 320-96, 0);
draw_image(sprites_back_idx, 2, 0);
} }
} }
// Show arrows if there are themes out of bounds // Show arrows if there are themes out of bounds
//---------------------------------------------------------------- //----------------------------------------------------------------
if(list->scroll > 0) if(list->scroll > 0)
draw_image(sprites_arrow_up_idx, 136, 220); draw_image(sprites_arrow_up_idx, 141, 220);
if(list->scroll + list->entries_loaded < list->entries_count) if(list->scroll + list->entries_loaded < list->entries_count)
draw_image(sprites_arrow_down_idx, 152, 220); draw_image(sprites_arrow_down_idx, 157, 220);
for(int i = list->scroll; i < (list->entries_loaded + list->scroll); i++) for(int i = list->scroll; i < (list->entries_loaded + list->scroll); i++)
{ {

View File

@@ -30,6 +30,7 @@
#include "draw.h" #include "draw.h"
#include "unicode.h" #include "unicode.h"
#include "ui_strings.h" #include "ui_strings.h"
#include "remote.h"
#include <archive.h> #include <archive.h>
#include <archive_entry.h> #include <archive_entry.h>
@@ -37,6 +38,48 @@
FS_Archive ArchiveSD; FS_Archive ArchiveSD;
FS_Archive ArchiveHomeExt; FS_Archive ArchiveHomeExt;
FS_Archive ArchiveThemeExt; FS_Archive ArchiveThemeExt;
FS_Archive ArchiveBadgeExt;
Result createExtSaveData(u32 extdataID)
{
u8 null_smdh[0x36C0] = {0};
Handle *handle = fsGetSessionHandle();
u32 *cmdbuf = getThreadCommandBuffer();
u32 directory_limit = 1000;
u32 file_limit = 1000;
cmdbuf[0] = 0x08300182;
cmdbuf[1] = MEDIATYPE_SD;
cmdbuf[2] = extdataID;
cmdbuf[3] = 0;
cmdbuf[4] = 0x36C0;
cmdbuf[5] = directory_limit;
cmdbuf[6] = file_limit;
cmdbuf[7] = (0x36C0 << 4) | 0xA;
cmdbuf[8] = (u32)&null_smdh;
Result ret = 0;
if ((ret = svcSendSyncRequest(*handle)))
return ret;
return cmdbuf[1];
}
Result init_sd(void)
{
Result res;
if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveSD, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")))) return res;
load_config();
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds"), FS_ATTRIBUTE_DIRECTORY);
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE), FS_ATTRIBUTE_DIRECTORY);
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE "/cache"), FS_ATTRIBUTE_DIRECTORY);
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE "/BadgeBackups"), FS_ATTRIBUTE_DIRECTORY);
return 0;
}
Result open_archives(void) Result open_archives(void)
{ {
@@ -74,13 +117,10 @@ Result open_archives(void)
archive2 = 0x00; archive2 = 0x00;
} }
if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveSD, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")))) return res;
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Themes"), FS_ATTRIBUTE_DIRECTORY); FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Themes"), FS_ATTRIBUTE_DIRECTORY);
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Splashes"), FS_ATTRIBUTE_DIRECTORY); FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Splashes"), FS_ATTRIBUTE_DIRECTORY);
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds"), FS_ATTRIBUTE_DIRECTORY); FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Badges"), FS_ATTRIBUTE_DIRECTORY);
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE), FS_ATTRIBUTE_DIRECTORY); FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/Badges/ThemePlaza Badges"), FS_ATTRIBUTE_DIRECTORY);
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, "/3ds/" APP_TITLE "/cache"), FS_ATTRIBUTE_DIRECTORY);
u32 homeMenuPath[3] = {MEDIATYPE_SD, archive2, 0}; u32 homeMenuPath[3] = {MEDIATYPE_SD, archive2, 0};
home.type = PATH_BINARY; home.type = PATH_BINARY;
@@ -101,6 +141,72 @@ Result open_archives(void)
return 0; return 0;
} }
Result open_badge_extdata()
{
Handle test_handle;
FS_Path badge;
Result res = 0;
u32 badgePath[3] = {MEDIATYPE_SD, 0x000014d1, 0};
badge.type = PATH_BINARY;
badge.size = 0xC;
badge.data = badgePath;
if(R_FAILED(res = FSUSER_OpenArchive(&ArchiveBadgeExt, ARCHIVE_EXTDATA, badge)))
{
if (R_SUMMARY(res) == RS_NOTFOUND)
{
DEBUG("Extdata not found - creating\n");
createExtSaveData(0x000014d1);
FSUSER_OpenArchive(&ArchiveBadgeExt, ARCHIVE_EXTDATA, badge);
} else
{
DEBUG("Unknown extdata error\n");
return res;
}
}
if (R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeData.dat"), FS_OPEN_READ, 0)))
{
if (R_SUMMARY(res) == RS_NOTFOUND)
{
FSUSER_CreateFile(ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeData.dat"), 0, BADGE_DATA_SIZE);
FSUSER_OpenFile(&test_handle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeData.dat"), FS_OPEN_WRITE, 0);
FSFILE_Flush(test_handle);
}
DEBUG("Error 0x%08ld opening BadgeData.dat, retrying\n", res);
}
FSFILE_Close(test_handle);
if(R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveBadgeExt, fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), FS_OPEN_READ, 0)))
{
DEBUG("Error 0x%08ld opening BadgeMngFile.dat, retrying\n", res);
if (R_SUMMARY(res) == RS_NOTFOUND)
{
remake_file(fsMakePath(PATH_ASCII, "/BadgeMngFile.dat"), ArchiveBadgeExt, BADGE_MNG_SIZE);
}
}
FSFILE_Close(test_handle);
if(R_FAILED(res = FSUSER_OpenFile(&test_handle, ArchiveSD, fsMakePath(PATH_ASCII, "/Badges/ThemePlaza Badges/_seticon.png"), FS_OPEN_READ, 0)))
{
FILE *fp = fopen("romfs:/tp_set.png", "rb");
fseek(fp, 0L, SEEK_END);
ssize_t size = ftell(fp);
char *icon_buf = malloc(size);
fseek(fp, 0L, SEEK_SET);
fread(icon_buf, 1, size, fp);
fclose(fp);
remake_file(fsMakePath(PATH_ASCII, "/Badges/ThemePlaza Badges/_seticon.png"), ArchiveSD, size);
buf_to_file(size, fsMakePath(PATH_ASCII, "/Badges/ThemePlaza Badges/_seticon.png"), ArchiveSD, icon_buf);
DEBUG("res: 0x%08lx\n", res);
free(icon_buf);
}
return 0;
}
Result close_archives(void) Result close_archives(void)
{ {
Result res; Result res;
@@ -108,6 +214,7 @@ Result close_archives(void)
if(R_FAILED(res = FSUSER_CloseArchive(ArchiveSD))) return res; if(R_FAILED(res = FSUSER_CloseArchive(ArchiveSD))) return res;
if(R_FAILED(res = FSUSER_CloseArchive(ArchiveHomeExt))) return res; if(R_FAILED(res = FSUSER_CloseArchive(ArchiveHomeExt))) return res;
if(R_FAILED(res = FSUSER_CloseArchive(ArchiveThemeExt))) return res; if(R_FAILED(res = FSUSER_CloseArchive(ArchiveThemeExt))) return res;
if(R_FAILED(res = FSUSER_CloseArchive(ArchiveBadgeExt))) return res;
return 0; return 0;
} }
@@ -127,19 +234,61 @@ u32 file_to_buf(FS_Path path, FS_Archive archive, char ** buf)
{ {
Handle file; Handle file;
Result res = 0; Result res = 0;
if (R_FAILED(res = FSUSER_OpenFile(&file, archive, path, FS_OPEN_READ, 0))) return 0; if (R_FAILED(res = FSUSER_OpenFile(&file, archive, path, FS_OPEN_READ, 0)))
{
DEBUG("file_to_buf failed - 0x%08lx\n", res);
return 0;
}
u64 size; u64 size;
FSFILE_GetSize(file, &size); FSFILE_GetSize(file, &size);
if(size != 0) if(size != 0)
{ {
*buf = calloc(1, size); *buf = calloc(1, size);
if (*buf == NULL)
{
DEBUG("Error allocating buffer - out of memory??\n");
return 0;
}
FSFILE_Read(file, NULL, 0, *buf, size); FSFILE_Read(file, NULL, 0, *buf, size);
} }
FSFILE_Close(file); FSFILE_Close(file);
return (u32)size; return (u32)size;
} }
s16 for_each_file_zip(u16 *zip_path, u32 (*zip_iter_callback)(char *filebuf, u64 file_size, const char *name, void *userdata), void *userdata)
{
struct archive *a = archive_read_new();
archive_read_support_format_zip(a);
ssize_t len = strulen(zip_path, 0x106);
char * path = calloc(len, sizeof(u16));
utf16_to_utf8((u8 *)path, zip_path, len * sizeof(u16));
DEBUG("Attempting to open zip %s\n", path);
int r = archive_read_open_filename(a, path, 0x4000);
free(path);
if(r != ARCHIVE_OK)
{
DEBUG("Invalid zip being opened\n");
return -1;
}
struct archive_entry *entry;
u64 file_size = 0;
while (archive_read_next_header(a, &entry) == ARCHIVE_OK)
{
file_size = archive_entry_size(entry);
char *buf = calloc(file_size, sizeof(char));
archive_read_data(a, buf, file_size);
zip_iter_callback(buf, file_size, archive_entry_pathname(entry), userdata);
free(buf);
}
archive_read_free(a);
return 0;
}
static u32 zip_to_buf(struct archive * a, const char * file_name, char ** buf) static u32 zip_to_buf(struct archive * a, const char * file_name, char ** buf)
{ {
struct archive_entry * entry; struct archive_entry * entry;
@@ -186,7 +335,7 @@ u32 zip_memory_to_buf(const char * file_name, void * zip_memory, size_t zip_size
u32 zip_file_to_buf(const char * file_name, const u16 * zip_path, char ** buf) u32 zip_file_to_buf(const char * file_name, const u16 * zip_path, char ** buf)
{ {
ssize_t len = strulen(zip_path, 0x106); ssize_t len = strulen(zip_path, 0x106);
char * path = calloc(sizeof(char), len * sizeof(u16)); char * path = calloc(len, sizeof(u16));
utf16_to_utf8((u8 *)path, zip_path, len * sizeof(u16)); utf16_to_utf8((u8 *)path, zip_path, len * sizeof(u16));
struct archive * a = archive_read_new(); struct archive * a = archive_read_new();
@@ -363,10 +512,33 @@ void remake_file(FS_Path path, FS_Archive archive, u32 size)
FSFILE_Close(handle); FSFILE_Close(handle);
FSUSER_DeleteFile(archive, path); FSUSER_DeleteFile(archive, path);
} }
FSUSER_CreateFile(archive, path, 0, size); Result res = FSUSER_CreateFile(archive, path, 0, size);
DEBUG("Remake file res: 0x%08lx\n", res);
char * buf = calloc(size, 1); char * buf = calloc(size, 1);
if (buf == NULL)
{
DEBUG("out of memory - not overwriting file?\n");
} else {
buf_to_file(size, path, archive, buf); buf_to_file(size, path, archive, buf);
free(buf); free(buf);
}
}
Result zero_handle_memeasy(Handle handle)
{
u64 size = 0;
u64 cur = 0;
FSFILE_GetSize(handle, &size);
char *zero_buf = calloc(1, 0x10000);
while (size > 0x10000)
{
FSFILE_Write(handle, NULL, cur, &zero_buf, 0x10000, 0);
cur += 0x10000;
size -= 0x10000;
}
FSFILE_Write(handle, NULL, cur, &zero_buf, size, 0);
free(zero_buf);
return 0;
} }
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)
@@ -385,16 +557,26 @@ static SwkbdCallbackResult fat32filter(void * user, const char ** ppMessage, con
} }
// assumes the input buffer is a ZIP. if it isn't, why are you calling this? // 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) void save_zip_to_sd(char * filename, u32 size, char * buf, RemoteMode mode)
{ {
static char path_to_file[32761]; // FAT32 paths can be quite long. static char path_to_file[32761]; // FAT32 paths can be quite long.
const int max_chars = 250; const int max_chars = 250;
char new_filename[max_chars + 5]; // .zip + \0 char new_filename[max_chars + 5]; // .zip + \0
renamed: renamed:
char * curr_filename;
if (mode == REMOTE_MODE_BADGES)
{
DEBUG("Remote mode badges! Saving to /Badges/ThemePlaza Badges/\n");
sprintf(path_to_file, "%s%s", "/Badges/ThemePlaza Badges/", filename);
curr_filename = path_to_file + strlen("/Badges/ThemePlaza Badges/");
} else
{
sprintf(path_to_file, "%s%s", main_paths[mode], filename); sprintf(path_to_file, "%s%s", main_paths[mode], filename);
curr_filename = path_to_file + strlen(main_paths[mode]);
}
DEBUG("Filtering out illegal chars...\n");
// filter out characters illegal in FAT32 filenames // filter out characters illegal in FAT32 filenames
char * curr_filename = path_to_file + strlen(main_paths[mode]);
char * illegal_char = curr_filename; char * illegal_char = curr_filename;
while ((illegal_char = strpbrk(illegal_char, ILLEGAL_CHARS))) while ((illegal_char = strpbrk(illegal_char, ILLEGAL_CHARS)))
{ {
@@ -414,6 +596,7 @@ renamed:
*illegal_char = '-'; *illegal_char = '-';
} }
DEBUG("Checking extension\n");
// ensure the extension is .zip // ensure the extension is .zip
char * extension = strrchr(path_to_file, '.'); char * extension = strrchr(path_to_file, '.');
if (extension == NULL || strcmp(extension, ".zip")) if (extension == NULL || strcmp(extension, ".zip"))

View File

@@ -147,8 +147,6 @@ void handle_scrolling(Entry_List_s * list)
// Scroll the menu up or down if the selected theme is out of its bounds // Scroll the menu up or down if the selected theme is out of its bounds
//---------------------------------------------------------------- //----------------------------------------------------------------
if(list->entries_count > list->entries_loaded) if(list->entries_count > list->entries_loaded)
{
for(int i = 0; i < list->entries_count; i++)
{ {
int change = 0; int change = 0;
@@ -168,14 +166,18 @@ void handle_scrolling(Entry_List_s * list)
{ {
change = -1; change = -1;
} }
else if(list->selected_entry == list->previous_selected+list->entries_loaded || list->selected_entry >= list->scroll + list->entries_loaded) else if(list->selected_entry == list->previous_selected+list->entries_loaded)
{ {
change = list->entries_loaded; change = list->entries_loaded;
} }
else if(list->selected_entry == list->previous_selected-list->entries_loaded || list->selected_entry < list->scroll) else if(list->selected_entry == list->previous_selected-list->entries_loaded)
{ {
change = -list->entries_loaded; change = -list->entries_loaded;
} }
else if(list->selected_entry >= list->scroll + list->entries_loaded || list->selected_entry < list->scroll)
{
change = list->selected_entry - list->scroll;
}
list->scroll += change; list->scroll += change;
@@ -189,7 +191,6 @@ void handle_scrolling(Entry_List_s * list)
else else
list->previous_selected += change; list->previous_selected += change;
} }
}
//---------------------------------------------------------------- //----------------------------------------------------------------
} }
@@ -310,6 +311,7 @@ void load_icons_thread(void * void_arg)
bool load_preview_from_buffer(char * row_pointers, u32 size, C2D_Image * preview_image, int * preview_offset, int height) bool load_preview_from_buffer(char * row_pointers, u32 size, C2D_Image * preview_image, int * preview_offset, int height)
{ {
int width = (uint32_t)((size / 4) / height); int width = (uint32_t)((size / 4) / height);
float tex_height = height > 512.0f ? 1024.0f : 512.0f;
free_preview(*preview_image); free_preview(*preview_image);
@@ -322,10 +324,10 @@ bool load_preview_from_buffer(char * row_pointers, u32 size, C2D_Image * preview
subt3x->left = 0.0f; subt3x->left = 0.0f;
subt3x->top = 1.0f; subt3x->top = 1.0f;
subt3x->right = width/512.0f; subt3x->right = width/512.0f;
subt3x->bottom = 1.0-(height/512.0f); subt3x->bottom = 1.0-(height/tex_height);
preview_image->subtex = subt3x; preview_image->subtex = subt3x;
C3D_TexInit(preview_image->tex, 512, 512, GPU_RGBA8); C3D_TexInit(preview_image->tex, 512, tex_height, GPU_RGBA8);
memset(preview_image->tex->data, 0, preview_image->tex->size); memset(preview_image->tex->data, 0, preview_image->tex->size);
@@ -440,6 +442,22 @@ void free_preview(C2D_Image preview)
// Initialize the audio struct // Initialize the audio struct
Result load_audio(const Entry_s * entry, audio_s * audio) Result load_audio(const Entry_s * entry, audio_s * audio)
{
audio->music_size = load_data("/bgm.bcstm", entry, &audio->music_buf);
if (audio->music_size == 0) {
free(audio);
DEBUG("<load_audio> File not found!\n");
return MAKERESULT(RL_FATAL, RS_NOTFOUND, RM_APPLICATION, RD_NOT_FOUND);
}
audio->cursor = 0;
audio->last_time = 0;
audio->current_block = 0;
audio->stop = false;
audio->active_channels = 0;
return MAKERESULT(RL_SUCCESS, RS_SUCCESS, RM_APPLICATION, RD_SUCCESS);
}
Result load_audio_ogg(const Entry_s * entry, audio_ogg_s * audio)
{ {
audio->filesize = load_data("/bgm.ogg", entry, &audio->filebuf); audio->filesize = load_data("/bgm.ogg", entry, &audio->filebuf);
if (audio->filesize == 0) { if (audio->filesize == 0) {

View File

@@ -33,6 +33,7 @@
#include "music.h" #include "music.h"
#include "remote.h" #include "remote.h"
#include "ui_strings.h" #include "ui_strings.h"
#include "badges.h"
#include <time.h> #include <time.h>
bool quit = false; bool quit = false;
@@ -57,6 +58,7 @@ Language_s language = {0};
int __stacksize__ = 64 * 1024; int __stacksize__ = 64 * 1024;
Result archive_result; Result archive_result;
Result badge_archive_result;
u32 old_time_limit; u32 old_time_limit;
const char * main_paths[MODE_AMOUNT] = { const char * main_paths[MODE_AMOUNT] = {
@@ -86,7 +88,9 @@ static void init_services(void)
APT_GetAppCpuTimeLimit(&old_time_limit); APT_GetAppCpuTimeLimit(&old_time_limit);
APT_SetAppCpuTimeLimit(30); APT_SetAppCpuTimeLimit(30);
httpcInit(0); httpcInit(0);
init_sd();
archive_result = open_archives(); archive_result = open_archives();
badge_archive_result = open_badge_extdata();
if(envIsHomebrew()) if(envIsHomebrew())
{ {
s64 out; s64 out;
@@ -123,6 +127,13 @@ static void stop_install_check(void)
} }
} }
static inline void wait_scroll(void)
{
released = true;
svcReleaseMutex(update_icons_mutex);
svcSleepThread(FASTSCROLL_WAIT);
}
static void exit_thread(void) static void exit_thread(void)
{ {
if(iconLoadingThread_arg.run_thread) if(iconLoadingThread_arg.run_thread)
@@ -324,11 +335,7 @@ static void jump_menu(Entry_List_s * list)
if(button == SWKBD_BUTTON_CONFIRM) if(button == SWKBD_BUTTON_CONFIRM)
{ {
list->selected_entry = atoi(numbuf) - 1; list->selected_entry = atoi(numbuf) - 1;
list->scroll = list->selected_entry; wait_scroll();
if(list->scroll >= list->entries_count - list->entries_loaded)
list->scroll = list->entries_count - list->entries_loaded - 1;
if (list->scroll < 0)
list->scroll = 0;
} }
} }
@@ -368,13 +375,6 @@ static void toggle_shuffle(Entry_List_s * list)
} }
} }
static inline void wait_scroll(void)
{
released = true;
svcReleaseMutex(update_icons_mutex);
svcSleepThread(FASTSCROLL_WAIT);
}
int main(void) int main(void)
{ {
srand(time(NULL)); srand(time(NULL));
@@ -453,7 +453,7 @@ int main(void)
if(preview_mode) if(preview_mode)
{ {
draw_preview(preview, preview_offset); draw_preview(preview, preview_offset, 1.0f);
} }
else { else {
if(!iconLoadingThread_arg.run_thread) if(!iconLoadingThread_arg.run_thread)
@@ -573,7 +573,7 @@ int main(void)
if(preview_mode) if(preview_mode)
{ {
end_frame(); end_frame();
draw_preview(preview, preview_offset); draw_preview(preview, preview_offset, 1.0f);
end_frame(); end_frame();
if(current_mode == MODE_THEMES && dspfirm) if(current_mode == MODE_THEMES && dspfirm)
{ {
@@ -605,11 +605,12 @@ int main(void)
} }
} }
int selected_entry = current_list->selected_entry;
Entry_s * current_entry = &current_list->entries[selected_entry];
if(preview_mode || current_list->entries == NULL) if(preview_mode || current_list->entries == NULL)
goto touch; goto touch;
int selected_entry = current_list->selected_entry;
Entry_s * current_entry = &current_list->entries[selected_entry];
if(install_mode) if(install_mode)
{ {
@@ -636,7 +637,7 @@ int main(void)
} else if (BETWEEN(320-96, x, 320-72)) } else if (BETWEEN(320-96, x, 320-72))
{ {
goto install_theme_bgm_only; goto install_theme_bgm_only;
} else if (BETWEEN(320-120, x, 320-96)) } else if (BETWEEN(2, x, 26))
{ {
goto install_leave; goto install_leave;
} }
@@ -785,6 +786,9 @@ int main(void)
break; break;
} }
} else if (BETWEEN(320-96, x, 320-72)) } else if (BETWEEN(320-96, x, 320-72))
{
goto badge_install;
} else if (BETWEEN(2, x, 26))
{ {
extra_mode = false; extra_mode = false;
extra_index = 1; extra_index = 1;
@@ -803,7 +807,7 @@ int main(void)
else if(kDown & KEY_DLEFT) else if(kDown & KEY_DLEFT)
{ {
browse_themeplaza: browse_themeplaza:
if(themeplaza_browser(current_mode)) if(themeplaza_browser((RemoteMode) current_mode))
{ {
current_mode = MODE_THEMES; current_mode = MODE_THEMES;
load_lists(lists); load_lists(lists);
@@ -828,6 +832,15 @@ int main(void)
draw_mode = DRAW_MODE_LIST; draw_mode = DRAW_MODE_LIST;
extra_index = 1; extra_index = 1;
} }
else if (kDown & KEY_DRIGHT)
{
badge_install:
extra_mode = false;
extra_index = 1;
draw_mode = DRAW_MODE_LIST;
draw_install(INSTALL_BADGES);
install_badges();
}
else if (kDown & KEY_R) else if (kDown & KEY_R)
{ {
extra_index = 2; extra_index = 2;
@@ -894,7 +907,15 @@ int main(void)
draw_mode = DRAW_MODE_LIST; draw_mode = DRAW_MODE_LIST;
extra_index = 1; extra_index = 1;
} }
else if(kDown &KEY_B) else if(kDown & KEY_DLEFT)
{
draw_install(INSTALL_DUMPING_BADGES);
extract_badges();
extra_mode = false;
draw_mode = DRAW_MODE_LIST;
extra_index = 1;
}
else if(kDown & KEY_B)
{ {
extra_index = 1; extra_index = 1;
} }
@@ -1020,14 +1041,14 @@ int main(void)
{ {
if(y < 24) if(y < 24)
{ {
if(BETWEEN(320-168, x, 320-144)) if(BETWEEN(320-144, x, 320-120))
{ {
if (current_mode == MODE_THEMES) if (current_mode == MODE_THEMES)
{ {
toggle_shuffle(current_list); toggle_shuffle(current_list);
} }
} }
else if(BETWEEN(320-144, x, 320-120)) else if(BETWEEN(2, x, 26))
{ {
extra_mode = true; extra_mode = true;
draw_mode = DRAW_MODE_EXTRA; draw_mode = DRAW_MODE_EXTRA;

View File

@@ -27,8 +27,34 @@
#include "music.h" #include "music.h"
#include "loading.h" #include "loading.h"
// Play a given audio struct // BCSTM Player adapted from BCSTM-Player by tobid7
Result update_audio(audio_s * audio) // https://github.com/NPI-D7/BCSTM-Player/blob/main/source/bcstm.hpp
u32 read32(char *music_buf, size_t *cursor)
{
u32 ret;
memcpy(&ret, music_buf + *cursor, 4);
*cursor += 4;
return ret;
}
u16 read16(char *music_buf, size_t *cursor)
{
u16 ret;
memcpy(&ret, music_buf + *cursor, 2);
*cursor += 2;
return ret;
}
u8 read8(char *music_buf, size_t *cursor)
{
u8 ret;
memcpy(&ret, music_buf + *cursor, 1);
*cursor += 1;
return ret;
}
Result update_audio_ogg(audio_ogg_s * audio)
{ {
u32 size = audio->wave_buf[audio->buf_pos].nsamples * 4 - audio->data_read; u32 size = audio->wave_buf[audio->buf_pos].nsamples * 4 - audio->data_read;
DEBUG("<update_audio> Audio Size: %ld\n", size); DEBUG("<update_audio> Audio Size: %ld\n", size);
@@ -64,10 +90,10 @@ Result update_audio(audio_s * audio)
return MAKERESULT(RL_SUCCESS, RS_SUCCESS, RM_APPLICATION, RD_SUCCESS); return MAKERESULT(RL_SUCCESS, RS_SUCCESS, RM_APPLICATION, RD_SUCCESS);
} }
void thread_audio(void * data) { void thread_audio_ogg(void * data) {
audio_s * audio = (audio_s *)data; audio_ogg_s * audio = (audio_ogg_s *)data;
while(!audio->stop) { while(!audio->stop) {
update_audio(audio); update_audio_ogg(audio);
} }
ndspChnWaveBufClear(0); ndspChnWaveBufClear(0);
ndspChnReset(0); ndspChnReset(0);
@@ -77,6 +103,265 @@ void thread_audio(void * data) {
linearFree((void *)audio->wave_buf[1].data_vaddr); linearFree((void *)audio->wave_buf[1].data_vaddr);
} }
void play_audio_ogg(audio_ogg_s * audio) {
audio->playing_thread = threadCreate(thread_audio_ogg, audio, 0x1000, 0x3F, 1, false);
}
void stop_audio_ogg(audio_ogg_s ** audio_ptr) {
audio_ogg_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;
}
typedef struct {
u16 type;
u32 target;
} bcstm_reference;
int init_audio(audio_s *audio)
{
u32 magic = read32(audio->music_buf, &audio->cursor);
DEBUG("Loading music, music_size: %d, magic: 0x%08lx\n", audio->music_size, magic);
audio->is_little_endian = read16(audio->music_buf, &audio->cursor) == 0xFEFF;
audio->info_offset = 0;
audio->data_offset = 0;
if (magic != 0x4D545343) // CSTM
{
free(audio->music_buf);
return -1;
}
audio->cursor = 0x10;
u16 sbc = read16(audio->music_buf, &audio->cursor);
audio->cursor += 2;
for (u16 i = 0; i < sbc; ++i)
{
u16 sec = read16(audio->music_buf, &audio->cursor);
audio->cursor += 2;
u32 off = read32(audio->music_buf, &audio->cursor);
audio->cursor += 4;
DEBUG("Reading sbc: %04x, %08lx\n", sec, off);
if (sec == 0x4000) // Info block
audio->info_offset = off;
if (sec == 0x4002) // Data block
audio->data_offset = off;
}
DEBUG("Info offset: 0x%08lx, data offset: 0x%08lx\n", audio->info_offset, audio->data_offset);
if (audio->data_offset == 0 || audio->info_offset == 0)
{
free(audio->music_buf);
return -2;
}
audio->cursor = audio->info_offset + 0x20;
if (read8(audio->music_buf, &audio->cursor) != 2) // Encoding - 2 is DSP_ADPCM
{
free(audio->music_buf);
return -3;
}
audio->is_looping = read8(audio->music_buf, &audio->cursor);
audio->channel_count = read8(audio->music_buf, &audio->cursor);
if (audio->channel_count > 2)
{
free(audio->music_buf);
return -4;
}
DEBUG("Channel count: %d, is looping: %d\n", audio->channel_count, audio->is_looping);
audio->cursor = audio->info_offset + 0x24;
audio->sample_rate = read32(audio->music_buf, &audio->cursor);
u32 _loop_pos = read32(audio->music_buf, &audio->cursor);
u32 _loop_end = read32(audio->music_buf, &audio->cursor);
audio->num_blocks = read32(audio->music_buf, &audio->cursor);
audio->block_size = read32(audio->music_buf, &audio->cursor);
audio->block_samples = read32(audio->music_buf, &audio->cursor);
audio->cursor += 4;
audio->last_block_samples = read32(audio->music_buf, &audio->cursor);
audio->last_block_size = read32(audio->music_buf, &audio->cursor);
DEBUG("sample_rate: %lu, loop_start: %lu, loop_end: %lu, num_blocks: %lu, block_size: %lu, block_samples: %lu, last_block_samples: %lu, last_block_size: %lu\n",
audio->sample_rate, _loop_pos, _loop_end, audio->num_blocks, audio->block_size, audio->block_samples, audio->last_block_samples, audio->last_block_size);
audio->loop_start = _loop_pos / audio->block_samples;
audio->loop_end = (_loop_end % audio->block_samples ? audio->num_blocks : _loop_end / audio->block_samples);
while (read32(audio->music_buf, &audio->cursor) != 0x4102); // find channel info header
audio->cursor -= 4;
u32 start_table = audio->cursor - 4;
bcstm_reference channel_refs[2] = {0};
for (u8 i = 0; i < audio->channel_count; ++i)
{
DEBUG("Processing channel_ref at %08x...\n", audio->cursor);
channel_refs[i].type = read16(audio->music_buf, &audio->cursor);
audio->cursor += 2;
channel_refs[i].target = start_table + read32(audio->music_buf, &audio->cursor);
DEBUG("Type: %04x; Target: %08lx\n", channel_refs[i].type, channel_refs[i].target);
}
for (u8 i = 0; i < audio->channel_count; ++i)
{
audio->cursor = channel_refs[i].target;
if (read16(audio->music_buf, &audio->cursor) != 0x300) continue; // Bad bcstm - channel info != DSP ADPCM info
audio->cursor += 2;
audio->cursor += read32(audio->music_buf, &audio->cursor) - 8;
memcpy(audio->adpcm_coefs[i], audio->music_buf + audio->cursor, sizeof(unsigned short) * 16);
audio->cursor += sizeof(unsigned short) * 16;
memcpy(&(audio->adpcm_data[i][0]), audio->music_buf + audio->cursor, sizeof(ndspAdpcmData));
audio->cursor += sizeof(ndspAdpcmData);
memcpy(&(audio->adpcm_data[i][1]), audio->music_buf + audio->cursor, sizeof(ndspAdpcmData));
audio->cursor += sizeof(ndspAdpcmData);
audio->cursor += 2; // skip padding
}
audio->cursor = audio->data_offset + 0x20;
return 0;
}
int start_play(audio_s *audio) {
audio->current_block = 0;
for (u8 i = 0; i < audio->channel_count; ++i)
{
audio->channel[i] = 0;
while (audio->channel[i] < 24 && ((audio->active_channels >> audio->channel[i]) & 1)) {
audio->channel[i]++;
}
if (audio->channel[i] == 24) {
return -1;
}
audio->active_channels |= 1 << audio->channel[i];
ndspChnWaveBufClear(audio->channel[i]);
static float mix[16];
ndspChnSetFormat(audio->channel[i], NDSP_FORMAT_ADPCM | NDSP_3D_SURROUND_PREPROCESSED);
ndspChnSetRate(audio->channel[i], audio->sample_rate);
if (audio->channel_count == 1)
{
mix[0] = mix[1] = 0.5f;
} else if (audio->channel_count == 2)
{
if (i == 0)
{
mix[0] = 0.8f;
mix[1] = 0.0f;
mix[2] = 0.2f;
mix[3] = 0.0f;
} else
{
mix[0] = 0.0f;
mix[1] = 0.8f;
mix[2] = 0.0f;
mix[3] = 0.2f;
}
}
ndspChnSetMix(audio->channel[i], mix);
ndspChnSetAdpcmCoefs(audio->channel[i], audio->adpcm_coefs[i]);
for (u8 j = 0; j < BUFFER_COUNT; ++j)
{
memset(&(audio->wave_buf[i][j]), 0, sizeof(ndspWaveBuf));
audio->wave_buf[i][j].status = NDSP_WBUF_DONE;
audio->buffer_data[i][j] = linearAlloc(audio->block_size);
}
}
return 0;
}
void fill_buffers(audio_s *audio)
{
DEBUG("Filling buffers...\n");
for (u8 bufIndex = 0; bufIndex < BUFFER_COUNT; ++bufIndex)
{
if (audio->wave_buf[0][bufIndex].status != NDSP_WBUF_DONE) continue;
if (audio->channel_count == 2 && audio->wave_buf[1][bufIndex].status != NDSP_WBUF_DONE) continue;
if (audio->is_looping && audio->current_block == audio->loop_end)
{
audio->current_block = audio->loop_start;
audio->cursor = audio->data_offset + 0x20 + audio->block_size * audio->channel_count * audio->loop_start;
}
if (!audio->is_looping && audio->current_block == audio->loop_end)
{
audio->stop = true;
return;
}
for (u8 channelIndex = 0; channelIndex < audio->channel_count; ++channelIndex)
{
ndspWaveBuf *buf = &(audio->wave_buf[channelIndex][bufIndex]);
memset(buf, 0, sizeof(ndspWaveBuf));
buf->data_adpcm = audio->buffer_data[channelIndex][bufIndex];
memcpy(buf->data_adpcm, audio->music_buf + audio->cursor, (audio->current_block == audio->num_blocks - 1) ? audio->last_block_size : audio->block_size);
audio->cursor += (audio->current_block == audio->num_blocks - 1) ? audio->last_block_size : audio->block_size;
DSP_FlushDataCache(buf->data_adpcm, audio->block_size);
if (audio->current_block == 0)
buf->adpcm_data = &(audio->adpcm_data[channelIndex][0]);
else if (audio->current_block == audio->loop_start)
buf->adpcm_data = &(audio->adpcm_data[channelIndex][1]);
if (audio->current_block == audio->num_blocks - 1)
buf->nsamples = audio->last_block_samples;
else
buf->nsamples = audio->block_samples;
ndspChnWaveBufAdd(audio->channel[channelIndex], buf);
}
audio->current_block++;
}
}
// Play a given audio struct
void update_audio(audio_s * audio)
{
u32 current_time = svcGetSystemTick();
if (current_time - audio->last_time > 1e8)
{
fill_buffers(audio);
audio->last_time = current_time;
}
}
void thread_audio(void * data) {
audio_s * audio = (audio_s *)data;
int res = init_audio(audio);
if (res < 0)
{
return;
}
start_play(audio);
while(!audio->stop) {
update_audio(audio);
}
for (u8 i = 0; i < audio->channel_count; ++i)
{
ndspChnWaveBufClear(audio->channel[i]);
ndspChnReset(audio->channel[i]);
audio->active_channels &= ~(1 << audio->channel[i]);
for (u8 j = 0; j < BUFFER_COUNT; ++j) {
linearFree(audio->buffer_data[i][j]);
}
}
free(audio->music_buf);
}
void play_audio(audio_s * audio) { void play_audio(audio_s * audio) {
audio->playing_thread = threadCreate(thread_audio, audio, 0x1000, 0x3F, 1, false); audio->playing_thread = threadCreate(thread_audio, audio, 0x1000, 0x3F, 1, false);
} }

View File

@@ -37,6 +37,9 @@
#include "conversion.h" #include "conversion.h"
#include "ui_strings.h" #include "ui_strings.h"
char *last_search = NULL;
json_int_t last_page = 1;
// forward declaration of special case used only here // forward declaration of special case used only here
// TODO: replace this travesty with a proper handler // 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 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);
@@ -50,6 +53,42 @@ static void free_icons(Entry_List_s * list)
} }
} }
/* Unnecessary with ThemePlaza providing smdh files for badges
static void load_remote_metadata(Entry_s * entry)
{
char *page_json = NULL;
char *api_url = NULL;
asprintf(&api_url, THEMEPLAZA_QUERY_ENTRY_INFO, entry->tp_download_id);
u32 json_len;
Result res = http_get(api_url, NULL, &page_json, &json_len, INSTALL_NONE, "application/json");
free(api_url);
if (R_FAILED(res))
{
free(page_json);
return;
}
if (json_len)
{
json_error_t error;
json_t *root = json_loadb(page_json, json_len, 0, &error);
if (root)
{
const char *key;
json_t *value;
json_object_foreach(root, key, value)
{
if (json_is_string(value) && !strcmp(key, THEMEPLAZA_JSON_TITLE))
utf8_to_utf16(entry->name, (u8 *) json_string_value(value), min(json_string_length(value), 0x41));
else if (json_is_string(value) && !strcmp(key, THEMEPLAZA_JSON_AUTHOR))
utf8_to_utf16(entry->author, (u8 *) json_string_value(value), min(json_string_length(value), 0x41));
else if (json_is_string(value) && !strcmp(key, THEMEPLAZA_JSON_DESC))
utf8_to_utf16(entry->desc, (u8 *) json_string_value(value), min(json_string_length(value), 0x81));
}
}
}
}
*/
static void load_remote_smdh(Entry_s * entry, C3D_Tex * into_tex, const Entry_Icon_s * icon_info, bool ignore_cache) static void load_remote_smdh(Entry_s * entry, C3D_Tex * into_tex, const Entry_Icon_s * icon_info, bool ignore_cache)
{ {
bool not_cached = true; bool not_cached = true;
@@ -126,7 +165,7 @@ static void load_remote_entries(Entry_List_s * list, json_t * ids_array, bool ig
} }
} }
static void load_remote_list(Entry_List_s * list, json_int_t page, EntryMode mode, bool ignore_cache) static void load_remote_list(Entry_List_s * list, json_int_t page, RemoteMode mode, bool ignore_cache)
{ {
if (page > list->tp_page_count) if (page > list->tp_page_count)
page = 1; page = 1;
@@ -136,10 +175,12 @@ static void load_remote_list(Entry_List_s * list, json_int_t page, EntryMode mod
list->selected_entry = 0; list->selected_entry = 0;
InstallType loading_screen = INSTALL_NONE; InstallType loading_screen = INSTALL_NONE;
if (mode == MODE_THEMES) if (mode == REMOTE_MODE_THEMES)
loading_screen = INSTALL_LOADING_REMOTE_THEMES; loading_screen = INSTALL_LOADING_REMOTE_THEMES;
else if (mode == MODE_SPLASHES) else if (mode == REMOTE_MODE_SPLASHES)
loading_screen = INSTALL_LOADING_REMOTE_SPLASHES; loading_screen = INSTALL_LOADING_REMOTE_SPLASHES;
else if (mode == REMOTE_MODE_BADGES)
loading_screen = INSTALL_LOADING_REMOTE_BADGES;
draw_install(loading_screen); draw_install(loading_screen);
char * page_json = NULL; char * page_json = NULL;
@@ -157,7 +198,7 @@ static void load_remote_list(Entry_List_s * list, json_int_t page, EntryMode mod
if (json_len) if (json_len)
{ {
list->tp_current_page = page; list->tp_current_page = page;
list->mode = mode; list->mode = (EntryMode) mode;
json_error_t error; json_error_t error;
json_t * root = json_loadb(page_json, json_len, 0, &error); json_t * root = json_loadb(page_json, json_len, 0, &error);
@@ -167,13 +208,20 @@ static void load_remote_list(Entry_List_s * list, json_int_t page, EntryMode mod
json_t * value; json_t * value;
json_object_foreach(root, key, value) json_object_foreach(root, key, value)
{ {
if (json_is_integer(value) && !strcmp(key, THEMEPLAZA_JSON_PAGE_COUNT)) if(json_is_true(value) && !strcmp(key, THEMEPLAZA_JSON_SUCCESS))
last_page = page;
else if (json_is_integer(value) && !strcmp(key, THEMEPLAZA_JSON_PAGE_COUNT))
list->tp_page_count = json_integer_value(value); list->tp_page_count = json_integer_value(value);
else if (json_is_array(value) && !strcmp(key, THEMEPLAZA_JSON_PAGE_IDS)) else if (json_is_array(value) && !strcmp(key, THEMEPLAZA_JSON_PAGE_IDS))
load_remote_entries(list, value, ignore_cache, loading_screen); load_remote_entries(list, value, ignore_cache, loading_screen);
else if (json_is_string(value) && !strcmp(key, THEMEPLAZA_JSON_ERROR_MESSAGE) else if (json_is_string(value) && !strcmp(key, THEMEPLAZA_JSON_ERROR_MESSAGE)
&& !strcmp(json_string_value(value), THEMEPLAZA_JSON_ERROR_MESSAGE_NOT_FOUND)) && !strcmp(json_string_value(value), THEMEPLAZA_JSON_ERROR_MESSAGE_NOT_FOUND))
{
throw_error(language.remote.no_results, ERROR_LEVEL_WARNING); throw_error(language.remote.no_results, ERROR_LEVEL_WARNING);
if (list->tp_search) free(list->tp_search);
asprintf(&list->tp_search, "%s", last_search);
list->tp_current_page = last_page;
}
} }
} }
else else
@@ -189,7 +237,7 @@ static void load_remote_list(Entry_List_s * list, json_int_t page, EntryMode mod
static u16 previous_path_preview[0x106]; static u16 previous_path_preview[0x106];
static bool load_remote_preview(const Entry_s * entry, C2D_Image * preview_image, int * preview_offset) static bool load_remote_preview(const Entry_s * entry, C2D_Image * preview_image, int * preview_offset, u32 height)
{ {
bool not_cached = true; bool not_cached = true;
@@ -224,7 +272,6 @@ static bool load_remote_preview(const Entry_s * entry, C2D_Image * preview_image
char * preview_buf = malloc(preview_size); char * preview_buf = malloc(preview_size);
u32 preview_buf_size = preview_size; u32 preview_buf_size = preview_size;
memcpy(preview_buf, preview_png, preview_size); memcpy(preview_buf, preview_png, preview_size);
u32 height = 480;
if (!(preview_buf_size = png_to_abgr(&preview_buf, preview_buf_size, &height))) if (!(preview_buf_size = png_to_abgr(&preview_buf, preview_buf_size, &height)))
{ {
@@ -288,7 +335,7 @@ static void load_remote_bgm(const Entry_s * entry)
free(bgm_ogg); free(bgm_ogg);
} }
static void download_remote_entry(Entry_s * entry, EntryMode mode) static void download_remote_entry(Entry_s * entry, RemoteMode mode)
{ {
char * download_url = NULL; char * download_url = NULL;
asprintf(&download_url, THEMEPLAZA_DOWNLOAD_FORMAT, entry->tp_download_id); asprintf(&download_url, THEMEPLAZA_DOWNLOAD_FORMAT, entry->tp_download_id);
@@ -359,7 +406,7 @@ static void jump_menu(Entry_List_s * list)
{ {
json_int_t newpage = (json_int_t)atoi(numbuf); json_int_t newpage = (json_int_t)atoi(numbuf);
if (newpage != list->tp_current_page) if (newpage != list->tp_current_page)
load_remote_list(list, newpage, list->mode, false); load_remote_list(list, newpage, (RemoteMode) list->mode, false);
} }
} }
@@ -380,10 +427,12 @@ static void search_menu(Entry_List_s * list)
SwkbdButton button = swkbdInputText(&swkbd, search, max_chars); SwkbdButton button = swkbdInputText(&swkbd, search, max_chars);
if (button == SWKBD_BUTTON_CONFIRM) if (button == SWKBD_BUTTON_CONFIRM)
{ {
free(last_search);
asprintf(&last_search, "%s", list->tp_search);
free(list->tp_search); free(list->tp_search);
list->tp_search = url_escape(search); list->tp_search = url_escape(search);
DEBUG("Search escaped: %s -> %s\n", search, list->tp_search); DEBUG("Search escaped: %s -> %s\n", search, list->tp_search);
load_remote_list(list, 1, list->mode, false); load_remote_list(list, 1, (RemoteMode) list->mode, false);
} }
free(search); free(search);
} }
@@ -411,7 +460,7 @@ static void change_selected(Entry_List_s * list, int change_value)
list->selected_entry = newval; list->selected_entry = newval;
} }
bool themeplaza_browser(EntryMode mode) bool themeplaza_browser(RemoteMode mode)
{ {
bool downloaded = false; bool downloaded = false;
@@ -438,11 +487,13 @@ bool themeplaza_browser(EntryMode mode)
bool preview_mode = false; bool preview_mode = false;
int preview_offset = 0; int preview_offset = 0;
audio_s * audio = NULL; audio_ogg_s * audio = NULL;
Entry_List_s list = { 0 }; Entry_List_s list = { 0 };
Entry_List_s * current_list = &list; Entry_List_s * current_list = &list;
current_list->tp_search = strdup(""); current_list->tp_search = strdup("");
last_search = strdup("");
last_page = 1;
list.entries_per_screen_v = entries_per_screen_v[mode]; list.entries_per_screen_v = entries_per_screen_v[mode];
list.entries_per_screen_h = entries_per_screen_h[mode]; list.entries_per_screen_h = entries_per_screen_h[mode];
@@ -489,13 +540,15 @@ bool themeplaza_browser(EntryMode mode)
if (preview_mode) if (preview_mode)
{ {
draw_preview(preview, preview_offset); if (mode == REMOTE_MODE_BADGES) draw_preview(preview, -40, 0.625f);
else draw_preview(preview, preview_offset, 1.0f);
} }
else else
{ {
Instructions_s instructions = language.remote_instructions[mode]; Instructions_s instructions = language.remote_instructions[mode];
if (extra_mode) if (extra_mode)
instructions = language.remote_extra_instructions; instructions = language.remote_extra_instructions[mode];
draw_grid_interface(current_list, instructions, extra_mode); draw_grid_interface(current_list, instructions, extra_mode);
} }
@@ -526,16 +579,22 @@ bool themeplaza_browser(EntryMode mode)
{ {
extra_mode = false; extra_mode = false;
} }
else if (kDown & KEY_DLEFT) else if (kDown & KEY_L)
{ {
extra_mode = false; extra_mode = false;
change_mode: mode = mode -1;
mode++; if (mode > REMOTE_MODE_AMOUNT) mode = REMOTE_MODE_AMOUNT -1;
mode %= MODE_AMOUNT; free(current_list->tp_search);
current_list->tp_search = strdup("");
load_remote_list(current_list, 1, mode, false);
}
else if (kDown & KEY_R)
{
extra_mode = false;
mode = mode + 1;
mode = mode % REMOTE_MODE_AMOUNT;
free(current_list->tp_search); free(current_list->tp_search);
current_list->tp_search = strdup(""); current_list->tp_search = strdup("");
load_remote_list(current_list, 1, mode, false); load_remote_list(current_list, 1, mode, false);
} }
else if (kDown & KEY_DUP) else if (kDown & KEY_DUP)
@@ -564,21 +623,22 @@ bool themeplaza_browser(EntryMode mode)
toggle_preview: toggle_preview:
if (!preview_mode) if (!preview_mode)
{ {
preview_mode = load_remote_preview(current_entry, &preview, &preview_offset); u32 height = mode == REMOTE_MODE_BADGES ? 1024 : 480;
if (mode == MODE_THEMES && dspfirm) preview_mode = load_remote_preview(current_entry, &preview, &preview_offset, height);
if (mode == REMOTE_MODE_THEMES && dspfirm)
{ {
load_remote_bgm(current_entry); load_remote_bgm(current_entry);
audio = calloc(1, sizeof(audio_s)); audio = calloc(1, sizeof(audio_ogg_s));
if (R_FAILED(load_audio(current_entry, audio))) audio = NULL; if (R_FAILED(load_audio_ogg(current_entry, audio))) audio = NULL;
if (audio != NULL) play_audio(audio); if (audio != NULL) play_audio_ogg(audio);
} }
} }
else else
{ {
preview_mode = false; preview_mode = false;
if (mode == MODE_THEMES && audio != NULL) if (mode == REMOTE_MODE_THEMES && audio != NULL)
{ {
stop_audio(&audio); stop_audio_ogg(&audio);
} }
} }
continue; continue;
@@ -588,9 +648,9 @@ bool themeplaza_browser(EntryMode mode)
if (preview_mode) if (preview_mode)
{ {
preview_mode = false; preview_mode = false;
if (mode == MODE_THEMES && audio != NULL) if (mode == REMOTE_MODE_THEMES && audio != NULL)
{ {
stop_audio(&audio); stop_audio_ogg(&audio);
} }
} }
else else
@@ -654,9 +714,9 @@ bool themeplaza_browser(EntryMode mode)
if (preview_mode) if (preview_mode)
{ {
preview_mode = false; preview_mode = false;
if (mode == MODE_THEMES && audio) if (mode == REMOTE_MODE_THEMES && audio)
{ {
stop_audio(&audio); stop_audio_ogg(&audio);
} }
continue; continue;
} }
@@ -681,7 +741,13 @@ bool themeplaza_browser(EntryMode mode)
} }
else if (BETWEEN(320 - 24, x, 320)) else if (BETWEEN(320 - 24, x, 320))
{ {
goto change_mode; mode++;
mode %= REMOTE_MODE_AMOUNT;
free(current_list->tp_search);
current_list->tp_search = strdup("");
load_remote_list(current_list, 1, mode, false);
} }
} }
else if (BETWEEN(240 - 24, y, 240) && BETWEEN(176, x, 320)) else if (BETWEEN(240 - 24, y, 240) && BETWEEN(176, x, 320))
@@ -721,7 +787,7 @@ bool themeplaza_browser(EntryMode mode)
if (audio) if (audio)
{ {
stop_audio(&audio); stop_audio_ogg(&audio);
} }
free_preview(preview); free_preview(preview);
@@ -729,6 +795,7 @@ bool themeplaza_browser(EntryMode mode)
free_icons(current_list); free_icons(current_list);
free(current_list->entries); free(current_list->entries);
free(current_list->tp_search); free(current_list->tp_search);
free(last_search);
return downloaded; return downloaded;
} }
@@ -933,7 +1000,7 @@ static size_t handle_data(char *ptr, size_t size, size_t nmemb, void *userdata)
static size_t curl_parse_header(char *buffer, size_t size, size_t nitems, void *userdata) static size_t curl_parse_header(char *buffer, size_t size, size_t nitems, void *userdata)
{ {
curl_header *header = (curl_header *) userdata; curl_header *header = (curl_header *) userdata;
for (int i = 0; i < size * nitems; ++i) for (size_t i = 0; i < size * nitems; ++i)
{ {
if (buffer[i] == '\n' || buffer[i] == '\r') if (buffer[i] == '\n' || buffer[i] == '\r')
{ {

View File

@@ -61,6 +61,7 @@ static Result install_theme_internal(const Entry_List_s * themes, int installmod
char * padded = NULL; char * padded = NULL;
int shuffle_count = 0; int shuffle_count = 0;
draw_loading_bar(shuffle_count, themes->shuffle_count + 1, INSTALL_SHUFFLE);
Handle body_cache_handle; Handle body_cache_handle;
if(installmode & THEME_INSTALL_BODY) if(installmode & THEME_INSTALL_BODY)
@@ -149,6 +150,7 @@ static Result install_theme_internal(const Entry_List_s * themes, int installmod
} }
shuffle_count++; shuffle_count++;
draw_loading_bar(shuffle_count, themes->shuffle_count + 1, INSTALL_SHUFFLE);
} }
} }
@@ -214,7 +216,7 @@ static Result install_theme_internal(const Entry_List_s * themes, int installmod
char * body_buf = NULL; char * body_buf = NULL;
u32 uncompressed_size = decompress_lz_file(fsMakePath(PATH_ASCII, "/BodyCache.bin"), ArchiveThemeExt, &body_buf); u32 uncompressed_size = decompress_lz_file(fsMakePath(PATH_ASCII, "/BodyCache.bin"), ArchiveThemeExt, &body_buf);
if (body_buf[5] != 1) if (body_buf != NULL && body_buf[5] != 1)
{ {
installmode |= THEME_INSTALL_BODY; installmode |= THEME_INSTALL_BODY;
body_buf[5] = 1; body_buf[5] = 1;

File diff suppressed because it is too large Load Diff

View File

@@ -26,7 +26,18 @@
#include "unicode.h" #include "unicode.h"
ssize_t strulen(const u16 * input, ssize_t max_len) void replace_chars(u16 *input, char *remove, u16 with)
{
for (u16 *cursor = input; *cursor != '\0'; cursor++)
{
if (strchr(remove, (char) (*cursor & 0xFF)))
{
*cursor = with;
}
}
}
size_t strulen(const u16 * input, ssize_t max_len)
{ {
for (int i = 0; i < max_len; i++) if (input[i] == 0) return i; for (int i = 0; i < max_len; i++) if (input[i] == 0) return i;
return max_len; return max_len;
@@ -55,13 +66,13 @@ void printu(u16 * input)
free(buf); free(buf);
} }
u16 * strucat(u16 * destination, const u16 * source) size_t strucat(u16 * destination, const u16 * source)
{ {
ssize_t dest_len = strulen(destination, 0x106); size_t dest_len = strulen(destination, 0x106);
ssize_t source_len = strulen(source, 0x106); size_t source_len = strulen(source, 0x106);
memcpy(&destination[dest_len], source, source_len * sizeof(u16)); memcpy(&destination[dest_len], source, source_len * sizeof(u16));
destination[min(dest_len + source_len, 0x106 - 1)] = 0; destination[min(dest_len + source_len, 0x106 - 1)] = 0;
return destination; return source_len;
} }