50 Commits

Author SHA1 Message Date
7035c416cf Properly free resources on invalid mime type 2024-05-20 21:33:46 -04:00
e498b3f24a Add libcurl for non-TP downloads 2024-05-20 21:31:24 -04:00
Alex Taber
610b21463d Update CONTRIBUTORS.md 2024-05-13 15:49:31 -04:00
arth
016e7fd738 Added Portuguese translation (#307) 2024-05-13 15:48:26 -04:00
Alex Taber
082b6f8f61 Update CONTRIBUTORS.md 2024-05-13 12:22:09 -04:00
20f3c503ad Update browser to not use combos 2024-05-12 21:51:13 -04:00
eae7a2250e Fix no themes/splashes buttons 2024-05-12 20:01:09 -04:00
22492339a6 Failsafe to prevent negative scrolls 2024-05-12 19:41:54 -04:00
9b098f6935 Add missing string, fix preview bug in browser 2024-05-12 17:46:55 -04:00
cooolgamer
3c55868183 Added French translation 2024-05-12 16:46:57 -04:00
Alex Taber
a43cbcca74 UX Overhaul (#305)
* Fix bgm checking bug

* Converted install and menu options into button menus rather than combos

* Fix sort order

* Various touch screen changes so that most functions can be done via touch

 * Dim theme list when navigating menus

* Translation framework implemented

* bug when no themes loaded fixed

* Fix theme preview regression introduced in d037691
2024-05-12 14:24:43 -04:00
Théo B
546d459696 Performance Improvements
Pass theme list & its entries around by reference rather than copying them.
Fix bug in async icon loading that caused icons to be loaded multiple times.

Original PR by @LiquidFenrir
2024-05-10 17:43:47 -04:00
d1f3dbf06b Throw warning on mono audio install. 2024-05-08 17:21:46 -04:00
cdf30f3bea Throw error on failed parental validation.
Also use CFGU instead of CFG for reading restrictions
2024-05-07 21:46:06 -04:00
c6b6f560be Require PIN for browser if browser access restricted 2024-05-07 21:25:45 -04:00
64d65417fa Disable home button while theme is installing 2024-04-26 00:21:10 -04:00
b19343e238 Draw no home icon in theme plaza browser 2024-04-24 23:37:13 -04:00
ecf9dc63ce Disable home button on theme install 2024-04-24 23:25:20 -04:00
7e2191ad86 Add x-zip-compressed to acceptable mime types
Closes #287
2024-04-24 14:42:10 -04:00
Jan
d037691418 assemble a splash preview if none was found (#277) 2024-04-24 14:32:54 -04:00
6b89496566 Properly handle Unicode file names on download
Closes #303. May still need additional testing
to ensure everything works properly, but preliminary
testing passes.
2024-04-24 14:17:55 -04:00
Théo B
36ca676c99 fix makefile (shader and texture rules) (#296)
* fix makefile (shader and texture rules)

they're now part of devkitARM base rules, not needed anymore, in fact there was a conflict. The t3x for romfs needed to be more specific. this is based on the changes to 3ds-examples/graphics/gpu/gpusprite which was the base of the post-citro2d rewrite Makefile

* copy paste error
2024-02-27 19:50:06 +00:00
cooolgamer
67fc17dddf Korean support (#290)
* Update themes.c

Addes KOR in dump themes

* Update fs.c

Added Kor support usable with This patch: https://github.com/ZeroSkill1/CTR-Hacking/tree/master/General-Hacking/Korean%20HOME%20Menu%20Theme%20Patch
2024-02-26 13:05:52 -05:00
Zemogiter
3fa73ee291 Update fs.h 2024-02-20 17:04:51 -05:00
Zemogiter
ae417f8f86 Compilation fixes 2024-02-14 09:30:04 -05:00
Dylan G
adccc70cca Fixed useless 404 error when a remote preview has no BGM 2022-07-31 20:41:22 +01:00
Dylan G
b66ca12039 Merge pull request #272 from LiquidFenrir/slight-music-rework
make audio safer
2022-06-17 23:05:22 +01:00
LiquidFenrir
4a56a883fa make audio safer
- centralized stop function
- freeing the struct not from the thread while waiting on handle in it
- thread not detached
- maybe fixes hang on exit from HM in ndsp status check loop
2022-06-14 13:02:15 +02:00
Dylan G
9ebfe387a0 Merge pull request #270 from LiquidFenrir/exit-fixes
fix multiple crashes
2022-06-10 23:07:34 +01:00
LiquidFenrir
806d0033de fix multiple crashes
on exit:
- bad timing when the install checks threads run, could crash
- quitting through HOME when a bgm was previewed in the browser
on http get failure:
- the quit flag would be enabled, but the browser wouldn't honour it
2022-06-05 11:49:09 +02:00
Dylan G
4c053bb447 Merge pull request #269 from LiquidFenrir/patch-2
fix dumping official body and bgm, and double free
2022-06-03 20:46:14 +01:00
Théo B
1ed6c46644 fix dumping official body and bgm, and double free
fixes #268
2022-06-03 21:40:23 +02:00
Dylan G
e22d09ba54 Typo in README
a stray backslash found its way into the first line
2022-05-30 14:24:23 +01:00
Dylan G
a0c16a64ec Search strings now escaped properly (fixes #267) 2022-05-30 12:04:18 +01:00
Dylan G
a1e7ed9924 Added header-only URL encoding/decoding 2022-05-30 12:03:11 +01:00
Dylan G
7f7fdc010a Fixed faulty FS error when a filename contains a / 2022-05-30 10:43:12 +01:00
Dylan G
b81a9aaa4c Dropped HTTPS for the browser as ThemePlaza is retiring TLSv1.1 2022-05-22 15:23:36 +01:00
Dylan G
6ba1ef111e Merge pull request #262 from LiquidFenrir/better-dump
add ability to dump all your official themes
2022-02-26 16:02:55 +00:00
Dylan G
0500b24431 Merge pull request #260 from LiquidFenrir/patch-1
Make BUF_TO_READ larger to fix audio in some cases
2022-02-26 15:47:30 +00:00
Dylan G
83071d3734 Merge pull request #250 from LiquidFenrir/shuffle-fix
make shuffle work on consoles that never used it
2022-02-26 15:47:11 +00:00
LiquidFenrir
4e2bea53c1 add ability to dump all your official themes
icon and name get extracted from the dlc data
requires libctru from commit 5f13628dac75206f0c97d29a7427ce8284d910f1 or older (added the am commands necessary to find the dlc data)
Makefile changed to reflect the macro change in unreleased libctru
2021-12-22 00:00:42 +01:00
Théo B
0da2594251 Make BUF_TO_READ larger to fix audio in some cases
If the preview .ogg has a samplerate > the BUF_TO_READ constant, there was an out of bounds write to the audio buffers which resulted in crackling.
Thus, upped it to 48000 (0x80-aligned, and pretty much the highest rate anything consumer goes). a bit big, but safe.
2021-09-26 12:10:25 +02:00
Dylan G
c5dc7448e4 Merge pull request #259 from LiquidFenrir/patch-1
Update CONTRIBUTORS.md
2021-08-08 19:05:37 +01:00
Théo B
999b764c26 Update CONTRIBUTORS.md
Might as well add my name there, since it's on the account as well now.
2021-08-08 19:27:36 +02:00
Dylan G
18cb5c616f Correctly handle cases where files already exist on the filesystem.
Also: patched a bug wherein the filename filter was acting up, transforming `file.zip` to `file-zip.zip`, for example.
2021-06-17 01:43:50 +01:00
Dylan G
3f2e4c03f3 Constness 2021-06-17 01:37:33 +01:00
Dylan G
163e12d38a Add issue templates (#249)
* Add issue templates

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

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

View File

@@ -1,6 +1,6 @@
# Main Contributors # Main Contributors
* Alex Taber ([@astronautlevel2](https://github.com/astronautlevel2)) * Alex Taber ([@astronautlevel2](https://github.com/astronautlevel2))
* [@LiquidFenrir](https://github.com/LiquidFenrir) * Théo B. ([@LiquidFenrir](https://github.com/LiquidFenrir))
* Dawid Eckert ([@daedreth](https://github.com/daedreth)) * Dawid Eckert ([@daedreth](https://github.com/daedreth))
* Dylan G. ([@helloman892](https://github.com/helloman892)) * Dylan G. ([@helloman892](https://github.com/helloman892))
* Nils P. ([@ZetaDesigns](https://github.com/ZetaDesigns)) * Nils P. ([@ZetaDesigns](https://github.com/ZetaDesigns))
@@ -17,3 +17,7 @@
* Joel ([@joel16](https://github.com/joel16)) * Joel ([@joel16](https://github.com/joel16))
* [@thedax](https://github.com/thedax) * [@thedax](https://github.com/thedax)
* [@Wryyyong](https://github.com/Wryyyong) * [@Wryyyong](https://github.com/Wryyyong)
# Translation Contributors
* [@cooolgamer](https://github.com/cooolgamer/) for French
* Arth ([@iveurne](https://github.com/iveurne)) for Portugese

View File

@@ -89,7 +89,8 @@ CFLAGS := -g -Wall -Wextra -O2 -mword-relocations \
-ffunction-sections \ -ffunction-sections \
$(ARCH) $(ARCH)
CFLAGS += $(INCLUDE) -DARM11 -D_3DS -D_GNU_SOURCE -DVERSION="\"$(VERSION)\"" -DUSER_AGENT="\"$(APP_TITLE)/$(VERSION)\"" -DAPP_TITLE="\"$(APP_TITLE)\"" CFLAGS += $(INCLUDE) -D__3DS__ -D_GNU_SOURCE -DVERSION="\"$(VERSION)\"" -DUSER_AGENT="\"$(APP_TITLE)/$(VERSION)\"" -DAPP_TITLE="\"$(APP_TITLE)\""
CFLAGS += `arm-none-eabi-pkg-config --cflags-only-other vorbisidec libarchive jansson libpng`
ifneq ($(strip $(CITRA_MODE)),) ifneq ($(strip $(CITRA_MODE)),)
CFLAGS += -DCITRA_MODE CFLAGS += -DCITRA_MODE
endif endif
@@ -99,7 +100,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
ASFLAGS := -g $(ARCH) ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lpng -lvorbisidec -logg -larchive -ljansson -lcitro2d -lcitro3d -lctrud -lm -lz LIBS := `arm-none-eabi-pkg-config --libs libcurl vorbisidec libarchive jansson libpng` -lcitro2d -lcitro3d -lctru -lm
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing # list of directories containing libraries, this must be the top level containing
@@ -146,13 +147,24 @@ else
endif endif
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
export T3XFILES := $(GFXFILES:.t3s=.t3x) #---------------------------------------------------------------------------------
ifeq ($(GFXBUILD),$(BUILD))
#---------------------------------------------------------------------------------
export T3XFILES := $(GFXFILES:.t3s=.t3x)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export ROMFS_T3XFILES := $(patsubst %.t3s, $(GFXBUILD)/%.t3x, $(GFXFILES))
export T3XHFILES := $(patsubst %.t3s, $(BUILD)/%.h, $(GFXFILES))
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES_BIN := $(addsuffix .o,$(BINFILES)) \ export OFILES_BIN := $(addsuffix .o,$(BINFILES)) \
$(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \ $(PICAFILES:.v.pica=.shbin.o) $(SHLISTFILES:.shlist=.shbin.o) \
$(if $(filter $(BUILD),$(GFXBUILD)),$(addsuffix .o,$(T3XFILES))) $(addsuffix .o,$(T3XFILES))
export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) export OFILES := $(OFILES_BIN) $(OFILES_SOURCES)
@@ -222,17 +234,38 @@ else
endif endif
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
all: all: $(BUILD) $(GFXBUILD) $(DEPSDIR) $(ROMFS_T3XFILES) $(T3XHFILES) $(OUTDIR)
@mkdir -p $(BUILD) $(GFXBUILD) $(OUTDIR)
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
@$(BANNERTOOL) makebanner $(BANNER_IMAGE_ARG) "$(BANNER_IMAGE)" $(BANNER_AUDIO_ARG) "$(BANNER_AUDIO)" -o "$(BUILD)/banner.bnr" @$(BANNERTOOL) makebanner $(BANNER_IMAGE_ARG) "$(BANNER_IMAGE)" $(BANNER_AUDIO_ARG) "$(BANNER_AUDIO)" -o "$(BUILD)/banner.bnr"
@$(BANNERTOOL) makesmdh -s "$(APP_TITLE)" -l "$(APP_DESCRIPTION)" -p "$(APP_AUTHOR)" -i "$(APP_ICON)" -f "$(ICON_FLAGS)" -o "$(BUILD)/icon.icn" @$(BANNERTOOL) makesmdh -s "$(APP_TITLE)" -l "$(APP_DESCRIPTION)" -p "$(APP_AUTHOR)" -i "$(APP_ICON)" -f "$(ICON_FLAGS)" -o "$(BUILD)/icon.icn"
$(MAKEROM) -f cia -o "$(OUTPUT).cia" -target t -exefslogo $(MAKEROM_ARGS) $(MAKEROM) -f cia -o "$(OUTPUT).cia" -target t -exefslogo $(MAKEROM_ARGS)
$(BUILD):
@mkdir -p $@
ifneq ($(GFXBUILD),$(BUILD))
$(GFXBUILD):
@mkdir -p $@
endif
ifneq ($(DEPSDIR),$(BUILD))
$(DEPSDIR):
@mkdir -p $@
endif
$(OUTDIR):
@mkdir -p $@
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
clean: clean:
@echo clean ... $(SILENTMSG) clean ...
@rm -fr $(BUILD) $(GFXBUILD) $(OUTDIR) @rm -fr $(BUILD) $(GFXBUILD) $(DEPSDIR) $(OUTDIR)
#---------------------------------------------------------------------------------
$(GFXBUILD)/%.t3x $(BUILD)/%.h : %.t3s
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@tex3ds -i $< -H $(BUILD)/$*.h -d $(DEPSDIR)/$*.d -o $(GFXBUILD)/$*.t3x
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
else else
@@ -255,42 +288,18 @@ $(OUTPUT).elf : $(OFILES)
@$(bin2o) @$(bin2o)
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
.PRECIOUS : %.t3x .PRECIOUS : %.t3x %.shbin
#---------------------------------------------------------------------------------
%.t3x.o %_t3x.h : %.t3x %.t3x.o %_t3x.h : %.t3x
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
@$(bin2o) $(SILENTMSG) $(notdir $<)
$(bin2o)
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
# rules for assembling GPU shaders %.shbin.o %_shbin.h : %.shbin
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
define shader-as $(SILENTMSG) $(notdir $<)
$(eval CURBIN := $*.shbin) $(bin2o)
$(eval DEPSFILE := $(DEPSDIR)/$*.shbin.d)
echo "$(CURBIN).o: $< $1" > $(DEPSFILE)
echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(CURBIN) | tr . _)`.h
echo "extern const u8" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(CURBIN) | tr . _)`.h
echo "extern const u32" `(echo $(CURBIN) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(CURBIN) | tr . _)`.h
picasso -o $(CURBIN) $1
bin2s $(CURBIN) | $(AS) -o $*.shbin.o
endef
%.shbin.o %_shbin.h : %.v.pica %.g.pica
@echo $(notdir $^)
@$(call shader-as,$^)
%.shbin.o %_shbin.h : %.v.pica
@echo $(notdir $<)
@$(call shader-as,$<)
%.shbin.o %_shbin.h : %.shlist
@echo $(notdir $<)
@$(call shader-as,$(foreach file,$(shell cat $<),$(dir $<)$(file)))
#---------------------------------------------------------------------------------
%.t3x %.h : %.t3s
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@tex3ds -i $< -H $*.h -d $*.d -o $(TOPDIR)/$(GFXBUILD)/$*.t3x
-include $(DEPSDIR)/*.d -include $(DEPSDIR)/*.d

View File

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

BIN
assets/back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

BIN
assets/bgm_only.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
assets/dump.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

Before

Width:  |  Height:  |  Size: 404 B

After

Width:  |  Height:  |  Size: 404 B

View File

Before

Width:  |  Height:  |  Size: 179 B

After

Width:  |  Height:  |  Size: 179 B

BIN
assets/no_home.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
assets/qr.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -11,13 +11,18 @@ battery4.png
battery5.png battery5.png
browse.png browse.png
charging.png charging.png
download.png install.png
exit.png exit.png
installed.png installed.png
list.png menu.png
no_home.png
preview.png preview.png
select.png select.png
shuffle.png shuffle.png
shuffle_no_bgm.png shuffle_no_bgm.png
sort.png sort.png
start.png start.png
qr.png
bgm_only.png
back.png
dump.png

View File

@@ -30,7 +30,7 @@
#include "common.h" #include "common.h"
typedef struct { typedef struct {
u16* camera_buffer; u16 * camera_buffer;
Handle event_stop; Handle event_stop;
Thread cam_thread, ui_thread; Thread cam_thread, ui_thread;
@@ -45,7 +45,7 @@ typedef struct {
bool any_update; bool any_update;
struct quirc* context; struct quirc * context;
} qr_data; } qr_data;
bool init_qr(void); bool init_qr(void);

View File

@@ -38,12 +38,24 @@
#define DEBUG(...) fprintf(stderr, __VA_ARGS__) #define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#define POS() DEBUG("%s (line %d)...\n", __func__, __LINE__) #define POS() DEBUG("%s (line %d)...\n", __func__, __LINE__)
#define DEBUGPOS(...) \ #define DEBUGPOS(...) do {\
POS(); \ POS(); \
DEBUG(__VA_ARGS__) DEBUG(__VA_ARGS__); \
} while(0)
static inline int min(const int a, const int b)
{
return a > b ? b : a;
}
static inline int max(const int a, const int b)
{
return a < b ? b : a;
}
#define FASTSCROLL_WAIT 1e8 #define FASTSCROLL_WAIT 1e8
#define BETWEEN(min, x, max) (min < x && x < max)
typedef enum { typedef enum {
MODE_THEMES = 0, MODE_THEMES = 0,
MODE_SPLASHES, MODE_SPLASHES,
@@ -51,6 +63,14 @@ typedef enum {
MODE_AMOUNT, MODE_AMOUNT,
} EntryMode; } EntryMode;
typedef enum {
DRAW_MODE_LIST = 0,
DRAW_MODE_INSTALL,
DRAW_MODE_EXTRA,
DRAW_MODE_AMOUNT,
} DrawMode;
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];

9
include/conversion.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef CONVERISON_H
#define CONVERISON_H
#include "common.h"
size_t bin_to_abgr(char ** bufp, size_t size);
size_t png_to_abgr(char ** bufp, size_t size, u32 *height);
#endif

View File

@@ -33,7 +33,7 @@
#define MAX_LINES 10 #define MAX_LINES 10
typedef enum { typedef enum InstallType_e {
INSTALL_LOADING_THEMES, INSTALL_LOADING_THEMES,
INSTALL_LOADING_SPLASHES, INSTALL_LOADING_SPLASHES,
INSTALL_LOADING_ICONS, INSTALL_LOADING_ICONS,
@@ -56,6 +56,7 @@ typedef enum {
INSTALL_LOADING_REMOTE_BGM, INSTALL_LOADING_REMOTE_BGM,
INSTALL_DUMPING_THEME, INSTALL_DUMPING_THEME,
INSTALL_DUMPING_ALL_THEMES,
INSTALL_NONE, INSTALL_NONE,
} InstallType; } InstallType;
@@ -84,6 +85,7 @@ typedef enum {
TEXT_INSTALL_LOADING_REMOTE_BGM, TEXT_INSTALL_LOADING_REMOTE_BGM,
TEXT_INSTALL_DUMPING_THEME, TEXT_INSTALL_DUMPING_THEME,
TEXT_INSTALL_DUMPING_ALL_THEMES,
// Other text // Other text
TEXT_VERSION, TEXT_VERSION,
@@ -147,8 +149,8 @@ typedef struct {
const char * instructions[BUTTONS_INFO_LINES][BUTTONS_INFO_COLUNMNS]; const char * instructions[BUTTONS_INFO_LINES][BUTTONS_INFO_COLUNMNS];
} Instructions_s; } Instructions_s;
extern C3D_RenderTarget* top; extern C3D_RenderTarget * top;
extern C3D_RenderTarget* bottom; extern C3D_RenderTarget * bottom;
extern C2D_TextBuf staticBuf, dynamicBuf; extern C2D_TextBuf staticBuf, dynamicBuf;
extern C2D_Text text[TEXT_AMOUNT]; extern C2D_Text text[TEXT_AMOUNT];
@@ -160,8 +162,8 @@ void start_frame(void);
void end_frame(void); void end_frame(void);
void set_screen(C3D_RenderTarget * screen); void set_screen(C3D_RenderTarget * screen);
void throw_error(char* error, ErrorLevel level); void throw_error(const char * error, ErrorLevel level);
bool draw_confirm(const char* conf_msg, Entry_List_s* list); bool draw_confirm(const char * conf_msg, Entry_List_s * list, DrawMode draw_mode);
void draw_preview(C2D_Image preview, int preview_offset); void draw_preview(C2D_Image preview, int preview_offset);
@@ -172,9 +174,10 @@ void draw_text(float x, float y, float z, float scaleX, float scaleY, Color colo
void draw_text_wrap(float x, float y, float z, float scaleX, float scaleY, Color color, const char * text, float max_width); void draw_text_wrap(float x, float y, float z, float scaleX, float scaleY, Color color, const char * text, float max_width);
void draw_text_wrap_scaled(float x, float y, float z, Color color, const char * text, float max_scale, float min_scale, float max_width); void draw_text_wrap_scaled(float x, float y, float z, Color color, const char * text, float max_scale, float min_scale, float max_width);
void draw_text_center(gfxScreen_t target, float y, float z, float scaleX, float scaleY, Color color, const char * text); void draw_text_center(gfxScreen_t target, float y, float z, float scaleX, float scaleY, Color color, const char * text);
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); void draw_grid_interface(Entry_List_s * list, Instructions_s instructions, int extra_mode);
void draw_interface(Entry_List_s* list, Instructions_s instructions); void draw_interface(Entry_List_s * list, Instructions_s instructions, DrawMode draw_mode);
#endif #endif

106
include/entries_list.h Normal file
View File

@@ -0,0 +1,106 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#ifndef ENTRIES_LIST_H
#define ENTRIES_LIST_H
#include "common.h"
#include <jansson.h>
typedef enum {
SORT_NONE,
SORT_NAME,
SORT_AUTHOR,
SORT_PATH,
} SortMode;
typedef struct {
u16 path[0x106];
bool is_zip;
bool in_shuffle;
bool no_bgm_shuffle;
bool installed;
u32 placeholder_color; // doubles as not-info-loaded when == 0
json_int_t tp_download_id;
u16 name[0x41];
u16 desc[0x81];
u16 author[0x41];
} Entry_s;
typedef struct {
Tex3DS_SubTexture subtex;
u16 x, y;
} Entry_Icon_s;
typedef struct {
Entry_s * entries;
int entries_count;
int entries_capacity;
C3D_Tex icons_texture;
Entry_Icon_s * icons_info;
int previous_scroll;
int scroll;
int previous_selected;
int selected_entry;
int shuffle_count;
EntryMode mode;
int entries_per_screen_v; // rows of entries on 1 screen
int entries_per_screen_h; // columns of entries on 1 screen
int entries_loaded; // amount of entries on 1 screen
int entry_size; // size in pixels of an entry icon
SortMode current_sort;
json_int_t tp_current_page;
json_int_t tp_page_count;
char * tp_search;
const char * loading_path;
} Entry_List_s;
void sort_by_name(Entry_List_s * list);
void sort_by_author(Entry_List_s * list);
void sort_by_filename(Entry_List_s * list);
void delete_entry(Entry_s * entry, bool is_file);
// assumes list has been memset to 0
typedef enum InstallType_e InstallType;
Result load_entries(const char * loading_path, Entry_List_s * list, const InstallType loading_screen);
u32 load_data(const char * filename, const Entry_s * entry, char ** buf);
C2D_Image get_icon_at(Entry_List_s * list, size_t index);
// assumes list doesn't have any elements yet
void list_init_capacity(Entry_List_s * list, const int init_capacity);
// assumes list has been inited with a non zero capacity
ssize_t list_add_entry(Entry_List_s * list);
#endif

View File

@@ -29,20 +29,41 @@
#include "common.h" #include "common.h"
#define ILLEGAL_CHARS "><\"?;:/\\+,.|[=]*"
extern FS_Archive ArchiveSD; extern FS_Archive ArchiveSD;
extern FS_Archive ArchiveHomeExt; extern FS_Archive ArchiveHomeExt;
extern FS_Archive ArchiveThemeExt; extern FS_Archive ArchiveThemeExt;
typedef struct {
u32 enable : 1;
u32 browser: 1;
u32 stereoscopic : 1;
u32 media_share : 1;
u32 online : 1;
u32 streetpass : 1;
u32 friends : 1;
u32 dsdownload : 1;
u32 shopping : 1;
u32 videos : 1;
u32 miiverse : 1;
u32 post : 1;
u32 null : 19;
u32 coppa : 1;
} Parental_Restrictions_s;
Result open_archives(void); Result open_archives(void);
Result close_archives(void); Result close_archives(void);
Result load_parental_controls(Parental_Restrictions_s *restrictions);
u32 file_to_buf(FS_Path path, FS_Archive archive, char** buf); u32 file_to_buf(FS_Path path, FS_Archive archive, char ** buf);
u32 zip_memory_to_buf(char *file_name, void * zip_memory, size_t zip_size, char ** buf); u32 zip_memory_to_buf(const char * file_name, void * zip_memory, size_t zip_size, char ** buf);
u32 zip_file_to_buf(char *file_name, u16 *zip_path, char **buf); u32 zip_file_to_buf(const char * file_name, const u16 * zip_path, char ** buf);
u32 decompress_lz_file(FS_Path file_name, FS_Archive archive, char **buf); 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);
void remake_file(FS_Path path, FS_Archive archive, u32 size); void remake_file(FS_Path path, FS_Archive archive, u32 size);
void save_zip_to_sd(char * filename, u32 size, char * buf, EntryMode mode);
#endif #endif

View File

@@ -1,145 +0,0 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#ifndef INSTRUCTIONS_H
#define INSTRUCTIONS_H
#include "draw.h"
#include "colors.h"
Instructions_s normal_instructions[MODE_AMOUNT] = {
{
.info_line = NULL,
.instructions = {
{
"\uE000 Hold to install",
"\uE001 Queue shuffle theme"
},
{
"\uE002 Hold for more",
"\uE003 Preview theme"
},
{
"\uE004 Switch to splashes",
"\uE005 Scan QR code"
},
{
"Exit",
"Delete from SD"
}
}
},
{
.info_line = NULL,
.instructions = {
{
"\uE000 Install splash",
"\uE001 Delete installed splash"
},
{
"\uE002 Hold for more",
"\uE003 Preview splash"
},
{
"\uE004 Switch to themes",
"\uE005 Scan QR code"
},
{
"Exit",
"Delete from SD"
}
}
}
};
Instructions_s install_instructions = {
.info_line = "Release \uE000 to cancel or hold \uE006 and release \uE000 to install",
.instructions = {
{
"\uE079 Normal install",
"\uE07A Shuffle install"
},
{
"\uE07B BGM-only install",
"\uE07C No-BGM install"
},
{
NULL,
NULL
},
{
"Exit",
NULL
}
}
};
Instructions_s extra_instructions[3] = {
{
.info_line = "Release \uE002 to cancel or hold \uE006 and release \uE002 to sort",
.instructions = {
{
"\uE079 Sort by name",
"\uE07A Sort by author"
},
{
"\uE07B Sort by filename",
NULL
},
{
NULL,
NULL
},
{
"Exit",
NULL
}
}
},
{
.info_line = "Release \uE002 to cancel or hold \uE006 and release \uE002 to do stuff",
.instructions = {
{
"\uE079 Jump in the list",
"\uE07A Reload broken icons"
},
{
"\uE07B Browse ThemePlaza",
"\uE07C Dump Current Theme"
},
{
"\uE004 Sorting menu",
NULL
},
{
"Exit",
NULL
}
}
},
};
#endif

View File

@@ -28,9 +28,16 @@
#define LOADING_H #define LOADING_H
#include "common.h" #include "common.h"
#include "entries_list.h"
#include "music.h" #include "music.h"
#include <jansson.h> #include <jansson.h>
// These values assume a horizontal orientation
#define TOP_SCREEN_WIDTH 400
#define BOTTOM_SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define SCREEN_COLOR_DEPTH 4
enum ICON_IDS_OFFSET { enum ICON_IDS_OFFSET {
ICONS_ABOVE = 0, ICONS_ABOVE = 0,
ICONS_VISIBLE, ICONS_VISIBLE,
@@ -39,14 +46,6 @@ enum ICON_IDS_OFFSET {
ICONS_OFFSET_AMOUNT, ICONS_OFFSET_AMOUNT,
}; };
typedef enum {
SORT_NONE,
SORT_NAME,
SORT_AUTHOR,
SORT_PATH,
} SortMode;
typedef struct { typedef struct {
u8 _padding1[4 + 2 + 2]; u8 _padding1[4 + 2 + 2];
@@ -55,76 +54,26 @@ typedef struct {
u16 author[0x40]; u16 author[0x40];
u8 _padding2[0x2000 - 0x200 + 0x30 + 0x8]; u8 _padding2[0x2000 - 0x200 + 0x30 + 0x8];
u16 small_icon[24*24]; u16 small_icon[24 * 24];
u16 big_icon[48*48]; u16 big_icon[48 * 48];
} Icon_s; } Icon_s;
typedef struct {
u16 name[0x41];
u16 desc[0x81];
u16 author[0x41];
u32 placeholder_color;
u16 path[0x106];
bool is_zip;
bool in_shuffle;
bool no_bgm_shuffle;
bool installed;
json_int_t tp_download_id;
} Entry_s;
typedef struct {
Entry_s * entries;
int entries_count;
C2D_Image ** icons;
int previous_scroll;
int scroll;
int previous_selected;
int selected_entry;
int shuffle_count;
EntryMode mode;
int entries_per_screen_v;
int entries_per_screen_h;
int entries_loaded;
int entry_size;
SortMode current_sort;
json_int_t tp_current_page;
json_int_t tp_page_count;
char * tp_search;
} Entry_List_s;
typedef struct { typedef struct {
void ** thread_arg; void ** thread_arg;
volatile bool run_thread; volatile bool run_thread;
} Thread_Arg_s; } Thread_Arg_s;
C2D_Image * loadTextureIcon(Icon_s *icon); void copy_texture_data(C3D_Tex * texture, const u16 * src, const Entry_Icon_s * current_icon);
void parse_smdh(Icon_s *icon, Entry_s * entry, const u16 * fallback_name); void parse_smdh(Icon_s * icon, Entry_s * entry, const u16 * fallback_name);
void sort_by_name(Entry_List_s * list);
void sort_by_author(Entry_List_s * list);
void sort_by_filename(Entry_List_s * list);
void delete_entry(Entry_s * entry, bool is_file); bool load_preview_from_buffer(char * row_pointers, u32 size, C2D_Image * preview_image, int * preview_offset, int height);
Result load_entries(const char * loading_path, Entry_List_s * list); bool load_preview(const Entry_List_s * list, C2D_Image * preview_image, int * preview_offset);
bool load_preview_from_buffer(void * buf, u32 size, C2D_Image * preview_image, int * preview_offset);
bool load_preview(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(Entry_s, audio_s *); Result load_audio(const Entry_s *, audio_s *);
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);
u32 load_data(char * filename, Entry_s entry, char ** buf);
#endif #endif

View File

@@ -34,7 +34,7 @@
#include <tremor/ivorbisfile.h> #include <tremor/ivorbisfile.h>
#include <tremor/ivorbiscodec.h> #include <tremor/ivorbiscodec.h>
#define BUF_TO_READ 40960 // How much data should be buffered at a time #define BUF_TO_READ 48000 // How much data should be buffered at a time
typedef struct { typedef struct {
OggVorbis_File vf; OggVorbis_File vf;
@@ -42,13 +42,14 @@ typedef struct {
float mix[12]; float mix[12];
u8 buf_pos; u8 buf_pos;
long data_read; long data_read;
char *filebuf; char * filebuf;
u32 filesize; u32 filesize;
volatile bool stop; volatile bool stop;
Handle finished; Thread playing_thread;
} audio_s; } audio_s;
void play_audio(audio_s *); void play_audio(audio_s *);
void stop_audio(audio_s **);
#endif #endif

View File

@@ -31,7 +31,7 @@
#include "draw.h" #include "draw.h"
#include <ctype.h> #include <ctype.h>
#define THEMEPLAZA_BASE_URL "https://themeplaza.art" #define THEMEPLAZA_BASE_URL "http://themeplaza.art"
#define THEMEPLAZA_API_URL "/api/anemone/v1" #define THEMEPLAZA_API_URL "/api/anemone/v1"
#define THEMEPLAZA_BASE_API_URL THEMEPLAZA_BASE_URL THEMEPLAZA_API_URL #define THEMEPLAZA_BASE_API_URL THEMEPLAZA_BASE_URL THEMEPLAZA_API_URL
@@ -51,7 +51,18 @@
#define CACHE_PATH_FORMAT "/3ds/" APP_TITLE "/cache/%" JSON_INTEGER_FORMAT #define CACHE_PATH_FORMAT "/3ds/" APP_TITLE "/cache/%" JSON_INTEGER_FORMAT
typedef struct {
char *result_buf;
size_t result_written;
size_t result_sz;
} curl_data;
typedef struct {
char *filename;
char *mime_type;
} curl_header;
bool themeplaza_browser(EntryMode mode); bool themeplaza_browser(EntryMode 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,7 +31,7 @@
#include "loading.h" #include "loading.h"
void splash_delete(void); void splash_delete(void);
void splash_install(Entry_s splash); void splash_install(const Entry_s * splash);
void splash_check_installed(void * void_arg); void splash_check_installed(void * void_arg);

View File

@@ -49,8 +49,9 @@ typedef struct {
u8 _padding1[0x13b8]; u8 _padding1[0x13b8];
ThemeEntry_s theme_entry; ThemeEntry_s theme_entry;
ThemeEntry_s shuffle_themes[MAX_SHUFFLE_THEMES]; ThemeEntry_s shuffle_themes[MAX_SHUFFLE_THEMES];
u8 _padding2[0xb]; u8 shuffle_seedA[0xb];
bool shuffle; u8 shuffle;
u8 shuffle_seedB[0xa];
} SaveData_dat_s; } SaveData_dat_s;
typedef struct { typedef struct {
@@ -63,19 +64,20 @@ typedef struct {
u32 dlc_theme_content_index; u32 dlc_theme_content_index;
u32 use_theme_cache; u32 use_theme_cache;
u8 _padding1[0x338 - 8*sizeof(u32)]; u8 _padding1[0x338 - 8 * sizeof(u32)];
u32 shuffle_body_sizes[MAX_SHUFFLE_THEMES]; u32 shuffle_body_sizes[MAX_SHUFFLE_THEMES];
u32 shuffle_music_sizes[MAX_SHUFFLE_THEMES]; u32 shuffle_music_sizes[MAX_SHUFFLE_THEMES];
} ThemeManage_bin_s; } ThemeManage_bin_s;
Result theme_install(Entry_s theme); Result theme_install(Entry_s * theme);
Result no_bgm_install(Entry_s theme); Result no_bgm_install(Entry_s * theme);
Result bgm_install(Entry_s theme); Result bgm_install(Entry_s * theme);
Result shuffle_install(Entry_List_s themes); Result shuffle_install(const Entry_List_s * themes);
Result dump_theme(void); Result dump_current_theme(void);
Result dump_all_themes(void);
void themes_check_installed(void * void_arg); void themes_check_installed(void * void_arg);

189
include/ui_strings.h Normal file
View File

@@ -0,0 +1,189 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#ifndef UISTRINGS_H
#define UISTRINGS_H
#include "colors.h"
#include "draw.h"
#include "common.h"
#define SPLASHES_STRINGS 2
#define THEMES_STRINGS 6
typedef struct {
const char *quit;
const char *thread_error;
const char *zip_not_theme_splash;
const char *file_not_zip;
const char *download_failed;
} Camera_Strings_s;
typedef struct {
const char *theme_mode;
const char *splash_mode;
const char *no_themes;
const char *no_splashes;
const char *qr_download;
const char *switch_splashes;
const char *switch_themes;
const char *quit;
const char *by;
const char *selected;
const char *sel;
const char *tp_theme_mode;
const char *tp_splash_mode;
const char *search;
const char *page;
const char *err_quit;
const char *warn_continue;
const char *yes_no;
const char *load_themes;
const char *load_splash;
const char *load_icons;
const char *install_splash;
const char *delete_splash;
const char *install_theme;
const char *install_shuffle;
const char *install_bgm;
const char *install_no_bgm;
const char *downloading;
const char *checking_dl;
const char *delete_sd;
const char *download_themes;
const char *download_splashes;
const char *download_preview;
const char *download_bgm;
const char *dump_single;
const char *dump_all_official;
float start_pos;
const char *shuffle;
} Draw_Strings_s;
typedef struct {
const char *illegal_input;
const char *new_or_overwrite;
const char *cancel;
const char *overwrite;
const char *rename;
const char *swkbd_fail;
const char *sd_full;
const char *fs_error;
} FS_Strings_s;
typedef struct {
const char *no_preview;
} Loading_Strings_s;
typedef struct {
const char *position_too_big;
const char *position_zero;
const char *jump_q;
const char *cancel;
const char *jump;
const char *no_theme_extdata;
const char *loading_qr;
const char *no_wifi;
const char *qr_homebrew;
const char *camera_broke;
const char *too_many_themes;
const char *not_enough_themes;
const char *uninstall_confirm;
const char *delete_confirm;
} Main_Strings_s;
typedef struct {
const char *no_results;
const char *check_wifi;
const char *new_page_big;
const char *new_page_zero;
const char *jump_page;
const char *cancel;
const char *jump;
const char *tags;
const char *search;
const char *parental_fail;
const char *zip_not_found;
const char *generic_httpc_error;
const char *http303_tp;
const char *http303;
const char *http404;
const char *http_err_url;
const char *http_errcode_generic;
const char *http401;
const char *http403;
const char *http407;
const char *http414;
const char *http418;
const char *http426;
const char *http451;
const char *http500;
const char *http502;
const char *http503;
const char *http504;
const char *http_unexpected;
} Remote_Strings_s;
typedef struct {
const char *no_splash_found;
const char *splash_disabled;
} Splashes_Strings_s;
typedef struct {
const char *no_body_found;
const char *mono_warn;
const char *illegal_char;
const char *name_folder;
const char *cancel;
const char *done;
} Themes_Strings_s;
typedef struct {
Instructions_s normal_instructions[MODE_AMOUNT];
Instructions_s install_instructions;
Instructions_s extra_instructions[3];
Camera_Strings_s camera;
Draw_Strings_s draw;
FS_Strings_s fs;
Loading_Strings_s loading;
Main_Strings_s main;
Remote_Strings_s remote;
Instructions_s remote_instructions[MODE_AMOUNT];
Instructions_s remote_extra_instructions;
Splashes_Strings_s splashes;
Themes_Strings_s themes;
} Language_s;
typedef enum {
LANGUAGE_EN,
LANGUAGE_AMOUNT,
} Language_Name;
Language_s init_strings(CFG_Language lang);
extern Language_s language;
#endif

View File

@@ -29,9 +29,9 @@
#include "common.h" #include "common.h"
ssize_t strulen(const u16*, ssize_t); ssize_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); u16 * strucat(u16 * destination, const u16 * source);
#endif #endif

88
include/urls.h Normal file
View File

@@ -0,0 +1,88 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
static char from_hex(char c)
{
return isdigit(c) ? c - '0' : tolower(c) - 'a' + 10;
}
static char to_hex(char code)
{
static char hex[] = "0123456789abcdef";
return hex[code & 15];
}
// ensure caller frees
char * url_escape(char * url)
{
char * ptr = url;
char * buf = malloc(3 * strlen(url) + 1);
char * ptr_buf = buf;
while (*ptr)
{
if (isalnum((int)*ptr) || strchr("-_.~", *ptr))
*ptr_buf++ = *ptr;
else if (*ptr == ' ')
*ptr_buf++ = '+';
else
{
*ptr_buf++ = '%';
*ptr_buf++ = to_hex(*ptr >> 4);
*ptr_buf++ = to_hex(*ptr & 15);
}
ptr++;
}
*ptr_buf = '\0';
return buf;
}
// ensure caller frees
char * url_unescape(char * url)
{
char * ptr = url;
char * buf = malloc(strlen(url) + 1);
char * ptr_buf = buf;
while (*ptr)
{
if (*ptr == '%')
{
if (ptr[1] && ptr[2])
{
*ptr_buf++ = from_hex(ptr[1]) << 4 | from_hex(ptr[2]);
ptr += 2;
}
}
else if (*ptr == '+')
*ptr_buf++ = ' ';
else
*ptr_buf++ = *ptr;
ptr++;
}
*ptr_buf = '\0';
return buf;
}

View File

@@ -32,11 +32,12 @@
#include "fs.h" #include "fs.h"
#include "loading.h" #include "loading.h"
#include "remote.h" #include "remote.h"
#include "ui_strings.h"
#include <archive.h> #include <archive.h>
#include <archive_entry.h> #include <archive_entry.h>
static void start_read(qr_data *data) static void start_read(qr_data * data)
{ {
LightLock_Lock(&data->mut); LightLock_Lock(&data->mut);
while(data->writer_waiting || data->writer_active) while(data->writer_waiting || data->writer_active)
@@ -47,7 +48,7 @@ static void start_read(qr_data *data)
AtomicIncrement(&data->num_readers_active); AtomicIncrement(&data->num_readers_active);
LightLock_Unlock(&data->mut); LightLock_Unlock(&data->mut);
} }
static void stop_read(qr_data *data) static void stop_read(qr_data * data)
{ {
LightLock_Lock(&data->mut); LightLock_Lock(&data->mut);
AtomicDecrement(&data->num_readers_active); AtomicDecrement(&data->num_readers_active);
@@ -57,7 +58,7 @@ static void stop_read(qr_data *data)
} }
LightLock_Unlock(&data->mut); LightLock_Unlock(&data->mut);
} }
static void start_write(qr_data *data) static void start_write(qr_data * data)
{ {
LightLock_Lock(&data->mut); LightLock_Lock(&data->mut);
data->writer_waiting = true; data->writer_waiting = true;
@@ -72,7 +73,7 @@ static void start_write(qr_data *data)
LightLock_Unlock(&data->mut); LightLock_Unlock(&data->mut);
} }
static void stop_write(qr_data *data) static void stop_write(qr_data * data)
{ {
LightLock_Lock(&data->mut); LightLock_Lock(&data->mut);
@@ -82,16 +83,16 @@ static void stop_write(qr_data *data)
LightLock_Unlock(&data->mut); LightLock_Unlock(&data->mut);
} }
static void capture_cam_thread(void *arg) static void capture_cam_thread(void * arg)
{ {
qr_data *data = (qr_data *) arg; qr_data * data = (qr_data *) arg;
Handle cam_events[3] = {0}; Handle cam_events[3] = {0};
cam_events[0] = data->event_stop; cam_events[0] = data->event_stop;
u32 transferUnit; u32 transferUnit;
const u32 bufsz = 400 * 240 * sizeof(u16); const u32 bufsz = 400 * 240 * sizeof(u16);
u16 *buffer = linearAlloc(bufsz); u16 * buffer = linearAlloc(bufsz);
camInit(); camInit();
CAMU_SetSize(SELECT_OUT1, SIZE_CTR_TOP_LCD, CONTEXT_A); CAMU_SetSize(SELECT_OUT1, SIZE_CTR_TOP_LCD, CONTEXT_A);
@@ -167,9 +168,9 @@ static void capture_cam_thread(void *arg)
LightEvent_Signal(&data->event_cam_info); LightEvent_Signal(&data->event_cam_info);
} }
static void update_ui(void *arg) static void update_ui(void * arg)
{ {
qr_data* data = (qr_data*) arg; qr_data * data = (qr_data *) arg;
C3D_Tex tex; C3D_Tex tex;
static const Tex3DS_SubTexture subt3x = { 400, 240, 0.0f, 1.0f, 400.0f/512.0f, 1.0f - (240.0f/256.0f) }; static const Tex3DS_SubTexture subt3x = { 400, 240, 0.0f, 1.0f, 400.0f/512.0f, 1.0f - (240.0f/256.0f) };
@@ -190,7 +191,7 @@ static void update_ui(void *arg)
for(u32 x = 0; x < 400; x++) { for(u32 x = 0; x < 400; x++) {
const u32 dstPos = ((((y >> 3) * (512 >> 3) + (x >> 3)) << 6) + ((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) | ((x & 4) << 2) | ((y & 4) << 3))); const u32 dstPos = ((((y >> 3) * (512 >> 3) + (x >> 3)) << 6) + ((x & 1) | ((y & 1) << 1) | ((x & 2) << 1) | ((y & 2) << 2) | ((x & 4) << 2) | ((y & 4) << 3)));
((u16*)tex.data)[dstPos] = data->camera_buffer[srcPos + x]; ((u16 *)tex.data)[dstPos] = data->camera_buffer[srcPos + x];
} }
} }
data->any_update = false; data->any_update = false;
@@ -200,7 +201,7 @@ static void update_ui(void *arg)
C2D_DrawImageAt((C2D_Image){ &tex, &subt3x }, 0.0f, 0.0f, 0.4f, NULL, 1.0f, 1.0f); C2D_DrawImageAt((C2D_Image){ &tex, &subt3x }, 0.0f, 0.0f, 0.4f, NULL, 1.0f, 1.0f);
set_screen(bottom); set_screen(bottom);
draw_text_center(GFX_BOTTOM, 4, 0.5, 0.5, 0.5, colors[COLOR_WHITE], "Press \uE005 To Quit"); draw_text_center(GFX_BOTTOM, 4, 0.5, 0.5, 0.5, colors[COLOR_WHITE], language.camera.quit);
end_frame(); end_frame();
} }
@@ -208,11 +209,11 @@ static void update_ui(void *arg)
LightEvent_Signal(&data->event_ui_info); LightEvent_Signal(&data->event_ui_info);
} }
static bool start_capture_cam(qr_data *data) static bool start_capture_cam(qr_data * data)
{ {
if((data->cam_thread = threadCreate(capture_cam_thread, data, 0x10000, 0x1A, 1, false)) == NULL) if((data->cam_thread = threadCreate(capture_cam_thread, data, 0x10000, 0x1A, 1, false)) == NULL)
{ {
throw_error("Capture cam thread creation failed\nPlease report this to the developers", ERROR_LEVEL_ERROR); throw_error(language.camera.thread_error, ERROR_LEVEL_ERROR);
LightEvent_Signal(&data->event_cam_info); LightEvent_Signal(&data->event_cam_info);
LightEvent_Signal(&data->event_ui_info); LightEvent_Signal(&data->event_ui_info);
return false; return false;
@@ -225,11 +226,11 @@ static bool start_capture_cam(qr_data *data)
return true; return true;
} }
static bool update_qr(qr_data *data, struct quirc_data* scan_data) static bool update_qr(qr_data * data, struct quirc_data * scan_data)
{ {
int w; int w;
int h; int h;
u8 *image = (u8*) quirc_begin(data->context, &w, &h); u8 * image = (u8 *)quirc_begin(data->context, &w, &h);
start_read(data); start_read(data);
for (int y = 0; y < h; y++) { for (int y = 0; y < h; y++) {
@@ -256,7 +257,7 @@ static bool update_qr(qr_data *data, struct quirc_data* scan_data)
return false; return false;
} }
static void start_qr(qr_data *data) static void start_qr(qr_data * data)
{ {
svcCreateEvent(&data->event_stop, RESET_STICKY); svcCreateEvent(&data->event_stop, RESET_STICKY);
LightEvent_Init(&data->event_cam_info, RESET_STICKY); LightEvent_Init(&data->event_cam_info, RESET_STICKY);
@@ -272,7 +273,7 @@ static void start_qr(qr_data *data)
quirc_resize(data->context, 400, 240); quirc_resize(data->context, 400, 240);
data->camera_buffer = calloc(1, 400 * 240 * sizeof(u16)); data->camera_buffer = calloc(1, 400 * 240 * sizeof(u16));
} }
static void exit_qr(qr_data *data) static void exit_qr(qr_data * data)
{ {
svcSignalEvent(data->event_stop); svcSignalEvent(data->event_stop);
@@ -308,7 +309,7 @@ bool init_qr(void)
start_qr(&data); start_qr(&data);
struct quirc_data* scan_data = calloc(1, sizeof(struct quirc_data)); struct quirc_data * scan_data = calloc(1, sizeof(struct quirc_data));
const bool ready = start_capture_cam(&data); const bool ready = start_capture_cam(&data);
bool finished = !ready; bool finished = !ready;
@@ -333,7 +334,8 @@ bool init_qr(void)
char * zip_buf = NULL; char * zip_buf = NULL;
char * filename = NULL; char * filename = NULL;
u32 zip_size; u32 zip_size;
Result res = http_get((char*)scan_data->payload, &filename, &zip_buf, &zip_size, INSTALL_DOWNLOAD, "application/zip");
Result res = http_get((char*)scan_data->payload, &filename, &zip_buf, &zip_size, INSTALL_DOWNLOAD, "application/zip; application/x-zip-compressed");
if (R_FAILED(res)) if (R_FAILED(res))
{ {
free(filename); free(filename);
@@ -350,7 +352,7 @@ bool init_qr(void)
{ {
draw_install(INSTALL_CHECKING_DOWNLOAD); draw_install(INSTALL_CHECKING_DOWNLOAD);
struct archive *a = archive_read_new(); struct archive * a = archive_read_new();
archive_read_support_format_zip(a); archive_read_support_format_zip(a);
int r = archive_read_open_memory(a, zip_buf, zip_size); int r = archive_read_open_memory(a, zip_buf, zip_size);
@@ -391,30 +393,23 @@ bool init_qr(void)
if(mode != MODE_AMOUNT) if(mode != MODE_AMOUNT)
{ {
char path_to_file[0x107] = {0}; save_zip_to_sd(filename, zip_size, zip_buf, mode);
sprintf(path_to_file, "%s%s", main_paths[mode], filename);
char * extension = strrchr(path_to_file, '.');
if (extension == NULL || strcmp(extension, ".zip"))
strcat(path_to_file, ".zip");
remake_file(fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_size);
buf_to_file(zip_size, fsMakePath(PATH_ASCII, path_to_file), ArchiveSD, zip_buf);
success = true; success = true;
} }
else else
{ {
throw_error("Zip downloaded is neither\na splash nor a theme.", ERROR_LEVEL_WARNING); throw_error(language.camera.zip_not_theme_splash, ERROR_LEVEL_WARNING);
} }
} }
else else
{ {
throw_error("File downloaded isn't a zip.", ERROR_LEVEL_WARNING); throw_error(language.camera.file_not_zip, ERROR_LEVEL_WARNING);
} }
free(zip_buf); free(zip_buf);
} }
else else
{ {
throw_error("Download failed.", ERROR_LEVEL_WARNING); throw_error(language.camera.download_failed, ERROR_LEVEL_WARNING);
} }
free(filename); free(filename);

129
source/conversion.c Normal file
View File

@@ -0,0 +1,129 @@
#include "conversion.h"
#include "draw.h"
#include <png.h>
static void rotate_agbr_counterclockwise(char ** bufp, size_t size, size_t width)
{
uint32_t * buf = (uint32_t*)*bufp;
uint32_t * out = malloc(size);
size_t pixel_count = (size/4);
size_t height = pixel_count/width;
for (uint32_t h = 0; h < height; ++h)
{
for (uint32_t w = 0; w < width; ++w)
{
size_t buf_index = (w + (h * width));
size_t out_index = (height * (width-1)) - (height * w) + h;
out[out_index] = buf[buf_index];
}
}
free(*bufp);
*bufp = (char*)out;
}
size_t bin_to_abgr(char ** bufp, size_t size)
{
size_t out_size = (size / 3) * 4;
char* buf = malloc(out_size);
for (size_t i = 0, j = 0; i < size; i+=3, j+=4)
{
(buf+j)[0] = 0xFF; // A
(buf+j)[1] = (*bufp+i)[0]; // B
(buf+j)[2] = (*bufp+i)[1]; // G
(buf+j)[3] = (*bufp+i)[2]; // R
}
free(*bufp);
*bufp = buf;
// splash screens contain the raw framebuffer to put on the screen
// because the screens are mounted at a 90 degree angle we also need to rotate
// the output
rotate_agbr_counterclockwise(bufp, out_size, 240);
return out_size;
}
size_t png_to_abgr(char ** bufp, size_t size, u32 *height)
{
size_t out_size = 0;
if(size < 8 || png_sig_cmp((png_bytep)*bufp, 0, 8))
{
throw_error("Invalid preview.png", ERROR_LEVEL_WARNING);
return out_size;
}
uint32_t * buf = (uint32_t*)*bufp;
FILE * fp = fmemopen(buf, size, "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);
*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);
// Read any color_type into 8bit depth, ABGR format.
// See http://www.libpng.org/pub/png/libpng-manual.txt
if(bit_depth == 16)
png_set_strip_16(png);
if(color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
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);
// These color_type don't have an alpha channel then fill it with 0xff.
if(color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE)
png_set_add_alpha(png, 0xFF, PNG_FILLER_BEFORE);
if(color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
//output ABGR
png_set_bgr(png);
png_set_swap_alpha(png);
png_read_update_info(png, info);
row_pointers = malloc(sizeof(png_bytep) * *height);
out_size = sizeof(u32) * (width * *height);
u32 * out = malloc(out_size);
for(u32 y = 0; y < *height; y++)
{
row_pointers[y] = (png_bytep)(out + (width * y));
}
png_read_image(png, row_pointers);
png_destroy_read_struct(&png, &info, NULL);
if (fp) fclose(fp);
if (row_pointers) free(row_pointers);
free(*bufp);
*bufp = (char*)out;
return out_size;
}

View File

@@ -27,13 +27,14 @@
#include "draw.h" #include "draw.h"
#include "unicode.h" #include "unicode.h"
#include "colors.h" #include "colors.h"
#include "ui_strings.h"
#include "sprites.h" #include "sprites.h"
#include <time.h> #include <time.h>
C3D_RenderTarget* top; C3D_RenderTarget * top;
C3D_RenderTarget* bottom; C3D_RenderTarget * bottom;
C2D_TextBuf staticBuf, dynamicBuf; C2D_TextBuf staticBuf, dynamicBuf;
static C2D_TextBuf widthBuf; static C2D_TextBuf widthBuf;
@@ -77,55 +78,56 @@ void init_screens(void)
C2D_TextParse(&text[TEXT_VERSION], staticBuf, VERSION); C2D_TextParse(&text[TEXT_VERSION], staticBuf, VERSION);
C2D_TextParse(&text[TEXT_THEME_MODE], staticBuf, "Theme mode"); C2D_TextParse(&text[TEXT_THEME_MODE], staticBuf, language.draw.theme_mode);
C2D_TextParse(&text[TEXT_SPLASH_MODE], staticBuf, "Splash mode"); C2D_TextParse(&text[TEXT_SPLASH_MODE], staticBuf, language.draw.splash_mode);
C2D_TextParse(&text[TEXT_NO_THEME_FOUND], staticBuf, "No theme found"); C2D_TextParse(&text[TEXT_NO_THEME_FOUND], staticBuf, language.draw.no_themes);
C2D_TextParse(&text[TEXT_NO_SPLASH_FOUND], staticBuf, "No splash found"); C2D_TextParse(&text[TEXT_NO_SPLASH_FOUND], staticBuf, language.draw.no_splashes);
C2D_TextParse(&text[TEXT_DOWNLOAD_FROM_QR], staticBuf, "Press \uE005 to download from QR"); C2D_TextParse(&text[TEXT_DOWNLOAD_FROM_QR], staticBuf, language.draw.qr_download);
C2D_TextParse(&text[TEXT_SWITCH_TO_SPLASHES], staticBuf, "Or \uE004 to switch to splashes"); C2D_TextParse(&text[TEXT_SWITCH_TO_SPLASHES], staticBuf, language.draw.switch_splashes);
C2D_TextParse(&text[TEXT_SWITCH_TO_THEMES], staticBuf, "Or \uE004 to switch to themes"); C2D_TextParse(&text[TEXT_SWITCH_TO_THEMES], staticBuf, language.draw.switch_themes);
C2D_TextParse(&text[TEXT_OR_START_TO_QUIT], staticBuf, "Or to quit"); C2D_TextParse(&text[TEXT_OR_START_TO_QUIT], staticBuf, language.draw.quit);
C2D_TextParse(&text[TEXT_BY_AUTHOR], staticBuf, "By "); C2D_TextParse(&text[TEXT_BY_AUTHOR], staticBuf, language.draw.by);
C2D_TextParse(&text[TEXT_SELECTED], staticBuf, "Selected:"); C2D_TextParse(&text[TEXT_SELECTED], staticBuf, language.draw.selected);
C2D_TextParse(&text[TEXT_SELECTED_SHORT], staticBuf, "Sel.:"); C2D_TextParse(&text[TEXT_SELECTED_SHORT], staticBuf, language.draw.sel);
C2D_TextParse(&text[TEXT_THEMEPLAZA_THEME_MODE], staticBuf, "ThemePlaza Theme mode"); C2D_TextParse(&text[TEXT_THEMEPLAZA_THEME_MODE], staticBuf, language.draw.tp_theme_mode);
C2D_TextParse(&text[TEXT_THEMEPLAZA_SPLASH_MODE], staticBuf, "ThemePlaza Splash mode"); C2D_TextParse(&text[TEXT_THEMEPLAZA_SPLASH_MODE], staticBuf, language.draw.tp_splash_mode);
C2D_TextParse(&text[TEXT_SEARCH], staticBuf, "Search..."); C2D_TextParse(&text[TEXT_SEARCH], staticBuf, language.draw.search);
C2D_TextParse(&text[TEXT_PAGE], staticBuf, "Page:"); C2D_TextParse(&text[TEXT_PAGE], staticBuf, language.draw.page);
C2D_TextParse(&text[TEXT_ERROR_QUIT], staticBuf, "Press \uE000 to quit."); C2D_TextParse(&text[TEXT_ERROR_QUIT], staticBuf, language.draw.err_quit);
C2D_TextParse(&text[TEXT_ERROR_CONTINUE], staticBuf, "Press \uE000 to continue."); C2D_TextParse(&text[TEXT_ERROR_CONTINUE], staticBuf, language.draw.warn_continue);
C2D_TextParse(&text[TEXT_CONFIRM_YES_NO], staticBuf, "\uE000 Yes \uE001 No"); C2D_TextParse(&text[TEXT_CONFIRM_YES_NO], staticBuf, language.draw.yes_no);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_THEMES], staticBuf, "Loading themes, please wait..."); C2D_TextParse(&text[TEXT_INSTALL_LOADING_THEMES], staticBuf, language.draw.load_themes);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_SPLASHES], staticBuf, "Loading splashes, please wait..."); C2D_TextParse(&text[TEXT_INSTALL_LOADING_SPLASHES], staticBuf, language.draw.load_splash);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_ICONS], staticBuf, "Loading icons, please wait..."); C2D_TextParse(&text[TEXT_INSTALL_LOADING_ICONS], staticBuf, language.draw.load_icons);
C2D_TextParse(&text[TEXT_INSTALL_SPLASH], staticBuf, "Installing a splash..."); C2D_TextParse(&text[TEXT_INSTALL_SPLASH], staticBuf, language.draw.install_splash);
C2D_TextParse(&text[TEXT_INSTALL_SPLASH_DELETE], staticBuf, "Deleting installed splash..."); C2D_TextParse(&text[TEXT_INSTALL_SPLASH_DELETE], staticBuf, language.draw.delete_splash);
C2D_TextParse(&text[TEXT_INSTALL_SINGLE], staticBuf, "Installing a single theme..."); C2D_TextParse(&text[TEXT_INSTALL_SINGLE], staticBuf, language.draw.install_theme);
C2D_TextParse(&text[TEXT_INSTALL_SHUFFLE], staticBuf, "Installing shuffle themes..."); C2D_TextParse(&text[TEXT_INSTALL_SHUFFLE], staticBuf, language.draw.install_shuffle);
C2D_TextParse(&text[TEXT_INSTALL_BGM], staticBuf, "Installing BGM-only theme..."); C2D_TextParse(&text[TEXT_INSTALL_BGM], staticBuf, language.draw.install_bgm);
C2D_TextParse(&text[TEXT_INSTALL_NO_BGM], staticBuf, "Installing theme without BGM..."); C2D_TextParse(&text[TEXT_INSTALL_NO_BGM], staticBuf, language.draw.install_no_bgm);
C2D_TextParse(&text[TEXT_INSTALL_DOWNLOAD], staticBuf, "Downloading..."); C2D_TextParse(&text[TEXT_INSTALL_DOWNLOAD], staticBuf, language.draw.downloading);
C2D_TextParse(&text[TEXT_INSTALL_CHECKING_DOWNLOAD], staticBuf, "Checking downloaded file..."); C2D_TextParse(&text[TEXT_INSTALL_CHECKING_DOWNLOAD], staticBuf, language.draw.checking_dl);
C2D_TextParse(&text[TEXT_INSTALL_ENTRY_DELETE], staticBuf, "Deleting from SD..."); C2D_TextParse(&text[TEXT_INSTALL_ENTRY_DELETE], staticBuf, language.draw.delete_sd);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_THEMES], staticBuf, "Downloading theme list, please wait..."); C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_THEMES], staticBuf, language.draw.download_themes);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_SPLASHES], staticBuf, "Downloading splash list, please wait..."); C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_SPLASHES], staticBuf, language.draw.download_splashes);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_PREVIEW], staticBuf, "Downloading preview, please wait..."); C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_PREVIEW], staticBuf, language.draw.download_preview);
C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_BGM], staticBuf, "Downloading BGM, please wait..."); C2D_TextParse(&text[TEXT_INSTALL_LOADING_REMOTE_BGM], staticBuf, language.draw.download_bgm);
C2D_TextParse(&text[TEXT_INSTALL_DUMPING_THEME], staticBuf, "Dumping theme, please wait..."); 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);
for(int i = 0; i < TEXT_AMOUNT; i++) for(int i = 0; i < TEXT_AMOUNT; i++)
C2D_TextOptimize(&text[i]); C2D_TextOptimize(&text[i]);
@@ -166,6 +168,15 @@ static void draw_image(int image_id, float x, float y)
C2D_DrawImageAt(C2D_SpriteSheetGetImage(spritesheet, image_id), x, y, 0.6f, NULL, 1.0f, 1.0f); C2D_DrawImageAt(C2D_SpriteSheetGetImage(spritesheet, image_id), x, y, 0.6f, NULL, 1.0f, 1.0f);
} }
void draw_home(u64 start_time, u64 cur_time)
{
float time_sec = (cur_time - start_time)/1000.0f;
float alpha = fmin(-1.333f * time_sec * time_sec + 2.666f * time_sec, 1.0f); // Quadratic regression to create fade effect
C2D_ImageTint tint = {};
C2D_AlphaImageTint(&tint, alpha);
C2D_DrawImageAt(C2D_SpriteSheetGetImage(spritesheet, sprites_no_home_idx), (320-64)/2, (240-64)/2, 1.0f, &tint, 1.0f, 1.0f);
}
static void get_text_dimensions(const char * text, float scaleX, float scaleY, float * width, float * height) static void get_text_dimensions(const char * text, float scaleX, float scaleY, float * width, float * height)
{ {
C2D_Text c2d_text; C2D_Text c2d_text;
@@ -275,7 +286,7 @@ void draw_base_interface(void)
set_screen(top); set_screen(top);
} }
void throw_error(char* error, ErrorLevel level) void throw_error(const char * error, ErrorLevel level)
{ {
Text bottom_text = TEXT_AMOUNT; Text bottom_text = TEXT_AMOUNT;
Color text_color = COLOR_WHITE; Color text_color = COLOR_WHITE;
@@ -308,12 +319,12 @@ void throw_error(char* error, ErrorLevel level)
} }
} }
bool draw_confirm(const char* conf_msg, Entry_List_s* list) bool draw_confirm(const char * conf_msg, Entry_List_s * list, DrawMode draw_mode)
{ {
while(aptMainLoop()) while(aptMainLoop())
{ {
Instructions_s instructions = {0}; Instructions_s instructions = {0};
draw_interface(list, instructions); draw_interface(list, instructions, draw_mode);
set_screen(top); set_screen(top);
draw_text_center(GFX_TOP, BUTTONS_Y_LINE_1, 0.5f, 0.7f, 0.7f, colors[COLOR_YELLOW], conf_msg); draw_text_center(GFX_TOP, BUTTONS_Y_LINE_1, 0.5f, 0.7f, 0.7f, colors[COLOR_YELLOW], conf_msg);
draw_c2d_text_center(GFX_TOP, BUTTONS_Y_LINE_3, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], &text[TEXT_CONFIRM_YES_NO]); draw_c2d_text_center(GFX_TOP, BUTTONS_Y_LINE_3, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], &text[TEXT_CONFIRM_YES_NO]);
@@ -358,7 +369,7 @@ void draw_loading_bar(u32 current, u32 max, InstallType type)
draw_base_interface(); draw_base_interface();
draw_install_handler(type); draw_install_handler(type);
set_screen(bottom); set_screen(bottom);
double percent = 100*((double)current/(double)max); double percent = 100 * ((double)current / (double)max);
u32 width = (u32)percent; u32 width = (u32)percent;
width *= 2; width *= 2;
C2D_DrawRectSolid(60-1, 110-1, 0.5f, 200+2, 20+2, colors[COLOR_CURSOR]); C2D_DrawRectSolid(60-1, 110-1, 0.5f, 200+2, 20+2, colors[COLOR_CURSOR]);
@@ -432,13 +443,13 @@ void draw_text_wrap(float x, float y, float z, float scaleX, float scaleY, Color
continue; continue;
} }
if((consumed = decode_utf8(&codepoint, (unsigned char*)text)) == -1) if((consumed = decode_utf8(&codepoint, (unsigned char *)text)) == -1)
break; break;
float character_width = scaleX * (fontGetCharWidthInfo(NULL, fontGlyphIndexFromCodePoint(NULL, codepoint))->charWidth); float character_width = scaleX * (fontGetCharWidthInfo(NULL, fontGlyphIndexFromCodePoint(NULL, codepoint))->charWidth);
if((current_width += character_width) > max_width) if((current_width += character_width) > max_width)
{ {
char* last_space = NULL; char * last_space = NULL;
for(int i = idx; i >= 0; i--) for(int i = idx; i >= 0; i--)
{ {
if(result[i] == ' ') if(result[i] == ' ')
@@ -488,27 +499,27 @@ void draw_text_wrap_scaled(float x, float y, float z, Color color, const char *
static void draw_entry_info(Entry_s * entry) static void draw_entry_info(Entry_s * entry)
{ {
char author[0x41] = {0}; char author[0x41] = {0};
utf16_to_utf8((u8*)author, entry->author, 0x40); utf16_to_utf8((u8 *)author, entry->author, 0x40);
draw_c2d_text(20, 35, 0.5, 0.5, 0.5, colors[COLOR_WHITE], &text[TEXT_BY_AUTHOR]); draw_c2d_text(20, 35, 0.5, 0.5, 0.5, colors[COLOR_WHITE], &text[TEXT_BY_AUTHOR]);
float width = 0; float width = 0;
C2D_TextGetDimensions(&text[TEXT_BY_AUTHOR], 0.5, 0.5, &width, NULL); C2D_TextGetDimensions(&text[TEXT_BY_AUTHOR], 0.5, 0.5, &width, NULL);
draw_text(20+width, 35, 0.5, 0.5, 0.5, colors[COLOR_WHITE], author); draw_text(20+width, 35, 0.5, 0.5, 0.5, colors[COLOR_WHITE], author);
char title[0x41] = {0}; char title[0x41] = {0};
utf16_to_utf8((u8*)title, entry->name, 0x40); utf16_to_utf8((u8 *)title, entry->name, 0x40);
draw_text(20, 50, 0.5, 0.7, 0.7, colors[COLOR_WHITE], title); draw_text(20, 50, 0.5, 0.7, 0.7, colors[COLOR_WHITE], title);
char description[0x81] = {0}; char description[0x81] = {0};
utf16_to_utf8((u8*)description, entry->desc, 0x80); utf16_to_utf8((u8 *)description, entry->desc, 0x80);
draw_text_wrap(20, 70, 0.5, 0.5, 0.5, colors[COLOR_WHITE], description, 363); draw_text_wrap(20, 70, 0.5, 0.5, 0.5, colors[COLOR_WHITE], description, 363);
} }
void draw_grid_interface(Entry_List_s* list, Instructions_s instructions) void draw_grid_interface(Entry_List_s * list, Instructions_s instructions, int extra_mode)
{ {
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[MODE_AMOUNT] = {
&text[TEXT_THEMEPLAZA_THEME_MODE], &text[TEXT_THEMEPLAZA_THEME_MODE],
&text[TEXT_THEMEPLAZA_SPLASH_MODE], &text[TEXT_THEMEPLAZA_SPLASH_MODE],
}; };
@@ -525,7 +536,7 @@ void draw_grid_interface(Entry_List_s* list, Instructions_s instructions)
draw_c2d_text(7, 3, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], &text[TEXT_SEARCH]); draw_c2d_text(7, 3, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], &text[TEXT_SEARCH]);
draw_image(sprites_list_idx, 320-96, 0); draw_image(sprites_back_idx, 320-96, 0);
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);
@@ -541,7 +552,7 @@ void draw_grid_interface(Entry_List_s* list, Instructions_s instructions)
current_entry = &list->entries[i]; current_entry = &list->entries[i];
char name[0x41] = {0}; char name[0x41] = {0};
utf16_to_utf8((u8*)name, current_entry->name, 0x40); utf16_to_utf8((u8 *)name, current_entry->name, 0x40);
int vertical_offset = 0; int vertical_offset = 0;
int horizontal_offset = i - list->scroll; int horizontal_offset = i - list->scroll;
@@ -553,18 +564,16 @@ void draw_grid_interface(Entry_List_s* list, Instructions_s instructions)
vertical_offset += 24; vertical_offset += 24;
horizontal_offset += 16; horizontal_offset += 16;
// theoretically impossible to have no icon when from the api
/* if(current_entry->placeholder_color == 0)
if(!current_entry->placeholder_color)
{ {
*/ const C2D_Image image = get_icon_at(list, i);
C2D_Image * image = list->icons[i]; C2D_DrawImageAt(image, horizontal_offset, vertical_offset, 0.5f, NULL, 1.0f, 1.0f);
C2D_DrawImageAt(*image, horizontal_offset, vertical_offset, 0.5f, NULL, 1.0f, 1.0f);
/*
} }
else else
{
C2D_DrawRectSolid(horizontal_offset, vertical_offset, 0.5f, list->entry_size, list->entry_size, current_entry->placeholder_color); C2D_DrawRectSolid(horizontal_offset, vertical_offset, 0.5f, list->entry_size, list->entry_size, current_entry->placeholder_color);
*/ }
if(i == selected_entry) if(i == selected_entry)
{ {
@@ -575,6 +584,11 @@ void draw_grid_interface(Entry_List_s* list, Instructions_s instructions)
C2D_DrawRectSolid(horizontal_offset+list->entry_size-border_width, vertical_offset, 0.5f, border_width, list->entry_size, colors[COLOR_CURSOR]); C2D_DrawRectSolid(horizontal_offset+list->entry_size-border_width, vertical_offset, 0.5f, border_width, list->entry_size, colors[COLOR_CURSOR]);
} }
} }
if (extra_mode)
{
C2D_DrawRectSolid(0, 24, 0.6f, 320, 240-48, C2D_Color32(0, 0, 0, 128));
}
char entries_count_str[0x20] = {0}; char entries_count_str[0x20] = {0};
sprintf(entries_count_str, "/%" JSON_INTEGER_FORMAT, list->tp_page_count); sprintf(entries_count_str, "/%" JSON_INTEGER_FORMAT, list->tp_page_count);
@@ -593,21 +607,21 @@ void draw_grid_interface(Entry_List_s* list, Instructions_s instructions)
draw_c2d_text(176, 219, 0.5, 0.6, 0.6, colors[COLOR_WHITE], &text[TEXT_PAGE]); draw_c2d_text(176, 219, 0.5, 0.6, 0.6, colors[COLOR_WHITE], &text[TEXT_PAGE]);
} }
void draw_interface(Entry_List_s* list, Instructions_s instructions) void draw_interface(Entry_List_s * list, Instructions_s instructions, DrawMode draw_mode)
{ {
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[MODE_AMOUNT] = {
&text[TEXT_THEME_MODE], &text[TEXT_THEME_MODE],
&text[TEXT_SPLASH_MODE], &text[TEXT_SPLASH_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]);
if(list->entries == NULL) if(list->entries == NULL || list->entries_count == 0)
{ {
C2D_Text* mode_found_string[MODE_AMOUNT] = { C2D_Text * mode_found_string[MODE_AMOUNT] = {
&text[TEXT_NO_THEME_FOUND], &text[TEXT_NO_THEME_FOUND],
&text[TEXT_NO_SPLASH_FOUND], &text[TEXT_NO_SPLASH_FOUND],
}; };
@@ -615,7 +629,7 @@ void draw_interface(Entry_List_s* list, Instructions_s instructions)
draw_c2d_text_center(GFX_TOP, 80, 0.5f, 0.7f, 0.7f, colors[COLOR_YELLOW], mode_found_string[current_mode]); draw_c2d_text_center(GFX_TOP, 80, 0.5f, 0.7f, 0.7f, colors[COLOR_YELLOW], mode_found_string[current_mode]);
draw_c2d_text_center(GFX_TOP, 110, 0.5f, 0.7f, 0.7f, colors[COLOR_YELLOW], &text[TEXT_DOWNLOAD_FROM_QR]); draw_c2d_text_center(GFX_TOP, 110, 0.5f, 0.7f, 0.7f, colors[COLOR_YELLOW], &text[TEXT_DOWNLOAD_FROM_QR]);
C2D_Text* mode_switch_string[MODE_AMOUNT] = { C2D_Text * mode_switch_string[MODE_AMOUNT] = {
&text[TEXT_SWITCH_TO_SPLASHES], &text[TEXT_SWITCH_TO_SPLASHES],
&text[TEXT_SWITCH_TO_THEMES], &text[TEXT_SWITCH_TO_THEMES],
}; };
@@ -625,18 +639,16 @@ void draw_interface(Entry_List_s* list, Instructions_s instructions)
C2D_ImageTint yellow_tint; C2D_ImageTint yellow_tint;
C2D_PlainImageTint(&yellow_tint, colors[COLOR_YELLOW], 1.0f); C2D_PlainImageTint(&yellow_tint, colors[COLOR_YELLOW], 1.0f);
C2D_SpriteSetPos(&sprite_start, 162, 173); C2D_SpriteSetPos(&sprite_start, language.draw.start_pos, 173);
C2D_SpriteSetScale(&sprite_start, 1.25f, 1.4f); C2D_SpriteSetScale(&sprite_start, 1.25f, 1.4f);
C2D_DrawSpriteTinted(&sprite_start, &yellow_tint); C2D_DrawSpriteTinted(&sprite_start, &yellow_tint);
C2D_SpriteSetScale(&sprite_start, 1.0f, 1.0f); C2D_SpriteSetScale(&sprite_start, 1.0f, 1.0f);
set_screen(bottom); set_screen(bottom);
draw_image(sprites_sort_idx, 320-144, 0); draw_image(sprites_qr_idx, 320-96, 0);
draw_image(sprites_download_idx, 320-120, 0); draw_image(sprites_browse_idx, 320-72, 0);
draw_image(sprites_browse_idx, 320-96, 0); draw_image(sprites_exit_idx, 320-48, 0);
draw_image(sprites_exit_idx, 320-72, 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]);
@@ -654,23 +666,46 @@ void draw_interface(Entry_List_s* list, Instructions_s instructions)
if(current_mode == MODE_THEMES) if(current_mode == MODE_THEMES)
{ {
char * shuffle_count_string = NULL; char * shuffle_count_string = NULL;
asprintf(&shuffle_count_string, "Shuffle: %i/10", 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(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);
free(shuffle_count_string); free(shuffle_count_string);
} }
draw_image(sprites_sort_idx, 320-144, 0); if (draw_mode == DRAW_MODE_LIST)
draw_image(sprites_download_idx, 320-120, 0); {
draw_image(sprites_browse_idx, 320-96, 0); draw_image(sprites_install_idx, 320-120, 0);
draw_image(sprites_exit_idx, 320-72, 0); draw_image(sprites_qr_idx, 320-96, 0);
draw_image(sprites_preview_idx, 320-48, 0); draw_image(sprites_exit_idx, 320-72, 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);
if (current_mode == MODE_THEMES)
{
draw_image(sprites_shuffle_idx, 320-168, 0);
}
}
else
{
if (draw_mode == DRAW_MODE_INSTALL)
{
draw_image(sprites_install_idx, 320-24, 0);
draw_image(sprites_shuffle_idx, 320-48, 0);
draw_image(sprites_shuffle_no_bgm_idx, 320-72, 0);
draw_image(sprites_bgm_only_idx, 320-96, 0);
draw_image(sprites_back_idx, 320-120, 0);
} else if (draw_mode == DRAW_MODE_EXTRA)
{
draw_image(sprites_browse_idx, 320-24, 0);
draw_image(sprites_dump_idx, 320-48, 0);
draw_image(sprites_sort_idx, 320-72, 0);
draw_image(sprites_back_idx, 320-96, 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, 152, 4); draw_image(sprites_arrow_up_idx, 136, 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, 152, 220);
@@ -681,7 +716,7 @@ void draw_interface(Entry_List_s* list, Instructions_s instructions)
current_entry = &list->entries[i]; current_entry = &list->entries[i];
char name[0x41] = {0}; char name[0x41] = {0};
utf16_to_utf8((u8*)name, current_entry->name, 0x40); utf16_to_utf8((u8 *)name, current_entry->name, 0x40);
int vertical_offset = i - list->scroll; int vertical_offset = i - list->scroll;
int horizontal_offset = 0; int horizontal_offset = 0;
@@ -719,14 +754,17 @@ void draw_interface(Entry_List_s* list, Instructions_s instructions)
C2D_DrawSpriteTinted(&sprite_installed, &tint); C2D_DrawSpriteTinted(&sprite_installed, &tint);
} }
if(!current_entry->placeholder_color) if(current_entry->placeholder_color == 0)
{ {
C2D_Image * image = NULL; C2D_Image image;
if(list->entries_count > list->entries_loaded*ICONS_OFFSET_AMOUNT) if(list->entries_count > list->entries_loaded * ICONS_OFFSET_AMOUNT)
image = list->icons[ICONS_VISIBLE*list->entries_loaded + (i - list->scroll)]; {
const int offset_to_visible_icons = ICONS_VISIBLE * list->entries_loaded;
image = get_icon_at(list, offset_to_visible_icons + (i - list->scroll));
}
else else
image = list->icons[i]; image = get_icon_at(list, i);
C2D_DrawImageAt(*image, horizontal_offset, vertical_offset, 0.5f, NULL, 1.0f, 1.0f); C2D_DrawImageAt(image, horizontal_offset, vertical_offset, 0.5f, NULL, 1.0f, 1.0f);
} }
else else
{ {
@@ -752,4 +790,8 @@ void draw_interface(Entry_List_s* list, Instructions_s instructions)
draw_c2d_text(176, 219, 0.5, 0.6, 0.6, colors[COLOR_WHITE], &text[TEXT_SELECTED]); draw_c2d_text(176, 219, 0.5, 0.6, 0.6, colors[COLOR_WHITE], &text[TEXT_SELECTED]);
else else
draw_c2d_text(176, 219, 0.5, 0.6, 0.6, colors[COLOR_WHITE], &text[TEXT_SELECTED_SHORT]); draw_c2d_text(176, 219, 0.5, 0.6, 0.6, colors[COLOR_WHITE], &text[TEXT_SELECTED_SHORT]);
if(draw_mode != DRAW_MODE_LIST)
{
C2D_DrawRectSolid(0, 24, 1.0f, 320, 240-48, C2D_Color32(0, 0, 0, 128));
}
} }

231
source/entries_list.c Normal file
View File

@@ -0,0 +1,231 @@
/*
* This file is part of Anemone3DS
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms 7.b and 7.c of GPLv3 apply to this file:
* * Requiring preservation of specified reasonable legal notices or
* author attributions in that material or in the Appropriate Legal
* Notices displayed by works containing it.
* * Prohibiting misrepresentation of the origin of that material,
* or requiring that modified versions of such material be marked in
* reasonable ways as different from the original version.
*/
#include "entries_list.h"
#include "loading.h"
#include "draw.h"
#include "fs.h"
#include "unicode.h"
void delete_entry(Entry_s * entry, bool is_file)
{
if(is_file)
FSUSER_DeleteFile(ArchiveSD, fsMakePath(PATH_UTF16, entry->path));
else
FSUSER_DeleteDirectoryRecursively(ArchiveSD, fsMakePath(PATH_UTF16, entry->path));
}
u32 load_data(const char * filename, const Entry_s * entry, char ** buf)
{
if(entry->is_zip)
{
return zip_file_to_buf(filename + 1, entry->path, buf); //the first character will always be '/' because of the other case
}
else
{
u16 path[0x106] = {0};
strucat(path, entry->path);
struacat(path, filename);
return file_to_buf(fsMakePath(PATH_UTF16, path), ArchiveSD, buf);
}
}
C2D_Image get_icon_at(Entry_List_s * list, size_t index)
{
return (C2D_Image){
.tex = &list->icons_texture,
.subtex = &list->icons_info[index].subtex,
};
}
static int compare_entries_base(const Entry_s * const a, const Entry_s * const b)
{
// entry->placeholder_color == 0 means it is not filled (no name, author, description)
// if a is filled and b is filled, return == 0
// if a is not filled and b is not filled, return == 0
// if a is not filled, return < 0
// if b is an unfilled entry, return > 0
return ((int)(a->placeholder_color != 0)) - ((int)(b->placeholder_color != 0));
}
typedef int (*sort_comparator)(const void *, const void *);
static int compare_entries_by_name(const void * a, const void * b)
{
const Entry_s * const entry_a = (const Entry_s *)a;
const Entry_s * const entry_b = (const Entry_s *)b;
const int base = compare_entries_base(entry_a, entry_b);
if(base)
return base;
return memcmp(entry_a->name, entry_b->name, 0x40 * sizeof(u16));
}
static int compare_entries_by_author(const void * a, const void * b)
{
const Entry_s * const entry_a = (const Entry_s *)a;
const Entry_s * const entry_b = (const Entry_s *)b;
const int base = compare_entries_base(entry_a, entry_b);
if(base)
return base;
return memcmp(entry_a->author, entry_b->author, 0x40 * sizeof(u16));
}
static int compare_entries_by_filename(const void * a, const void * b)
{
const Entry_s * const entry_a = (const Entry_s *)a;
const Entry_s * const entry_b = (const Entry_s *)b;
const int base = compare_entries_base(entry_a, entry_b);
if(base)
return base;
return memcmp(entry_a->path, entry_b->path, 0x106 * sizeof(u16));
}
static void sort_list(Entry_List_s * list, sort_comparator compare_entries)
{
if(list->entries != NULL && list->entries != NULL)
qsort(list->entries, list->entries_count, sizeof(Entry_s), compare_entries); //alphabet sort
}
void sort_by_name(Entry_List_s * list)
{
sort_list(list, compare_entries_by_name);
list->current_sort = SORT_NAME;
}
void sort_by_author(Entry_List_s * list)
{
sort_list(list, compare_entries_by_author);
list->current_sort = SORT_AUTHOR;
}
void sort_by_filename(Entry_List_s * list)
{
sort_list(list, compare_entries_by_filename);
list->current_sort = SORT_PATH;
}
#define LOADING_DIR_ENTRIES_COUNT 16
static FS_DirectoryEntry loading_dir_entries[LOADING_DIR_ENTRIES_COUNT];
Result load_entries(const char * loading_path, Entry_List_s * list, const InstallType loading_screen)
{
Handle dir_handle;
Result res = FSUSER_OpenDirectory(&dir_handle, ArchiveSD, fsMakePath(PATH_ASCII, loading_path));
if(R_FAILED(res))
{
DEBUG("Failed to open folder: %s\n", loading_path);
return res;
}
list_init_capacity(list, LOADING_DIR_ENTRIES_COUNT);
u32 entries_read = LOADING_DIR_ENTRIES_COUNT;
while(entries_read == LOADING_DIR_ENTRIES_COUNT)
{
res = FSDIR_Read(dir_handle, &entries_read, LOADING_DIR_ENTRIES_COUNT, loading_dir_entries);
if(R_FAILED(res))
break;
for(u32 i = 0; i < entries_read; ++i)
{
const FS_DirectoryEntry * const dir_entry = &loading_dir_entries[i];
const bool is_zip = !strcmp(dir_entry->shortExt, "ZIP");
if(!(dir_entry->attributes & FS_ATTRIBUTE_DIRECTORY) && !is_zip)
continue;
const ssize_t new_entry_index = list_add_entry(list);
if(new_entry_index < 0)
{
// out of memory: still allow use of currently loaded entries.
// Many things might die, depending on the heap layout after
entries_read = 0;
break;
}
Entry_s * const current_entry = &list->entries[new_entry_index];
memset(current_entry, 0, sizeof(Entry_s));
struacat(current_entry->path, loading_path);
strucat(current_entry->path, dir_entry->name);
current_entry->is_zip = is_zip;
}
}
FSDIR_Close(dir_handle);
list->loading_path = loading_path;
const int loading_bar_ticks = list->entries_count / 10;
for(int i = 0, j = 0; i < list->entries_count; ++i)
{
// replaces (i % loading_bar_ticks) == 0
if(++j >= loading_bar_ticks)
{
j = 0;
draw_loading_bar(i, list->entries_count, loading_screen);
}
Entry_s * const current_entry = &list->entries[i];
char * buf = NULL;
u32 buflen = load_data("/info.smdh", current_entry, &buf);
parse_smdh(buflen == sizeof(Icon_s) ? (Icon_s *)buf : NULL, current_entry, current_entry->path + strlen(loading_path));
free(buf);
}
return res;
}
void list_init_capacity(Entry_List_s * list, const int init_capacity)
{
list->entries = malloc(init_capacity * sizeof(Entry_s));
list->entries_capacity = init_capacity;
}
#define LIST_CAPACITY_THRESHOLD 512
ssize_t list_add_entry(Entry_List_s * list)
{
if(list->entries_count == list->entries_capacity)
{
int next_capacity = list->entries_capacity;
// expand by doubling until we hit LIST_CAPACITY_THRESHOLD
// then simply increment by that, to have less extra space leftover
if(next_capacity < LIST_CAPACITY_THRESHOLD)
{
next_capacity *= 2;
}
else
{
next_capacity += LIST_CAPACITY_THRESHOLD;
}
Entry_s * const new_list = realloc(list->entries, next_capacity * sizeof(Entry_s));
if(new_list == NULL)
{
return -1;
}
list->entries = new_list;
list->entries_capacity = next_capacity;
}
return list->entries_count++;
}

View File

@@ -27,7 +27,9 @@
#include <strings.h> #include <strings.h>
#include "fs.h" #include "fs.h"
#include "draw.h"
#include "unicode.h" #include "unicode.h"
#include "ui_strings.h"
#include <archive.h> #include <archive.h>
#include <archive_entry.h> #include <archive_entry.h>
@@ -63,6 +65,10 @@ Result open_archives(void)
archive1 = 0x000002ce; archive1 = 0x000002ce;
archive2 = 0x00000098; archive2 = 0x00000098;
break; break;
case 5:
archive1 = 0x000002cf;
archive2 = 0x000000a9;
break;
default: default:
archive1 = 0x00; archive1 = 0x00;
archive2 = 0x00; archive2 = 0x00;
@@ -106,7 +112,18 @@ Result close_archives(void)
return 0; return 0;
} }
u32 file_to_buf(FS_Path path, FS_Archive archive, char** buf) Result load_parental_controls(Parental_Restrictions_s *restrictions)
{
char parental_data[0xC0] = {0};
Result res;
if (R_FAILED(res = CFGU_GetConfigInfoBlk2(0xC0, 0x000C0000, &parental_data))) return res;
memcpy(restrictions, parental_data, 4);
return 0;
}
u32 file_to_buf(FS_Path path, FS_Archive archive, char ** buf)
{ {
Handle file; Handle file;
Result res = 0; Result res = 0;
@@ -123,9 +140,9 @@ u32 file_to_buf(FS_Path path, FS_Archive archive, char** buf)
return (u32)size; return (u32)size;
} }
static u32 zip_to_buf(struct archive *a, 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;
bool found = false; bool found = false;
u64 file_size = 0; u64 file_size = 0;
@@ -151,9 +168,9 @@ static u32 zip_to_buf(struct archive *a, char *file_name, char ** buf)
return (u32)file_size; return (u32)file_size;
} }
u32 zip_memory_to_buf(char *file_name, void * zip_memory, size_t zip_size, char ** buf) u32 zip_memory_to_buf(const char * file_name, void * zip_memory, size_t zip_size, char ** buf)
{ {
struct archive *a = archive_read_new(); struct archive * a = archive_read_new();
archive_read_support_format_zip(a); archive_read_support_format_zip(a);
int r = archive_read_open_memory(a, zip_memory, zip_size); int r = archive_read_open_memory(a, zip_memory, zip_size);
@@ -166,13 +183,13 @@ u32 zip_memory_to_buf(char *file_name, void * zip_memory, size_t zip_size, char
return zip_to_buf(a, file_name, buf); return zip_to_buf(a, file_name, buf);
} }
u32 zip_file_to_buf(char *file_name, 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(sizeof(char), 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();
archive_read_support_format_zip(a); archive_read_support_format_zip(a);
int r = archive_read_open_filename(a, path, 0x4000); int r = archive_read_open_filename(a, path, 0x4000);
@@ -186,7 +203,7 @@ u32 zip_file_to_buf(char *file_name, u16 *zip_path, char **buf)
return zip_to_buf(a, file_name, buf); return zip_to_buf(a, file_name, buf);
} }
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)
{ {
Handle handle; Handle handle;
Result res = 0; Result res = 0;
@@ -196,7 +213,7 @@ Result buf_to_file(u32 size, FS_Path path, FS_Archive archive, char *buf)
return 0; return 0;
} }
u32 decompress_lz_file(FS_Path file_name, FS_Archive archive, char **buf) u32 decompress_lz_file(FS_Path file_name, FS_Archive archive, char ** buf)
{ {
Handle handle; Handle handle;
Result res = 0; Result res = 0;
@@ -207,7 +224,7 @@ u32 decompress_lz_file(FS_Path file_name, FS_Archive archive, char **buf)
u64 size; u64 size;
FSFILE_GetSize(handle, &size); FSFILE_GetSize(handle, &size);
char *temp_buf = NULL; char * temp_buf = NULL;
if(size != 0) if(size != 0)
{ {
@@ -301,9 +318,9 @@ u32 decompress_lz_file(FS_Path file_name, FS_Archive archive, char **buf)
// if i figure out a dynamic programming algorithm which ends up being significantly // if i figure out a dynamic programming algorithm which ends up being significantly
// faster. Otherwise, I think this is probably a fine implementation. // faster. Otherwise, I think this is probably a fine implementation.
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)
{ {
char *output_buf = calloc(1, size * 2); char * output_buf = calloc(1, size * 2);
u32 output_size = 0; u32 output_size = 0;
u32 mask_pos = 0; u32 mask_pos = 0;
u32 bytes_processed = 0; u32 bytes_processed = 0;
@@ -347,7 +364,122 @@ void remake_file(FS_Path path, FS_Archive archive, u32 size)
FSUSER_DeleteFile(archive, path); FSUSER_DeleteFile(archive, path);
} }
FSUSER_CreateFile(archive, path, 0, size); FSUSER_CreateFile(archive, path, 0, size);
char *buf = calloc(size, 1); char * buf = calloc(size, 1);
buf_to_file(size, path, archive, buf); buf_to_file(size, path, archive, buf);
free(buf); free(buf);
} }
static SwkbdCallbackResult fat32filter(void * user, const char ** ppMessage, const char * text, size_t textlen)
{
(void)textlen;
(void)user;
*ppMessage = language.fs.illegal_input;
if(strpbrk(text, ILLEGAL_CHARS))
{
DEBUG("illegal filename: %s\n", text);
return SWKBD_CALLBACK_CONTINUE;
}
return SWKBD_CALLBACK_OK;
}
// assumes the input buffer is a ZIP. if it isn't, why are you calling this?
void save_zip_to_sd(char * filename, u32 size, char * buf, EntryMode mode)
{
static char path_to_file[32761]; // FAT32 paths can be quite long.
const int max_chars = 250;
char new_filename[max_chars + 5]; // .zip + \0
renamed:
sprintf(path_to_file, "%s%s", main_paths[mode], filename);
// filter out characters illegal in FAT32 filenames
char * curr_filename = path_to_file + strlen(main_paths[mode]);
char * illegal_char = curr_filename;
while ((illegal_char = strpbrk(illegal_char, ILLEGAL_CHARS)))
{
DEBUG("Illegal char found in filename: %c\n", *illegal_char);
if (*illegal_char == '.')
{
// skip initial . (this is allowed)
if (illegal_char == curr_filename)
continue;
// skip extension delimiter
if (strpbrk(illegal_char + 1, ".") == NULL)
{
illegal_char++;
continue;
}
}
*illegal_char = '-';
}
// ensure the extension is .zip
char * extension = strrchr(path_to_file, '.');
if (extension == NULL || strcmp(extension, ".zip"))
strcat(path_to_file, ".zip");
DEBUG("path: %s\n", path_to_file);
u16 utf16path[0x106] = {0};
utf8_to_utf16(utf16path, (u8 *) path_to_file, 0x106);
FS_Path path = fsMakePath(PATH_UTF16, utf16path);
// check if file already exists, and if it does, prompt the user
// to overwrite or change name (or exit)
Result res = FSUSER_CreateFile(ArchiveSD, path, 0, size);
if (R_FAILED(res))
{
if (res == (long)0xC82044BE)
{
DEBUG("File already exists\n");
SwkbdState swkbd;
swkbdInit(&swkbd, SWKBD_TYPE_NORMAL, 3, max_chars / 2);
swkbdSetHintText(&swkbd, language.fs.new_or_overwrite);
swkbdSetFeatures(&swkbd, SWKBD_PREDICTIVE_INPUT | SWKBD_DARKEN_TOP_SCREEN);
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, language.fs.cancel, false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_MIDDLE, language.fs.overwrite, false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, language.fs.rename, true);
swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, SWKBD_FILTER_CALLBACK, -1);
swkbdSetFilterCallback(&swkbd, &fat32filter, NULL);
SwkbdButton button = swkbdInputText(&swkbd, new_filename, max_chars);
switch (button)
{
case SWKBD_BUTTON_RIGHT:
DEBUG("Renaming to %s\n", new_filename);
strcat(new_filename, ".zip");
filename = new_filename;
goto renamed;
case SWKBD_BUTTON_MIDDLE:
// we good
DEBUG("Overwriting %s\n", filename);
break;
case SWKBD_BUTTON_LEFT:
// do nothing
DEBUG("File rename cancelled\n");
return;
case SWKBD_BUTTON_NONE:
DEBUG("SWKBD broke wtf??? :- %x\n", swkbdGetResult(&swkbd));
return throw_error(language.fs.swkbd_fail, ERROR_LEVEL_WARNING);
}
}
else if (res == (long)0xC86044D2)
{
DEBUG("SD card is full\n");
return throw_error(language.fs.sd_full, ERROR_LEVEL_WARNING);
}
else
{
DEBUG("error: %lx\n", res);
return throw_error(language.fs.fs_error, ERROR_LEVEL_ERROR);
}
}
DEBUG("Saving to SD: %s\n", path_to_file);
remake_file(path, ArchiveSD, size);
buf_to_file(size, path, ArchiveSD, buf);
}

View File

@@ -29,197 +29,53 @@
#include "unicode.h" #include "unicode.h"
#include "music.h" #include "music.h"
#include "draw.h" #include "draw.h"
#include "conversion.h"
#include "ui_strings.h"
#include <png.h> #include <png.h>
void delete_entry(Entry_s * entry, bool is_file) void copy_texture_data(C3D_Tex * texture, const u16 * src, const Entry_Icon_s * current_icon)
{ {
if(is_file) // pointer to rgb565, offset by the number of rows and columns specified by current_icon
FSUSER_DeleteFile(ArchiveSD, fsMakePath(PATH_UTF16, entry->path)); // (reminder that this is z order curve storage)
else u16 * dest = ((u16 *)texture->data) + (current_icon->y * texture->width) + (current_icon->x * 8);
FSUSER_DeleteDirectoryRecursively(ArchiveSD, fsMakePath(PATH_UTF16, entry->path));
}
u32 load_data(char * filename, Entry_s entry, char ** buf)
{
if(entry.is_zip)
{
return zip_file_to_buf(filename+1, entry.path, buf); //the first character will always be '/' because of the other case
}
else
{
u16 path[0x106] = {0};
strucat(path, entry.path);
struacat(path, filename);
return file_to_buf(fsMakePath(PATH_UTF16, path), ArchiveSD, buf);
}
}
// Function taken and adapted from https://github.com/BernardoGiordano/Checkpoint/blob/master/3ds/source/title.cpp
C2D_Image * loadTextureIcon(Icon_s *icon)
{
if(icon == NULL)
return NULL;
C2D_Image * image = calloc(1, sizeof(C2D_Image));
C3D_Tex* tex = malloc(sizeof(C3D_Tex));
static const Tex3DS_SubTexture subt3x = { 48, 48, 0.0f, 48/64.0f, 48/64.0f, 0.0f };
image->tex = tex;
image->subtex = &subt3x;
C3D_TexInit(image->tex, 64, 64, GPU_RGB565);
u16* dest = (u16*)image->tex->data + (64-48)*64;
u16* src = icon->big_icon;
for (int j = 0; j < 48; j += 8) for (int j = 0; j < 48; j += 8)
{ {
memcpy(dest, src, 48*8*sizeof(u16)); memcpy(dest, src, 48 * 8 * sizeof(u16));
src += 48*8; src += 48 * 8;
dest += 64*8; dest += texture->width * 8;
} }
GSPGPU_InvalidateDataCache(texture->data, texture->size);
return image;
} }
void parse_smdh(Icon_s *icon, Entry_s * entry, const u16 * fallback_name) void parse_smdh(Icon_s * icon, Entry_s * entry, const u16 * fallback_name)
{ {
if(icon == NULL) if(icon == NULL)
{ {
memcpy(entry->name, fallback_name, 0x80); memcpy(entry->name, fallback_name, 0x80);
utf8_to_utf16(entry->desc, (u8*)"No description", 0x100); utf8_to_utf16(entry->desc, (u8 *)"No description", 0x100);
utf8_to_utf16(entry->author, (u8*)"Unknown author", 0x80); utf8_to_utf16(entry->author, (u8 *)"Unknown author", 0x80);
entry->placeholder_color = C2D_Color32(rand() % 255, rand() % 255, rand() % 255, 255); entry->placeholder_color = C2D_Color32(rand() % 255, rand() % 255, rand() % 255, 255);
return; return;
} }
memcpy(entry->name, icon->name, 0x40 * sizeof(u16));
memcpy(entry->name, icon->name, 0x40*sizeof(u16)); memcpy(entry->desc, icon->desc, 0x80 * sizeof(u16));
memcpy(entry->desc, icon->desc, 0x80*sizeof(u16)); memcpy(entry->author, icon->author, 0x40 * sizeof(u16));
memcpy(entry->author, icon->author, 0x40*sizeof(u16)); entry->placeholder_color = 0;
} }
static C2D_Image * load_entry_icon(Entry_s entry) static Icon_s * load_entry_icon(const Entry_s * entry)
{ {
char *info_buffer = NULL; char * info_buffer = NULL;
u64 size = load_data("/info.smdh", entry, &info_buffer); u32 size = load_data("/info.smdh", entry, &info_buffer);
if(!size) return NULL; if(size != sizeof(Icon_s))
Icon_s * smdh = (Icon_s *)info_buffer;
C2D_Image* out = loadTextureIcon(smdh);
free(info_buffer);
return out;
}
typedef int (*sort_comparator)(const void *, const void *);
static int compare_entries_by_name(const void * a, const void * b)
{
Entry_s *entry_a = (Entry_s *)a;
Entry_s *entry_b = (Entry_s *)b;
return memcmp(entry_a->name, entry_b->name, 0x40*sizeof(u16));
}
static int compare_entries_by_author(const void * a, const void * b)
{
Entry_s *entry_a = (Entry_s *)a;
Entry_s *entry_b = (Entry_s *)b;
return memcmp(entry_a->author, entry_b->author, 0x40*sizeof(u16));
}
static int compare_entries_by_filename(const void * a, const void * b)
{
Entry_s *entry_a = (Entry_s *)a;
Entry_s *entry_b = (Entry_s *)b;
return memcmp(entry_a->path, entry_b->path, 0x106*sizeof(u16));
}
static void sort_list(Entry_List_s * list, sort_comparator compare_entries)
{
if(list->entries != NULL && list->entries != NULL)
qsort(list->entries, list->entries_count, sizeof(Entry_s), compare_entries); //alphabet sort
}
void sort_by_name(Entry_List_s * list)
{
sort_list(list, compare_entries_by_name);
list->current_sort = SORT_NAME;
}
void sort_by_author(Entry_List_s * list)
{
sort_list(list, compare_entries_by_author);
list->current_sort = SORT_AUTHOR;
}
void sort_by_filename(Entry_List_s * list)
{
sort_list(list, compare_entries_by_filename);
list->current_sort = SORT_PATH;
}
Result load_entries(const char * loading_path, Entry_List_s * list)
{
Handle dir_handle;
Result res = FSUSER_OpenDirectory(&dir_handle, ArchiveSD, fsMakePath(PATH_ASCII, loading_path));
if(R_FAILED(res))
{ {
DEBUG("Failed to open folder: %s\n", loading_path); free(info_buffer);
return res; return NULL;
} }
u32 entries_read = 1; return (Icon_s *)info_buffer;
while(entries_read)
{
FS_DirectoryEntry dir_entry = {0};
res = FSDIR_Read(dir_handle, &entries_read, 1, &dir_entry);
if(R_FAILED(res) || entries_read == 0)
break;
if(!(dir_entry.attributes & FS_ATTRIBUTE_DIRECTORY) && strcmp(dir_entry.shortExt, "ZIP"))
continue;
u16 path[0x106] = {0};
struacat(path, loading_path);
strucat(path, dir_entry.name);
char * buf = NULL;
if (!strcmp(dir_entry.shortExt, "ZIP"))
{
zip_file_to_buf("info.smdh", path, &buf);
}
else
{
const ssize_t len = strulen(path, 0x106);
struacat(path, "/info.smdh");
file_to_buf(fsMakePath(PATH_UTF16, path), ArchiveSD, &buf);
memset(&path[len], 0, (0x106 - len) * sizeof(u16));
}
list->entries_count++;
Entry_s * new_list = realloc(list->entries, list->entries_count * sizeof(Entry_s));
if(new_list == NULL)
{
// out of memory: still allow use of currently loaded entries.
// Many things might die, depending on the heap layout after
list->entries_count--;
free(buf);
break;
}
else
list->entries = new_list;
Entry_s * current_entry = &(list->entries[list->entries_count-1]);
memset(current_entry, 0, sizeof(Entry_s));
parse_smdh((Icon_s *)buf, current_entry, dir_entry.name);
free(buf);
memcpy(current_entry->path, path, 0x106 * sizeof(u16));
current_entry->is_zip = !strcmp(dir_entry.shortExt, "ZIP");
}
FSDIR_Close(dir_handle);
return res;
} }
void load_icons_first(Entry_List_s * list, bool silent) void load_icons_first(Entry_List_s * list, bool silent)
@@ -231,7 +87,7 @@ void load_icons_first(Entry_List_s * list, bool silent)
int starti = 0, endi = 0; int starti = 0, endi = 0;
if(list->entries_count <= list->entries_loaded*ICONS_OFFSET_AMOUNT) if(list->entries_count <= list->entries_loaded * ICONS_OFFSET_AMOUNT)
{ {
DEBUG("small load\n"); DEBUG("small load\n");
// if the list is one that doesnt need swapping, load everything at once // if the list is one that doesnt need swapping, load everything at once
@@ -241,40 +97,44 @@ void load_icons_first(Entry_List_s * list, bool silent)
{ {
DEBUG("extended load\n"); DEBUG("extended load\n");
// otherwise, load around to prepare for swapping // otherwise, load around to prepare for swapping
starti = list->scroll - list->entries_loaded*ICONS_VISIBLE; starti = list->scroll - list->entries_loaded * ICONS_VISIBLE;
endi = starti + list->entries_loaded*ICONS_OFFSET_AMOUNT; endi = starti + list->entries_loaded * ICONS_OFFSET_AMOUNT;
} }
list->icons = calloc(endi-starti, sizeof(C2D_Image*)); for(int entry_i = starti, icon_i = 0; entry_i < endi; ++entry_i, ++icon_i)
C2D_Image ** icons = list->icons;
for(int i = starti; i < endi; i++)
{ {
if(!silent) if(!silent)
draw_loading_bar(i - starti, endi-starti, INSTALL_LOADING_ICONS); draw_loading_bar(icon_i, endi-starti, INSTALL_LOADING_ICONS);
int offset = i; int offset = entry_i;
if(offset < 0) if(offset < 0)
offset += list->entries_count; offset += list->entries_count;
if(offset >= list->entries_count) if(offset >= list->entries_count)
offset -= list->entries_count; offset -= list->entries_count;
Entry_s current_entry = list->entries[offset]; Entry_s * const current_entry = &list->entries[offset];
icons[i-starti] = load_entry_icon(current_entry); Icon_s * const smdh = load_entry_icon(current_entry);
if(smdh != NULL)
{
if(current_entry->placeholder_color == 0)
parse_smdh(smdh, current_entry, current_entry->path + strlen(list->loading_path));
copy_texture_data(&list->icons_texture, smdh->big_icon, &list->icons_info[icon_i]);
free(smdh);
}
} }
} }
static void reverse(C2D_Image * a[], int sz) { static void reverse(Entry_Icon_s a[], int sz) {
int i, j; int i, j;
Entry_Icon_s tmp;
for (i = 0, j = sz; i < j; i++, j--) { for (i = 0, j = sz; i < j; i++, j--) {
C2D_Image * tmp = a[i]; memcpy(&tmp, &a[i], sizeof(Entry_Icon_s));
a[i] = a[j]; memcpy(&a[i], &a[j], sizeof(Entry_Icon_s));
a[j] = tmp; memcpy(&a[j], &tmp, sizeof(Entry_Icon_s));
} }
} }
static void rotate(C2D_Image * array[], int size, int amt) { static void rotate(Entry_Icon_s array[], int size, int amt) {
if (amt < 0) if (amt < 0)
amt = size + amt; amt = size + amt;
reverse(array, size-amt-1); reverse(array, size-amt-1);
@@ -292,11 +152,11 @@ void handle_scrolling(Entry_List_s * list)
{ {
int change = 0; int change = 0;
if(list->entries_count > list->entries_loaded*2 && list->previous_scroll < list->entries_loaded && list->selected_entry >= list->entries_count - list->entries_loaded) if(list->entries_count > list->entries_loaded * 2 && list->previous_scroll < list->entries_loaded && list->selected_entry >= list->entries_count - list->entries_loaded)
{ {
list->scroll = list->entries_count - list->entries_loaded; list->scroll = list->entries_count - list->entries_loaded;
} }
else if(list->entries_count > list->entries_loaded*2 && list->selected_entry < list->entries_loaded && list->previous_selected >= list->entries_count - list->entries_loaded) else if(list->entries_count > list->entries_loaded * 2 && list->selected_entry < list->entries_loaded && list->previous_selected >= list->entries_count - list->entries_loaded)
{ {
list->scroll = 0; list->scroll = 0;
} }
@@ -340,13 +200,13 @@ static bool load_icons(Entry_List_s * current_list, Handle mutex)
handle_scrolling(current_list); handle_scrolling(current_list);
if(current_list->entries_count <= current_list->entries_loaded*ICONS_OFFSET_AMOUNT || current_list->previous_scroll == current_list->scroll) if(current_list->entries_count <= current_list->entries_loaded * ICONS_OFFSET_AMOUNT || current_list->previous_scroll == current_list->scroll)
return false; // return if the list is one that doesnt need swapping, or if nothing changed return false; // return if the list is one that doesnt need swapping, or if nothing changed
#define SIGN(x) (x > 0 ? 1 : ((x < 0) ? -1 : 0)) #define SIGN(x) (x > 0 ? 1 : ((x < 0) ? -1 : 0))
int delta = current_list->scroll - current_list->previous_scroll; int delta = current_list->scroll - current_list->previous_scroll;
if(abs(delta) >= current_list->entries_count - current_list->entries_loaded*(ICONS_OFFSET_AMOUNT-1)) if(abs(delta) >= current_list->entries_count - current_list->entries_loaded * (ICONS_OFFSET_AMOUNT-1))
delta = -SIGN(delta) * (current_list->entries_count - abs(delta)); delta = -SIGN(delta) * (current_list->entries_count - abs(delta));
int starti = current_list->scroll; int starti = current_list->scroll;
@@ -359,28 +219,28 @@ static bool load_icons(Entry_List_s * current_list, Handle mutex)
} }
int ctr = 0; int ctr = 0;
Entry_s ** entries = calloc(abs(delta), sizeof(Entry_s *)); Entry_s ** const entries = calloc(abs(delta), sizeof(Entry_s *));
int * indexes = calloc(abs(delta), sizeof(int)); int * const indexes = calloc(abs(delta), sizeof(int));
bool released = false; bool released = false;
C2D_Image ** icons = current_list->icons; Entry_Icon_s * const icons = current_list->icons_info;
for(int i = starti; i != endi; i++, ctr++) for(int i = starti; i != endi; i++, ctr++)
{ {
int index = 0; int index = 0;
int offset = i; int offset = i;
rotate(icons, ICONS_OFFSET_AMOUNT*current_list->entries_loaded, -1*SIGN(delta)); rotate(icons, ICONS_OFFSET_AMOUNT * current_list->entries_loaded, -SIGN(delta));
if(delta > 0) if(delta > 0)
{ {
index = current_list->entries_loaded*ICONS_OFFSET_AMOUNT - delta + i - starti; index = current_list->entries_loaded * ICONS_OFFSET_AMOUNT - delta + i - starti;
offset += current_list->entries_loaded*ICONS_UNDER - delta; offset += current_list->entries_loaded * ICONS_UNDER - delta;
} }
else else
{ {
index = 0 - delta - 1 + i - starti; index = 0 - delta - 1 + i - starti;
offset -= current_list->entries_loaded*ICONS_VISIBLE; offset -= current_list->entries_loaded * ICONS_VISIBLE;
i -= 2; //i-- twice to counter the i++, needed only for this case i -= 2; //i-- twice to counter the i++, needed only for this case
} }
@@ -395,7 +255,7 @@ static bool load_icons(Entry_List_s * current_list, Handle mutex)
#undef SIGN #undef SIGN
if(abs(delta) < 4) if(abs(delta) <= current_list->entries_loaded)
{ {
svcReleaseMutex(mutex); svcReleaseMutex(mutex);
released = true; released = true;
@@ -406,19 +266,19 @@ static bool load_icons(Entry_List_s * current_list, Handle mutex)
endi = abs(delta); endi = abs(delta);
for(int i = starti; i < endi; i++) for(int i = starti; i < endi; i++)
{ {
Entry_s * current_entry = entries[i]; Entry_s * const current_entry = entries[i];
int index = indexes[i]; const int index = indexes[i];
const Entry_Icon_s * const current_icon = &icons[index];
C2D_Image * image = icons[index]; Icon_s * const smdh = load_entry_icon(current_entry);
if (icons[index] != NULL) if(smdh != NULL)
{ {
C3D_TexDelete(image->tex); if(current_entry->placeholder_color == 0)
free(image->tex); parse_smdh(smdh, current_entry, current_entry->path + strlen(current_list->loading_path));
free(image); copy_texture_data(&current_list->icons_texture, smdh->big_icon, current_icon);
free(smdh);
} }
icons[index] = load_entry_icon(*current_entry);
if(!released && i > endi/2) if(!released && i > endi/2)
{ {
svcReleaseMutex(mutex); svcReleaseMutex(mutex);
@@ -438,92 +298,22 @@ void load_icons_thread(void * void_arg)
{ {
Thread_Arg_s * arg = (Thread_Arg_s *)void_arg; Thread_Arg_s * arg = (Thread_Arg_s *)void_arg;
Handle mutex = *(Handle *)arg->thread_arg[1]; Handle mutex = *(Handle *)arg->thread_arg[1];
do do {
{
svcWaitSynchronization(mutex, U64_MAX); svcWaitSynchronization(mutex, U64_MAX);
volatile Entry_List_s * current_list = *(volatile Entry_List_s **)arg->thread_arg[0]; Entry_List_s * const current_list = *(Entry_List_s ** volatile)arg->thread_arg[0];
bool released = load_icons((Entry_List_s *)current_list, mutex); const bool released = load_icons(current_list, mutex);
if(!released) if(!released)
svcReleaseMutex(mutex); svcReleaseMutex(mutex);
} } while(arg->run_thread);
while(arg->run_thread);
} }
bool load_preview_from_buffer(void * buf, u32 size, C2D_Image * preview_image, int * preview_offset) bool load_preview_from_buffer(char * row_pointers, u32 size, C2D_Image * preview_image, int * preview_offset, int height)
{ {
if(size < 8 || png_sig_cmp(buf, 0, 8)) int width = (uint32_t)((size / 4) / height);
{
throw_error("Invalid preview.png", ERROR_LEVEL_WARNING);
return false;
}
png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
png_infop info = png_create_info_struct(png);
if(setjmp(png_jmpbuf(png)))
{
png_destroy_read_struct(&png, &info, NULL);
return false;
}
FILE * fp = fmemopen(buf, size, "rb");
png_init_io(png, fp);
png_read_info(png, info);
int width = png_get_image_width(png, info);
int 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);
// Read any color_type into 8bit depth, ABGR format.
// See http://www.libpng.org/pub/png/libpng-manual.txt
if(bit_depth == 16)
png_set_strip_16(png);
if(color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png);
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
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);
// These color_type don't have an alpha channel then fill it with 0xff.
if(color_type == PNG_COLOR_TYPE_RGB ||
color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_PALETTE)
png_set_add_alpha(png, 0xFF, PNG_FILLER_BEFORE);
if(color_type == PNG_COLOR_TYPE_GRAY ||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png);
//output ABGR
png_set_bgr(png);
png_set_swap_alpha(png);
png_read_update_info(png, info);
png_bytep * row_pointers = malloc(sizeof(png_bytep) * height);
for(int y = 0; y < height; y++) {
row_pointers[y] = (png_byte*)malloc(png_get_rowbytes(png,info));
}
png_read_image(png, row_pointers);
fclose(fp);
png_destroy_read_struct(&png, &info, NULL);
free_preview(*preview_image); free_preview(*preview_image);
C3D_Tex* tex = malloc(sizeof(C3D_Tex)); C3D_Tex * tex = malloc(sizeof(C3D_Tex));
preview_image->tex = tex; preview_image->tex = tex;
Tex3DS_SubTexture * subt3x = malloc(sizeof(Tex3DS_SubTexture)); Tex3DS_SubTexture * subt3x = malloc(sizeof(Tex3DS_SubTexture));
@@ -540,50 +330,101 @@ bool load_preview_from_buffer(void * buf, u32 size, C2D_Image * preview_image, i
memset(preview_image->tex->data, 0, preview_image->tex->size); memset(preview_image->tex->data, 0, preview_image->tex->size);
for(int j = 0; j < height; j++) { for(int j = 0; j < height; j++) {
png_bytep row = row_pointers[j]; png_bytep row = (png_bytep)(row_pointers + (width * 4 * j));
for(int i = 0; i < width; i++) { for(int i = 0; i < width; i++) {
png_bytep px = &(row[i * 4]); png_bytep px = &(row[i * 4]);
u32 dst = ((((j >> 3) * (512 >> 3) + (i >> 3)) << 6) + ((i & 1) | ((j & 1) << 1) | ((i & 2) << 1) | ((j & 2) << 2) | ((i & 4) << 2) | ((j & 4) << 3))) * 4; u32 dst = ((((j >> 3) * (512 >> 3) + (i >> 3)) << 6) + ((i & 1) | ((j & 1) << 1) | ((i & 2) << 1) | ((j & 2) << 2) | ((i & 4) << 2) | ((j & 4) << 3))) * 4;
memcpy(preview_image->tex->data + dst, px, sizeof(u32)); memcpy(preview_image->tex->data + dst, px, sizeof(u32));
} }
free(row_pointers[j]);
} }
free(row_pointers); *preview_offset = (width - TOP_SCREEN_WIDTH) / 2;
*preview_offset = (width-400)/2;
return true; return true;
} }
static u16 previous_path_preview[0x106] = {0}; static u16 previous_path_preview[0x106] = {0};
bool load_preview(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)
{ {
if(list.entries == NULL) return false; if(list->entries == NULL) return false;
Entry_s entry = list.entries[list.selected_entry]; const Entry_s * entry = &list->entries[list->selected_entry];
if(!memcmp(&previous_path_preview, &entry.path, 0x106*sizeof(u16))) return true; if(!memcmp(&previous_path_preview, &entry->path, 0x106 * sizeof(u16))) return true;
char *preview_buffer = NULL; char * preview_buffer = NULL;
u64 size = load_data("/preview.png", entry, &preview_buffer); u32 size = load_data("/preview.png", entry, &preview_buffer);
u32 height = 480;
if(!size) if(size)
{
if (!(size = png_to_abgr(&preview_buffer, size, &height)))
{
return false;
}
}
else
{ {
free(preview_buffer); free(preview_buffer);
throw_error("No preview found.", ERROR_LEVEL_WARNING);
return false; const int top_size = TOP_SCREEN_WIDTH * SCREEN_HEIGHT * SCREEN_COLOR_DEPTH;
const int out_size = top_size * 2;
char * rgba_buffer = malloc(out_size);
memset(rgba_buffer, 0, out_size);
bool found_splash = false;
// try to assembly a preview from the splash screens
size = load_data("/splash.bin", entry, &preview_buffer);
if (size)
{
found_splash = true;
bin_to_abgr(&preview_buffer, size);
memcpy(rgba_buffer, preview_buffer, top_size);
free(preview_buffer);
}
size = load_data("/splashbottom.bin", entry, &preview_buffer);
if (size)
{
found_splash = true;
bin_to_abgr(&preview_buffer, size);
const int buffer_width = TOP_SCREEN_WIDTH * SCREEN_COLOR_DEPTH;
const int bottom_buffer_width = BOTTOM_SCREEN_WIDTH * SCREEN_COLOR_DEPTH;
const int bottom_centered_offset = (TOP_SCREEN_WIDTH - BOTTOM_SCREEN_WIDTH) / 2;
// Store the bottom splash screen under the top splash and centered
for (int i = 0; i < SCREEN_HEIGHT; ++i)
memcpy(
rgba_buffer + top_size + (buffer_width * i) + (bottom_centered_offset * SCREEN_COLOR_DEPTH),
preview_buffer + (bottom_buffer_width * i),
bottom_buffer_width
);
free(preview_buffer);
}
if (!found_splash)
{
free(rgba_buffer);
throw_error(language.loading.no_preview, ERROR_LEVEL_WARNING);
return false;
}
size = out_size;
preview_buffer = rgba_buffer;
} }
bool ret = load_preview_from_buffer(preview_buffer, size, preview_image, preview_offset); bool ret = load_preview_from_buffer(preview_buffer, size, preview_image, preview_offset, height);
free(preview_buffer); free(preview_buffer);
if(ret) if(ret)
{ {
// mark the new preview as loaded for optimisation // mark the new preview as loaded for optimisation
memcpy(&previous_path_preview, &entry.path, 0x106*sizeof(u16)); memcpy(&previous_path_preview, &entry->path, 0x106 * sizeof(u16));
} }
return ret; return ret;
@@ -594,11 +435,11 @@ void free_preview(C2D_Image preview)
if(preview.tex) if(preview.tex)
C3D_TexDelete(preview.tex); C3D_TexDelete(preview.tex);
free(preview.tex); free(preview.tex);
free((Tex3DS_SubTexture*)preview.subtex); free((Tex3DS_SubTexture *)preview.subtex);
} }
// Initialize the audio struct // Initialize the audio struct
Result load_audio(Entry_s entry, audio_s *audio) Result load_audio(const Entry_s * entry, audio_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) {
@@ -608,12 +449,11 @@ Result load_audio(Entry_s entry, audio_s *audio)
} }
audio->mix[0] = audio->mix[1] = 1.0f; // Determines volume for the 12 (?) different outputs. See http://smealum.github.io/ctrulib/channel_8h.html#a30eb26f1972cc3ec28370263796c0444 audio->mix[0] = audio->mix[1] = 1.0f; // Determines volume for the 12 (?) different outputs. See http://smealum.github.io/ctrulib/channel_8h.html#a30eb26f1972cc3ec28370263796c0444
svcCreateEvent(&audio->finished, RESET_STICKY);
ndspChnSetInterp(0, NDSP_INTERP_LINEAR); ndspChnSetInterp(0, NDSP_INTERP_LINEAR);
ndspChnSetMix(0, audio->mix); // See mix comment above ndspChnSetMix(0, audio->mix); // See mix comment above
FILE *file = fmemopen(audio->filebuf, audio->filesize, "rb"); FILE * file = fmemopen(audio->filebuf, audio->filesize, "rb");
DEBUG("<load_audio> Filesize: %ld\n", audio->filesize); DEBUG("<load_audio> Filesize: %ld\n", audio->filesize);
if(file != NULL) if(file != NULL)
{ {
@@ -627,7 +467,7 @@ Result load_audio(Entry_s entry, audio_s *audio)
return MAKERESULT(RL_FATAL, RS_INVALIDARG, RM_APPLICATION, RD_NO_DATA); return MAKERESULT(RL_FATAL, RS_INVALIDARG, RM_APPLICATION, RD_NO_DATA);
} }
vorbis_info *vi = ov_info(&audio->vf, -1); vorbis_info * vi = ov_info(&audio->vf, -1);
ndspChnSetRate(0, vi->rate);// Set sample rate to what's read from the ogg file ndspChnSetRate(0, vi->rate);// Set sample rate to what's read from the ogg file
if (vi->channels == 2) { if (vi->channels == 2) {
DEBUG("<load_audio> Using stereo\n"); DEBUG("<load_audio> Using stereo\n");

View File

@@ -1,6 +1,6 @@
/* /*
* This file is part of Anemone3DS * This file is part of Anemone3DS
* Copyright (C) 2016-2020 Contributors in CONTRIBUTORS.md * Copyright (C) 2015-2020 Contributors in CONTRIBUTORS.md
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@@ -32,25 +32,29 @@
#include "camera.h" #include "camera.h"
#include "music.h" #include "music.h"
#include "remote.h" #include "remote.h"
#include "instructions.h" #include "ui_strings.h"
#include <time.h> #include <time.h>
bool quit = false; bool quit = false;
bool dspfirm = false; bool dspfirm = false;
audio_s * audio = NULL; static audio_s * audio = NULL;
static bool homebrew = false; static bool homebrew = false;
static bool installed_themes = false; static bool installed_themes = false;
bool home_displayed = false;
u64 time_home_pressed = 0;
static Thread iconLoadingThread = {0}; static Thread iconLoadingThread = {0};
static Thread_Arg_s iconLoadingThread_arg = {0}; static Thread_Arg_s iconLoadingThread_arg = {0};
static Handle update_icons_mutex; static Handle update_icons_mutex;
static bool released = false; static bool released = false;
static Thread installCheckThreads[MODE_AMOUNT] = {0}; static Thread install_check_threads[MODE_AMOUNT] = {0};
static Thread_Arg_s installCheckThreads_arg[MODE_AMOUNT] = {0}; static Thread_Arg_s install_check_threads_arg[MODE_AMOUNT] = {0};
static Entry_List_s lists[MODE_AMOUNT] = {0}; static Entry_List_s lists[MODE_AMOUNT] = {0};
Language_s language = {0};
int __stacksize__ = 64 * 1024; int __stacksize__ = 64 * 1024;
Result archive_result; Result archive_result;
u32 old_time_limit; u32 old_time_limit;
@@ -81,7 +85,6 @@ static void init_services(void)
dspfirm = !ndspInit(); dspfirm = !ndspInit();
APT_GetAppCpuTimeLimit(&old_time_limit); APT_GetAppCpuTimeLimit(&old_time_limit);
APT_SetAppCpuTimeLimit(30); APT_SetAppCpuTimeLimit(30);
// aptSetHomeAllowed(false);
httpcInit(0); httpcInit(0);
archive_result = open_archives(); archive_result = open_archives();
if(envIsHomebrew()) if(envIsHomebrew())
@@ -107,7 +110,16 @@ static void stop_install_check(void)
{ {
for(int i = 0; i < MODE_AMOUNT; i++) for(int i = 0; i < MODE_AMOUNT; i++)
{ {
installCheckThreads_arg[i].run_thread = false; install_check_threads_arg[i].run_thread = false;
}
for(int i = 0; i < MODE_AMOUNT; i++)
{
if(install_check_threads[i] == NULL)
continue;
threadJoin(install_check_threads[i], U64_MAX);
threadFree(install_check_threads[i]);
install_check_threads[i] = NULL;
} }
} }
@@ -121,32 +133,18 @@ static void exit_thread(void)
svcWaitSynchronization(update_icons_mutex, U64_MAX); svcWaitSynchronization(update_icons_mutex, U64_MAX);
threadJoin(iconLoadingThread, U64_MAX); threadJoin(iconLoadingThread, U64_MAX);
threadFree(iconLoadingThread); threadFree(iconLoadingThread);
iconLoadingThread = NULL;
} }
} }
static void free_icons(Entry_List_s * list)
{
int amount = list->entries_count;
if(list->entries_count > list->entries_loaded*ICONS_OFFSET_AMOUNT)
amount = list->entries_loaded*ICONS_OFFSET_AMOUNT;
for(int i = 0; i < amount; i++)
{
if (list->icons[i] == NULL) continue;
C3D_TexDelete(list->icons[i]->tex);
free(list->icons[i]->tex);
free(list->icons[i]);
}
free(list->icons);
}
void free_lists(void) void free_lists(void)
{ {
stop_install_check(); stop_install_check();
for(int i = 0; i < MODE_AMOUNT; i++) for(int i = 0; i < MODE_AMOUNT; i++)
{ {
Entry_List_s * current_list = &lists[i]; Entry_List_s * const current_list = &lists[i];
free_icons(current_list); C3D_TexDelete(&current_list->icons_texture);
free(current_list->icons_info);
free(current_list->entries); free(current_list->entries);
memset(current_list, 0, sizeof(Entry_List_s)); memset(current_list, 0, sizeof(Entry_List_s));
} }
@@ -157,8 +155,7 @@ void exit_function(bool power_pressed)
{ {
if(audio) if(audio)
{ {
audio->stop = true; stop_audio(&audio);
svcWaitSynchronization(audio->finished, U64_MAX);
} }
free_lists(); free_lists();
svcCloseHandle(update_icons_mutex); svcCloseHandle(update_icons_mutex);
@@ -187,6 +184,18 @@ static void start_thread(void)
} }
} }
static u32 next_or_equal_power_of_2(u32 v)
{
v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;
return v;
}
static void load_lists(Entry_List_s * lists) static void load_lists(Entry_List_s * lists)
{ {
free_lists(); free_lists();
@@ -200,37 +209,69 @@ static void load_lists(Entry_List_s * lists)
draw_install(loading_screen); draw_install(loading_screen);
Entry_List_s * current_list = &lists[i]; Entry_List_s * const current_list = &lists[i];
current_list->mode = i; current_list->mode = i;
current_list->entries_per_screen_v = entries_per_screen_v[i]; current_list->entries_per_screen_v = entries_per_screen_v[i];
current_list->entries_per_screen_h = 1; current_list->entries_per_screen_h = 1;
current_list->entries_loaded = current_list->entries_per_screen_v * current_list->entries_per_screen_h; current_list->entries_loaded = current_list->entries_per_screen_v * current_list->entries_per_screen_h;
current_list->entry_size = entry_size[i]; current_list->entry_size = entry_size[i];
Result res = load_entries(main_paths[i], current_list);
const int x_component = max(current_list->entries_per_screen_h, current_list->entries_per_screen_v);
const int y_component = min(current_list->entries_per_screen_h, current_list->entries_per_screen_v);
// A texture must have power of 2 dimensions (not necessarily the same)
// so, get the power of two greater than or equal to:
// - the size of the largest length (row or column) of icons for the width
// - the size of all of those lengths to fit the total for the height
C3D_TexInit(&current_list->icons_texture,
next_or_equal_power_of_2(x_component * current_list->entry_size),
next_or_equal_power_of_2(y_component * current_list->entry_size * ICONS_OFFSET_AMOUNT),
GPU_RGB565);
C3D_TexSetFilter(&current_list->icons_texture, GPU_NEAREST, GPU_NEAREST);
const float inv_width = 1.0f / current_list->icons_texture.width;
const float inv_height = 1.0f / current_list->icons_texture.height;
current_list->icons_info = (Entry_Icon_s *)calloc(x_component * y_component * ICONS_OFFSET_AMOUNT, sizeof(Entry_Icon_s));
for(int j = 0; j < y_component * ICONS_OFFSET_AMOUNT; ++j)
{
const int index = j * x_component;
for(int h = 0; h < x_component; ++h)
{
Entry_Icon_s * const icon_info = &current_list->icons_info[index + h];
icon_info->x = h * current_list->entry_size;
icon_info->y = j * current_list->entry_size;
icon_info->subtex.width = current_list->entry_size;
icon_info->subtex.height = current_list->entry_size;
icon_info->subtex.left = icon_info->x * inv_width;
icon_info->subtex.top = 1.0f - (icon_info->y * inv_height);
icon_info->subtex.right = icon_info->subtex.left + (icon_info->subtex.width * inv_width);
icon_info->subtex.bottom = icon_info->subtex.top - (icon_info->subtex.height * inv_height);
}
}
Result res = load_entries(main_paths[i], current_list, loading_screen);
if(R_SUCCEEDED(res)) if(R_SUCCEEDED(res))
{ {
if(current_list->entries_count > current_list->entries_loaded*ICONS_OFFSET_AMOUNT) if(current_list->entries_count > current_list->entries_loaded * ICONS_OFFSET_AMOUNT)
iconLoadingThread_arg.run_thread = true; iconLoadingThread_arg.run_thread = true;
sort_by_name(current_list);
DEBUG("total: %i\n", current_list->entries_count); DEBUG("total: %i\n", current_list->entries_count);
sort_by_name(current_list);
load_icons_first(current_list, false); load_icons_first(current_list, false);
void (*install_check_function)(void*) = NULL; void (*install_check_function)(void *) = NULL;
if(i == MODE_THEMES) if(i == MODE_THEMES)
install_check_function = themes_check_installed; install_check_function = themes_check_installed;
else if(i == MODE_SPLASHES) else if(i == MODE_SPLASHES)
install_check_function = splash_check_installed; install_check_function = splash_check_installed;
Thread_Arg_s * current_arg = &installCheckThreads_arg[i]; Thread_Arg_s * current_arg = &install_check_threads_arg[i];
current_arg->run_thread = true; current_arg->run_thread = true;
current_arg->thread_arg = (void**)current_list; current_arg->thread_arg = (void **)current_list;
if(install_check_function != NULL) if(install_check_function != NULL)
{ {
installCheckThreads[i] = threadCreate(install_check_function, current_arg, __stacksize__, 0x3f, -2, true); install_check_threads[i] = threadCreate(install_check_function, current_arg, __stacksize__, 0x3f, -2, false);
svcSleepThread(1e8); svcSleepThread(1e8);
} }
} }
@@ -238,18 +279,18 @@ static void load_lists(Entry_List_s * lists)
start_thread(); start_thread();
} }
static SwkbdCallbackResult jump_menu_callback(void* entries_count, const char** ppMessage, const char* text, size_t textlen) static SwkbdCallbackResult jump_menu_callback(void * entries_count, const char ** ppMessage, const char * text, size_t textlen)
{ {
(void)textlen; (void)textlen;
int typed_value = atoi(text); int typed_value = atoi(text);
if(typed_value > *(int*)entries_count) if(typed_value > *(int *)entries_count)
{ {
*ppMessage = "The new position has to be\nsmaller or equal to the\nnumber of entries!"; *ppMessage = language.main.position_too_big;
return SWKBD_CALLBACK_CONTINUE; return SWKBD_CALLBACK_CONTINUE;
} }
else if(typed_value == 0) else if(typed_value == 0)
{ {
*ppMessage = "The new position has to\nbe positive!"; *ppMessage = language.main.position_zero;
return SWKBD_CALLBACK_CONTINUE; return SWKBD_CALLBACK_CONTINUE;
} }
return SWKBD_CALLBACK_OK; return SWKBD_CALLBACK_OK;
@@ -270,11 +311,11 @@ static void jump_menu(Entry_List_s * list)
sprintf(numbuf, "%i", list->selected_entry); sprintf(numbuf, "%i", list->selected_entry);
swkbdSetInitialText(&swkbd, numbuf); swkbdSetInitialText(&swkbd, numbuf);
sprintf(numbuf, "Where do you want to jump to?\nMay cause icons to reload."); sprintf(numbuf, language.main.jump_q);
swkbdSetHintText(&swkbd, numbuf); swkbdSetHintText(&swkbd, numbuf);
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false); swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, language.main.cancel, false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Jump", true); swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, language.main.jump, true);
swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, 0, max_chars); swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, 0, max_chars);
swkbdSetFilterCallback(&swkbd, jump_menu_callback, &list->entries_count); swkbdSetFilterCallback(&swkbd, jump_menu_callback, &list->entries_count);
@@ -286,6 +327,8 @@ static void jump_menu(Entry_List_s * list)
list->scroll = list->selected_entry; list->scroll = list->selected_entry;
if(list->scroll >= list->entries_count - list->entries_loaded) if(list->scroll >= list->entries_count - list->entries_loaded)
list->scroll = list->entries_count - list->entries_loaded - 1; list->scroll = list->entries_count - list->entries_loaded - 1;
if (list->scroll < 0)
list->scroll = 0;
} }
} }
@@ -336,6 +379,9 @@ int main(void)
{ {
srand(time(NULL)); srand(time(NULL));
init_services(); init_services();
CFG_Language lang;
CFGU_GetSystemLanguage(&lang);
language = init_strings(lang);
init_screens(); init_screens();
svcCreateMutex(&update_icons_mutex, true); svcCreateMutex(&update_icons_mutex, true);
@@ -361,7 +407,9 @@ int main(void)
int preview_offset = 0; int preview_offset = 0;
bool install_mode = false; bool install_mode = false;
DrawMode draw_mode = DRAW_MODE_LIST;
bool extra_mode = false; bool extra_mode = false;
int extra_index = 1;
C2D_Image preview = {0}; C2D_Image preview = {0};
while(aptMainLoop()) while(aptMainLoop())
@@ -373,10 +421,16 @@ int main(void)
return 0; return 0;
} }
if (aptCheckHomePressRejected() && !home_displayed)
{
time_home_pressed = svcGetSystemTick() / CPU_TICKS_PER_MSEC;
home_displayed = true;
}
#ifndef CITRA_MODE #ifndef CITRA_MODE
if(R_FAILED(archive_result) && current_mode == MODE_THEMES) if(R_FAILED(archive_result) && current_mode == MODE_THEMES)
{ {
throw_error("Theme extdata does not exist!\nSet a default theme from the home menu.", ERROR_LEVEL_ERROR); throw_error(language.main.no_theme_extdata, ERROR_LEVEL_ERROR);
quit = true; quit = true;
continue; continue;
} }
@@ -389,22 +443,12 @@ int main(void)
current_list = &lists[current_mode]; current_list = &lists[current_mode];
Instructions_s instructions = normal_instructions[current_mode]; Instructions_s instructions = language.normal_instructions[current_mode];
if(install_mode) if(install_mode)
instructions = install_instructions; instructions = language.install_instructions;
if(extra_mode) if(extra_mode)
{ {
int index = 1; instructions = language.extra_instructions[extra_index];
bool key_l = (kDown | kHeld) & KEY_L;
bool key_r = (kDown | kHeld) & KEY_R;
if(key_l ^ key_r)
{
if(key_l)
index = 0;
// else if(key_r) // uncomment when we use the right menu. we don't for now
// index = 2;
}
instructions = extra_instructions[index];
} }
if(preview_mode) if(preview_mode)
@@ -427,17 +471,58 @@ int main(void)
svcWaitSynchronization(update_icons_mutex, U64_MAX); svcWaitSynchronization(update_icons_mutex, U64_MAX);
} }
draw_interface(current_list, instructions); draw_interface(current_list, instructions, draw_mode);
svcSleepThread(1e7); svcSleepThread(1e7);
released = false; released = false;
} }
if (home_displayed)
{
u64 cur_time = svcGetSystemTick() / CPU_TICKS_PER_MSEC;
draw_home(time_home_pressed, cur_time);
if (cur_time - time_home_pressed > 2000) home_displayed = false;
}
end_frame(); end_frame();
if(kDown & KEY_START) quit = true; if(kDown & KEY_START) quit = true;
if(!install_mode && !extra_mode) if(current_list->entries_count == 0)
{
if (kDown & KEY_R)
{
goto enable_qr;
} else if (kDown & KEY_L)
{
goto switch_mode;
} else if (kDown & KEY_TOUCH)
{
touchPosition touch = {0};
hidTouchRead(&touch);
u16 x = touch.px;
u16 y = touch.py;
if (y < 24)
{
if(BETWEEN(320-24, x, 320))
{
goto switch_mode;
} else if(BETWEEN(320-48, x, 320-24))
{
quit = true;
continue;
} else if(BETWEEN(320-72, x, 320-48))
{
goto browse_themeplaza;
} else if(BETWEEN(320-96, x, 320-72))
{
goto enable_qr;
}
}
}
}
else if(!install_mode && !extra_mode)
{ {
if(!preview_mode && kDown & KEY_L) //toggle between splashes and themes if(!preview_mode && kDown & KEY_L) //toggle between splashes and themes
{ {
@@ -450,7 +535,7 @@ int main(void)
{ {
enable_qr: enable_qr:
draw_base_interface(); draw_base_interface();
draw_text_center(GFX_TOP, 100, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], "Loading QR Scanner..."); draw_text_center(GFX_TOP, 100, 0.5f, 0.6f, 0.6f, colors[COLOR_WHITE], language.main.loading_qr);
end_frame(); end_frame();
if(R_SUCCEEDED(camInit())) if(R_SUCCEEDED(camInit()))
{ {
@@ -466,15 +551,15 @@ int main(void)
} }
else else
{ {
throw_error("Please connect to Wi-Fi before scanning QR codes", ERROR_LEVEL_WARNING); throw_error(language.main.no_wifi, ERROR_LEVEL_WARNING);
} }
} }
else else
{ {
if(homebrew) if(homebrew)
throw_error("QR scanning doesnt work from the Homebrew\nLauncher, use the ThemePlaza browser instead.", ERROR_LEVEL_WARNING); throw_error(language.main.qr_homebrew, ERROR_LEVEL_WARNING);
else else
throw_error("Your camera seems to have a problem,\nunable to scan QR codes.", ERROR_LEVEL_WARNING); throw_error(language.main.camera_broke, ERROR_LEVEL_WARNING);
} }
continue; continue;
@@ -484,7 +569,7 @@ int main(void)
toggle_preview: toggle_preview:
if(!preview_mode) if(!preview_mode)
{ {
preview_mode = load_preview(*current_list, &preview, &preview_offset); preview_mode = load_preview(current_list, &preview, &preview_offset);
if(preview_mode) if(preview_mode)
{ {
end_frame(); end_frame();
@@ -493,7 +578,7 @@ int main(void)
if(current_mode == MODE_THEMES && dspfirm) if(current_mode == MODE_THEMES && dspfirm)
{ {
audio = calloc(1, sizeof(audio_s)); audio = calloc(1, sizeof(audio_s));
Result r = load_audio(current_list->entries[current_list->selected_entry], audio); Result r = load_audio(&current_list->entries[current_list->selected_entry], audio);
if (R_SUCCEEDED(r)) play_audio(audio); if (R_SUCCEEDED(r)) play_audio(audio);
else audio = NULL; else audio = NULL;
} }
@@ -504,9 +589,7 @@ int main(void)
preview_mode = false; preview_mode = false;
if(current_mode == MODE_THEMES && audio) if(current_mode == MODE_THEMES && audio)
{ {
audio->stop = true; stop_audio(&audio);
svcWaitSynchronization(audio->finished, U64_MAX);
audio = NULL;
} }
} }
continue; continue;
@@ -516,9 +599,7 @@ int main(void)
preview_mode = false; preview_mode = false;
if(current_mode == MODE_THEMES && audio) if(current_mode == MODE_THEMES && audio)
{ {
audio->stop = true; stop_audio(&audio);
svcWaitSynchronization(audio->finished, U64_MAX);
audio = NULL;
} }
continue; continue;
} }
@@ -532,88 +613,138 @@ int main(void)
if(install_mode) if(install_mode)
{ {
if(kUp & KEY_A) if ((kDown | kHeld) & KEY_TOUCH)
install_mode = false;
if(!install_mode)
{ {
if((kDown | kHeld) & KEY_DLEFT) touchPosition touch = {0};
hidTouchRead(&touch);
u16 x = touch.px;
u16 y = touch.py;
if (kDown & KEY_TOUCH)
{ {
draw_install(INSTALL_BGM); if (y < 24)
if(R_SUCCEEDED(bgm_install(*current_entry)))
{ {
for(int i = 0; i < current_list->entries_count; i++) if (BETWEEN(320-24, x, 320))
{ {
Entry_s * theme = &current_list->entries[i]; goto install_theme_single;
if(theme == current_entry) } else if (BETWEEN(320-48, x, 320-24))
theme->installed = true; {
else goto install_theme_shuffle;
theme->installed = false; } else if (BETWEEN(320-72, x, 320-48))
{
goto install_theme_no_bgm;
} else if (BETWEEN(320-96, x, 320-72))
{
goto install_theme_bgm_only;
} else if (BETWEEN(320-120, x, 320-96))
{
goto install_leave;
} }
installed_themes = true;
} }
} }
else if((kDown | kHeld) & KEY_DUP) }
if(kDown & KEY_B)
{
install_leave:
install_mode = false;
draw_mode = DRAW_MODE_LIST;
}
else if(kDown & KEY_DLEFT)
{
install_theme_bgm_only:
install_mode = false;
draw_mode = DRAW_MODE_LIST;
aptSetHomeAllowed(false);
draw_install(INSTALL_BGM);
if(R_SUCCEEDED(bgm_install(current_entry)))
{ {
draw_install(INSTALL_SINGLE); for(int i = 0; i < current_list->entries_count; i++)
if(R_SUCCEEDED(theme_install(*current_entry)))
{ {
for(int i = 0; i < current_list->entries_count; i++) #define BETWEEN(min, x, max) (min < x && x < max)
{ Entry_s * theme = &current_list->entries[i];
Entry_s * theme = &current_list->entries[i]; if(theme == current_entry)
if(theme == current_entry) theme->installed = true;
theme->installed = true; else
else theme->installed = false;
theme->installed = false;
}
installed_themes = true;
} }
installed_themes = true;
} }
else if((kDown | kHeld) & KEY_DRIGHT) }
else if(kDown & KEY_DUP)
{
install_theme_single:
install_mode = false;
draw_mode = DRAW_MODE_LIST;
aptSetHomeAllowed(false);
draw_install(INSTALL_SINGLE);
if(R_SUCCEEDED(theme_install(current_entry)))
{ {
draw_install(INSTALL_NO_BGM); for(int i = 0; i < current_list->entries_count; i++)
if(R_SUCCEEDED(no_bgm_install(*current_entry)))
{ {
for(int i = 0; i < current_list->entries_count; i++) Entry_s * theme = &current_list->entries[i];
{ if(theme == current_entry)
Entry_s * theme = &current_list->entries[i]; theme->installed = true;
if(theme == current_entry) else
theme->installed = true; theme->installed = false;
else
theme->installed = false;
}
installed_themes = true;
} }
installed_themes = true;
} }
else if((kDown | kHeld) & KEY_DDOWN) }
else if(kDown & KEY_DRIGHT)
{
install_theme_no_bgm:
install_mode = false;
draw_mode = DRAW_MODE_LIST;
aptSetHomeAllowed(false);
draw_install(INSTALL_NO_BGM);
if(R_SUCCEEDED(no_bgm_install(current_entry)))
{ {
if(current_list->shuffle_count > MAX_SHUFFLE_THEMES) for(int i = 0; i < current_list->entries_count; i++)
{ {
throw_error("You have too many themes selected.", ERROR_LEVEL_WARNING); Entry_s * theme = &current_list->entries[i];
} if(theme == current_entry)
else if(current_list->shuffle_count < 2) theme->installed = true;
{ else
throw_error("You don't have enough themes selected.", ERROR_LEVEL_WARNING); theme->installed = false;
} }
installed_themes = true;
}
}
else if(kDown & KEY_DDOWN)
{
install_theme_shuffle:
install_mode = false;
draw_mode = DRAW_MODE_LIST;
if(current_list->shuffle_count > MAX_SHUFFLE_THEMES)
{
throw_error(language.main.too_many_themes, ERROR_LEVEL_WARNING);
}
else if(current_list->shuffle_count < 2)
{
throw_error(language.main.not_enough_themes, ERROR_LEVEL_WARNING);
}
else
{
aptSetHomeAllowed(false);
draw_install(INSTALL_SHUFFLE);
Result res = shuffle_install(current_list);
if(R_FAILED(res)) DEBUG("shuffle install result: %lx\n", res);
else else
{ {
draw_install(INSTALL_SHUFFLE); for(int i = 0; i < current_list->entries_count; i++)
Result res = shuffle_install(*current_list);
if(R_FAILED(res)) DEBUG("shuffle install result: %lx\n", res);
else
{ {
for(int i = 0; i < current_list->entries_count; i++) Entry_s * theme = &current_list->entries[i];
if(theme->in_shuffle)
{ {
Entry_s * theme = &current_list->entries[i]; theme->in_shuffle = false;
if(theme->in_shuffle) theme->installed = true;
{
theme->in_shuffle = false;
theme->installed = true;
}
else theme->installed = false;
} }
current_list->shuffle_count = 0; else theme->installed = false;
installed_themes = true;
} }
current_list->shuffle_count = 0;
installed_themes = true;
} }
} }
} }
@@ -621,61 +752,151 @@ int main(void)
} }
else if(extra_mode) else if(extra_mode)
{ {
if(kUp & KEY_X) if((kDown | kHeld) & KEY_TOUCH)
extra_mode = false;
if(!extra_mode)
{ {
bool key_l = (kDown | kHeld) & KEY_L; touchPosition touch = {0};
bool key_r = (kDown | kHeld) & KEY_R; hidTouchRead(&touch);
if(!(key_l ^ key_r)) u16 x = touch.px;
u16 y = touch.py;
if (kDown & KEY_TOUCH)
{ {
if((kDown | kHeld) & KEY_DLEFT) if (y < 24)
{ {
browse_themeplaza: if (BETWEEN(320-24, x, 320))
if(themeplaza_browser(current_mode))
{ {
current_mode = MODE_THEMES; goto browse_themeplaza;
load_lists(lists); } else if (BETWEEN(320-48, x, 320-24))
{
goto dump_single;
} else if (BETWEEN(320-72, x, 320-48))
{
switch (current_list->current_sort)
{
case SORT_NAME:
goto sort_author;
break;
case SORT_AUTHOR:
goto sort_path;
break;
case SORT_PATH:
goto sort_name;
break;
default:
break;
}
} else if (BETWEEN(320-96, x, 320-72))
{
extra_mode = false;
extra_index = 1;
draw_mode = DRAW_MODE_LIST;
} }
} }
else if((kDown | kHeld) & KEY_DUP)
{
jump:
jump_menu(current_list);
}
else if((kDown | kHeld) & KEY_DDOWN)
{
load_icons_first(current_list, false);
}
else if((kDown | kHeld) & KEY_DRIGHT)
{
draw_install(INSTALL_DUMPING_THEME);
Result res = dump_theme();
if (R_FAILED(res)) DEBUG("Dump theme result: %lx\n", res);
else load_lists(lists);
}
} }
else if(key_l) }
else if(extra_index == 1)
{
if(kDown & KEY_B)
{ {
if((kDown | kHeld) & KEY_DLEFT) extra_mode = false;
draw_mode = DRAW_MODE_LIST;
}
else if(kDown & KEY_DLEFT)
{
browse_themeplaza:
if(themeplaza_browser(current_mode))
{ {
sort_path: current_mode = MODE_THEMES;
sort_by_filename(current_list); load_lists(lists);
load_icons_first(current_list, false);
}
else if(((kDown | kHeld)) & KEY_DUP)
{
sort_name:
sort_by_name(current_list);
load_icons_first(current_list, false);
}
else if(((kDown | kHeld)) & KEY_DDOWN)
{
sort_author:
sort_by_author(current_list);
load_icons_first(current_list, false);
} }
extra_mode = false;
draw_mode = DRAW_MODE_LIST;
extra_index = 1;
}
else if(kDown & KEY_DUP)
{
jump:
jump_menu(current_list);
extra_mode = false;
draw_mode = DRAW_MODE_LIST;
extra_index = 1;
}
else if(kDown & KEY_DDOWN)
{
load_icons_first(current_list, false);
extra_mode = false;
draw_mode = DRAW_MODE_LIST;
extra_index = 1;
}
else if (kDown & KEY_R)
{
extra_index = 2;
}
else if(kDown & KEY_L)
{
extra_index = 0;
}
}
else if(extra_index == 0)
{
if(kDown & KEY_DLEFT)
{
sort_path:
sort_by_filename(current_list);
load_icons_first(current_list, false);
extra_mode = false;
draw_mode = DRAW_MODE_LIST;
extra_index = 1;
}
else if(kDown & KEY_DUP)
{
sort_name:
sort_by_name(current_list);
load_icons_first(current_list, false);
extra_mode = false;
draw_mode = DRAW_MODE_LIST;
extra_index = 1;
}
else if(kDown & KEY_DDOWN)
{
sort_author:
sort_by_author(current_list);
load_icons_first(current_list, false);
extra_mode = false;
draw_mode = DRAW_MODE_LIST;
extra_index = 1;
}
else if (kDown & KEY_B)
{
extra_index = 1;
}
}
else if(extra_index == 2)
{
if(kDown & KEY_DUP)
{
dump_single:
draw_install(INSTALL_DUMPING_THEME);
Result res = dump_current_theme();
if (R_FAILED(res)) DEBUG("Dump theme result: %lx\n", res);
else load_lists(lists);
extra_mode = false;
draw_mode = DRAW_MODE_LIST;
extra_index = 1;
}
else if(kDown & KEY_DDOWN)
{
draw_install(INSTALL_DUMPING_ALL_THEMES);
Result res = dump_all_themes();
if (R_FAILED(res)) DEBUG("Dump all themes result: %lx\n", res);
else load_lists(lists);
extra_mode = false;
draw_mode = DRAW_MODE_LIST;
extra_index = 1;
}
else if(kDown &KEY_B)
{
extra_index = 1;
} }
} }
continue; continue;
@@ -689,10 +910,11 @@ int main(void)
{ {
case MODE_THEMES: case MODE_THEMES:
install_mode = true; install_mode = true;
draw_mode = DRAW_MODE_INSTALL;
break; break;
case MODE_SPLASHES: case MODE_SPLASHES:
draw_install(INSTALL_SPLASH); draw_install(INSTALL_SPLASH);
splash_install(*current_entry); splash_install(current_entry);
for(int i = 0; i < current_list->entries_count; i++) for(int i = 0; i < current_list->entries_count; i++)
{ {
Entry_s * splash = &current_list->entries[i]; Entry_s * splash = &current_list->entries[i];
@@ -714,7 +936,7 @@ int main(void)
toggle_shuffle(current_list); toggle_shuffle(current_list);
break; break;
case MODE_SPLASHES: case MODE_SPLASHES:
if(draw_confirm("Are you sure you would like to delete\nthe installed splash?", current_list)) if(draw_confirm(language.main.uninstall_confirm, current_list, draw_mode))
{ {
draw_install(INSTALL_SPLASH_DELETE); draw_install(INSTALL_SPLASH_DELETE);
splash_delete(); splash_delete();
@@ -727,10 +949,11 @@ int main(void)
else if(kDown & KEY_X) else if(kDown & KEY_X)
{ {
extra_mode = true; extra_mode = true;
draw_mode = DRAW_MODE_EXTRA;
} }
else if(kDown & KEY_SELECT) else if(kDown & KEY_SELECT)
{ {
if(draw_confirm("Are you sure you would like to delete this?", current_list)) if(draw_confirm(language.main.delete_confirm, current_list, draw_mode))
{ {
draw_install(INSTALL_ENTRY_DELETE); draw_install(INSTALL_ENTRY_DELETE);
delete_entry(current_entry, current_entry->is_zip); delete_entry(current_entry, current_entry->is_zip);
@@ -789,43 +1012,49 @@ int main(void)
u16 x = touch.px; u16 x = touch.px;
u16 y = touch.py; u16 y = touch.py;
u16 arrowStartX = 152; u16 arrowStartX = 136;
u16 arrowEndX = arrowStartX+16; u16 arrowEndX = arrowStartX+16;
#define BETWEEN(min, x, max) (min < x && x < max)
if(kDown & KEY_TOUCH) if(kDown & KEY_TOUCH)
{ {
if(y < 24) if(y < 24)
{ {
if(current_list->entries != NULL && BETWEEN(arrowStartX, x, arrowEndX) && current_list->scroll > 0) if(BETWEEN(320-168, x, 320-144))
{ {
change_selected(current_list, -current_list->entries_per_screen_v); if (current_mode == MODE_THEMES)
{
toggle_shuffle(current_list);
}
} }
else if(BETWEEN(320-144, x, 320-120)) else if(BETWEEN(320-144, x, 320-120))
{ {
switch(current_list->current_sort) extra_mode = true;
{ draw_mode = DRAW_MODE_EXTRA;
case SORT_NAME:
goto sort_author;
break;
case SORT_AUTHOR:
goto sort_path;
break;
case SORT_PATH:
goto sort_name;
break;
default:
break;
}
} }
else if(BETWEEN(320-120, x, 320-96)) else if(BETWEEN(320-120, x, 320-96))
{ {
goto enable_qr; if (current_mode == MODE_THEMES)
{
install_mode = true;
draw_mode = DRAW_MODE_INSTALL;
} else if (current_mode == MODE_SPLASHES)
{
draw_install(INSTALL_SPLASH);
splash_install(current_entry);
for(int i = 0; i < current_list->entries_count; i++)
{
Entry_s * splash = &current_list->entries[i];
if(splash == current_entry)
splash->installed = true;
else
splash->installed = false;
}
}
} }
else if(BETWEEN(320-96, x, 320-72)) else if(BETWEEN(320-96, x, 320-72))
{ {
goto browse_themeplaza; goto enable_qr;
} }
else if(BETWEEN(320-72, x, 320-48)) else if(BETWEEN(320-72, x, 320-48))
{ {
@@ -842,7 +1071,11 @@ int main(void)
} }
else if(y >= 216) else if(y >= 216)
{ {
if(current_list->entries != NULL && BETWEEN(arrowStartX, x, arrowEndX) && current_list->scroll < current_list->entries_count - current_list->entries_per_screen_v) if(current_list->entries != NULL && BETWEEN(arrowStartX, x, arrowEndX) && current_list->scroll > 0)
{
change_selected(current_list, -current_list->entries_per_screen_v);
}
else if(current_list->entries != NULL && BETWEEN(arrowStartX + 16, x, arrowEndX + 16) && current_list->scroll < current_list->entries_count - current_list->entries_per_screen_v)
{ {
change_selected(current_list, current_list->entries_per_screen_v); change_selected(current_list, current_list->entries_per_screen_v);
} }
@@ -858,7 +1091,7 @@ int main(void)
{ {
for(int i = 0; i < current_list->entries_loaded; i++) for(int i = 0; i < current_list->entries_loaded; i++)
{ {
u16 miny = 24 + current_list->entry_size*i; u16 miny = 24 + current_list->entry_size * i;
u16 maxy = miny + current_list->entry_size; u16 maxy = miny + current_list->entry_size;
if(BETWEEN(miny, y, maxy) && current_list->scroll + i < current_list->entries_count) if(BETWEEN(miny, y, maxy) && current_list->scroll + i < current_list->entries_count)
{ {

View File

@@ -28,7 +28,7 @@
#include "loading.h" #include "loading.h"
// Play a given audio struct // Play a given audio struct
Result update_audio(audio_s *audio) Result update_audio(audio_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);
@@ -36,7 +36,7 @@ Result update_audio(audio_s *audio)
{ {
DEBUG("<update_audio> Attempting ov_read\n"); DEBUG("<update_audio> Attempting ov_read\n");
int bitstream; int bitstream;
u32 read = ov_read(&audio->vf, (char*)audio->wave_buf[audio->buf_pos].data_vaddr + audio->data_read, size, &bitstream); // read 1 vorbis packet into wave buffer u32 read = ov_read(&audio->vf, (char *)audio->wave_buf[audio->buf_pos].data_vaddr + audio->data_read, size, &bitstream); // read 1 vorbis packet into wave buffer
DEBUG("<update_audio> ov_read successful\n"); DEBUG("<update_audio> ov_read successful\n");
if (read <= 0) // EoF or error if (read <= 0) // EoF or error
@@ -64,22 +64,31 @@ 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(void * data) {
audio_s *audio = (audio_s*)data; audio_s * audio = (audio_s *)data;
while(!audio->stop) { while(!audio->stop) {
update_audio(audio); update_audio(audio);
} }
free(audio->filebuf); ndspChnWaveBufClear(0);
ndspChnReset(0);
ov_clear(&audio->vf); ov_clear(&audio->vf);
linearFree((void*)audio->wave_buf[0].data_vaddr); free(audio->filebuf);
linearFree((void*)audio->wave_buf[1].data_vaddr); linearFree((void *)audio->wave_buf[0].data_vaddr);
while (audio->wave_buf[0].status != NDSP_WBUF_DONE || audio->wave_buf[1].status != NDSP_WBUF_DONE) svcSleepThread(1e7); linearFree((void *)audio->wave_buf[1].data_vaddr);
svcSignalEvent(audio->finished);
svcSleepThread(1e8);
svcCloseHandle(audio->finished);
free(audio);
} }
void play_audio(audio_s *audio) { void play_audio(audio_s * audio) {
threadCreate(thread_audio, audio, 0x1000, 0x3F, 1, true); audio->playing_thread = threadCreate(thread_audio, audio, 0x1000, 0x3F, 1, false);
}
void stop_audio(audio_s ** audio_ptr) {
audio_s * audio = *audio_ptr;
if(audio->playing_thread)
{
audio->stop = true;
threadJoin(audio->playing_thread, U64_MAX);
threadFree(audio->playing_thread);
}
free(audio);
*audio_ptr = NULL;
} }

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,7 @@
#include "unicode.h" #include "unicode.h"
#include "fs.h" #include "fs.h"
#include "draw.h" #include "draw.h"
#include "ui_strings.h"
void splash_delete(void) void splash_delete(void)
{ {
@@ -34,7 +35,7 @@ void splash_delete(void)
remove("/luma/splashbottom.bin"); remove("/luma/splashbottom.bin");
} }
void splash_install(Entry_s splash) void splash_install(const Entry_s * splash)
{ {
char *screen_buf = NULL; char *screen_buf = NULL;
@@ -54,7 +55,7 @@ void splash_install(Entry_s splash)
if(size == 0 && bottom_size == 0) if(size == 0 && bottom_size == 0)
{ {
throw_error("No splash.bin or splashbottom.bin found.\nIs this a splash?", ERROR_LEVEL_WARNING); throw_error(language.splashes.no_splash_found, ERROR_LEVEL_WARNING);
} }
else else
{ {
@@ -65,7 +66,7 @@ void splash_install(Entry_s splash)
if(config_buf[0xC] == 0) if(config_buf[0xC] == 0)
{ {
free(config_buf); free(config_buf);
throw_error("WARNING: Splashes are disabled in Luma Config", ERROR_LEVEL_WARNING); throw_error(language.splashes.splash_disabled, ERROR_LEVEL_WARNING);
} }
} }
} }
@@ -103,8 +104,8 @@ void splash_check_installed(void * void_arg)
for(int i = 0; i < list->entries_count && arg->run_thread; i++) for(int i = 0; i < list->entries_count && arg->run_thread; i++)
{ {
Entry_s * splash = &list->entries[i]; Entry_s * splash = &list->entries[i];
top_size = load_data("/splash.bin", *splash, &top_buf); top_size = load_data("/splash.bin", splash, &top_buf);
bottom_size = load_data("/splashbottom.bin", *splash, &bottom_buf); bottom_size = load_data("/splashbottom.bin", splash, &bottom_buf);
if(!top_size && !bottom_size) if(!top_size && !bottom_size)
{ {
@@ -127,4 +128,4 @@ void splash_check_installed(void * void_arg)
} }
} }
#endif #endif
} }

View File

@@ -28,29 +28,31 @@
#include "unicode.h" #include "unicode.h"
#include "fs.h" #include "fs.h"
#include "draw.h" #include "draw.h"
#include "ui_strings.h"
#define BODY_CACHE_SIZE 0x150000 #define BODY_CACHE_SIZE 0x150000
#define BGM_MAX_SIZE 0x337000 #define BGM_MAX_SIZE 0x337000
static Result install_theme_internal(Entry_List_s themes, int installmode) static Result install_theme_internal(const Entry_List_s * themes, int installmode)
{ {
Result res = 0; Result res = 0;
char* music = NULL; char * music = NULL;
u32 music_size = 0; u32 music_size = 0;
u32 shuffle_music_sizes[MAX_SHUFFLE_THEMES] = {0}; u32 shuffle_music_sizes[MAX_SHUFFLE_THEMES] = {0};
char* body = NULL; char * body = NULL;
u32 body_size = 0; u32 body_size = 0;
u32 shuffle_body_sizes[MAX_SHUFFLE_THEMES] = {0}; u32 shuffle_body_sizes[MAX_SHUFFLE_THEMES] = {0};
bool mono_audio = false;
if(installmode & THEME_INSTALL_SHUFFLE) if(installmode & THEME_INSTALL_SHUFFLE)
{ {
if(themes.shuffle_count < 2) if(themes->shuffle_count < 2)
{ {
DEBUG("not enough themes selected for shuffle\n"); DEBUG("not enough themes selected for shuffle\n");
return MAKERESULT(RL_USAGE, RS_INVALIDARG, RM_COMMON, RD_INVALID_SELECTION); return MAKERESULT(RL_USAGE, RS_INVALIDARG, RM_COMMON, RD_INVALID_SELECTION);
} }
if(themes.shuffle_count > MAX_SHUFFLE_THEMES) if(themes->shuffle_count > MAX_SHUFFLE_THEMES)
{ {
DEBUG("too many themes selected for shuffle\n"); DEBUG("too many themes selected for shuffle\n");
return MAKERESULT(RL_USAGE, RS_INVALIDARG, RM_COMMON, RD_INVALID_SELECTION); return MAKERESULT(RL_USAGE, RS_INVALIDARG, RM_COMMON, RD_INVALID_SELECTION);
@@ -67,20 +69,20 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
FSUSER_OpenFile(&body_cache_handle, ArchiveThemeExt, fsMakePath(PATH_ASCII, "/BodyCache_rd.bin"), FS_OPEN_WRITE, 0); FSUSER_OpenFile(&body_cache_handle, ArchiveThemeExt, fsMakePath(PATH_ASCII, "/BodyCache_rd.bin"), FS_OPEN_WRITE, 0);
} }
for(int i = 0; i < themes.entries_count; i++) for(int i = 0; i < themes->entries_count; i++)
{ {
Entry_s * current_theme = &themes.entries[i]; const Entry_s * current_theme = &themes->entries[i];
if(current_theme->in_shuffle) if(current_theme->in_shuffle)
{ {
if(installmode & THEME_INSTALL_BODY) if(installmode & THEME_INSTALL_BODY)
{ {
body_size = load_data("/body_LZ.bin", *current_theme, &body); body_size = load_data("/body_LZ.bin", current_theme, &body);
if(body_size == 0) if(body_size == 0)
{ {
free(body); free(body);
DEBUG("body not found\n"); DEBUG("body not found\n");
throw_error("No body_LZ.bin found - is this a theme?", ERROR_LEVEL_WARNING); throw_error(language.themes.no_body_found, ERROR_LEVEL_WARNING);
return MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, RD_NOT_FOUND); return MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, RD_NOT_FOUND);
} }
@@ -108,7 +110,7 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
} }
else else
{ {
music_size = load_data("/bgm.bcstm", *current_theme, &music); music_size = load_data("/bgm.bcstm", current_theme, &music);
if(music_size > BGM_MAX_SIZE) if(music_size > BGM_MAX_SIZE)
{ {
@@ -116,6 +118,14 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
DEBUG("bgm too big\n"); DEBUG("bgm too big\n");
return MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, RD_TOO_LARGE); return MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, RD_TOO_LARGE);
} }
if (music_size > 0)
{
if (music[0x62] == 1)
{
mono_audio = true;
}
}
} }
shuffle_music_sizes[shuffle_count] = music_size; shuffle_music_sizes[shuffle_count] = music_size;
@@ -162,7 +172,7 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
} }
else else
{ {
Entry_s current_theme = themes.entries[themes.selected_entry]; const Entry_s * current_theme = &themes->entries[themes->selected_entry];
if(installmode & THEME_INSTALL_BODY) if(installmode & THEME_INSTALL_BODY)
{ {
@@ -171,7 +181,7 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
{ {
free(body); free(body);
DEBUG("body not found\n"); DEBUG("body not found\n");
throw_error("No body_LZ.bin found - is this a theme?", ERROR_LEVEL_WARNING); throw_error(language.themes.no_body_found, ERROR_LEVEL_WARNING);
return MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, RD_NOT_FOUND); return MAKERESULT(RL_PERMANENT, RS_CANCELED, RM_APPLICATION, RD_NOT_FOUND);
} }
@@ -193,11 +203,16 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
if (music_size != 0) if (music_size != 0)
{ {
if (music[0x62] == 1)
{
mono_audio = true;
}
remake_file(fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, BGM_MAX_SIZE); remake_file(fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, BGM_MAX_SIZE);
res = buf_to_file(music_size, fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, music); res = buf_to_file(music_size, fsMakePath(PATH_ASCII, "/BgmCache.bin"), ArchiveThemeExt, music);
free(music); free(music);
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[5] != 1)
{ {
@@ -219,7 +234,7 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
} }
//---------------------------------------- //----------------------------------------
char* thememanage_buf = NULL; char * thememanage_buf = NULL;
file_to_buf(fsMakePath(PATH_ASCII, "/ThemeManage.bin"), ArchiveThemeExt, &thememanage_buf); file_to_buf(fsMakePath(PATH_ASCII, "/ThemeManage.bin"), ArchiveThemeExt, &thememanage_buf);
ThemeManage_bin_s * theme_manage = (ThemeManage_bin_s *)thememanage_buf; ThemeManage_bin_s * theme_manage = (ThemeManage_bin_s *)thememanage_buf;
@@ -256,79 +271,88 @@ static Result install_theme_internal(Entry_List_s themes, int installmode)
//---------------------------------------- //----------------------------------------
//---------------------------------------- //----------------------------------------
char* savedata_buf = NULL; char * savedata_buf = NULL;
u32 savedata_size = file_to_buf(fsMakePath(PATH_ASCII, "/SaveData.dat"), ArchiveHomeExt, &savedata_buf); u32 savedata_size = file_to_buf(fsMakePath(PATH_ASCII, "/SaveData.dat"), ArchiveHomeExt, &savedata_buf);
SaveData_dat_s* savedata = (SaveData_dat_s*)savedata_buf; SaveData_dat_s * savedata = (SaveData_dat_s *)savedata_buf;
memset(&savedata->theme_entry, 0, sizeof(ThemeEntry_s)); memset(&savedata->theme_entry, 0, sizeof(ThemeEntry_s));
savedata->theme_entry.type = 3;
savedata->theme_entry.index = 0xff;
savedata->shuffle = (installmode & THEME_INSTALL_SHUFFLE); savedata->shuffle = (installmode & THEME_INSTALL_SHUFFLE) ? 1 : 0;
memset(savedata->shuffle_themes, 0, sizeof(ThemeEntry_s) * MAX_SHUFFLE_THEMES);
if(installmode & THEME_INSTALL_SHUFFLE) if(installmode & THEME_INSTALL_SHUFFLE)
{ {
memset(savedata->shuffle_themes, 0, sizeof(ThemeEntry_s)*MAX_SHUFFLE_THEMES); for(int i = 0; i < themes->shuffle_count; i++)
for(int i = 0; i < themes.shuffle_count; i++)
{ {
savedata->shuffle_themes[i].type = 3; savedata->shuffle_themes[i].type = 3;
savedata->shuffle_themes[i].index = i; savedata->shuffle_themes[i].index = i;
} }
const u8 shuffle_seed[0xB] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
memcpy(savedata->shuffle_seedA, shuffle_seed, 0xB);
memcpy(savedata->shuffle_seedB, shuffle_seed, 0xA);
}
else
{
savedata->theme_entry.type = 3;
savedata->theme_entry.index = 0xff;
} }
res = buf_to_file(savedata_size, fsMakePath(PATH_ASCII, "/SaveData.dat"), ArchiveHomeExt, savedata_buf); res = buf_to_file(savedata_size, fsMakePath(PATH_ASCII, "/SaveData.dat"), ArchiveHomeExt, savedata_buf);
free(savedata_buf); free(savedata_buf);
if(R_FAILED(res)) return res; if(R_FAILED(res)) return res;
//---------------------------------------- //----------------------------------------
if (mono_audio)
{
throw_error(language.themes.mono_warn, ERROR_LEVEL_WARNING);
}
return 0; return 0;
} }
inline Result theme_install(Entry_s theme) Result theme_install(Entry_s * theme)
{ {
Entry_List_s list = {0}; Entry_List_s list = {0};
list.entries_count = 1; list.entries_count = 1;
list.entries = &theme; list.entries = theme;
list.selected_entry = 0; list.selected_entry = 0;
return install_theme_internal(list, THEME_INSTALL_BODY | THEME_INSTALL_BGM); return install_theme_internal(&list, THEME_INSTALL_BODY | THEME_INSTALL_BGM);
} }
inline Result bgm_install(Entry_s theme) Result bgm_install(Entry_s * theme)
{ {
Entry_List_s list = {0}; Entry_List_s list = {0};
list.entries_count = 1; list.entries_count = 1;
list.entries = &theme; list.entries = theme;
list.selected_entry = 0; list.selected_entry = 0;
return install_theme_internal(list, THEME_INSTALL_BGM); return install_theme_internal(&list, THEME_INSTALL_BGM);
} }
inline Result no_bgm_install(Entry_s theme) Result no_bgm_install(Entry_s * theme)
{ {
Entry_List_s list = {0}; Entry_List_s list = {0};
list.entries_count = 1; list.entries_count = 1;
list.entries = &theme; list.entries = theme;
list.selected_entry = 0; list.selected_entry = 0;
return install_theme_internal(list, THEME_INSTALL_BODY); return install_theme_internal(&list, THEME_INSTALL_BODY);
} }
inline Result shuffle_install(Entry_List_s themes) Result shuffle_install(const Entry_List_s * themes)
{ {
return install_theme_internal(themes, THEME_INSTALL_SHUFFLE | THEME_INSTALL_BODY | THEME_INSTALL_BGM); return install_theme_internal(themes, THEME_INSTALL_SHUFFLE | THEME_INSTALL_BODY | THEME_INSTALL_BGM);
} }
static SwkbdCallbackResult static SwkbdCallbackResult
dir_name_callback(void *data, const char ** ppMessage, const char * text, size_t textlen) dir_name_callback(void * data, const char ** ppMessage, const char * text, size_t textlen)
{ {
(void)textlen; (void)textlen;
(void)data; (void)data;
if(strpbrk(text, "><\"?;:/\\+,.|[=]")) if(strpbrk(text, "><\"?;:/\\+,.|[=]"))
{ {
*ppMessage = "Illegal character used."; *ppMessage = language.themes.illegal_char;
return SWKBD_CALLBACK_CONTINUE; return SWKBD_CALLBACK_CONTINUE;
} }
return SWKBD_CALLBACK_OK; return SWKBD_CALLBACK_OK;
} }
Result dump_theme(void) Result dump_current_theme(void)
{ {
const int max_chars = 255; const int max_chars = 255;
char * output_dir = calloc(max_chars + 1, sizeof(char)); char * output_dir = calloc(max_chars + 1, sizeof(char));
@@ -336,10 +360,10 @@ Result dump_theme(void)
SwkbdState swkbd; SwkbdState swkbd;
swkbdInit(&swkbd, SWKBD_TYPE_WESTERN, 2, max_chars); swkbdInit(&swkbd, SWKBD_TYPE_WESTERN, 2, max_chars);
swkbdSetHintText(&swkbd, "Name of output folder"); swkbdSetHintText(&swkbd, language.themes.name_folder);
swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, "Cancel", false); swkbdSetButton(&swkbd, SWKBD_BUTTON_LEFT, language.themes.cancel, false);
swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, "Done", true); swkbdSetButton(&swkbd, SWKBD_BUTTON_RIGHT, language.themes.done, true);
swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, 0, max_chars); swkbdSetValidation(&swkbd, SWKBD_NOTEMPTY_NOTBLANK, 0, max_chars);
swkbdSetFilterCallback(&swkbd, dir_name_callback, NULL); swkbdSetFilterCallback(&swkbd, dir_name_callback, NULL);
@@ -356,14 +380,14 @@ Result dump_theme(void)
struacat(path, output_dir); struacat(path, output_dir);
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_UTF16, path), FS_ATTRIBUTE_DIRECTORY); FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_UTF16, path), FS_ATTRIBUTE_DIRECTORY);
char *thememanage_buf = NULL; char * thememanage_buf = NULL;
file_to_buf(fsMakePath(PATH_ASCII, "/ThemeManage.bin"), ArchiveThemeExt, &thememanage_buf); file_to_buf(fsMakePath(PATH_ASCII, "/ThemeManage.bin"), ArchiveThemeExt, &thememanage_buf);
ThemeManage_bin_s * theme_manage = (ThemeManage_bin_s *)thememanage_buf; ThemeManage_bin_s * theme_manage = (ThemeManage_bin_s *)thememanage_buf;
u32 theme_size = theme_manage->body_size; u32 theme_size = theme_manage->body_size;
u32 bgm_size = theme_manage->music_size; u32 bgm_size = theme_manage->music_size;
free(thememanage_buf); free(thememanage_buf);
char *temp_buf = NULL; char * temp_buf = NULL;
file_to_buf(fsMakePath(PATH_ASCII, "/BodyCache.bin"), ArchiveThemeExt, &temp_buf); file_to_buf(fsMakePath(PATH_ASCII, "/BodyCache.bin"), ArchiveThemeExt, &temp_buf);
u16 path_output[0x107] = { 0 }; u16 path_output[0x107] = { 0 };
memcpy(path_output, path, 0x107); memcpy(path_output, path, 0x107);
@@ -381,7 +405,7 @@ Result dump_theme(void)
free(temp_buf); free(temp_buf);
temp_buf = NULL; temp_buf = NULL;
char *smdh_file = calloc(1, 0x36c0); char * smdh_file = calloc(1, 0x36c0);
smdh_file[0] = 0x53; // SMDH magic smdh_file[0] = 0x53; // SMDH magic
smdh_file[1] = 0x4d; smdh_file[1] = 0x4d;
smdh_file[2] = 0x44; smdh_file[2] = 0x44;
@@ -415,6 +439,249 @@ Result dump_theme(void)
return 0; return 0;
} }
Result dump_all_themes(void)
{
const u32 high_id = 0x0004008c;
u32 low_id = 0;
u8 regionCode, language;
Result res = CFGU_SecureInfoGetRegion(&regionCode);
if(R_FAILED(res))
return res;
res = CFGU_GetSystemLanguage(&language);
if(R_FAILED(res))
return res;
switch(regionCode)
{
case CFG_REGION_JPN:
low_id = 0x00008200;
break;
case CFG_REGION_USA:
low_id = 0x00008f00;
break;
case CFG_REGION_EUR:
low_id = 0x00009800;
break;
case CFG_REGION_KOR:
low_id = 0x0000a900;
break;
default:
return -1;
}
const char * region_arr[7] = {
"JPN",
"USA",
"EUR",
"AUS",
"CHN",
"KOR",
"TWN",
};
const char * language_arr[12] = {
"jp",
"en",
"fr",
"de",
"it",
"es",
"zh",
"ko",
"nl",
"pt",
"ru",
"tw",
};
res = amAppInit();
if(R_FAILED(res))
return res;
Icon_s * smdh_data = calloc(1, sizeof(Icon_s));
smdh_data->_padding1[0] = 0x53; // SMDH magic
smdh_data->_padding1[1] = 0x4d;
smdh_data->_padding1[2] = 0x44;
smdh_data->_padding1[3] = 0x48;
utf8_to_utf16(smdh_data->author, (u8 *)"Nintendo", 0x40);
utf8_to_utf16(smdh_data->desc, (u8 *)"Official theme. For personal use only. Do not redistribute.", 0x80);
for(u32 dlc_index = 0; dlc_index <= 0xFF; ++dlc_index)
{
const u64 titleId = ((u64)high_id << 32) | (low_id | dlc_index);
u32 count = 0;
res = AMAPP_GetDLCContentInfoCount(&count, MEDIATYPE_SD, titleId);
if(res == (Result)0xd8a083fa)
{
res = 0;
break;
}
else if(R_FAILED(res))
{
break;
}
AM_ContentInfo * contentInfos = calloc(count, sizeof(AM_ContentInfo));
u32 readcount = 0;
res = AMAPP_ListDLCContentInfos(&readcount, MEDIATYPE_SD, titleId, count, 0, contentInfos);
if(R_FAILED(res))
{
free(contentInfos);
break;
}
u32 archivePath[4] = {low_id | dlc_index, high_id, MEDIATYPE_SD, 0};
FS_Path ncch_path;
ncch_path.type = PATH_BINARY;
ncch_path.size = 0x10;
ncch_path.data = archivePath;
FS_Archive ncch_archive;
res = FSUSER_OpenArchive(&ncch_archive, ARCHIVE_SAVEDATA_AND_CONTENT, ncch_path);
if(R_FAILED(res))
{
free(contentInfos);
break;
}
u32 metadataPath[5] = {0, 0, 0, 0, 0};
FS_Path metadata_path;
metadata_path.type = PATH_BINARY;
metadata_path.size = 0x14;
metadata_path.data = metadataPath;
Handle metadata_fh;
res = FSUSER_OpenFile(&metadata_fh, ncch_archive, metadata_path, FS_OPEN_READ, 0);
if(R_FAILED(res))
{
FSUSER_CloseArchive(ncch_archive);
free(contentInfos);
break;
}
res = romfsMountFromFile(metadata_fh, 0, "meta");
if(R_FAILED(res))
{
FSFILE_Close(metadata_fh);
FSUSER_CloseArchive(ncch_archive);
free(contentInfos);
break;
}
char contentinfoarchive_path[40] = {0};
sprintf(contentinfoarchive_path, "meta:/ContentInfoArchive_%s_%s.bin", region_arr[regionCode], language_arr[language]);
FILE * fh = fopen(contentinfoarchive_path, "rb");
if(fh != NULL)
{
for(u32 i = 0; i < readcount; ++i)
{
if(i == 0) continue;
AM_ContentInfo * content = &contentInfos[i];
if((content->flags & (AM_CONTENT_DOWNLOADED | AM_CONTENT_OWNED)) == (AM_CONTENT_DOWNLOADED | AM_CONTENT_OWNED))
{
long off = 0x8 + 0xC8 * i;
fseek(fh, off, SEEK_SET);
char content_data[0xc8] = {0};
fread(content_data, 1, 0xc8, fh);
u32 extra_index = 0;
memcpy(&extra_index, content_data + 0xC0, 4);
metadataPath[1] = content->index;
Handle theme_fh;
res = FSUSER_OpenFile(&theme_fh, ncch_archive, metadata_path, FS_OPEN_READ, 0);
if(R_FAILED(res))
{
DEBUG("theme open romfs error: %08lx\n", res);
break;
}
romfsMountFromFile(theme_fh, 0, "theme");
char themename[0x41] = {0};
memcpy(themename, content_data, 0x40);
char * illegal_char = themename;
while ((illegal_char = strpbrk(illegal_char, ILLEGAL_CHARS)))
{
*illegal_char = '-';
}
char path[0x107] = { 0 };
sprintf(path, "/Themes/Dump-%02lx-%ld-%s", dlc_index, extra_index, themename);
DEBUG("theme folder to create: %s\n", path);
FSUSER_CreateDirectory(ArchiveSD, fsMakePath(PATH_ASCII, path), FS_ATTRIBUTE_DIRECTORY);
memset(smdh_data->name, 0, sizeof(smdh_data->name));
utf8_to_utf16(smdh_data->name, (u8 *)(content_data + 0), 0x40);
FILE * theme_file = fopen("theme:/body_LZ.bin", "rb");
if(theme_file)
{
fseek(theme_file, 0, SEEK_END);
long theme_size = ftell(theme_file);
fseek(theme_file, 0, SEEK_SET);
char * theme_data = malloc(theme_size);
fread(theme_data, 1, theme_size, theme_file);
fclose(theme_file);
char themepath[0x107] = {0};
sprintf(themepath, "%s/body_LZ.bin", path);
remake_file(fsMakePath(PATH_ASCII, themepath), ArchiveSD, theme_size);
buf_to_file(theme_size, fsMakePath(PATH_ASCII, themepath), ArchiveSD, theme_data);
free(theme_data);
}
FILE * bgm_file = fopen("theme:/bgm.bcstm", "rb");
if(bgm_file)
{
fseek(bgm_file, 0, SEEK_END);
long bgm_size = ftell(bgm_file);
fseek(bgm_file, 0, SEEK_SET);
char * bgm_data = malloc(bgm_size);
fread(bgm_data, 1, bgm_size, bgm_file);
fclose(bgm_file);
char bgmpath[0x107] = {0};
sprintf(bgmpath, "%s/bgm.bcstm", path);
remake_file(fsMakePath(PATH_ASCII, bgmpath), ArchiveSD, bgm_size);
buf_to_file(bgm_size, fsMakePath(PATH_ASCII, bgmpath), ArchiveSD, bgm_data);
free(bgm_data);
}
romfsUnmount("theme");
char icondatapath[0x107] = {0};
sprintf(icondatapath, "meta:/icons/%ld.icn", extra_index);
FILE * iconfile = fopen(icondatapath, "rb");
fread(smdh_data->big_icon, 1, sizeof(smdh_data->big_icon), iconfile);
fclose(iconfile);
strcat(path, "/info.smdh");
remake_file(fsMakePath(PATH_ASCII, path), ArchiveSD, 0x36c0);
buf_to_file(0x36c0, fsMakePath(PATH_ASCII, path), ArchiveSD, (char *)smdh_data);
}
}
fclose(fh);
fh = NULL;
}
free(contentInfos);
contentInfos = NULL;
romfsUnmount("meta");
// don't need to close the file opened for the metadata, romfsUnmount took ownership
FSUSER_CloseArchive(ncch_archive);
}
free(smdh_data);
amExit();
return res;
}
void themes_check_installed(void * void_arg) void themes_check_installed(void * void_arg)
{ {
Thread_Arg_s * arg = (Thread_Arg_s *)void_arg; Thread_Arg_s * arg = (Thread_Arg_s *)void_arg;
@@ -422,25 +689,25 @@ void themes_check_installed(void * void_arg)
if(list == NULL || list->entries == NULL) return; if(list == NULL || list->entries == NULL) return;
#ifndef CITRA_MODE #ifndef CITRA_MODE
char* savedata_buf = NULL; char * savedata_buf = NULL;
u32 savedata_size = file_to_buf(fsMakePath(PATH_ASCII, "/SaveData.dat"), ArchiveHomeExt, &savedata_buf); u32 savedata_size = file_to_buf(fsMakePath(PATH_ASCII, "/SaveData.dat"), ArchiveHomeExt, &savedata_buf);
if(!savedata_size) return; if(!savedata_size) return;
SaveData_dat_s* savedata = (SaveData_dat_s*)savedata_buf; SaveData_dat_s * savedata = (SaveData_dat_s *)savedata_buf;
bool shuffle = savedata->shuffle; bool shuffle = savedata->shuffle;
free(savedata_buf); free(savedata_buf);
#define HASH_SIZE_BYTES 256/8 #define HASH_SIZE_BYTES 256/8
u8 body_hash[MAX_SHUFFLE_THEMES][HASH_SIZE_BYTES]; u8 body_hash[MAX_SHUFFLE_THEMES][HASH_SIZE_BYTES];
memset(body_hash, 0, MAX_SHUFFLE_THEMES*HASH_SIZE_BYTES); memset(body_hash, 0, MAX_SHUFFLE_THEMES * HASH_SIZE_BYTES);
char* thememanage_buf = NULL; char * thememanage_buf = NULL;
u32 theme_manage_size = file_to_buf(fsMakePath(PATH_ASCII, "/ThemeManage.bin"), ArchiveThemeExt, &thememanage_buf); u32 theme_manage_size = file_to_buf(fsMakePath(PATH_ASCII, "/ThemeManage.bin"), ArchiveThemeExt, &thememanage_buf);
if(!theme_manage_size) return; if(!theme_manage_size) return;
ThemeManage_bin_s * theme_manage = (ThemeManage_bin_s *)thememanage_buf; ThemeManage_bin_s * theme_manage = (ThemeManage_bin_s *)thememanage_buf;
u32 single_body_size = theme_manage->body_size; u32 single_body_size = theme_manage->body_size;
u32 shuffle_body_sizes[MAX_SHUFFLE_THEMES] = {0}; u32 shuffle_body_sizes[MAX_SHUFFLE_THEMES] = {0};
memcpy(shuffle_body_sizes, theme_manage->shuffle_body_sizes, sizeof(u32)*MAX_SHUFFLE_THEMES); memcpy(shuffle_body_sizes, theme_manage->shuffle_body_sizes, sizeof(u32) * MAX_SHUFFLE_THEMES);
free(thememanage_buf); free(thememanage_buf);
if(shuffle) if(shuffle)
@@ -451,7 +718,7 @@ void themes_check_installed(void * void_arg)
for(int i = 0; i < MAX_SHUFFLE_THEMES; i++) for(int i = 0; i < MAX_SHUFFLE_THEMES; i++)
{ {
FSUSER_UpdateSha256Context(body_buf + BODY_CACHE_SIZE*i, shuffle_body_sizes[i], body_hash[i]); FSUSER_UpdateSha256Context(body_buf + BODY_CACHE_SIZE * i, shuffle_body_sizes[i], body_hash[i]);
} }
free(body_buf); free(body_buf);
@@ -472,7 +739,7 @@ void themes_check_installed(void * void_arg)
{ {
Entry_s * theme = &list->entries[i]; Entry_s * theme = &list->entries[i];
char * theme_body = NULL; char * theme_body = NULL;
u32 theme_body_size = load_data("/body_LZ.bin", *theme, &theme_body); u32 theme_body_size = load_data("/body_LZ.bin", theme, &theme_body);
if(!theme_body_size) return; if(!theme_body_size) return;
u8 theme_body_hash[HASH_SIZE_BYTES]; u8 theme_body_hash[HASH_SIZE_BYTES];

1048
source/ui_strings.c Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -26,40 +26,42 @@
#include "unicode.h" #include "unicode.h"
ssize_t strulen(const u16 *input, ssize_t max_len) ssize_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;
} }
void struacat(u16 *input, const char *addition) void struacat(u16 * input, const char * addition)
{ {
ssize_t len = strulen(input, 0x106); const ssize_t len = strulen(input, 0x106);
for (u16 i = len; i < strlen(addition) + len; i++) const u16 stop_at = strlen(addition);
for (u16 i = 0; i < stop_at; i++)
{ {
input[i] = addition[i - len]; input[i + len] = addition[i];
} }
input[strlen(addition) + len] = 0; input[stop_at + len] = 0;
} }
void printu(u16 *input) void printu(u16 * input)
{ {
ssize_t in_len = strulen(input, 0x106); ssize_t in_len = strulen(input, 0x106);
ssize_t buf_len = in_len + 1; // Plus 1 for proper null termination ssize_t buf_len = in_len + 1; // Plus 1 for proper null termination
wchar_t *buf = calloc(buf_len, sizeof(wchar_t)); wchar_t * buf = calloc(buf_len, sizeof(wchar_t));
utf16_to_utf32((u32*)buf, input, buf_len); utf16_to_utf32((u32 *)buf, input, buf_len);
char cbuf[0x106]; char cbuf[0x106];
sprintf(cbuf, "%ls\n", buf); sprintf(cbuf, "%ls\n", buf);
DEBUG(cbuf); DEBUG(cbuf);
free(buf); free(buf);
} }
u16 *strucat(u16 *destination, const u16 *source) u16 * strucat(u16 * destination, const u16 * source)
{ {
ssize_t dest_len = strulen(destination, 0x106); ssize_t dest_len = strulen(destination, 0x106);
ssize_t source_len = strulen(source, 0x106); ssize_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;
return destination; return destination;
} }