Compare commits
158 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2c53af56cc | ||
|
7bdfce172c | ||
|
34f7f16cb1 | ||
|
73d7108a62 | ||
|
422039f727 | ||
|
3f73855170 | ||
|
6e654ec2af | ||
|
5c85dd784b | ||
|
8c87b1c46f | ||
|
47fd18cabb | ||
|
c1640f4b54 | ||
|
aef37e10f7 | ||
|
95411fe5e9 | ||
|
65f23b0bde | ||
|
20c1f00972 | ||
|
fcbc56acc4 | ||
|
244d058f1b | ||
|
df09e9ed1b | ||
|
45efcfcb98 | ||
|
16958ac4ba | ||
|
c69f4c56c5 | ||
|
3e451a9c8b | ||
|
090054fc86 | ||
|
e2a16a83e0 | ||
|
ba330cfd84 | ||
|
f4067d64f3 | ||
|
88fe495bd8 | ||
|
7f1a9f4f29 | ||
|
ec24d595d6 | ||
|
5dfb9ec328 | ||
|
a4ad7b01b7 | ||
|
6ee5d36084 | ||
|
981749cfb7 | ||
|
9eda227d33 | ||
|
d11585589e | ||
|
42a4ad9787 | ||
|
b40d558458 | ||
|
387850d301 | ||
|
38b48a8609 | ||
|
bd3466f1e1 | ||
|
e5d9851250 | ||
|
15baea1e44 | ||
|
c2743dcdd5 | ||
|
233b765300 | ||
|
a747d92826 | ||
|
24ce4ff924 | ||
|
4ecd7401b6 | ||
|
a150cbe167 | ||
|
2feb085504 | ||
|
97ab367379 | ||
|
793b912efd | ||
|
42efd240de | ||
|
7d01d059df | ||
|
6a75feeb46 | ||
|
d7c37c6861 | ||
|
fa3d93d649 | ||
|
ea4db4ff02 | ||
|
d9effc3143 | ||
|
6c84575ef7 | ||
|
ce35f40f31 | ||
|
78da39d0a2 | ||
|
f2e085b6be | ||
|
3ceb44fd78 | ||
|
a123712db8 | ||
|
48a4819e67 | ||
|
d6c780256f | ||
|
dcad6f2afa | ||
|
753a97ef7b | ||
|
f0ef77b2f2 | ||
|
9e257d7606 | ||
|
7c029c18b0 | ||
|
3ea2af46a5 | ||
|
e91758984c | ||
|
527ecb6ce3 | ||
|
85318335fb | ||
|
6ec7388834 | ||
|
1e3f057b23 | ||
|
29caa76884 | ||
|
d4af9cd2b9 | ||
|
4739c8e730 | ||
|
2a3564f53a | ||
|
8e8e62ac33 | ||
|
d2bb1da2fa | ||
|
0580a6bcdc | ||
|
357913b0bd | ||
|
bb53a8ac81 | ||
|
bc6d98a534 | ||
|
1483f4d012 | ||
|
d616ed02a7 | ||
|
1435a2fb3b | ||
|
982120a9fd | ||
|
2695d48ba7 | ||
|
35f48a59d0 | ||
|
8888bff85f | ||
|
784dbc3623 | ||
|
d97eebc26f | ||
|
e648cc9485 | ||
|
86632292b0 | ||
|
ba4c80d76d | ||
|
6d6fb4e3a3 | ||
|
45a10488b1 | ||
|
59e2d7a306 | ||
|
11dccb4fd0 | ||
|
55efa03f15 | ||
|
437895a3c8 | ||
|
80e2e0aae7 | ||
|
b28ddcaddc | ||
|
0abcb1172a | ||
|
40e971ba99 | ||
|
8b86b36393 | ||
|
afa9bd49d0 | ||
|
e1a1bac230 | ||
|
a059b856b5 | ||
|
020a9f61ba | ||
|
e01ca3150e | ||
|
6e11672e20 | ||
|
589f3a5581 | ||
|
1c8a446920 | ||
|
d39efdaee1 | ||
|
69e11599f4 | ||
|
a9d7a47f2e | ||
|
5b35642de7 | ||
|
992c4c482b | ||
|
985dc946fe | ||
|
93591ef3e1 | ||
|
8909142fb4 | ||
|
4987cbddb1 | ||
|
8034053d99 | ||
|
13676f64fd | ||
|
d36044daea | ||
|
1e2c2cc877 | ||
|
03e7b110e7 | ||
|
509b24578c | ||
|
00e96fd53b | ||
|
c4d22af61e | ||
|
dbd1958837 | ||
|
7c3bb175d9 | ||
|
6544353245 | ||
|
4539b8e78d | ||
|
94b1d648d1 | ||
|
bbf6bf50f7 | ||
|
1e232372d2 | ||
|
4563794505 | ||
|
8506e1b316 | ||
|
f738578dc1 | ||
|
e61285c25e | ||
|
1364106d73 | ||
|
22740b8f4b | ||
|
03a4de8c25 | ||
|
5731858272 | ||
|
ebced71ec3 | ||
|
e684bb1b71 | ||
|
a20cdb5781 | ||
|
9aaac6f01e | ||
|
27d195e118 | ||
|
7d5617254a | ||
|
e93a484e86 | ||
|
bec1047ab9 |
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.*/
|
||||
*~
|
||||
*.exe
|
||||
*.o
|
||||
@ -11,3 +12,5 @@ build
|
||||
*.pfs0
|
||||
*.nacp
|
||||
*.nro
|
||||
test.*
|
||||
switch
|
||||
|
20
Makefile
@ -1,18 +1,30 @@
|
||||
export APP_VERSION := v2.0.0
|
||||
export APP_VERSION := 3.5.1
|
||||
|
||||
ifeq ($(RELEASE),)
|
||||
export APP_VERSION := $(APP_VERSION)-$(shell git describe --dirty --always)
|
||||
endif
|
||||
|
||||
.PHONY: clean all nx pc dist-bin
|
||||
|
||||
all: nx pc
|
||||
|
||||
dist-bin:
|
||||
romfs:
|
||||
@mkdir -p romfs
|
||||
|
||||
romfs/assets.zip : romfs assets
|
||||
@rm -f romfs/assets.zip
|
||||
@zip -rj romfs/assets.zip assets
|
||||
|
||||
dist-bin: romfs/assets.zip
|
||||
$(MAKE) -f Makefile.nx dist-bin
|
||||
|
||||
nx:
|
||||
nx: romfs/assets.zip
|
||||
$(MAKE) -f Makefile.nx
|
||||
|
||||
pc:
|
||||
pc: romfs/assets.zip
|
||||
$(MAKE) -f Makefile.pc
|
||||
|
||||
clean:
|
||||
@rm -Rf romfs
|
||||
$(MAKE) -f Makefile.pc clean
|
||||
$(MAKE) -f Makefile.nx clean
|
||||
|
79
Makefile.nx
@ -15,7 +15,7 @@ include $(DEVKITPRO)/libnx/switch_rules
|
||||
# SOURCES is a list of directories containing source code
|
||||
# DATA is a list of directories containing data files
|
||||
# INCLUDES is a list of directories containing header files
|
||||
# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm".
|
||||
# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional)
|
||||
#
|
||||
# NO_ICON: if set to anything, do not use icon.
|
||||
# NO_NACP: if set to anything, no .nacp file is generated.
|
||||
@ -28,15 +28,25 @@ include $(DEVKITPRO)/libnx/switch_rules
|
||||
# - <Project name>.jpg
|
||||
# - icon.jpg
|
||||
# - <libnx folder>/default_icon.jpg
|
||||
#
|
||||
# CONFIG_JSON is the filename of the NPDM config file (.json), relative to the project folder.
|
||||
# If not set, it attempts to use one of the following (in this order):
|
||||
# - <Project name>.json
|
||||
# - config.json
|
||||
# If a JSON file is provided or autodetected, an ExeFS PFS0 (.nsp) is built instead
|
||||
# of a homebrew executable (.nro). This is intended to be used for sysmodules.
|
||||
# NACP building is skipped as well.
|
||||
#---------------------------------------------------------------------------------
|
||||
TARGET := $(notdir $(CURDIR))
|
||||
BUILD := build
|
||||
SOURCES := common/ nx_main/ nx_main/loaders/
|
||||
DATA := data
|
||||
INCLUDES := include
|
||||
EXEFS_SRC := exefs_src
|
||||
ROMFS := romfs
|
||||
|
||||
DIST_PATH := $(TARGET)_$(APP_VERSION)
|
||||
DIST_PATH := $(TARGET)_v$(APP_VERSION)
|
||||
|
||||
APP_AUTHOR := switchbrew
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
@ -44,16 +54,16 @@ DIST_PATH := $(TARGET)_$(APP_VERSION)
|
||||
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
$(ARCH) $(DEFINES)
|
||||
$(ARCH) $(DEFINES) `freetype-config --cflags`
|
||||
|
||||
CFLAGS += $(INCLUDE) -DSWITCH -DVERSION=\"$(APP_VERSION)\"
|
||||
CFLAGS += $(INCLUDE) -D__SWITCH__ -DVERSION=\"v$(APP_VERSION)\"
|
||||
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
|
||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
||||
|
||||
ASFLAGS := -g $(ARCH)
|
||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||
|
||||
LIBS := -lnx -lm
|
||||
LIBS := -ldeko3d -lphysfs `freetype-config --libs` -lconfig -lturbojpeg -lpng
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
@ -96,8 +106,10 @@ else
|
||||
endif
|
||||
#---------------------------------------------------------------------------------
|
||||
|
||||
export OFILES := $(addsuffix .o,$(BINFILES)) \
|
||||
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
|
||||
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
||||
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||
|
||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||
@ -105,7 +117,18 @@ export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||
|
||||
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
|
||||
|
||||
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
|
||||
ifeq ($(strip $(CONFIG_JSON)),)
|
||||
jsons := $(wildcard *.json)
|
||||
ifneq (,$(findstring $(TARGET).json,$(jsons)))
|
||||
export APP_JSON := $(TOPDIR)/$(TARGET).json
|
||||
else
|
||||
ifneq (,$(findstring config.json,$(jsons)))
|
||||
export APP_JSON := $(TOPDIR)/config.json
|
||||
endif
|
||||
endif
|
||||
else
|
||||
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
|
||||
endif
|
||||
|
||||
ifeq ($(strip $(ICON)),)
|
||||
icons := $(wildcard *.jpg)
|
||||
@ -132,6 +155,10 @@ ifneq ($(APP_TITLEID),)
|
||||
export NACPFLAGS += --titleid=$(APP_TITLEID)
|
||||
endif
|
||||
|
||||
ifneq ($(ROMFS),)
|
||||
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
||||
endif
|
||||
|
||||
.PHONY: $(BUILD) clean all dist-bin
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
@ -144,7 +171,11 @@ $(BUILD):
|
||||
#---------------------------------------------------------------------------------
|
||||
clean:
|
||||
@echo clean ...
|
||||
@rm -fr $(BUILD) $(TARGET).pfs0 $(TARGET).nso $(TARGET).nro $(TARGET).nacp $(TARGET).elf
|
||||
ifeq ($(strip $(APP_JSON)),)
|
||||
@rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf
|
||||
else
|
||||
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf
|
||||
endif
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
dist-bin: all
|
||||
@ -161,11 +192,9 @@ DEPENDS := $(OFILES:.o=.d)
|
||||
#---------------------------------------------------------------------------------
|
||||
# main targets
|
||||
#---------------------------------------------------------------------------------
|
||||
all : $(OUTPUT).pfs0 $(OUTPUT).nro
|
||||
ifeq ($(strip $(APP_JSON)),)
|
||||
|
||||
$(OUTPUT).pfs0 : $(OUTPUT).nso
|
||||
|
||||
$(OUTPUT).nso : $(OUTPUT).elf
|
||||
all : $(OUTPUT).nro
|
||||
|
||||
ifeq ($(strip $(NO_NACP)),)
|
||||
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
|
||||
@ -173,17 +202,25 @@ else
|
||||
$(OUTPUT).nro : $(OUTPUT).elf
|
||||
endif
|
||||
|
||||
else
|
||||
|
||||
all : $(OUTPUT).nsp
|
||||
|
||||
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
|
||||
|
||||
$(OUTPUT).nso : $(OUTPUT).elf
|
||||
|
||||
endif
|
||||
|
||||
menu.o : $(TOPDIR)/Makefile
|
||||
$(OUTPUT).elf : $(OFILES)
|
||||
|
||||
$(OFILES_SRC) : $(HFILES_BIN)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# you need a rule like this for each extension you use as binary data
|
||||
#---------------------------------------------------------------------------------
|
||||
%.bin.o : %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
%.nxfnt.o : %.nxfnt
|
||||
%.bin.o %_bin.h : %.bin
|
||||
#---------------------------------------------------------------------------------
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
103
Makefile.pc
@ -1,93 +1,20 @@
|
||||
# canned command sequence for binary data
|
||||
#---------------------------------------------------------------------------------
|
||||
define bin2o
|
||||
bin2s $< | $(AS) -o $(@)
|
||||
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"_end[];" > `(echo $(<F) | tr . _)`.h
|
||||
echo "extern const u8" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`"[];" >> `(echo $(<F) | tr . _)`.h
|
||||
echo "extern const u32" `(echo $(<F) | sed -e 's/^\([0-9]\)/_\1/' | tr . _)`_size";" >> `(echo $(<F) | tr . _)`.h
|
||||
endef
|
||||
HOST_OS := $(shell uname -s)
|
||||
|
||||
test : pc_main/main.cpp pc_main/pc_launch.c \
|
||||
common/menu.c common/font.c common/language.c common/launch.c \
|
||||
common/menu-entry.c common/menu-list.c common/text.c \
|
||||
common/nanojpeg.c common/ui.c common/math.c common/theme.c \
|
||||
build_pc/tahoma24.o build_pc/tahoma12.o build_pc/interuimedium20.o build_pc/interuimedium30.o \
|
||||
build_pc/interuiregular14.o build_pc/interuiregular18.o \
|
||||
build_pc/invalid_icon.bin.o build_pc/folder_icon.bin.o \
|
||||
build_pc/button_a_light.bin.o build_pc/button_a_dark.bin.o build_pc/button_b_light.bin.o build_pc/button_b_dark.bin.o build_pc/hbmenu_logo_light.bin.o build_pc/hbmenu_logo_dark.bin.o
|
||||
gcc -Wall -O2 -g0 -DVERSION=\"$(APP_VERSION)\" $^ -lsfml-graphics -lsfml-window -lsfml-system -lstdc++ -lm -I. -Ibuild_pc -o $@
|
||||
|
||||
build_pc/tahoma12.o : data/tahoma12.nxfnt
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/tahoma24.o : data/tahoma24.nxfnt
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/interuimedium20.o : data/interuimedium20.nxfnt
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/interuimedium30.o : data/interuimedium30.nxfnt
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/interuiregular14.o : data/interuiregular14.nxfnt
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/interuiregular18.o : data/interuiregular18.nxfnt
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/invalid_icon.bin.o : data/invalid_icon.bin
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/folder_icon.bin.o : data/folder_icon.bin
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/button_a_light.bin.o : data/button_a_light.bin
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/button_a_dark.bin.o : data/button_a_dark.bin
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/button_b_light.bin.o : data/button_b_light.bin
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/button_b_dark.bin.o : data/button_b_dark.bin
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/hbmenu_logo_light.bin.o : data/hbmenu_logo_light.bin
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/hbmenu_logo_dark.bin.o : data/hbmenu_logo_dark.bin
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
ifeq ($(strip $(HOST_OS)),Darwin)
|
||||
BIN2S_FLAGS := --apple-llvm
|
||||
endif
|
||||
|
||||
ifneq (,$(findstring MINGW,$(HOST_OS)))
|
||||
EXTRA_CFLAGS="-D__USE_MINGW_ANSI_STDIO"
|
||||
EXTRA_LDFLAGS="-lws2_32"
|
||||
endif
|
||||
|
||||
test : pc_main/main.cpp pc_main/pc_launch.c pc_main/pc_power.c pc_main/pc_netstatus.c pc_main/pc_thermalstatus.c \
|
||||
common/menu.c common/font.c common/language.c common/launch.c common/worker.c common/status.c \
|
||||
common/menu-entry.c common/menu-list.c common/message-box.c common/text.c \
|
||||
common/ui.c common/assets.c common/math.c common/theme.c \
|
||||
common/netloader.c
|
||||
gcc -Wall -O2 -g -DVERSION=\"v$(APP_VERSION)\" $(EXTRA_CFLAGS) `pkg-config freetype2 --cflags` $^ -lsfml-graphics -lsfml-window -lsfml-system -lstdc++ -lpthread `pkg-config freetype2 --libs` -lm -lphysfs -lz -lconfig -lturbojpeg -lpng $(EXTRA_LDFLAGS) -I. -iquote $(DEVKITPRO)/libnx/include -Ibuild_pc -g -o $@
|
||||
|
||||
clean:
|
||||
rm -rf build_pc/ test
|
||||
rm -rf build_pc/ test test.*
|
||||
|
30
README.md
@ -1,13 +1,31 @@
|
||||
#### Usage
|
||||
See [Switchbrew](http://switchbrew.org/index.php?title=Homebrew_Applications) for SD layout, applications, etc.
|
||||
### Usage
|
||||
See [Homebrew_Applications](https://switchbrew.org/wiki/Homebrew_Applications) for SD layout and applications, etc. See [Switchbrew](https://switchbrew.org/wiki/Homebrew_Menu) for hbmenu docs.
|
||||
|
||||
#### Download
|
||||
### Download
|
||||
The latest release is available from the [releases](https://github.com/switchbrew/nx-hbmenu/releases/latest) page.
|
||||
|
||||
#### Building
|
||||
Build with ```make nx``` or just run ```make```.
|
||||
### Building
|
||||
Build for the Nintendo Switch with ```make nx``` and for the PC with ```make pc```.
|
||||
Running ```make``` builds for both systems.
|
||||
|
||||
The following [pacman packages](https://devkitpro.org/wiki/devkitPro_pacman) are required to build for Switch:
|
||||
- `switch-dev`
|
||||
- `switch-freetype`
|
||||
- `switch-libconfig`
|
||||
- `switch-libjpeg-turbo`
|
||||
- `switch-physfs`
|
||||
|
||||
The following libraries are required to build for PC:
|
||||
- `libfreetype`
|
||||
- `libconfig`
|
||||
- `libjpeg-turbo`
|
||||
- `libphysfs`
|
||||
|
||||
Building for Switch/PC requires `zip`.
|
||||
|
||||
Since C11 threads are used, building for the PC may fail if C11 threads are not available.
|
||||
|
||||
#### Credits
|
||||
|
||||
* This uses code based on 3DS [new-hbmenu](https://github.com/fincs/new-hbmenu).
|
||||
* [nanojpeg](https://svn.emphy.de/nanojpeg/trunk/nanojpeg/nanojpeg.c) is used for handling JPEG icons. Due to this some JPEGs are not [supported](https://github.com/switchbrew/nx-hbmenu/issues/7).
|
||||
* `libjpeg-turbo` is used for handling JPEG icons. This library doesn't support lossless JPEG (likewise for official sw which uses `libjpeg-turbo`).
|
||||
|
BIN
assets/airplane_icon.bin
Normal file
BIN
assets/battery_icon.bin
Normal file
BIN
assets/charging_icon.bin
Normal file
BIN
assets/eth_icon.bin
Normal file
BIN
assets/eth_none_icon.bin
Normal file
1
assets/theme_icon_dark.bin
Normal file
1
assets/theme_icon_light.bin
Normal file
BIN
assets/wifi1_icon.bin
Normal file
BIN
assets/wifi2_icon.bin
Normal file
BIN
assets/wifi3_icon.bin
Normal file
BIN
assets/wifi_none_icon.bin
Normal file
277
common/assets.c
Normal file
@ -0,0 +1,277 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <physfs.h>
|
||||
#include <png.h>
|
||||
|
||||
#define GENASSET(_p, _mode, _w, _h) {{.path = _p, .imageMode = _mode, .imageSize = {_w, _h}}, {}}
|
||||
|
||||
static bool g_assetsInitialized = 0;
|
||||
assetsDataEntry g_assetsDataList[AssetId_Max][2] = {
|
||||
GENASSET("battery_icon.bin", IMAGE_MODE_RGBA32, 24, 24),
|
||||
GENASSET("charging_icon.bin", IMAGE_MODE_RGBA32, 24, 24),
|
||||
GENASSET("folder_icon.bin", IMAGE_MODE_RGB24, 256, 256),
|
||||
GENASSET("invalid_icon.bin", IMAGE_MODE_RGB24, 256, 256),
|
||||
GENASSET("hbmenu_logo_dark.bin", IMAGE_MODE_RGBA32, 140, 60),
|
||||
GENASSET("hbmenu_logo_light.bin", IMAGE_MODE_RGBA32, 140, 60),
|
||||
GENASSET("theme_icon_dark.bin", IMAGE_MODE_RGB24, 256, 256),
|
||||
GENASSET("theme_icon_light.bin", IMAGE_MODE_RGB24, 256, 256),
|
||||
GENASSET("airplane_icon.bin", IMAGE_MODE_RGBA32, 24, 24),
|
||||
GENASSET("wifi_none_icon.bin", IMAGE_MODE_RGBA32, 24, 24),
|
||||
GENASSET("wifi1_icon.bin", IMAGE_MODE_RGBA32, 24, 24),
|
||||
GENASSET("wifi2_icon.bin", IMAGE_MODE_RGBA32, 24, 24),
|
||||
GENASSET("wifi3_icon.bin", IMAGE_MODE_RGBA32, 24, 24),
|
||||
GENASSET("eth_icon.bin", IMAGE_MODE_RGBA32, 24, 24),
|
||||
GENASSET("eth_none_icon.bin", IMAGE_MODE_RGBA32, 24, 24),
|
||||
GENASSET("", IMAGE_MODE_RGB24, 1280, 720),
|
||||
};
|
||||
|
||||
static void assetsClearEntry(assetsDataEntry *entry) {
|
||||
free(entry->buffer);
|
||||
|
||||
entry->size = 0;
|
||||
entry->buffer = NULL;
|
||||
|
||||
memset(entry, 0, sizeof(*entry));
|
||||
}
|
||||
|
||||
static void assetsSetPixelSize(assetsDataEntry *entry) {
|
||||
switch (entry->imageMode) {
|
||||
case IMAGE_MODE_RGB24:
|
||||
entry->pixSize = 3;
|
||||
break;
|
||||
|
||||
case IMAGE_MODE_RGBA32:
|
||||
entry->pixSize = 4;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Result assetsInit(void) {
|
||||
bool ret=false;
|
||||
int i, stopi;
|
||||
assetsDataEntry *entry = NULL;
|
||||
char tmp_path[PATH_MAX];
|
||||
|
||||
if (g_assetsInitialized) return 0;
|
||||
|
||||
#ifdef __SWITCH__
|
||||
Result rc = romfsInit();
|
||||
if (R_FAILED(rc)) return rc;
|
||||
#endif
|
||||
|
||||
memset(tmp_path, 0, sizeof(tmp_path));
|
||||
|
||||
#ifdef __SWITCH__
|
||||
strncpy(tmp_path, "romfs:/assets.zip", sizeof(tmp_path)-1);
|
||||
#else
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s/romfs/assets.zip", menuGetRootBasePath());
|
||||
#endif
|
||||
|
||||
if (PHYSFS_mount(tmp_path, "", 0)) {
|
||||
ret=true;
|
||||
for (i=0; i<AssetId_Max; i++) {
|
||||
stopi = i;
|
||||
entry = &g_assetsDataList[i][0];
|
||||
if (entry->path[0]) {
|
||||
ret = assetsLoadData(i, NULL, NULL);
|
||||
if (!ret) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
for (i=0; i<stopi; i++) {
|
||||
assetsClearEntry(&g_assetsDataList[i][0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) g_assetsInitialized = 1;
|
||||
|
||||
PHYSFS_unmount(tmp_path);
|
||||
}
|
||||
|
||||
#ifdef __SWITCH__
|
||||
romfsExit();
|
||||
return ret ? 0 : MAKERESULT(Module_Libnx, LibnxError_IoError);
|
||||
#else
|
||||
return ret ? 0 : 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
void assetsExit(void) {
|
||||
if (!g_assetsInitialized) return;
|
||||
g_assetsInitialized = 0;
|
||||
|
||||
for (int i=0; i<AssetId_Max; i++) {
|
||||
assetsClearEntry(&g_assetsDataList[i][0]);
|
||||
}
|
||||
|
||||
assetsClearTheme();
|
||||
}
|
||||
|
||||
void assetsClearTheme(void) {
|
||||
for (int i=0; i<AssetId_Max; i++) {
|
||||
assetsClearEntry(&g_assetsDataList[i][1]);
|
||||
}
|
||||
}
|
||||
|
||||
bool assetsLoadJpgFromMemory(u8 *indata, size_t indata_size, u8 *outdata, ImageMode imageMode, size_t width, size_t height) {
|
||||
int w,h,samp;
|
||||
|
||||
tjhandle _jpegDecompressor = tjInitDecompress();
|
||||
|
||||
if (_jpegDecompressor == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tjDecompressHeader2(_jpegDecompressor, indata, indata_size, &w, &h, &samp) == -1) {
|
||||
tjDestroy(_jpegDecompressor);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (w != width || h != height ) {
|
||||
tjDestroy(_jpegDecompressor);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tjDecompress2(_jpegDecompressor, indata, indata_size, outdata, w, 0, h, imageMode == IMAGE_MODE_RGB24 ? TJPF_RGB : TJPF_RGBA, TJFLAG_ACCURATEDCT) == -1) {
|
||||
tjDestroy(_jpegDecompressor);
|
||||
return false;
|
||||
}
|
||||
|
||||
tjDestroy(_jpegDecompressor);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool assetsLoadPngFromMemory(u8 *indata, size_t indata_size, u8 *outdata, ImageMode imageMode, size_t width, size_t height) {
|
||||
png_image image;
|
||||
bool ret=true;
|
||||
|
||||
memset(&image, 0, sizeof(image));
|
||||
image.version = PNG_IMAGE_VERSION;
|
||||
|
||||
if (png_image_begin_read_from_memory(&image, indata, indata_size) != 0) {
|
||||
if (image.width != width || image.height != height)
|
||||
ret = false;
|
||||
|
||||
if (ret) image.format = imageMode == IMAGE_MODE_RGB24 ? PNG_FORMAT_RGB : PNG_FORMAT_RGBA;
|
||||
|
||||
if (ret && png_image_finish_read(&image, NULL, outdata, 0, NULL) == 0)
|
||||
ret = false;
|
||||
}
|
||||
else
|
||||
ret = false;
|
||||
|
||||
png_image_free(&image);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool assetsPhysfsReadFile(const char *path, u8 **data_buf, size_t *filesize, bool nul_term) {
|
||||
bool ret=true;
|
||||
*data_buf = NULL;
|
||||
if (filesize) *filesize = 0;
|
||||
|
||||
PHYSFS_Stat tmpstat={0};
|
||||
if (!(PHYSFS_stat(path, &tmpstat) && tmpstat.filesize!=-1)) ret = false;
|
||||
|
||||
if (ret) {
|
||||
size_t bufsize = tmpstat.filesize;
|
||||
if (nul_term) bufsize++;
|
||||
*data_buf = (u8*)malloc(bufsize);
|
||||
if (*data_buf) memset(*data_buf, 0, bufsize);
|
||||
else ret = false;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
PHYSFS_File *f = PHYSFS_openRead(path);
|
||||
if (f==NULL) ret = false;
|
||||
else {
|
||||
ret = PHYSFS_readBytes(f, *data_buf, tmpstat.filesize) == tmpstat.filesize;
|
||||
PHYSFS_close(f);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
if (filesize) *filesize = tmpstat.filesize;
|
||||
}
|
||||
else {
|
||||
free(*data_buf);
|
||||
*data_buf = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool assetsLoadData(AssetId id, const char *path, int *imageSize) {
|
||||
if (id < 0 || id >= AssetId_Max) return false;
|
||||
|
||||
assetsDataEntry *entry = &g_assetsDataList[id][path ? 1 : 0];
|
||||
if (entry->initialized) return false;
|
||||
|
||||
if (path) memset(entry, 0, sizeof(*entry));
|
||||
|
||||
if (imageSize) {
|
||||
entry->imageSize[0] = imageSize[0];
|
||||
entry->imageSize[1] = imageSize[1];
|
||||
}
|
||||
|
||||
if (path) entry->imageMode = g_assetsDataList[id][0].imageMode;
|
||||
assetsSetPixelSize(entry);
|
||||
entry->size = entry->imageSize[0] * entry->imageSize[1] * entry->pixSize;
|
||||
|
||||
if (path) strncpy(entry->path, path, sizeof(entry->path)-1);
|
||||
|
||||
const char* ext = getExtension(entry->path);
|
||||
bool ret=true;
|
||||
size_t filesize=0;
|
||||
if (ext==NULL) ret = false;
|
||||
|
||||
u8 *data_buf = NULL;
|
||||
|
||||
if (ret) {
|
||||
entry->buffer = (u8*)malloc(entry->size);
|
||||
if (entry->buffer) memset(entry->buffer, 0, entry->size);
|
||||
else ret = false;
|
||||
}
|
||||
|
||||
if (ret) ret = assetsPhysfsReadFile(entry->path, &data_buf, &filesize, false);
|
||||
|
||||
if (ret) {
|
||||
if (strcasecmp(ext, ".bin")==0) {
|
||||
if (filesize != entry->size) ret = false;
|
||||
|
||||
if (ret) memcpy(entry->buffer, data_buf, entry->size);
|
||||
}
|
||||
else if (strcasecmp(ext, ".jpg")==0 || strcasecmp(ext, ".jpeg")==0)
|
||||
ret = assetsLoadJpgFromMemory(data_buf, filesize, entry->buffer, entry->imageMode, entry->imageSize[0], entry->imageSize[1]);
|
||||
else if (strcasecmp(ext, ".png")==0)
|
||||
ret = assetsLoadPngFromMemory(data_buf, filesize, entry->buffer, entry->imageMode, entry->imageSize[0], entry->imageSize[1]);
|
||||
else
|
||||
ret = false; // File extension not recognized.
|
||||
}
|
||||
|
||||
if (ret) entry->initialized = true;
|
||||
else assetsClearEntry(entry);
|
||||
|
||||
free(data_buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void assetsGetData(AssetId id, assetsDataEntry **out) {
|
||||
if (out) *out = NULL;
|
||||
if (id < 0 || id >= AssetId_Max) return;
|
||||
|
||||
u32 pos = g_assetsDataList[id][1].initialized ? 1 : 0;
|
||||
assetsDataEntry *entry = &g_assetsDataList[id][pos];
|
||||
if (entry->initialized) *out = entry;
|
||||
}
|
||||
|
||||
u8 *assetsGetDataBuffer(AssetId id) {
|
||||
assetsDataEntry *entry = NULL;
|
||||
|
||||
assetsGetData(id, &entry);
|
||||
return entry ? entry->buffer : NULL;
|
||||
}
|
||||
|
44
common/assets.h
Normal file
@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
typedef enum {
|
||||
AssetId_battery_icon,
|
||||
AssetId_charging_icon,
|
||||
AssetId_folder_icon,
|
||||
AssetId_invalid_icon,
|
||||
AssetId_hbmenu_logo_dark,
|
||||
AssetId_hbmenu_logo_light,
|
||||
AssetId_theme_icon_dark,
|
||||
AssetId_theme_icon_light,
|
||||
AssetId_airplane_icon,
|
||||
AssetId_wifi_none_icon,
|
||||
AssetId_wifi1_icon,
|
||||
AssetId_wifi2_icon,
|
||||
AssetId_wifi3_icon,
|
||||
AssetId_eth_icon,
|
||||
AssetId_eth_none_icon,
|
||||
AssetId_background_image,
|
||||
|
||||
AssetId_Max,
|
||||
} AssetId;
|
||||
|
||||
typedef struct {
|
||||
bool initialized;
|
||||
u8 *buffer;
|
||||
size_t size;
|
||||
ImageMode imageMode;
|
||||
size_t pixSize;
|
||||
size_t imageSize[2];
|
||||
char path[PATH_MAX];
|
||||
} assetsDataEntry;
|
||||
|
||||
Result assetsInit(void);
|
||||
void assetsExit(void);
|
||||
void assetsClearTheme(void);
|
||||
bool assetsPhysfsReadFile(const char *path, u8 **data_buf, size_t *filesize, bool nul_term);
|
||||
bool assetsLoadData(AssetId id, const char *path, int *imageSize);
|
||||
void assetsGetData(AssetId id, assetsDataEntry **out);
|
||||
u8 *assetsGetDataBuffer(AssetId id);
|
||||
|
||||
bool assetsLoadJpgFromMemory(u8 *indata, size_t indata_size, u8 *outdata, ImageMode imageMode, size_t width, size_t height);
|
||||
|
113
common/common.h
@ -10,16 +10,34 @@
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <threads.h>
|
||||
#ifndef __APPLE__
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
#include <math.h>
|
||||
#ifdef SWITCH
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
typedef uint8_t u8;
|
||||
typedef uint16_t u16;
|
||||
typedef uint32_t u32;
|
||||
typedef uint64_t u64;
|
||||
typedef int8_t s8;
|
||||
typedef int32_t s32;
|
||||
typedef u32 Result;
|
||||
|
||||
typedef void (*workerThreadFunc)(void *);
|
||||
|
||||
#ifdef _WIN32
|
||||
#define DIRECTORY_SEPARATOR_CHAR '\\'
|
||||
static const char DIRECTORY_SEPARATOR[] = "\\";
|
||||
#else
|
||||
#define DIRECTORY_SEPARATOR_CHAR '/'
|
||||
static const char DIRECTORY_SEPARATOR[] = "/";
|
||||
#endif
|
||||
|
||||
|
||||
#define M_TAU (2*M_PI)
|
||||
|
||||
@ -30,23 +48,76 @@ typedef union {
|
||||
};
|
||||
} color_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
ThemeLayoutId_Logo,
|
||||
ThemeLayoutId_HbmenuVersion,
|
||||
ThemeLayoutId_LoaderInfo,
|
||||
ThemeLayoutId_AttentionText,
|
||||
ThemeLayoutId_LogInfo,
|
||||
ThemeLayoutId_InfoMsg,
|
||||
ThemeLayoutId_MenuPath,
|
||||
ThemeLayoutId_MenuTypeMsg,
|
||||
ThemeLayoutId_MsgBoxSeparator,
|
||||
ThemeLayoutId_MsgBoxBottomText,
|
||||
ThemeLayoutId_BackgroundImage,
|
||||
ThemeLayoutId_BackWave,
|
||||
ThemeLayoutId_MiddleWave,
|
||||
ThemeLayoutId_FrontWave,
|
||||
ThemeLayoutId_ButtonA,
|
||||
ThemeLayoutId_ButtonAText,
|
||||
ThemeLayoutId_ButtonB,
|
||||
ThemeLayoutId_ButtonBText,
|
||||
ThemeLayoutId_ButtonY,
|
||||
ThemeLayoutId_ButtonYText,
|
||||
ThemeLayoutId_ButtonM,
|
||||
ThemeLayoutId_ButtonMText,
|
||||
ThemeLayoutId_ButtonX,
|
||||
ThemeLayoutId_ButtonXText,
|
||||
ThemeLayoutId_NetworkIcon,
|
||||
ThemeLayoutId_BatteryCharge,
|
||||
ThemeLayoutId_BatteryIcon,
|
||||
ThemeLayoutId_ChargingIcon,
|
||||
ThemeLayoutId_Status,
|
||||
ThemeLayoutId_Temperature,
|
||||
ThemeLayoutId_MenuList,
|
||||
ThemeLayoutId_MenuListTiles,
|
||||
ThemeLayoutId_MenuListIcon,
|
||||
ThemeLayoutId_MenuListName,
|
||||
ThemeLayoutId_MenuActiveEntryIcon,
|
||||
ThemeLayoutId_MenuActiveEntryName,
|
||||
ThemeLayoutId_MenuActiveEntryAuthor,
|
||||
ThemeLayoutId_MenuActiveEntryVersion,
|
||||
ThemeLayoutId_Total,
|
||||
} ThemeLayoutId;
|
||||
|
||||
// when building for pc we need to include these separately
|
||||
#ifndef __SWITCH__
|
||||
#include "switch/nro.h"
|
||||
#include "switch/nacp.h"
|
||||
#endif
|
||||
|
||||
#include "font.h"
|
||||
#include "nacp.h"
|
||||
#include "menu.h"
|
||||
#include "text.h"
|
||||
#include "ui.h"
|
||||
#include "assets.h"
|
||||
#include "launch.h"
|
||||
#include "nanojpeg.h"
|
||||
#include "worker.h"
|
||||
#include <turbojpeg.h>
|
||||
#include "math.h"
|
||||
#include "theme.h"
|
||||
#include "message-box.h"
|
||||
#include "power.h"
|
||||
#include "netloader.h"
|
||||
#include "netstatus.h"
|
||||
#include "thermalstatus.h"
|
||||
#include "status.h"
|
||||
|
||||
// when building for pc we need to include nro.h separately
|
||||
#ifndef SWITCH
|
||||
#include "nro.h"
|
||||
#endif
|
||||
|
||||
void menuStartup();
|
||||
void menuLoop();
|
||||
void menuStartupPath(void);
|
||||
void menuStartup(void);
|
||||
void themeMenuStartup(void);
|
||||
void menuLoop(void);
|
||||
|
||||
static inline uint8_t BlendColor(uint32_t src, uint32_t dst, uint8_t alpha)
|
||||
{
|
||||
@ -64,14 +135,14 @@ static inline color_t MakeColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
return clr;
|
||||
}
|
||||
|
||||
#ifdef SWITCH
|
||||
#ifdef __SWITCH__
|
||||
extern uint8_t* g_framebuf;
|
||||
extern u32 g_framebuf_width;
|
||||
static inline void DrawPixel(uint32_t x, uint32_t y, color_t clr)
|
||||
{
|
||||
if (x >= 1280 || y >= 720)
|
||||
return;
|
||||
u32 off = (y * g_framebuf_width + x)*4;
|
||||
u32 off = y*g_framebuf_width + x*4;
|
||||
g_framebuf[off] = BlendColor(g_framebuf[off], clr.r, clr.a); off++;
|
||||
g_framebuf[off] = BlendColor(g_framebuf[off], clr.g, clr.a); off++;
|
||||
g_framebuf[off] = BlendColor(g_framebuf[off], clr.b, clr.a); off++;
|
||||
@ -81,7 +152,7 @@ static inline void DrawPixelRaw(uint32_t x, uint32_t y, color_t clr)
|
||||
{
|
||||
if (x >= 1280 || y >= 720)
|
||||
return;
|
||||
u32 off = (y * g_framebuf_width + x)*4;
|
||||
u32 off = y*g_framebuf_width + x*4;
|
||||
*((u32*)&g_framebuf[off]) = clr.r | (clr.g<<8) | (clr.b<<16) | (0xff<<24);
|
||||
}
|
||||
static inline void Draw4PixelsRaw(uint32_t x, uint32_t y, color_t clr)
|
||||
@ -91,12 +162,12 @@ static inline void Draw4PixelsRaw(uint32_t x, uint32_t y, color_t clr)
|
||||
|
||||
u32 color = clr.r | (clr.g<<8) | (clr.b<<16) | (0xff<<24);
|
||||
u128 val = color | ((u128)color<<32) | ((u128)color<<64) | ((u128)color<<96);
|
||||
u32 off = (y * g_framebuf_width + x)*4;
|
||||
u32 off = y*g_framebuf_width + x*4;
|
||||
*((u128*)&g_framebuf[off]) = val;
|
||||
}
|
||||
static inline color_t FetchPixelColor(uint32_t x, uint32_t y)
|
||||
{
|
||||
u32 off = (y * g_framebuf_width + x)*4;
|
||||
u32 off = y*g_framebuf_width + x*4;
|
||||
u32 val = *((u32*)&g_framebuf[off]);
|
||||
u8 r = (u8)val;
|
||||
u8 g = (u8)(val>>8);
|
||||
@ -135,5 +206,13 @@ static inline color_t FetchPixelColor(uint32_t x, uint32_t y)
|
||||
#endif
|
||||
|
||||
void DrawPixel(uint32_t x, uint32_t y, color_t clr);
|
||||
void DrawText(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t clr, const char* text);
|
||||
void DrawTextTruncate(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t clr, const char* text, uint32_t max_width, const char* end_text);
|
||||
void DrawText(u32 font, uint32_t x, uint32_t y, color_t clr, const char* text);
|
||||
void DrawTextFromLayout(ThemeLayoutId id, color_t clr, const char* text);
|
||||
void DrawTextFromLayoutRelative(ThemeLayoutId id, int base_x, int base_y, int *inPos, int *outPos, color_t clr, const char* text, const char align);
|
||||
void DrawTextTruncate(u32 font, uint32_t x, uint32_t y, color_t clr, const char* text, uint32_t max_width, const char* end_text);
|
||||
void GetTextDimensions(u32 font, const char* text, uint32_t* width_out, uint32_t* height_out);
|
||||
uint32_t GetTextXCoordinate(u32 font, uint32_t rX, const char* text, const char align);
|
||||
uint32_t GetTextYCoordinate(u32 font, uint32_t rY, const char* text, const char align);
|
||||
|
||||
bool fontInitialize(void);
|
||||
void fontExit();
|
||||
|
334
common/font.c
@ -1,6 +1,77 @@
|
||||
#include "common.h"
|
||||
|
||||
static inline const ffnt_page_t* FontGetPage(const ffnt_header_t* font, uint32_t page_id)
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
#ifdef __SWITCH__
|
||||
#define FONT_FACES_MAX PlSharedFontType_Total
|
||||
#else
|
||||
#define FONT_FACES_MAX 2
|
||||
#endif
|
||||
|
||||
#ifdef __SWITCH__
|
||||
static bool s_plinited;
|
||||
#endif
|
||||
|
||||
static FT_Error s_font_libret=1, s_font_facesret[FONT_FACES_MAX];
|
||||
|
||||
static FT_Library s_font_library;
|
||||
static FT_Face s_font_faces[FONT_FACES_MAX];
|
||||
static FT_Face s_font_lastusedface;
|
||||
static s32 s_font_faces_total = 0;
|
||||
|
||||
static bool FontSetType(u32 font)
|
||||
{
|
||||
s32 i=0;
|
||||
u32 scale=0;
|
||||
FT_Error ret=0;
|
||||
|
||||
switch(font)
|
||||
{
|
||||
case interuiregular14:
|
||||
scale = 4;
|
||||
break;
|
||||
|
||||
case interuiregular18:
|
||||
scale = 5;
|
||||
break;
|
||||
|
||||
case interuimedium20:
|
||||
scale = 6;
|
||||
break;
|
||||
|
||||
case fontscale7:
|
||||
scale = 7;
|
||||
break;
|
||||
|
||||
case interuimedium30:
|
||||
scale = 8;
|
||||
break;
|
||||
|
||||
case largestar:
|
||||
scale = 18;
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
for (i=0; i<s_font_faces_total; i++) {
|
||||
ret = FT_Set_Char_Size(
|
||||
s_font_faces[i], /* handle to face object */
|
||||
0, /* char_width in 1/64th of points */
|
||||
scale*64, /* char_height in 1/64th of points */
|
||||
300, /* horizontal device resolution */
|
||||
300); /* vertical device resolution */
|
||||
|
||||
if (ret) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*static inline const ffnt_page_t* FontGetPage(const ffnt_header_t* font, uint32_t page_id)
|
||||
{
|
||||
//__builtin_printf("GetPage %u\n", (unsigned int)page_id);
|
||||
if (page_id >= font->npages)
|
||||
@ -9,27 +80,69 @@ static inline const ffnt_page_t* FontGetPage(const ffnt_header_t* font, uint32_t
|
||||
if (ent->size == 0)
|
||||
return NULL;
|
||||
return (const ffnt_page_t*)((const uint8_t*)font + ent->offset);
|
||||
}
|
||||
}*/
|
||||
|
||||
static inline bool FontLoadGlyph(glyph_t* glyph, const ffnt_header_t* font, uint32_t codepoint)
|
||||
static inline bool FontLoadGlyph(glyph_t* glyph, u32 font, uint32_t codepoint)
|
||||
{
|
||||
FT_Face face=0;
|
||||
FT_Error ret=0;
|
||||
FT_GlyphSlot slot;
|
||||
FT_UInt glyph_index;
|
||||
FT_Bitmap* bitmap;
|
||||
s32 i=0;
|
||||
|
||||
//__builtin_printf("LoadGlyph %u\n", (unsigned int)codepoint);
|
||||
const ffnt_page_t* page = FontGetPage(font, codepoint >> 8);
|
||||
/*const ffnt_page_t* page = FontGetPage(font, codepoint >> 8);
|
||||
if (!page)
|
||||
return false;
|
||||
|
||||
codepoint &= 0xFF;
|
||||
uint32_t off = page->hdr.pos[codepoint];
|
||||
if (off == ~(uint32_t)0)
|
||||
return false;
|
||||
return false;*/
|
||||
|
||||
if (s_font_faces_total==0) return false;
|
||||
|
||||
for (i=0; i<s_font_faces_total; i++) {
|
||||
face = s_font_faces[i];
|
||||
s_font_lastusedface = face;
|
||||
glyph_index = FT_Get_Char_Index(face, codepoint);
|
||||
if (glyph_index==0) continue;
|
||||
|
||||
ret = FT_Load_Glyph(
|
||||
face, /* handle to face object */
|
||||
glyph_index, /* glyph index */
|
||||
FT_LOAD_DEFAULT);
|
||||
|
||||
if (ret==0)
|
||||
{
|
||||
ret = FT_Render_Glyph( face->glyph, /* glyph slot */
|
||||
FT_RENDER_MODE_NORMAL); /* render mode */
|
||||
}
|
||||
|
||||
if (ret) return false;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
slot = face->glyph;
|
||||
bitmap = &slot->bitmap;
|
||||
|
||||
//__builtin_printf("%c %u\n", (char)codepoint, (unsigned int)off);
|
||||
glyph->width = page->hdr.widths[codepoint];
|
||||
/*glyph->width = page->hdr.widths[codepoint];
|
||||
glyph->height = page->hdr.heights[codepoint];
|
||||
glyph->advance = page->hdr.advances[codepoint];
|
||||
glyph->posX = page->hdr.posX[codepoint];
|
||||
glyph->posY = page->hdr.posY[codepoint];
|
||||
glyph->data = &page->data[off];
|
||||
glyph->data = &page->data[off];*/
|
||||
|
||||
glyph->width = bitmap->width;
|
||||
glyph->height = bitmap->rows;
|
||||
glyph->pitch = bitmap->pitch;
|
||||
glyph->data = bitmap->buffer;
|
||||
glyph->advance = slot->advance.x >> 6;
|
||||
glyph->posX = slot->bitmap_left;
|
||||
glyph->posY = slot->bitmap_top;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -38,16 +151,17 @@ static void DrawGlyph(uint32_t x, uint32_t y, color_t clr, const glyph_t* glyph)
|
||||
uint32_t i, j;
|
||||
const uint8_t* data = glyph->data;
|
||||
x += glyph->posX;
|
||||
y += glyph->posY;
|
||||
y -= glyph->posY; //y += glyph->posY;
|
||||
//__builtin_printf("DrawGlyph %u %u %08X\n", (unsigned int)x, (unsigned int)y, (unsigned int)clr.abgr);
|
||||
for (j = 0; j < glyph->height; j ++)
|
||||
{
|
||||
for (i = 0; i < glyph->width; i ++)
|
||||
{
|
||||
clr.a = *data++;
|
||||
clr.a = data[i];
|
||||
if (!clr.a) continue;
|
||||
DrawPixel(x+i, y+j, clr);
|
||||
}
|
||||
data+= glyph->pitch;
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,11 +232,15 @@ static inline uint32_t DecodeUTF8(const char** ptr)
|
||||
return 0xFFFD;
|
||||
}
|
||||
|
||||
static void DrawText_(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t clr, const char* text, uint32_t max_width, const char* end_text)
|
||||
static void DrawText_(u32 font, uint32_t x, uint32_t y, color_t clr, const char* text, uint32_t max_width, const char* end_text)
|
||||
{
|
||||
//__builtin_printf("DrawText %u %u %08X %s\n", (unsigned int)x, (unsigned int)y, (unsigned int)clr.abgr, text);
|
||||
y += font->baseline;
|
||||
//y += font->baseline;
|
||||
uint32_t origX = x;
|
||||
if (s_font_faces_total==0) return;
|
||||
if (!FontSetType(font)) return;
|
||||
s_font_lastusedface = s_font_faces[0];
|
||||
|
||||
while (*text)
|
||||
{
|
||||
if (max_width && x-origX >= max_width) {
|
||||
@ -142,7 +260,7 @@ static void DrawText_(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t
|
||||
}
|
||||
|
||||
x = origX;
|
||||
y += font->height;
|
||||
y += s_font_lastusedface->size->metrics.height / 64;
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -157,12 +275,200 @@ static void DrawText_(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t
|
||||
}
|
||||
}
|
||||
|
||||
void DrawText(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t clr, const char* text)
|
||||
void DrawText(u32 font, uint32_t x, uint32_t y, color_t clr, const char* text)
|
||||
{
|
||||
DrawText_(font, x, y, clr, text, 0, NULL);
|
||||
}
|
||||
|
||||
void DrawTextTruncate(const ffnt_header_t* font, uint32_t x, uint32_t y, color_t clr, const char* text, uint32_t max_width, const char* end_text)
|
||||
void DrawTextFromLayout(ThemeLayoutId id, color_t clr, const char* text)
|
||||
{
|
||||
ThemeLayoutObject *obj = &themeCurrent.layoutObjects[id];
|
||||
if (!obj->visible) return;
|
||||
|
||||
DrawText(obj->font, obj->posStart[0], obj->posStart[1], clr, text);
|
||||
}
|
||||
|
||||
void DrawTextFromLayoutRelative(ThemeLayoutId id, int base_x, int base_y, int *inPos, int *outPos, color_t clr, const char* text, const char align)
|
||||
{
|
||||
ThemeLayoutObject *obj = &themeCurrent.layoutObjects[id];
|
||||
|
||||
base_x = obj->posType ? base_x + inPos[0] : inPos[0];
|
||||
base_y = obj->posType ? base_y + inPos[1] : inPos[1];
|
||||
|
||||
base_x = GetTextXCoordinate(obj->font, base_x, text, align);
|
||||
|
||||
if (outPos) {
|
||||
outPos[0] = base_x;
|
||||
outPos[1] = base_y;
|
||||
}
|
||||
|
||||
obj->posFinal[0] = base_x;
|
||||
obj->posFinal[1] = base_y;
|
||||
|
||||
if (!obj->visible) return;
|
||||
|
||||
GetTextDimensions(obj->font, text, &obj->textSize[0], &obj->textSize[1]);
|
||||
|
||||
DrawText(obj->font, base_x, base_y, clr, text);
|
||||
}
|
||||
|
||||
void DrawTextTruncate(u32 font, uint32_t x, uint32_t y, color_t clr, const char* text, uint32_t max_width, const char* end_text)
|
||||
{
|
||||
DrawText_(font, x, y, clr, text, max_width, end_text);
|
||||
}
|
||||
|
||||
void GetTextDimensions(u32 font, const char* text, uint32_t* width_out, uint32_t* height_out)
|
||||
{
|
||||
uint32_t x = 0;
|
||||
uint32_t width = 0, height = 0;
|
||||
if (s_font_faces_total==0) return;
|
||||
if (!FontSetType(font)) return;
|
||||
s_font_lastusedface = s_font_faces[0];
|
||||
|
||||
while (*text)
|
||||
{
|
||||
glyph_t glyph;
|
||||
uint32_t codepoint = DecodeUTF8(&text);
|
||||
|
||||
if (codepoint == '\n')
|
||||
{
|
||||
x = 0;
|
||||
height += s_font_lastusedface->size->metrics.height / 64;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!FontLoadGlyph(&glyph, font, codepoint))
|
||||
{
|
||||
if (!FontLoadGlyph(&glyph, font, '?'))
|
||||
continue;
|
||||
}
|
||||
|
||||
x += glyph.advance;
|
||||
|
||||
if (x > width)
|
||||
width = x;
|
||||
}
|
||||
|
||||
if(width_out) *width_out = width;
|
||||
if(height_out) *height_out = height;
|
||||
}
|
||||
|
||||
bool fontInitialize(void)
|
||||
{
|
||||
FT_Error ret=0;
|
||||
s32 i;
|
||||
|
||||
for (i=0; i<FONT_FACES_MAX; i++) s_font_facesret[i] = 1;
|
||||
|
||||
ret = FT_Init_FreeType(&s_font_library);
|
||||
s_font_libret = ret;
|
||||
if (s_font_libret) return false;
|
||||
|
||||
#ifdef __SWITCH__
|
||||
PlFontData fonts[PlSharedFontType_Total];
|
||||
|
||||
Result rc=0;
|
||||
rc = plInitialize(PlServiceType_User);
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
s_plinited = true;
|
||||
rc = plGetSharedFont(textGetLanguageCode(), fonts, FONT_FACES_MAX, &s_font_faces_total);
|
||||
}
|
||||
if (R_FAILED(rc)) return false;
|
||||
|
||||
for (i=0; i<s_font_faces_total; i++) {
|
||||
ret = FT_New_Memory_Face( s_font_library,
|
||||
fonts[i].address, /* first byte in memory */
|
||||
fonts[i].size, /* size in bytes */
|
||||
0, /* face_index */
|
||||
&s_font_faces[i]);
|
||||
|
||||
s_font_facesret[i] = ret;
|
||||
if (ret) return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
//These are loaded from "<original cwd>/fonts/<index>.ttf", these are user-supplied. Ideally these should be from plu SharedFont.
|
||||
#ifndef __SWITCH__
|
||||
char fontpath[PATH_MAX+1];
|
||||
|
||||
for (i=0; i<FONT_FACES_MAX; i++) {
|
||||
memset(fontpath, 0, sizeof(fontpath));
|
||||
snprintf(fontpath, sizeof(fontpath)-1, "%s%sfonts%s%u.ttf", menuGetRootBasePath(), DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, i);
|
||||
|
||||
ret = FT_New_Face( s_font_library,
|
||||
fontpath,
|
||||
0,
|
||||
&s_font_faces[i]);
|
||||
|
||||
s_font_facesret[i] = ret;
|
||||
if (ret) return false;
|
||||
|
||||
s_font_faces_total++;
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void fontExit()
|
||||
{
|
||||
s32 i=0;
|
||||
|
||||
for (i=0; i<s_font_faces_total; i++)
|
||||
if (s_font_facesret[i]==0) FT_Done_Face(s_font_faces[i]);
|
||||
|
||||
if (s_font_libret==0) FT_Done_FreeType(s_font_library);
|
||||
|
||||
#ifdef __SWITCH__
|
||||
if (s_plinited) plExit();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*Automatically gives you the desired x-coordinate
|
||||
*based on the string length and desired alignment
|
||||
*rY=reference point... where to align around
|
||||
*align='t','b','c' translates to (top,bottom,center)
|
||||
*'t' aligned, top of text aligns with rY,
|
||||
*you get the rest....
|
||||
*/
|
||||
uint32_t GetTextYCoordinate(u32 font, uint32_t rY, const char* text, const char align) {
|
||||
uint32_t height_o,width;
|
||||
GetTextDimensions(font,text,&width,&height_o);
|
||||
uint32_t height = (uint32_t)height_o;
|
||||
uint32_t fC = (rY-height);
|
||||
|
||||
switch(align){
|
||||
case 't':
|
||||
default:
|
||||
return rY;
|
||||
case 'c':
|
||||
return (rY+(height>>1));//>>1 is a bitwise shift for dividing by 2
|
||||
case 'b':
|
||||
if(fC<=0) return 0;
|
||||
else return fC;
|
||||
}
|
||||
}
|
||||
|
||||
/*Automatically gives you the desired x-coordinate
|
||||
*based on the string length and desired alignment
|
||||
*rX=reference point... where to align around
|
||||
*text=string you want to display
|
||||
*align='r','l','c' translates to (right,left,center)
|
||||
*'r' aligned, rX location = end of string, you get the rest...
|
||||
*/
|
||||
uint32_t GetTextXCoordinate(u32 font, uint32_t rX, const char* text, const char align) {
|
||||
uint32_t height,width_o;
|
||||
GetTextDimensions(font,text,&width_o,&height);
|
||||
uint32_t fC = (rX-width_o);
|
||||
|
||||
switch(align){
|
||||
case 'r':
|
||||
if(fC<0) return 0;
|
||||
else return fC;
|
||||
case 'c':
|
||||
return (rX-(width_o>>1));//>>1 is a bitwise shift for dividing by 2
|
||||
case 'l':
|
||||
default:
|
||||
return rX;
|
||||
}
|
||||
}
|
||||
|
@ -26,19 +26,21 @@ typedef struct {
|
||||
|
||||
typedef struct {
|
||||
uint8_t width, height;
|
||||
int8_t posX, posY, advance;
|
||||
int8_t posX, posY, advance, pitch;
|
||||
const uint8_t* data;
|
||||
} glyph_t;
|
||||
|
||||
extern const ffnt_header_t tahoma24_nxfnt;
|
||||
extern const ffnt_header_t tahoma12_nxfnt;
|
||||
extern const ffnt_header_t interuimedium20_nxfnt;
|
||||
//extern const ffnt_header_t tahoma24_nxfnt;//These tahoma fonts aren't used anymore.
|
||||
//extern const ffnt_header_t tahoma12_nxfnt;
|
||||
/*extern const ffnt_header_t interuimedium20_nxfnt;
|
||||
extern const ffnt_header_t interuimedium30_nxfnt;
|
||||
extern const ffnt_header_t interuiregular14_nxfnt;
|
||||
extern const ffnt_header_t interuiregular18_nxfnt;
|
||||
#define tahoma24 &tahoma24_nxfnt
|
||||
#define tahoma12 &tahoma12_nxfnt
|
||||
#define interuimedium20 &interuimedium20_nxfnt
|
||||
#define interuimedium30 &interuimedium30_nxfnt
|
||||
#define interuiregular14 &interuiregular14_nxfnt
|
||||
#define interuiregular18 &interuiregular18_nxfnt
|
||||
extern const ffnt_header_t interuiregular18_nxfnt;*/
|
||||
//#define tahoma24 &tahoma24_nxfnt
|
||||
//#define tahoma12 &tahoma12_nxfnt
|
||||
#define interuimedium20 2//&interuimedium20_nxfnt
|
||||
#define interuimedium30 3//&interuimedium30_nxfnt
|
||||
#define interuiregular14 0//&interuiregular14_nxfnt
|
||||
#define interuiregular18 1//&interuiregular18_nxfnt
|
||||
#define fontscale7 4
|
||||
#define largestar 5
|
||||
|
@ -1,18 +1,18 @@
|
||||
#include "language.h"
|
||||
#include "language.h"
|
||||
|
||||
#ifdef SWITCH
|
||||
#ifdef __SWITCH__
|
||||
#define STR_JP(_str) [SetLanguage_JA] = _str
|
||||
#define STR_EN(_str) [SetLanguage_ENUS] = _str, [SetLanguage_ENGB] = _str
|
||||
#define STR_FR(_str) [SetLanguage_FR] = _str, [SetLanguage_FRCA] = _str
|
||||
#define STR_DE(_str) [SetLanguage_DE] = _str
|
||||
#define STR_IT(_str) [SetLanguage_IT] = _str
|
||||
#define STR_ES(_str) [SetLanguage_ES] = _str, [SetLanguage_ES419] = _str
|
||||
#define STR_ZH(_str) [SetLanguage_ZHCN] = _str
|
||||
#define STR_ZH_HANS(_str) [SetLanguage_ZHCN] = _str, [SetLanguage_ZHHANS] = _str
|
||||
#define STR_KO(_str) [SetLanguage_KO] = _str
|
||||
#define STR_NL(_str) [SetLanguage_NL] = _str
|
||||
#define STR_PT(_str) [SetLanguage_PT] = _str
|
||||
#define STR_RU(_str) [SetLanguage_RU] = _str
|
||||
#define STR_TW(_str) [SetLanguage_ZHTW] = _str
|
||||
#define STR_ZH_HANT(_str) [SetLanguage_ZHTW] = _str, [SetLanguage_ZHHANT] = _str
|
||||
#else
|
||||
#define STR_JP(_str) [0] = _str
|
||||
#define STR_EN(_str) [1] = _str
|
||||
@ -20,15 +20,15 @@
|
||||
#define STR_DE(_str) [3] = _str
|
||||
#define STR_IT(_str) [4] = _str
|
||||
#define STR_ES(_str) [5] = _str
|
||||
#define STR_ZH(_str) [6] = _str
|
||||
#define STR_ZH_HANS(_str) [6] = _str
|
||||
#define STR_KO(_str) [7] = _str
|
||||
#define STR_NL(_str) [8] = _str
|
||||
#define STR_PT(_str) [9] = _str
|
||||
#define STR_RU(_str) [10] = _str
|
||||
#define STR_TW(_str) [11] = _str
|
||||
#define STR_ZH_HANT(_str) [11] = _str
|
||||
#endif
|
||||
|
||||
const char* const g_strings[StrId_Max][16] =
|
||||
const char* const g_strings[StrId_Max][17] =
|
||||
{
|
||||
[StrId_Loading] =
|
||||
{
|
||||
@ -42,8 +42,16 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_NL("Laden…"),
|
||||
STR_KO("로딩중…"),
|
||||
STR_RU("загрузка…"),
|
||||
STR_ZH("加载中…"),
|
||||
STR_TW("加載中…"),
|
||||
STR_ZH_HANS("加载中…"),
|
||||
STR_ZH_HANT("載入中…"),
|
||||
},
|
||||
|
||||
[StrId_AppletMode] =
|
||||
{
|
||||
STR_EN("● Applet Mode ●"),
|
||||
STR_ES("● Modo Applet ●"),
|
||||
STR_FR("● Mode Applet ●"),
|
||||
STR_ZH_HANS("● 小程序模式 ●"),
|
||||
},
|
||||
|
||||
[StrId_Directory] =
|
||||
@ -58,8 +66,8 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_NL("Map"),
|
||||
STR_KO("디렉토리"),
|
||||
STR_RU("каталог"),
|
||||
STR_ZH("目录"),
|
||||
STR_TW("資料夾"),
|
||||
STR_ZH_HANS("目录"),
|
||||
STR_ZH_HANT("資料夾"),
|
||||
},
|
||||
|
||||
/*[StrId_DefaultLongTitle] =
|
||||
@ -74,8 +82,8 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_NL("Homebrew toepassing"),
|
||||
STR_KO("홈브류 애플리케이션"),
|
||||
STR_RU("приложение хомебреw"),
|
||||
STR_ZH("自制应用程序"),
|
||||
STR_TW("自製程式"),
|
||||
STR_ZH_HANS("自制应用程序"),
|
||||
STR_ZH_HANT("自製程式"),
|
||||
},*/
|
||||
|
||||
[StrId_DefaultPublisher] =
|
||||
@ -85,29 +93,29 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_DE("Unbekannter Autor"),
|
||||
STR_FR("Auteur inconnu"),
|
||||
STR_IT("Autore sconosciuto"),
|
||||
STR_JP("未知の作者"),
|
||||
STR_JP("作者不明"),
|
||||
STR_PT("Autor Desconhecido"),
|
||||
STR_NL("Auteur onbekend"),
|
||||
STR_KO("작자미상"),
|
||||
STR_KO("알 수 없는 개발자"),
|
||||
STR_RU("неизвестный автор"),
|
||||
STR_ZH("未知作者"),
|
||||
STR_TW("未知作者"),
|
||||
STR_ZH_HANS("未知作者"),
|
||||
STR_ZH_HANT("作者未知"),
|
||||
},
|
||||
|
||||
[StrId_IOError] =
|
||||
{
|
||||
STR_EN("I/O Error"),
|
||||
STR_ES("Error de E/S"),
|
||||
STR_DE("E/A-Fehler"),
|
||||
STR_DE("I/O-Fehler"),
|
||||
STR_FR("Erreur d'E/S"),
|
||||
STR_IT("Errore di I/O"),
|
||||
STR_IT("Errore I/O"),
|
||||
STR_JP("入出力エラー"),
|
||||
STR_PT("Erro de E/S"),
|
||||
STR_NL("I/O Fout"),
|
||||
STR_KO("I/O 에러"),
|
||||
STR_KO("입출력 오류"),
|
||||
STR_RU("I/O-ошибка"),
|
||||
STR_ZH("读写出错"),
|
||||
STR_TW("讀寫錯誤"),
|
||||
STR_ZH_HANS("读写出错"),
|
||||
STR_ZH_HANT("取存錯誤"),
|
||||
},
|
||||
|
||||
[StrId_CouldNotOpenFile] =
|
||||
@ -122,8 +130,15 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_NL("Kan bestand niet openen:\n%s"),
|
||||
STR_KO("파일을 열 수 없습니다:\n%s"),
|
||||
STR_RU("Не могу открыть файл:\n%s"),
|
||||
STR_ZH("无法打开文件:\n%s"),
|
||||
STR_TW("開啓檔案失敗:\n%s"),
|
||||
STR_ZH_HANS("无法打开文件:\n%s"),
|
||||
STR_ZH_HANT("無法開啟檔案:\n%s"),
|
||||
},
|
||||
|
||||
[StrId_NroNotFound] =
|
||||
{
|
||||
STR_EN("Could not find executable: %s"),
|
||||
STR_FR("Impossible trouver l'exécutable : %s"),
|
||||
STR_ZH_HANS("找不到可执行文件"),
|
||||
},
|
||||
|
||||
[StrId_NoAppsFound_Title] =
|
||||
@ -138,8 +153,8 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_NL("Geen toepassingen gevonden"),
|
||||
STR_KO("애플리케이션을 찾을 수 없습니다"),
|
||||
STR_RU("приложение не найдено"),
|
||||
STR_ZH("找不到可执行的自制程序"),
|
||||
STR_TW("未能找到可執行的自製程式"),
|
||||
STR_ZH_HANS("找不到可执行的自制程序"),
|
||||
STR_ZH_HANT("沒有可執行的自製程式"),
|
||||
},
|
||||
|
||||
[StrId_NoAppsFound_Msg] =
|
||||
@ -157,7 +172,7 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_DE(
|
||||
"Auf der SD-Karte wurden keine Anwendungen\n"
|
||||
"gefunden. Stelle sicher, dass ein Verzeichnis\n"
|
||||
"namens /switch im Wurzelverzeichnis der SD-Karte\n"
|
||||
"namens /switch im Hauptverzeichnis der SD-Karte\n"
|
||||
"existiert und Anwendungen enthält!"
|
||||
),
|
||||
STR_FR(
|
||||
@ -174,7 +189,7 @@ const char* const g_strings[StrId_Max][16] =
|
||||
),
|
||||
STR_JP(
|
||||
"SDカードにアプリケーションが見つかりませんでした。\n"
|
||||
"SDカードのルートに「/switch」という名前のフォルダを\n"
|
||||
"SDカードのルートに「switch」という名前のフォルダを\n"
|
||||
"作成してください。"
|
||||
),
|
||||
STR_PT(
|
||||
@ -190,27 +205,51 @@ const char* const g_strings[StrId_Max][16] =
|
||||
"en de toepassingen bevat."
|
||||
),
|
||||
STR_KO(
|
||||
"애플리케이션을 SD 카드에서 찾을 수 없습니다.\n"
|
||||
"SD 카드 최상단에 /switch 라는 이름의 폴더가 있는지,\n"
|
||||
"애플리케이션을 포함하고 있는지 확인해 주십시오."
|
||||
"SD 카드에서 애플리케이션을 찾을 수 없습니다.\n"
|
||||
"SD 카드 최상위에 /switch 폴더가 있고\n"
|
||||
"애플리케이션을 포함하는지 확인해 주십시오."
|
||||
),
|
||||
STR_RU(
|
||||
"На SD-карте нет приложений.\n"
|
||||
"Убедитесь, что на карте SD есть каталог с\n"
|
||||
"названием switch и она содержит приложения."
|
||||
),
|
||||
STR_ZH(
|
||||
"内存卡找不到任何可执行的应用程序。\n"
|
||||
"请在内存卡的根目录建立「switch」子目录,\n"
|
||||
"并存放自制应用软件至该目录。"
|
||||
STR_ZH_HANS(
|
||||
"找不到任何自制程序(nro)。\n"
|
||||
"在SD卡根目录建立“switch”文件夹,\n"
|
||||
"并将自制程序(nro)放在其中。"
|
||||
),
|
||||
STR_TW(
|
||||
"記憶體找不到任何可執行的應用程式。\n"
|
||||
"請在記憶體建立「switch」資料夾,\n"
|
||||
"然後儲存自製軟體到此處。"
|
||||
STR_ZH_HANT(
|
||||
"記憶卡內沒有可供執行的應用程式。\n"
|
||||
"請在根目錄下建立「switch」資料夾,\n"
|
||||
"並將自製軟體複製到switch資料夾內。"
|
||||
),
|
||||
},
|
||||
|
||||
[StrId_LastLoadResult] =
|
||||
{
|
||||
STR_EN("The last application returned an error:"),
|
||||
STR_ES("La última aplicación devolvió un error:"),
|
||||
STR_DE("Die letzte Anwendung erzeugte einen Fehler:"),
|
||||
STR_FR("La dernière application a retourné une erreur:"),
|
||||
STR_IT("L'ultima applicazione ha restituito un errore:"),
|
||||
STR_JP("直前に実行したアプリでエラーが発生しました:"),
|
||||
STR_KO("최근 애플리케이션에서 오류가 발생했습니다:"),
|
||||
STR_ZH_HANS("程序运行后出现错误:"),
|
||||
STR_ZH_HANT("程式執行後出現錯誤:"),
|
||||
},
|
||||
|
||||
[StrId_AppLaunchError] =
|
||||
{
|
||||
STR_EN("Failed to launch the application:"),
|
||||
STR_DE("Konnte die Anwendung nicht starten:"),
|
||||
STR_FR("Erreur au lancement de l'application:"),
|
||||
STR_IT("Errore nell'avvio dell'applicazione:"),
|
||||
STR_ES("No se ha podido iniciar la aplicación:"),
|
||||
STR_ZH_HANS("运行程序时发生错误:"),
|
||||
STR_ZH_HANT("執行程式時發生錯誤:"),
|
||||
},
|
||||
|
||||
[StrId_AppInfo_Author] =
|
||||
{
|
||||
STR_EN("Author"),
|
||||
@ -218,13 +257,13 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_DE("Autor"),
|
||||
STR_FR("Auteur"),
|
||||
STR_IT("Autore"),
|
||||
STR_JP("著者"),
|
||||
STR_JP("作者"),
|
||||
STR_PT("Autor"),
|
||||
STR_NL("Auteur"),
|
||||
STR_KO("저자"),
|
||||
STR_KO("개발자"),
|
||||
STR_RU("автор"),
|
||||
STR_ZH("作者"),
|
||||
STR_TW("作者"),
|
||||
STR_ZH_HANS("作者"),
|
||||
STR_ZH_HANT("作者"),
|
||||
},
|
||||
|
||||
[StrId_AppInfo_Version] =
|
||||
@ -237,10 +276,10 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_JP("バージョン"),
|
||||
STR_PT("Versão"),
|
||||
STR_NL("Versie"),
|
||||
STR_KO("번역"),
|
||||
STR_KO("버전"),
|
||||
STR_RU("Версия"),
|
||||
STR_ZH("版"),
|
||||
STR_TW("版"),
|
||||
STR_ZH_HANS("版本"),
|
||||
STR_ZH_HANT("版本"),
|
||||
},
|
||||
|
||||
[StrId_Actions_Launch] =
|
||||
@ -248,37 +287,128 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_EN("Launch"),
|
||||
STR_ES("Lanzamiento"),
|
||||
STR_DE("Starten"),
|
||||
STR_FR("Lancement"),
|
||||
STR_IT("Lanciare"),
|
||||
STR_JP("打ち上げ"),
|
||||
STR_FR("Lancer"),
|
||||
STR_IT("Avvia"),
|
||||
STR_JP("起動"),
|
||||
STR_PT("Lançamento"),
|
||||
STR_NL("Lancering"),
|
||||
STR_KO("쏘다"),
|
||||
STR_KO("실행"),
|
||||
STR_RU("запуск"),
|
||||
STR_ZH("发射"),
|
||||
STR_TW("发射"),
|
||||
STR_ZH_HANS("发射"),
|
||||
STR_ZH_HANT("啟動"),
|
||||
},
|
||||
|
||||
[StrId_Actions_Open] =
|
||||
{
|
||||
STR_EN("Open"),
|
||||
STR_ES("Abierto"),
|
||||
STR_ES("Abrir"),
|
||||
STR_DE("Öffnen"),
|
||||
STR_FR("Ouvrir"),
|
||||
STR_IT("Aperto"),
|
||||
STR_JP("開いた"),
|
||||
STR_PT("Aberto"),
|
||||
STR_IT("Apri"),
|
||||
STR_JP("開く"),
|
||||
STR_PT("Abrir"),
|
||||
STR_NL("Open"),
|
||||
STR_KO("열다"),
|
||||
STR_KO("열기"),
|
||||
STR_RU("открыто"),
|
||||
STR_ZH("打开"),
|
||||
STR_TW("打开"),
|
||||
STR_ZH_HANS("打开"),
|
||||
STR_ZH_HANT("開啟"),
|
||||
},
|
||||
|
||||
[StrId_Actions_Back] =
|
||||
{
|
||||
STR_EN("Back"),
|
||||
STR_ES("Volver"),
|
||||
STR_DE("Zurück"),
|
||||
STR_FR("Retour"),
|
||||
STR_IT("Indietro"),
|
||||
STR_JP("戻る"),
|
||||
STR_PT("Regressar"),
|
||||
STR_NL("Terug"),
|
||||
STR_KO("뒤로 가기"),
|
||||
STR_RU("возвращаться"),
|
||||
STR_ZH_HANS("返回"),
|
||||
STR_ZH_HANT("返回"),
|
||||
},
|
||||
|
||||
[StrId_MsgBox_OK] =
|
||||
{
|
||||
STR_EN("OK"),
|
||||
STR_DE("OK"),
|
||||
STR_FR("OK"),
|
||||
STR_IT("OK"),
|
||||
STR_ES("Aceptar"),
|
||||
STR_JP("了解"),
|
||||
STR_KO("확인"),
|
||||
STR_ZH_HANS("确认"),
|
||||
STR_ZH_HANT("確認"),
|
||||
},
|
||||
|
||||
[StrId_Actions_Apply] =
|
||||
{
|
||||
STR_EN("Apply"),
|
||||
STR_FR("Appliquer"),
|
||||
STR_DE("Anwenden"),
|
||||
STR_ES("Aplicar"),
|
||||
STR_IT("Applica"),
|
||||
STR_JP("適用"),
|
||||
STR_KO("적용"),
|
||||
STR_ZH_HANS("应用"),
|
||||
STR_ZH_HANT("套用"),
|
||||
},
|
||||
|
||||
[StrId_Actions_Star] =
|
||||
{
|
||||
STR_EN("Star"),
|
||||
STR_ES("Agregar a favoritos"),
|
||||
STR_IT("Aggiungi ai preferiti"),
|
||||
STR_FR("Ajouter aux favoris"),
|
||||
STR_ZH_HANS("收藏"),
|
||||
},
|
||||
|
||||
[StrId_Actions_Unstar] =
|
||||
{
|
||||
STR_EN("Unstar"),
|
||||
STR_ES("Borrar de favoritos"),
|
||||
STR_IT("Rimuovi dai preferiti"),
|
||||
STR_FR("Retirer des favoris"),
|
||||
STR_ZH_HANS("取消收藏"),
|
||||
},
|
||||
|
||||
[StrId_ThemeMenu] =
|
||||
{
|
||||
STR_EN("Theme Menu"),
|
||||
STR_FR("Menu thèmes"),
|
||||
STR_DE("Theme Menü"),
|
||||
STR_ES("Menú temático"),
|
||||
STR_IT("Tema Menu"),
|
||||
STR_JP("テーマメニュー"),
|
||||
STR_KO("테마 메뉴"),
|
||||
STR_ZH_HANS("主题菜单"),
|
||||
STR_ZH_HANT("主題選單"),
|
||||
},
|
||||
|
||||
[StrId_ThemeNotApplied] =
|
||||
{
|
||||
STR_EN("Theme cannot be applied because an error occurred."),
|
||||
STR_DE("Das Theme konnte nicht geladen werden, da ein Fehler aufgetreten ist."),
|
||||
STR_FR("Le thème ne peut pas être appliqué car une erreur est survenue."),
|
||||
STR_ES("El tema no se pudo aplicar porque se ha producido un error."),
|
||||
STR_IT("Il tema non è stato applicato a causa di un errore."),
|
||||
STR_JP("エラーが発生したため、テーマを適用できませんでした。"),
|
||||
STR_KO("오류가 발생 했기 때문에 테마를 적용할 수 없습니다."),
|
||||
STR_ZH_HANS("由于发生错误, 无法应用主题。"),
|
||||
STR_ZH_HANT("出現錯誤,無法套用主題。"),
|
||||
},
|
||||
|
||||
[StrId_DefaultThemeName] =
|
||||
{
|
||||
STR_EN("Default Theme"),
|
||||
STR_FR("Thème par défaut"),
|
||||
STR_DE("Standard Theme"),
|
||||
STR_IT("Tema di default"),
|
||||
STR_ES("Tema por defecto"),
|
||||
STR_ZH_HANS("默认主题"),
|
||||
STR_ZH_HANT("預設主題"),
|
||||
},
|
||||
|
||||
/*[StrId_Reboot] =
|
||||
@ -315,8 +445,8 @@ const char* const g_strings[StrId_Max][16] =
|
||||
" \xEE\x80\x81 Annulla"
|
||||
),
|
||||
STR_JP(
|
||||
"\xEE\x81\xB3HOMEに戻ることはできません。\n"
|
||||
"コンソールが今すぐ再起動する。\n\n"
|
||||
" \xEE\x81\xB3HOME に戻ることができませんでした。\n"
|
||||
"今すぐ本体を再起動してください。\n\n"
|
||||
" \xEE\x80\x80 再起動\n"
|
||||
" \xEE\x80\x81 キャンセル"
|
||||
),
|
||||
@ -333,9 +463,9 @@ const char* const g_strings[StrId_Max][16] =
|
||||
" \xEE\x80\x81 Annuleren"
|
||||
),
|
||||
STR_KO(
|
||||
"\xEE\x81\xB3홈으로 돌아갈 수 없습니다.\n"
|
||||
"당신의 기기를 리부팅 하려 합니다.\n\n"
|
||||
" \xEE\x80\x80 리부팅\n"
|
||||
"\xEE\x81\xB3HOME 으로 돌아갈 수 없습니다.\n"
|
||||
"콘솔을 재부팅할 것 입니다.\n\n"
|
||||
" \xEE\x80\x80 재부팅\n"
|
||||
" \xEE\x80\x81 취소"
|
||||
),
|
||||
STR_RU(
|
||||
@ -344,13 +474,13 @@ const char* const g_strings[StrId_Max][16] =
|
||||
" \xEE\x80\x80 Перезагрузите\n"
|
||||
" \xEE\x80\x81 Отмена"
|
||||
),
|
||||
STR_ZH(
|
||||
STR_ZH_HANS(
|
||||
"无法返回至主机的 \xEE\x81\xB3HOME 菜单。\n"
|
||||
"您需要重新启动您的 3DS 设备。\n\n"
|
||||
" \xEE\x80\x80 重启设备\n"
|
||||
" \xEE\x80\x81 取消操作"
|
||||
),
|
||||
STR_TW(
|
||||
STR_ZH_HANT(
|
||||
"無法返回至主機的 \xEE\x81\xB3HOME 選單。\n"
|
||||
"您需要重新啓動您的 3DS 設備。\n\n"
|
||||
" \xEE\x80\x80 重啓設備\n"
|
||||
@ -391,8 +521,8 @@ const char* const g_strings[StrId_Max][16] =
|
||||
" \xEE\x80\x82 Riavvia"
|
||||
),
|
||||
STR_JP(
|
||||
"あなたは今すぐ\xEE\x81\xB3HOMEに戻されます。\n\n"
|
||||
" \xEE\x80\x80 戻る\n"
|
||||
" \xEE\x81\xB3HOME に戻ろうとしています。\n\n"
|
||||
" \xEE\x80\x80 了解\n"
|
||||
" \xEE\x80\x81 キャンセル\n"
|
||||
" \xEE\x80\x82 再起動"
|
||||
),
|
||||
@ -409,10 +539,10 @@ const char* const g_strings[StrId_Max][16] =
|
||||
" \xEE\x80\x82 Herstarten"
|
||||
),
|
||||
STR_KO(
|
||||
"\xEE\x81\xB3홈으로 돌아가려 합니다.\n\n"
|
||||
" \xEE\x80\x80 이동\n"
|
||||
"\xEE\x81\xB3HOME 으로 돌아갈 것 입니다.\n\n"
|
||||
" \xEE\x80\x80 돌아가기\n"
|
||||
" \xEE\x80\x81 취소\n"
|
||||
" \xEE\x80\x82 리부팅"
|
||||
" \xEE\x80\x82 재부팅"
|
||||
),
|
||||
STR_RU(
|
||||
"Вы возвращаетесь в \xEE\x81\xB3HOME.\n\n"
|
||||
@ -420,13 +550,13 @@ const char* const g_strings[StrId_Max][16] =
|
||||
" \xEE\x80\x81 Отмена\n"
|
||||
" \xEE\x80\x82 Перезагрузите"
|
||||
),
|
||||
STR_ZH(
|
||||
STR_ZH_HANS(
|
||||
"您即将返回到主機的 \xEE\x81\xB3HOME 菜单。\n\n"
|
||||
" \xEE\x80\x80 确认返回\n"
|
||||
" \xEE\x80\x81 取消操作\n"
|
||||
" \xEE\x80\x82 重启设备"
|
||||
),
|
||||
STR_TW(
|
||||
STR_ZH_HANT(
|
||||
"您即將返回到主機的 \xEE\x81\xB3HOME 選單。\n\n"
|
||||
" \xEE\x80\x80 確認返回\n"
|
||||
" \xEE\x80\x81 取消操作\n"
|
||||
@ -438,7 +568,7 @@ const char* const g_strings[StrId_Max][16] =
|
||||
{
|
||||
STR_EN("Title selector"),
|
||||
STR_ES("Selector de título"),
|
||||
STR_DE("Titel-Selektor"),
|
||||
STR_DE("Titel-Auswahl"),
|
||||
STR_FR("Sélecteur de titre"),
|
||||
STR_IT("Selettore del titolo"),
|
||||
STR_JP("タイトルセレクタ"),
|
||||
@ -446,18 +576,18 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_NL("Titel selector"),
|
||||
STR_KO("타이틀 선택기"),
|
||||
STR_RU("Селектор заголовков"),
|
||||
STR_ZH("应用启动器"),
|
||||
STR_TW("自製程式啓動器"),
|
||||
STR_ZH_HANS("应用启动器"),
|
||||
STR_ZH_HANT("自製程式啓動器"),
|
||||
},
|
||||
|
||||
[StrId_ErrorReadingTitleMetadata] =
|
||||
{
|
||||
STR_EN("Error reading title metadata.\n%08lX%08lX@%d"),
|
||||
STR_ES("Error leyendo los metadatos de los títulos.\n%08lX%08lX@%d"),
|
||||
STR_DE("Fehler beim lesen der Titel-Metadaten.\n%08lX%08lX@%d"),
|
||||
STR_DE("Fehler beim Lesen der Titel-Metadaten.\n%08lX%08lX@%d"),
|
||||
STR_FR(
|
||||
"Erreur lors de la lecture des métadonnées\n"
|
||||
"de titre.\n%08lX%08lX@%d"
|
||||
"du titre.\n%08lX%08lX@%d"
|
||||
),
|
||||
STR_IT("Errore nella lettura dei metadata dei titoli.\n%08lX%08lX@%d"),
|
||||
STR_JP("タイトルメタデータを読み取ることができませんでした。\n%08lX%08lX@%d"),
|
||||
@ -465,8 +595,8 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_NL("Fout bij het lezen van titel metadata.\n%08lX%08lX@%d"),
|
||||
STR_KO("타이틀 메타데이터를 읽는데 실패하였습니다.\n%08lX%08lX@%d"),
|
||||
STR_RU("Ошибка чтения метаданных заголовка\n.%08lX%08lX@%d"),
|
||||
STR_ZH("读取软件相关信息时发生错误:\n%08lX%08lX@%d"),
|
||||
STR_TW("讀取軟體相關數據時發生錯誤:\n%08lX%08lX@%d"),
|
||||
STR_ZH_HANS("读取软件相关信息时发生错误:\n%08lX%08lX@%d"),
|
||||
STR_ZH_HANT("讀取軟體相關資訊時發生錯誤:\n%08lX%08lX@%d"),
|
||||
},
|
||||
|
||||
[StrId_NoTitlesFound] =
|
||||
@ -479,10 +609,10 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_JP("タイトルが見つかりませんでした。"),
|
||||
STR_PT("Nenhum título foi encontrado."),
|
||||
STR_NL("Geen titels gevonden."),
|
||||
STR_KO("타이틀을 찾지 못하였습니다."),
|
||||
STR_KO("타이틀을 찾을 수 없습니다."),
|
||||
STR_RU("Заголовки не обнаружены"),
|
||||
STR_ZH("主机内找不到任何软件。"),
|
||||
STR_TW("主機内找不到任何軟體。"),
|
||||
STR_ZH_HANS("主机内找不到任何软件。"),
|
||||
STR_ZH_HANT("主機内找不到任何軟體。"),
|
||||
},
|
||||
|
||||
[StrId_SelectTitle] =
|
||||
@ -537,12 +667,12 @@ const char* const g_strings[StrId_Max][16] =
|
||||
" \xEE\x80\x80 Выберите\n"
|
||||
" \xEE\x80\x81 Отмена"
|
||||
),
|
||||
STR_ZH(
|
||||
STR_ZH_HANS(
|
||||
"请选择一个目标软件。\n\n"
|
||||
" \xEE\x80\x80 确认\n"
|
||||
" \xEE\x80\x81 取消"
|
||||
),
|
||||
STR_TW(
|
||||
STR_ZH_HANT(
|
||||
"請選擇一個目標軟體。\n\n"
|
||||
" \xEE\x80\x80 確認\n"
|
||||
" \xEE\x80\x81 取消"
|
||||
@ -577,7 +707,7 @@ const char* const g_strings[StrId_Max][16] =
|
||||
"Utilizza un exploit diverso."
|
||||
),
|
||||
STR_JP(
|
||||
"この自家製のエクスプロイトは、ターゲットタイトルの\n"
|
||||
"この自作エクスプロイトでは、ターゲットタイトルの\n"
|
||||
"下でアプリを起動することができません。\n"
|
||||
"別のエクスプロイトを使用してください。"
|
||||
),
|
||||
@ -592,8 +722,8 @@ const char* const g_strings[StrId_Max][16] =
|
||||
"Gebruik een andere exploit."
|
||||
),
|
||||
STR_KO(
|
||||
"이 홈브류 익스플로잇은 해당 타이틀에서 어플리케이션을\n"
|
||||
"실행시키는 것을 지원하지 않습니다.\n"
|
||||
"이 홈브류 익스플로잇은 해당 타이틀에서 애플리케이션을\n"
|
||||
"실행하는 것을 지원하지 않습니다.\n"
|
||||
"다른 익스플로잇을 사용해 주십시오."
|
||||
),
|
||||
STR_RU(
|
||||
@ -601,14 +731,14 @@ const char* const g_strings[StrId_Max][16] =
|
||||
"приложений под целевыми заголовками.\n"
|
||||
"Пожалуйста, используйте другой эксплойт."
|
||||
),
|
||||
STR_ZH(
|
||||
STR_ZH_HANS(
|
||||
"您所利用漏洞启动的「自制软件启动器」,\n"
|
||||
"无法在当前选中的软件中启动自制软件。\n"
|
||||
"请使用其它的漏洞来启动「自制软件启动器」。"
|
||||
),
|
||||
STR_TW(
|
||||
STR_ZH_HANT(
|
||||
"您所利用漏洞開啓的「自製軟體啓動器」\n"
|
||||
"無法在當前選中的軟體啓動自製軟件。\n"
|
||||
"無法在當前選中的軟體啓動自製軟體。\n"
|
||||
"請利用其它漏洞來啓動「自製軟體啓動器」。"
|
||||
),
|
||||
},
|
||||
@ -625,7 +755,7 @@ const char* const g_strings[StrId_Max][16] =
|
||||
),
|
||||
STR_DE(
|
||||
"Die ausgewählte Anwendung benötigt einen\n"
|
||||
"Titel der nicht installiert ist"
|
||||
"Titel der nicht installiert ist."
|
||||
),
|
||||
STR_FR(
|
||||
"L'application sélectionnée requiert un titre\n"
|
||||
@ -637,7 +767,7 @@ const char* const g_strings[StrId_Max][16] =
|
||||
),
|
||||
STR_JP(
|
||||
"このアプリを実行するために\n"
|
||||
"適切なタイトルがインストールされていません。"
|
||||
"必要なタイトルがインストールされていません。"
|
||||
),
|
||||
STR_PT(
|
||||
"A aplicação que acabou de tentar executar requer\n"
|
||||
@ -648,37 +778,37 @@ const char* const g_strings[StrId_Max][16] =
|
||||
"vereist een titel die niet geinstalleerd is."
|
||||
),
|
||||
STR_KO(
|
||||
"시도한 애플리케이션의 실행에 필요한 타이틀이\n"
|
||||
"시스템에 설치되어 있지 않습니다."
|
||||
"해당 애플리케이션은 시스템에 설치되지 않은\n"
|
||||
"타이틀을 요구합니다."
|
||||
),
|
||||
STR_RU(
|
||||
"Для приложения требуется зависимость,\n"
|
||||
"которая не установлена."
|
||||
),
|
||||
STR_ZH(
|
||||
STR_ZH_HANS(
|
||||
"主机找不到该应用程序\n"
|
||||
"所需求的软件。"
|
||||
),
|
||||
STR_TW(
|
||||
STR_ZH_HANT(
|
||||
"主機找不到該應用程式\n"
|
||||
"所需求的軟體。"
|
||||
),
|
||||
},*/
|
||||
|
||||
/*[StrId_NetLoader] =
|
||||
[StrId_NetLoader] =
|
||||
{
|
||||
STR_EN("3dslink NetLoader"),
|
||||
STR_ES("Cargador de programas 3dslink"),
|
||||
STR_DE("3dslink Netzwerk-Loader"),
|
||||
STR_FR("Chargeur de programme 3dslink"),
|
||||
STR_IT("Caricamento programmi 3dslink"),
|
||||
STR_JP("3dslinkネットローダ"),
|
||||
STR_PT("Carregador de programas 3dslink"),
|
||||
STR_NL("3dslink netwerk lader"),
|
||||
STR_KO("3dslink 넷로더"),
|
||||
STR_RU("Загрузчик 3dslink"),
|
||||
STR_ZH("3dslink 网络执行模块"),
|
||||
STR_TW("3dslink 網路執行模組"),
|
||||
STR_EN("NetLoader"),
|
||||
STR_ES("Cargador de programas"),
|
||||
STR_DE("Netzwerk-Loader"),
|
||||
STR_FR("NetLoader"),
|
||||
STR_IT("Caricamento programmi"),
|
||||
STR_JP("ネットローダ"),
|
||||
STR_PT("Carregador de programas"),
|
||||
STR_NL("netwerk lader"),
|
||||
STR_KO("네트워크 로더"),
|
||||
STR_RU("Загрузчик"),
|
||||
STR_ZH_HANS("网络执行模块"),
|
||||
STR_ZH_HANT("網路執行模組"),
|
||||
},
|
||||
|
||||
[StrId_NetLoaderUnavailable] =
|
||||
@ -686,15 +816,15 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_EN("The NetLoader is currently unavailable."),
|
||||
STR_ES("El cargador de programas no está disponible."),
|
||||
STR_DE("Der Netzwerk-Loader ist zur Zeit nicht verfügbar."),
|
||||
STR_FR("Le chargeur de programme 3dslink est indisponible."),
|
||||
STR_IT("Il caricamento programmi 3dslink non è disponibile."),
|
||||
STR_JP("3dslinkネットローダを利用できません。"),
|
||||
STR_FR("Le programme nxlink est indisponible."),
|
||||
STR_IT("Il caricamento programmi nxlink non è disponibile."),
|
||||
STR_JP("nxlinkネットローダは現在利用できません。"),
|
||||
STR_PT("O carregador de programas está de momento indisponível."),
|
||||
STR_NL("De netwerk lader is niet beschikbaar."),
|
||||
STR_KO("넷로더는 현재 사용이 불가능 합니다."),
|
||||
STR_KO("현재 네트워크 로더는 사용이 불가합니다."),
|
||||
STR_RU("Загрузчик в настоящее время недоступен."),
|
||||
STR_ZH("无法启动 3dslink 网络执行模块。"),
|
||||
STR_TW("無法啓動 3dslink 網路執行模組。"),
|
||||
STR_ZH_HANS("无法启动 nxlink 网络执行模块。"),
|
||||
STR_ZH_HANT("無法啓動 nxlink 網路執行模組。"),
|
||||
},
|
||||
|
||||
[StrId_NetLoaderError] =
|
||||
@ -707,81 +837,74 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_JP("エラーが発生しました。\n技術的な詳細:[%s:%d]"),
|
||||
STR_PT("Ocorreu um erro.\nDetalhes técnicos: [%s:%d]"),
|
||||
STR_NL("Er is een fout opgetreden\nTechnische details: [%s:%d]"),
|
||||
STR_KO("에러가 발생했습니다.\n상세: [%s:%d]"),
|
||||
STR_KO("오류가 발생했습니다.\n기술적인 세부사항: [%s:%d]"),
|
||||
STR_RU("Произошла ошибка.\nТехнические подробности: [%s:%d]"),
|
||||
STR_ZH("发生错误。\n详细错误信息:[%s:%d]"),
|
||||
STR_TW("發生錯誤。\n詳細錯誤資訊:[%s:%d]"),
|
||||
STR_ZH_HANS("发生错误。\n详细错误信息:[%s:%d]"),
|
||||
STR_ZH_HANT("發生錯誤。\n詳細錯誤資訊:[%s:%d]"),
|
||||
},
|
||||
|
||||
[StrId_NetLoaderOffline] =
|
||||
{
|
||||
STR_EN("Offline, waiting for network…\n\n\n \xEE\x80\x81 Cancel"),
|
||||
STR_ZH("无法连接网络,等待网络连接…\n\n\n \xEE\x80\x81 取消"),
|
||||
STR_TW("當前離線,等待網路連線…\n\n\n \xEE\x80\x81 取消"),
|
||||
STR_IT("Disconnesso, in attesa della connessione…\n\n\n \xEE\x80\x81 Annullare"),
|
||||
STR_EN("Offline, waiting for network…"),
|
||||
STR_DE("Offline, warte auf Netzwerk…"),
|
||||
STR_FR("Hors-ligne, en attente d'une connection..."),
|
||||
STR_IT("Disconnesso, in attesa della connessione…"),
|
||||
STR_ES("Desconectado, esperando a la red..."),
|
||||
STR_JP("オフラインです。ネットワーク接続を待っています…"),
|
||||
STR_KO("연결 끊김, 네트워크 기다리는 중…"),
|
||||
STR_ZH_HANS("无法连接网络,等待网络连接…"),
|
||||
STR_ZH_HANT("目前已離線,等待網路連線…"),
|
||||
},
|
||||
|
||||
[StrId_NetLoaderActive] =
|
||||
{
|
||||
STR_EN(
|
||||
"Waiting for 3dslink to connect…\n"
|
||||
"IP Addr: %lu.%lu.%lu.%lu, Port: %d\n\n"
|
||||
" \xEE\x80\x81 Cancel"
|
||||
"Waiting for nxlink to connect…\n"
|
||||
"IP Addr: %lu.%lu.%lu.%lu, Port: %d"
|
||||
),
|
||||
STR_ES(
|
||||
"Esperando a que se conecte 3dslink…\n"
|
||||
"Dir.IP: %lu.%lu.%lu.%lu, Puerto: %d\n\n"
|
||||
" \xEE\x80\x81 Cancelar"
|
||||
"Esperando a que se conecte nxlink…\n"
|
||||
"Dir.IP: %lu.%lu.%lu.%lu, Puerto: %d"
|
||||
),
|
||||
STR_DE(
|
||||
"Warte auf Verbindung von 3dslink…\n"
|
||||
"IP Addr: %lu.%lu.%lu.%lu, Port: %d\n\n"
|
||||
" \xEE\x80\x81 Abbrechen"
|
||||
"Warte auf Verbindung von nxlink…\n"
|
||||
"IP Addr: %lu.%lu.%lu.%lu, Port: %d"
|
||||
),
|
||||
STR_FR(
|
||||
"En attente de la connexion de 3dslink…\n"
|
||||
"Adr. IP : %lu.%lu.%lu.%lu, Port : %d\n\n"
|
||||
" \xEE\x80\x81 Annuler"
|
||||
"En attente de la connexion de nxlink…\n"
|
||||
"Adr. IP : %lu.%lu.%lu.%lu, Port : %d"
|
||||
),
|
||||
STR_IT(
|
||||
"In attesa della connessione di 3dslink…\n"
|
||||
"Ind. IP : %lu.%lu.%lu.%lu, Porta : %d\n\n"
|
||||
" \xEE\x80\x81 Annullare"
|
||||
"In attesa della connessione di nxlink…\n"
|
||||
"Ind. IP : %lu.%lu.%lu.%lu, Porta : %d"
|
||||
),
|
||||
STR_JP(
|
||||
"3dslinkが接続するのを待っている…\n"
|
||||
"IPアドレス:%lu.%lu.%lu.%lu, ポート番号:%d\n\n"
|
||||
" \xEE\x80\x81 キャンセル"
|
||||
"nxlinkが接続されるのを待っています…\n"
|
||||
"IPアドレス:%lu.%lu.%lu.%lu, ポート番号:%d"
|
||||
),
|
||||
STR_PT(
|
||||
"A aguardar pela conexão do 3dslink…\n"
|
||||
"End. IP: %lu.%lu.%lu.%lu, Porta: %d\n\n"
|
||||
" \xEE\x80\x81 Cancelar"
|
||||
"A aguardar pela conexão do nxlink…\n"
|
||||
"End. IP: %lu.%lu.%lu.%lu, Porta: %d"
|
||||
),
|
||||
STR_NL(
|
||||
"Wachten op 3dslink verbinding…\n"
|
||||
"IP Addr: %lu.%lu.%lu.%lu, Poort: %d\n\n"
|
||||
" \xEE\x80\x81 Annuleren"
|
||||
"Wachten op nxlink verbinding…\n"
|
||||
"IP Addr: %lu.%lu.%lu.%lu, Poort: %d"
|
||||
),
|
||||
STR_KO(
|
||||
"3dslink 의 연결을 대기중…\n"
|
||||
"IP 주소: %lu.%lu.%lu.%lu, 포트: %d\n\n"
|
||||
" \xEE\x80\x81 취소"
|
||||
"nxlink의 연결을 대기중…\n"
|
||||
"IP 주소: %lu.%lu.%lu.%lu, 포트: %d"
|
||||
),
|
||||
STR_RU(
|
||||
"Ожидание подключения 3dslink…\n"
|
||||
"айпи адрес: %lu.%lu.%lu.%lu, Порт: %d\n\n"
|
||||
" \xEE\x80\x81 Отмена"
|
||||
"Ожидание подключения nxlink…\n"
|
||||
"айпи адрес: %lu.%lu.%lu.%lu, Порт: %d"
|
||||
),
|
||||
STR_ZH(
|
||||
"等待 3dslink 连接…\n"
|
||||
"IP 地址:%lu.%lu.%lu.%lu,端口:%d\n\n"
|
||||
" \xEE\x80\x81 取消等待"
|
||||
STR_ZH_HANS(
|
||||
"等待 nxlink 连接…\n"
|
||||
"IP 地址:%lu.%lu.%lu.%lu,端口:%d"
|
||||
),
|
||||
STR_TW(
|
||||
"等待 3dslink 連接…\n"
|
||||
"IP 位址:%lu.%lu.%lu.%lu,連接埠:%d\n\n"
|
||||
" \xEE\x80\x81 取消等待"
|
||||
STR_ZH_HANT(
|
||||
"等待 nxlink 連接…\n"
|
||||
"IP 位址:%lu.%lu.%lu.%lu,連接埠:%d"
|
||||
),
|
||||
},
|
||||
|
||||
@ -808,8 +931,8 @@ const char* const g_strings[StrId_Max][16] =
|
||||
"%zu di %zu KiB scritti"
|
||||
),
|
||||
STR_JP(
|
||||
"データが転送されます…\n"
|
||||
"%zu / %zu KiB 書かれた"
|
||||
"データを転送しています…\n"
|
||||
"%zu / %zu KiB 転送済み"
|
||||
),
|
||||
STR_PT(
|
||||
"A transferir…\n"
|
||||
@ -821,20 +944,19 @@ const char* const g_strings[StrId_Max][16] =
|
||||
),
|
||||
STR_KO(
|
||||
"전송중…\n"
|
||||
"%zu / %zu KiB 전송"
|
||||
"%zu / %zu KiB 쓰여짐"
|
||||
),
|
||||
STR_RU(
|
||||
"Передача…\n"
|
||||
"%zu из %zu КИБ написано"
|
||||
),
|
||||
STR_ZH(
|
||||
STR_ZH_HANS(
|
||||
"正在传输…\n"
|
||||
"已完成 %zu / %zu KiB"
|
||||
),
|
||||
STR_TW(
|
||||
STR_ZH_HANT(
|
||||
"正在傳輸…\n"
|
||||
"已完成 %zu / %zu KiB"
|
||||
),
|
||||
},*/
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,24 +1,34 @@
|
||||
#pragma once
|
||||
#ifdef SWITCH
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
StrId_Loading = 0,
|
||||
StrId_AppletMode,
|
||||
StrId_Directory,
|
||||
StrId_DefaultPublisher,
|
||||
StrId_IOError,
|
||||
StrId_CouldNotOpenFile,
|
||||
StrId_NroNotFound,
|
||||
|
||||
StrId_NoAppsFound_Title,
|
||||
StrId_NoAppsFound_Msg,
|
||||
|
||||
StrId_LastLoadResult,
|
||||
StrId_AppLaunchError,
|
||||
|
||||
StrId_AppInfo_Author,
|
||||
StrId_AppInfo_Version,
|
||||
StrId_Actions_Launch,
|
||||
StrId_Actions_Open,
|
||||
StrId_Actions_Back,
|
||||
StrId_Actions_Apply,
|
||||
StrId_Actions_Star,
|
||||
StrId_Actions_Unstar,
|
||||
|
||||
StrId_MsgBox_OK,
|
||||
|
||||
StrId_Reboot,
|
||||
StrId_ReturnToHome,
|
||||
@ -38,8 +48,12 @@ typedef enum
|
||||
StrId_NetLoaderActive,
|
||||
StrId_NetLoaderTransferring,
|
||||
|
||||
StrId_ThemeMenu,
|
||||
StrId_ThemeNotApplied,
|
||||
StrId_DefaultThemeName,
|
||||
|
||||
StrId_Max,
|
||||
} StrId;
|
||||
|
||||
extern const char* const g_strings[StrId_Max][16];
|
||||
extern const char* const g_strings[StrId_Max][17];
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
size_t launchAddArg(argData_s* ad, const char* arg) {
|
||||
size_t len = strlen(arg)+1;
|
||||
if ((ad->dst+len) >= (char*)(ad+1)) return len; // Overflow
|
||||
if ((ad->dst+len) >= (char*)(ad->buf + sizeof(ad->buf))) return len; // Overflow
|
||||
ad->buf[0]++;
|
||||
strcpy(ad->dst, arg);
|
||||
ad->dst += len;
|
||||
|
@ -16,7 +16,7 @@ typedef struct
|
||||
//void (* useTitle)(u64 tid, u8 mediatype);
|
||||
} loaderFuncs_s;
|
||||
|
||||
void launchInit(void);
|
||||
bool launchInit(void);
|
||||
void launchExit(void);
|
||||
const loaderFuncs_s* launchGetLoader(void);
|
||||
size_t launchAddArg(argData_s* ad, const char* arg);
|
||||
|
@ -4,6 +4,6 @@
|
||||
|
||||
float approxSin(float x);
|
||||
|
||||
inline float clamp(float x, float min, float max) {
|
||||
static inline float clamp(float x, float min, float max) {
|
||||
return fmin(fmax(x, min), max);
|
||||
}
|
@ -1,17 +1,19 @@
|
||||
#include "common.h"
|
||||
#include <physfs.h>
|
||||
|
||||
void menuEntryInit(menuEntry_s* me, MenuEntryType type) {
|
||||
memset(me, 0, sizeof(*me));
|
||||
me->type = type;
|
||||
}
|
||||
|
||||
void menuEntryFree(menuEntry_s* me) {
|
||||
void menuEntryFree(menuEntry_s* me, bool skip_icongfx) {
|
||||
me->icon_size = 0;
|
||||
if (me->icon) {
|
||||
free(me->icon);
|
||||
me->icon = NULL;
|
||||
}
|
||||
|
||||
if (!skip_icongfx) {
|
||||
if (me->icon_gfx) {
|
||||
free(me->icon_gfx);
|
||||
me->icon_gfx = NULL;
|
||||
@ -21,6 +23,7 @@ void menuEntryFree(menuEntry_s* me) {
|
||||
free(me->icon_gfx_small);
|
||||
me->icon_gfx_small = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (me->nacp) {
|
||||
free(me->nacp);
|
||||
@ -33,6 +36,11 @@ bool fileExists(const char* path) {
|
||||
return stat(path, &st)==0 && S_ISREG(st.st_mode);
|
||||
}
|
||||
|
||||
bool fsobjExists(const char* path) {
|
||||
struct stat st;
|
||||
return stat(path, &st)==0;
|
||||
}
|
||||
|
||||
static bool menuEntryLoadEmbeddedIcon(menuEntry_s* me) {
|
||||
NroHeader header;
|
||||
NroAssetHeader asset_header;
|
||||
@ -73,6 +81,57 @@ static bool menuEntryLoadEmbeddedIcon(menuEntry_s* me) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool menuEntryLoadExternalIcon(menuEntry_s* me, const char* path, bool data_source) {
|
||||
struct stat st;
|
||||
|
||||
if (data_source) {
|
||||
return assetsPhysfsReadFile(path, &me->icon, &me->icon_size, false);
|
||||
}
|
||||
|
||||
if(stat(path, &st)==-1) return false;
|
||||
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) return false;
|
||||
|
||||
me->icon_size = st.st_size;
|
||||
me->icon = (uint8_t*)malloc(me->icon_size);
|
||||
if (me->icon == NULL) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
memset(me->icon, 0, me->icon_size);
|
||||
|
||||
bool ok = fread(me->icon, me->icon_size, 1, f) == 1;
|
||||
fclose(f);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool menuEntryImportIconGfx(menuEntry_s* me, uint8_t* icon_gfx, uint8_t* icon_gfx_small) {
|
||||
size_t tmpsize;
|
||||
|
||||
if (icon_gfx == NULL || icon_gfx_small == NULL) return false;
|
||||
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryIcon];
|
||||
ThemeLayoutObject *layoutobj2 = &themeCurrent.layoutObjects[ThemeLayoutId_MenuListIcon];
|
||||
|
||||
tmpsize = layoutobj->size[0]*layoutobj->size[1]*3;
|
||||
me->icon_gfx = (uint8_t*)malloc(tmpsize);
|
||||
if (me->icon_gfx) memcpy(me->icon_gfx, icon_gfx, tmpsize);
|
||||
|
||||
if (me->icon_gfx) {
|
||||
tmpsize = layoutobj2->size[0]*layoutobj2->size[1]*3;
|
||||
me->icon_gfx_small = (uint8_t*)malloc(tmpsize);
|
||||
if (me->icon_gfx_small) memcpy(me->icon_gfx_small, icon_gfx_small, tmpsize);
|
||||
|
||||
if (me->icon_gfx_small == NULL) {
|
||||
free(me->icon_gfx);
|
||||
me->icon_gfx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return me->icon_gfx && me->icon_gfx_small;
|
||||
}
|
||||
|
||||
static bool menuEntryLoadEmbeddedNacp(menuEntry_s* me) {
|
||||
NroHeader header;
|
||||
NroAssetHeader asset_header;
|
||||
@ -117,6 +176,26 @@ static bool menuEntryLoadEmbeddedNacp(menuEntry_s* me) {
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool menuEntryLoadExternalNacp(menuEntry_s* me, const char* path) {
|
||||
struct stat st;
|
||||
|
||||
if(stat(path, &st)==-1) return false;
|
||||
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) return false;
|
||||
|
||||
me->nacp = (NacpStruct*)malloc(sizeof(NacpStruct));
|
||||
if (me->nacp == NULL) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
memset(me->nacp, 0, sizeof(NacpStruct));
|
||||
|
||||
bool ok = fread(me->nacp, sizeof(NacpStruct), 1, f) == 1;
|
||||
fclose(f);
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*static void fixSpaceNewLine(char* buf) {
|
||||
char *outp = buf, *inp = buf;
|
||||
char lastc = 0;
|
||||
@ -131,20 +210,29 @@ static bool menuEntryLoadEmbeddedNacp(menuEntry_s* me) {
|
||||
} while (lastc);
|
||||
}*/
|
||||
|
||||
bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut) {
|
||||
static char tempbuf[PATH_MAX+1];
|
||||
bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut, bool check_exists) {
|
||||
int i=0, tmplen;
|
||||
menu_s *menu_fileassoc = menuFileassocGetCurrent();
|
||||
menuEntry_s* fileassoc_me = NULL;
|
||||
char *strptr = NULL;
|
||||
char tempbuf[PATH_MAX+16];
|
||||
//bool isOldAppFolder = false;
|
||||
|
||||
if (check_exists && !fsobjExists(me->path)) return false;
|
||||
|
||||
tempbuf[PATH_MAX] = 0;
|
||||
strcpy(me->name, name);
|
||||
strncpy(me->name, name, sizeof(me->name)-1);
|
||||
|
||||
if (me->type == ENTRY_TYPE_FOLDER)
|
||||
{
|
||||
//Check for <dirpath>/<dirname>.nro
|
||||
snprintf(tempbuf, sizeof(tempbuf)-1, "%.*s/%.*s.nro", (int)sizeof(tempbuf)/2, me->path, (int)sizeof(tempbuf)/2-7, name);
|
||||
bool found = fileExists(tempbuf);
|
||||
bool fileassoc_flag = 0;
|
||||
|
||||
//Use the first .nro found in the directory, if there's only 1 NRO in the directory. Only used for paths starting with "sdmc:/switch/".
|
||||
if (!found && strncmp(me->path, "sdmc:/switch/", 13)==0) {
|
||||
tmplen = strlen(menuGetRootPath());
|
||||
if (!found && strncmp(me->path, menuGetRootPath(), tmplen)==0 && me->path[tmplen]=='/') {
|
||||
DIR* dir;
|
||||
struct dirent* dp;
|
||||
u32 nro_count=0;
|
||||
@ -169,22 +257,44 @@ bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut) {
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
if (!found && menu_fileassoc->nEntries > 0) {
|
||||
fileassoc_flag = 1;
|
||||
dir = opendir(me->path);
|
||||
if (dir) {
|
||||
while ((dp = readdir(dir))) {
|
||||
if (dp->d_name[0]=='.')//Check this here so that it's consistent with menuScan().
|
||||
continue;
|
||||
|
||||
for (fileassoc_me = menu_fileassoc->firstEntry, i = 0; fileassoc_me; fileassoc_me = fileassoc_me->next, i++) {
|
||||
if (!fileassoc_me->fileassoc_type) continue; //Only handle fileassoc entries for filenames, not file_extensions.
|
||||
|
||||
if (strcmp(dp->d_name, fileassoc_me->fileassoc_str)) continue;
|
||||
|
||||
snprintf(tempbuf, sizeof(tempbuf)-1, "%.*s/%.*s", (int)sizeof(tempbuf)/2, me->path, (int)sizeof(tempbuf)/2-7, dp->d_name);
|
||||
found = fileExists(tempbuf);
|
||||
if (found) break;
|
||||
}
|
||||
if (found) break;
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
{
|
||||
//isOldAppFolder = true;
|
||||
shortcut = false;
|
||||
me->type = ENTRY_TYPE_FILE;
|
||||
me->type = fileassoc_flag ? ENTRY_TYPE_FILE_OTHER : ENTRY_TYPE_FILE;
|
||||
strcpy(me->path, tempbuf);
|
||||
}
|
||||
}
|
||||
|
||||
if (me->type == ENTRY_TYPE_FILE)
|
||||
{
|
||||
strcpy(me->name, name);
|
||||
strcpy(me->author, textGetString(StrId_DefaultPublisher));
|
||||
strcpy(me->version, "1.0.0");
|
||||
strncpy(me->author, textGetString(StrId_DefaultPublisher), sizeof(me->author)-1);
|
||||
strncpy(me->version, "1.0.0", sizeof(me->version)-1);
|
||||
|
||||
//shortcut_s sc;
|
||||
|
||||
@ -221,7 +331,7 @@ bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut) {
|
||||
char* ext = getExtension(tempbuf);
|
||||
|
||||
strcpy(ext, ".jpg");
|
||||
iconLoaded = menuEntryLoadExternalIcon(me, tempbuf);
|
||||
iconLoaded = menuEntryLoadExternalIcon(me, tempbuf, false);
|
||||
if (iconLoaded) break;
|
||||
|
||||
if (isOldAppFolder)
|
||||
@ -229,7 +339,7 @@ bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut) {
|
||||
char* slash = getSlash(tempbuf);
|
||||
|
||||
strcpy(slash, "/icon.jpg");
|
||||
iconLoaded = menuEntryLoadExternalIcon(me, tempbuf);
|
||||
iconLoaded = menuEntryLoadExternalIcon(me, tempbuf, false);
|
||||
if (iconLoaded) break;
|
||||
}*/
|
||||
|
||||
@ -294,67 +404,394 @@ bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut) {
|
||||
shortcutFree(&sc);*/
|
||||
}
|
||||
|
||||
if (me->type == ENTRY_TYPE_THEME) {
|
||||
config_t cfg = {0};
|
||||
config_init(&cfg);
|
||||
config_setting_t *themeInfo;
|
||||
const char *name,
|
||||
*author = textGetString(StrId_DefaultPublisher),
|
||||
*version = "1.0.0";
|
||||
|
||||
const char* cfg_path = me->path;
|
||||
const char* theme_archive_path = NULL;
|
||||
const char* ext = getExtension(me->path);
|
||||
bool good_cfg = false;
|
||||
bool is_archive = false;
|
||||
#ifdef __SWITCH__
|
||||
bool is_romfs = false;
|
||||
if (strcasecmp(ext, ".romfs")==0) {
|
||||
if (R_FAILED(romfsMountFromFsdev(me->path, 0, "themetmp")))
|
||||
return false;
|
||||
is_romfs = true;
|
||||
cfg_path = "themetmp:/theme.cfg";
|
||||
theme_archive_path = "themetmp:/";
|
||||
}
|
||||
#endif
|
||||
|
||||
if (strcasecmp(ext, ".romfs")!=0 && strcasecmp(ext, ".cfg")!=0) {
|
||||
theme_archive_path = me->path;
|
||||
}
|
||||
if (theme_archive_path) {
|
||||
if (!PHYSFS_mount(theme_archive_path, "themetmp", 0)) cfg_path = NULL;
|
||||
else {
|
||||
is_archive = true;
|
||||
cfg_path = "themetmp/theme.cfg";
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg_path) {
|
||||
if (!is_archive) good_cfg = config_read_file(&cfg, cfg_path);
|
||||
else {
|
||||
u8 *cfg_buf = NULL;
|
||||
good_cfg = assetsPhysfsReadFile(cfg_path, &cfg_buf, NULL, true);
|
||||
if (good_cfg) good_cfg = config_read_string(&cfg, (char*)cfg_buf);
|
||||
free(cfg_buf);
|
||||
}
|
||||
}
|
||||
|
||||
if (good_cfg) {
|
||||
themeInfo = config_lookup(&cfg, "themeInfo");
|
||||
if (themeInfo != NULL) {
|
||||
if(config_setting_lookup_string(themeInfo, "name", &name))
|
||||
strncpy(me->name, name, sizeof(me->name)-1);
|
||||
config_setting_lookup_string(themeInfo, "author", &author);
|
||||
config_setting_lookup_string(themeInfo, "version", &version);
|
||||
}
|
||||
}
|
||||
|
||||
strncpy(me->author, author, sizeof(me->author)-1);
|
||||
strncpy(me->version, version, sizeof(me->version)-1);
|
||||
config_destroy(&cfg);
|
||||
|
||||
if (good_cfg && is_archive) {
|
||||
bool iconLoaded = false;
|
||||
|
||||
iconLoaded = menuEntryLoadExternalIcon(me, "themetmp/icon.jpg", true);
|
||||
|
||||
if (iconLoaded) menuEntryParseIcon(me);
|
||||
}
|
||||
|
||||
if (is_archive) PHYSFS_unmount(theme_archive_path);
|
||||
|
||||
#ifdef __SWITCH__
|
||||
if (is_romfs) romfsUnmount("themetmp");
|
||||
#endif
|
||||
}
|
||||
|
||||
if (me->type == ENTRY_TYPE_FILE_OTHER)
|
||||
{
|
||||
if (menu_fileassoc->nEntries == 0) return false;
|
||||
|
||||
for (fileassoc_me = menu_fileassoc->firstEntry, i = 0; fileassoc_me; fileassoc_me = fileassoc_me->next, i++) {
|
||||
//For fileassoc_type==0 compare the extension, otherwise compare the filename.
|
||||
if (!fileassoc_me->fileassoc_type) {
|
||||
strptr = getExtension(me->path);
|
||||
}
|
||||
if (fileassoc_me->fileassoc_type) {
|
||||
strptr = getSlash(me->path);
|
||||
if (strptr[0] == '/') strptr++;
|
||||
}
|
||||
|
||||
if (strcmp(strptr, fileassoc_me->fileassoc_str)) continue;
|
||||
|
||||
//At this point a match was found.
|
||||
|
||||
me->type = ENTRY_TYPE_FILE;
|
||||
|
||||
//Attempt to load the icon from {me->path filepath with extension .jpg}, then on failure use the icon data from fileassoc_me.
|
||||
memset(tempbuf, 0, sizeof(tempbuf));
|
||||
strncpy(tempbuf, me->path, sizeof(tempbuf));
|
||||
tempbuf[sizeof(tempbuf)-1] = 0;
|
||||
strptr = getExtension(tempbuf);
|
||||
strncpy(strptr, ".jpg", sizeof(tempbuf)-1 - ((ptrdiff_t)strptr - (ptrdiff_t)tempbuf));
|
||||
|
||||
bool iconLoaded = false;
|
||||
|
||||
iconLoaded = menuEntryLoadExternalIcon(me, tempbuf, false);
|
||||
|
||||
if (iconLoaded) menuEntryParseIcon(me);
|
||||
|
||||
if (iconLoaded && !(me->icon_gfx && me->icon_gfx_small)) iconLoaded = 0;
|
||||
|
||||
if (!iconLoaded && fileassoc_me->icon_gfx && fileassoc_me->icon_gfx_small)
|
||||
iconLoaded = menuEntryImportIconGfx(me, fileassoc_me->icon_gfx, fileassoc_me->icon_gfx_small);
|
||||
|
||||
//Attempt to load the nacp from {me->path filepath with extension .nacp}, then on failure use the config from fileassoc_me.
|
||||
memset(tempbuf, 0, sizeof(tempbuf));
|
||||
strncpy(tempbuf, me->path, sizeof(tempbuf));
|
||||
tempbuf[sizeof(tempbuf)-1] = 0;
|
||||
strptr = getExtension(tempbuf);
|
||||
strncpy(strptr, ".nacp", sizeof(tempbuf)-1 - ((ptrdiff_t)strptr - (ptrdiff_t)tempbuf));
|
||||
|
||||
bool nacpLoaded = menuEntryLoadExternalNacp(me, tempbuf);
|
||||
|
||||
if (nacpLoaded) menuEntryParseNacp(me);
|
||||
else {
|
||||
strncpy(me->author, fileassoc_me->author, sizeof(me->author));
|
||||
me->author[sizeof(me->author)-1] = 0;
|
||||
|
||||
strncpy(me->version, fileassoc_me->version, sizeof(me->version));
|
||||
me->version[sizeof(me->version)-1] = 0;
|
||||
}
|
||||
|
||||
// Initialize the argument data
|
||||
argData_s* ad = &me->args;
|
||||
argData_s* ad_assoc = &fileassoc_me->args;
|
||||
char *arg_src = (char*)&ad_assoc->buf[1];
|
||||
bool ftoken_found=0;
|
||||
ad->dst = (char*)&ad->buf[1];
|
||||
|
||||
for (u32 argi=0; argi<ad_assoc->buf[0]; argi++, arg_src+= strlen(arg_src)+1) {
|
||||
if (argi) {
|
||||
strptr = strchr(arg_src, '%');
|
||||
if (strptr && strptr[0] && strptr[1] && (strptr == arg_src || strptr[-1] != '\\')) {
|
||||
if (strptr[1] == 'f') {
|
||||
memset(tempbuf, 0, sizeof(tempbuf));
|
||||
snprintf(tempbuf, sizeof(tempbuf)-1, "%.*s%s%s", (int)((uintptr_t)strptr-(uintptr_t)arg_src), arg_src, me->path, &strptr[2]);
|
||||
launchAddArg(ad, tempbuf);
|
||||
ftoken_found = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launchAddArg(ad, arg_src);
|
||||
}
|
||||
if (!ftoken_found) launchAddArg(ad, me->path);
|
||||
|
||||
strncpy(me->path, fileassoc_me->path, sizeof(me->path));
|
||||
me->path[sizeof(me->path)-1] = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//check for .filename.star in same path
|
||||
strptr = getSlash(me->path);
|
||||
if (strptr[0] == '/') strptr++;
|
||||
int strptrLen = strlen(strptr);
|
||||
snprintf(me->starpath, sizeof(me->starpath)-1, "%.*s.%.*s.star", (int)(strlen(me->path) - strptrLen), me->path, (int)strptrLen, strptr);
|
||||
me->starred = fileExists(me->starpath);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void menuEntryParseIcon(menuEntry_s* me) {
|
||||
uint8_t *imageptr = NULL;
|
||||
size_t imagesize = 256*256*3;
|
||||
void menuEntryFileassocLoad(const char* filepath) {
|
||||
bool success=0, iconLoaded=0;
|
||||
menuEntry_s* me = NULL;
|
||||
|
||||
config_setting_t *fileassoc = NULL, *targets = NULL, *target = NULL, *app_args = NULL, *target_args = NULL;
|
||||
config_t cfg = {0};
|
||||
int targets_len=0, args_len=0, i;
|
||||
const char *strptr = NULL;
|
||||
|
||||
char app_path[PATH_MAX+8];
|
||||
char main_icon_path[PATH_MAX+1];
|
||||
char target_icon_path[PATH_MAX+1];
|
||||
char target_file_extension[PATH_MAX+1];
|
||||
char target_filename[PATH_MAX+1];
|
||||
|
||||
char app_author[ENTRY_AUTHORLENGTH+2];
|
||||
char app_version[ENTRY_VERLENGTH+2];
|
||||
|
||||
uint8_t *app_icon_gfx = NULL;
|
||||
uint8_t *app_icon_gfx_small = NULL;
|
||||
|
||||
config_init(&cfg);
|
||||
|
||||
memset(app_path, 0, sizeof(app_path));
|
||||
memset(main_icon_path, 0, sizeof(main_icon_path));
|
||||
memset(app_author, 0, sizeof(app_author));
|
||||
memset(app_version, 0, sizeof(app_version));
|
||||
|
||||
if (!fileExists(filepath)) return;
|
||||
|
||||
if (config_read_file(&cfg, filepath)) {
|
||||
fileassoc = config_lookup(&cfg, "fileassoc");
|
||||
|
||||
if (fileassoc != NULL) {
|
||||
if (config_setting_lookup_string(fileassoc, "app_path", &strptr))
|
||||
snprintf(app_path, sizeof(app_path)-1, "%s%s", menuGetRootBasePath(), strptr);
|
||||
if (config_setting_lookup_string(fileassoc, "icon_path", &strptr))
|
||||
snprintf(main_icon_path, sizeof(main_icon_path)-1, "%s%s", menuGetRootBasePath(), strptr);
|
||||
app_args = config_setting_lookup(fileassoc, "app_args");
|
||||
targets = config_setting_lookup(fileassoc, "targets");
|
||||
|
||||
if (app_path[0] && targets) {
|
||||
targets_len = config_setting_length(targets);
|
||||
|
||||
if (targets_len > 0) {
|
||||
//Load the author/version and icon data with the NRO app path.
|
||||
me = menuCreateEntry(ENTRY_TYPE_FILE);
|
||||
success = 0;
|
||||
if (me) {
|
||||
strncpy(me->path, app_path, sizeof(me->path)-1);
|
||||
me->path[sizeof(me->path)-1] = 0;
|
||||
|
||||
strptr = getSlash(app_path);
|
||||
if(strptr[0] == '/') strptr++;
|
||||
|
||||
if (menuEntryLoad(me, strptr, 0, true)) {
|
||||
strncpy(app_author, me->author, sizeof(app_author));
|
||||
app_author[sizeof(app_author)-1] = 0;
|
||||
strncpy(app_version, me->version, sizeof(app_version));
|
||||
app_version[sizeof(app_version)-1] = 0;
|
||||
app_icon_gfx = me->icon_gfx;
|
||||
app_icon_gfx_small = me->icon_gfx_small;
|
||||
success = 1;
|
||||
}
|
||||
|
||||
menuDeleteEntry(me, success);
|
||||
me = NULL;
|
||||
}
|
||||
|
||||
//Process the targets list.
|
||||
if (success) {
|
||||
for (i=0; i<targets_len; i++) {
|
||||
target = config_setting_get_elem(targets, i);
|
||||
if (target == NULL) continue;
|
||||
|
||||
memset(target_icon_path, 0, sizeof(target_icon_path));
|
||||
memset(target_file_extension, 0, sizeof(target_file_extension));
|
||||
memset(target_filename, 0, sizeof(target_filename));
|
||||
|
||||
if (config_setting_lookup_string(target, "icon_path", &strptr))
|
||||
snprintf(target_icon_path, sizeof(target_icon_path)-1, "%s%s", menuGetRootBasePath(), strptr);
|
||||
if (config_setting_lookup_string(target, "file_extension", &strptr))
|
||||
strncpy(target_file_extension, strptr, sizeof(target_file_extension)-1);
|
||||
if (config_setting_lookup_string(target, "filename", &strptr))
|
||||
strncpy(target_filename, strptr, sizeof(target_filename)-1);
|
||||
target_args = config_setting_lookup(target, "app_args");
|
||||
|
||||
//string_is_set for target_file_extension and target_filename must differ: only 1 can be set, not both set or both not set.
|
||||
if ((target_file_extension[0]!=0) == (target_filename[0]!=0)) continue;
|
||||
|
||||
me = menuCreateEntry(ENTRY_TYPE_FILEASSOC);
|
||||
iconLoaded = 0;
|
||||
|
||||
if (me) {
|
||||
strncpy(me->path, app_path, sizeof(me->path));
|
||||
me->path[sizeof(me->path)-1] = 0;
|
||||
strncpy(me->author, app_author, sizeof(me->author));
|
||||
me->author[sizeof(me->author)-1] = 0;
|
||||
strncpy(me->version, app_version, sizeof(me->version));
|
||||
me->version[sizeof(me->version)-1] = 0;
|
||||
|
||||
if (target_file_extension[0]) {
|
||||
me->fileassoc_type = 0;
|
||||
strncpy(me->fileassoc_str, target_file_extension, sizeof(me->fileassoc_str));
|
||||
} else if (target_filename[0]) {
|
||||
me->fileassoc_type = 1;
|
||||
strncpy(me->fileassoc_str, target_filename, sizeof(me->fileassoc_str));
|
||||
}
|
||||
me->fileassoc_str[sizeof(me->fileassoc_str)-1] = 0;
|
||||
|
||||
if (target_icon_path[0]) iconLoaded = menuEntryLoadExternalIcon(me, target_icon_path, false);
|
||||
if (!iconLoaded && main_icon_path[0]) iconLoaded = menuEntryLoadExternalIcon(me, main_icon_path, false);
|
||||
|
||||
if (iconLoaded) {
|
||||
menuEntryParseIcon(me);
|
||||
} else {
|
||||
iconLoaded = menuEntryImportIconGfx(me, app_icon_gfx, app_icon_gfx_small);
|
||||
}
|
||||
|
||||
argData_s* ad = &me->args;
|
||||
ad->dst = (char*)&ad->buf[1];
|
||||
launchAddArg(ad, me->path);
|
||||
|
||||
config_setting_t *config_args = target_args ? target_args : app_args;
|
||||
if (config_args) {
|
||||
args_len = config_setting_length(config_args);
|
||||
for (int argi=0; argi<args_len; argi++) {
|
||||
strptr = config_setting_get_string_elem(config_args, argi);
|
||||
if (strptr==NULL) continue;
|
||||
|
||||
launchAddArg(ad, strptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (me) menuFileassocAddEntry(me);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
free(app_icon_gfx);
|
||||
free(app_icon_gfx_small);
|
||||
}
|
||||
|
||||
config_destroy(&cfg);
|
||||
}
|
||||
|
||||
void menuEntryParseIcon(menuEntry_s* me) {
|
||||
if (me->icon_size==0 || me->icon==NULL) return;
|
||||
|
||||
njInit();
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryIcon];
|
||||
ThemeLayoutObject *layoutobj2 = &themeCurrent.layoutObjects[ThemeLayoutId_MenuListIcon];
|
||||
|
||||
if (njDecode(me->icon, me->icon_size) != NJ_OK) {
|
||||
njDone();
|
||||
return;
|
||||
}
|
||||
size_t imagesize = layoutobj->imageSize[0]*layoutobj->imageSize[1]*3;
|
||||
bool ret=true;
|
||||
uint8_t *tmp_gfx = (uint8_t*)malloc(imagesize);
|
||||
|
||||
if (tmp_gfx == NULL) ret = false;
|
||||
|
||||
if (ret) ret = assetsLoadJpgFromMemory(me->icon, me->icon_size, tmp_gfx, IMAGE_MODE_RGB24, layoutobj->imageSize[0], layoutobj->imageSize[1]);
|
||||
|
||||
if (ret) me->icon_gfx = downscaleImg(tmp_gfx, layoutobj->imageSize[0], layoutobj->imageSize[1], layoutobj->size[0], layoutobj->size[1], IMAGE_MODE_RGB24);
|
||||
|
||||
if (ret && me->icon_gfx==NULL) ret = false;
|
||||
|
||||
me->icon_size = 0;
|
||||
free(me->icon);
|
||||
me->icon = NULL;
|
||||
|
||||
if ((njGetWidth() != 256 || njGetHeight() != 256 || (size_t)njGetImageSize() != imagesize) || njIsColor() != 1) {//The decoded image must be RGB and 256x256.
|
||||
njDone();
|
||||
return;
|
||||
if (ret) me->icon_gfx_small = downscaleImg(tmp_gfx, layoutobj->imageSize[0], layoutobj->imageSize[1], layoutobj2->size[0], layoutobj2->size[1], IMAGE_MODE_RGB24);
|
||||
|
||||
if (!ret || me->icon_gfx_small == NULL) {
|
||||
free(me->icon_gfx);
|
||||
me->icon_gfx = NULL;
|
||||
}
|
||||
|
||||
imageptr = njGetImage();
|
||||
if (imageptr == NULL) {
|
||||
njDone();
|
||||
return;
|
||||
}
|
||||
|
||||
me->icon_gfx = (uint8_t*)malloc(imagesize);
|
||||
if (me->icon_gfx == NULL) {
|
||||
njDone();
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(me->icon_gfx, imageptr, imagesize);
|
||||
|
||||
njDone();
|
||||
|
||||
me->icon_gfx_small = downscaleIcon(me->icon_gfx);
|
||||
free(tmp_gfx);
|
||||
}
|
||||
|
||||
uint8_t *downscaleIcon(const uint8_t *image) {
|
||||
uint8_t *out = (uint8_t*)malloc(140*140*3);
|
||||
uint8_t *downscaleImg(const uint8_t *image, int srcWidth, int srcHeight, int destWidth, int destHeight, ImageMode mode) {
|
||||
uint8_t *out;
|
||||
|
||||
switch (mode) {
|
||||
case IMAGE_MODE_RGBA32:
|
||||
out = (uint8_t*)malloc(destWidth*destHeight*4);
|
||||
break;
|
||||
|
||||
default:
|
||||
out = (uint8_t*)malloc(destWidth*destHeight*3);
|
||||
break;
|
||||
}
|
||||
|
||||
if (out == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (srcWidth == destWidth && srcHeight == destHeight) {
|
||||
memcpy(out, image, destWidth*destHeight*(mode==IMAGE_MODE_RGBA32 ? 4 : 3));
|
||||
return out;
|
||||
}
|
||||
|
||||
int tmpx, tmpy;
|
||||
int pos;
|
||||
float sourceX, sourceY;
|
||||
int destWidth = 140, destHeight = 140;
|
||||
float xScale = 256.0 / (float)destWidth;
|
||||
float yScale = 256.0 / (float)destHeight;
|
||||
float xScale = (float)srcWidth / (float)destWidth;
|
||||
float yScale = (float)srcHeight / (float)destHeight;
|
||||
int pixelX, pixelY;
|
||||
uint8_t r1, r2, r3, r4;
|
||||
uint8_t g1, g2, g3, g4;
|
||||
uint8_t b1, b2, b3, b4;
|
||||
uint8_t a1, a2, a3, a4;
|
||||
float fx, fy, fx1, fy1;
|
||||
int w1, w2, w3, w4;
|
||||
|
||||
@ -366,26 +803,57 @@ uint8_t *downscaleIcon(const uint8_t *image) {
|
||||
pixelY = (int)sourceY;
|
||||
|
||||
// get colours from four surrounding pixels
|
||||
pos = ((pixelY + 0) * 256 + pixelX + 0) * 3;
|
||||
if (mode == IMAGE_MODE_RGBA32)
|
||||
pos = ((pixelY + 0) * srcWidth + pixelX + 0) * 4;
|
||||
else
|
||||
pos = ((pixelY + 0) * srcWidth + pixelX + 0) * 3;
|
||||
|
||||
r1 = image[pos+0];
|
||||
g1 = image[pos+1];
|
||||
b1 = image[pos+2];
|
||||
|
||||
pos = ((pixelY + 0) * 256 + pixelX + 1) * 3;
|
||||
if (mode == IMAGE_MODE_RGBA32)
|
||||
a1 = image[pos+3];
|
||||
|
||||
|
||||
if (mode == IMAGE_MODE_RGBA32)
|
||||
pos = ((pixelY + 0) * srcWidth + pixelX + 1) * 4;
|
||||
else
|
||||
pos = ((pixelY + 0) * srcWidth + pixelX + 1) * 3;
|
||||
|
||||
r2 = image[pos+0];
|
||||
g2 = image[pos+1];
|
||||
b2 = image[pos+2];
|
||||
|
||||
pos = ((pixelY + 1) * 256 + pixelX + 0) * 3;
|
||||
if (mode == IMAGE_MODE_RGBA32)
|
||||
a2 = image[pos+3];
|
||||
|
||||
|
||||
if (mode == IMAGE_MODE_RGBA32)
|
||||
pos = ((pixelY + 1) * srcWidth + pixelX + 0) * 4;
|
||||
else
|
||||
pos = ((pixelY + 1) * srcWidth + pixelX + 0) * 3;
|
||||
|
||||
r3 = image[pos+0];
|
||||
g3 = image[pos+1];
|
||||
b3 = image[pos+2];
|
||||
|
||||
pos = ((pixelY + 1) * 256 + pixelX + 1) * 3;
|
||||
if (mode == IMAGE_MODE_RGBA32)
|
||||
a3 = image[pos+3];
|
||||
|
||||
|
||||
if (mode == IMAGE_MODE_RGBA32)
|
||||
pos = ((pixelY + 1) * srcWidth + pixelX + 1) * 4;
|
||||
else
|
||||
pos = ((pixelY + 1) * srcWidth + pixelX + 1) * 3;
|
||||
|
||||
r4 = image[pos+0];
|
||||
g4 = image[pos+1];
|
||||
b4 = image[pos+2];
|
||||
|
||||
if (mode == IMAGE_MODE_RGBA32)
|
||||
a4 = image[pos+3];
|
||||
|
||||
// determine weights
|
||||
fx = sourceX - pixelX;
|
||||
fy = sourceY - pixelY;
|
||||
@ -398,10 +866,17 @@ uint8_t *downscaleIcon(const uint8_t *image) {
|
||||
w4 = (int)(fx*fy*256.0);
|
||||
|
||||
// set output pixels
|
||||
if (mode == IMAGE_MODE_RGBA32)
|
||||
pos = ((tmpy*destWidth) + tmpx) * 4;
|
||||
else
|
||||
pos = ((tmpy*destWidth) + tmpx) * 3;
|
||||
|
||||
out[pos+0] = (uint8_t)((r1 * w1 + r2 * w2 + r3 * w3 + r4 * w4) >> 8);
|
||||
out[pos+1] = (uint8_t)((g1 * w1 + g2 * w2 + g3 * w3 + g4 * w4) >> 8);
|
||||
out[pos+2] = (uint8_t)((b1 * w1 + b2 * w2 + b3 * w3 + b4 * w4) >> 8);
|
||||
|
||||
if (mode == IMAGE_MODE_RGBA32)
|
||||
out[pos+3] = (uint8_t)((a1 * w1 + a2 * w2 + a3 * w3 + a4 * w4) >> 8);
|
||||
}
|
||||
}
|
||||
|
||||
@ -409,13 +884,24 @@ uint8_t *downscaleIcon(const uint8_t *image) {
|
||||
}
|
||||
|
||||
void menuEntryParseNacp(menuEntry_s* me) {
|
||||
int lang = 0;//TODO: Update this once libnx supports settings get-language.
|
||||
NacpLanguageEntry *langentry = NULL;
|
||||
|
||||
if (me->nacp==NULL) return;
|
||||
|
||||
strncpy(me->name, me->nacp->lang[lang].name, sizeof(me->name)-1);
|
||||
strncpy(me->author, me->nacp->lang[lang].author, sizeof(me->author)-1);
|
||||
strncpy(me->version, me->nacp->version, sizeof(me->version)-1);
|
||||
strncpy(me->version, me->nacp->display_version, sizeof(me->version)-1);
|
||||
|
||||
#ifdef __SWITCH__
|
||||
Result rc=0;
|
||||
rc = nacpGetLanguageEntry(me->nacp, &langentry);
|
||||
|
||||
if (R_SUCCEEDED(rc) && langentry!=NULL) {
|
||||
#else
|
||||
langentry = &me->nacp->lang[0];
|
||||
if (1) {
|
||||
#endif
|
||||
strncpy(me->name, langentry->name, sizeof(me->name)-1);
|
||||
strncpy(me->author, langentry->author, sizeof(me->author)-1);
|
||||
}
|
||||
|
||||
free(me->nacp);
|
||||
me->nacp = NULL;
|
||||
|
@ -1,25 +1,29 @@
|
||||
#include "common.h"
|
||||
|
||||
static menu_s s_menu[2];
|
||||
static bool s_curMenu;
|
||||
static menu_s s_menuFileassoc[2];
|
||||
static bool s_curMenu, s_curMenuFileassoc;
|
||||
|
||||
menu_s* menuGetCurrent(void) {
|
||||
return &s_menu[s_curMenu];
|
||||
}
|
||||
|
||||
static menuEntry_s* menuCreateEntry(MenuEntryType type) {
|
||||
menu_s* menuFileassocGetCurrent(void) {
|
||||
return &s_menuFileassoc[s_curMenuFileassoc];
|
||||
}
|
||||
|
||||
menuEntry_s* menuCreateEntry(MenuEntryType type) {
|
||||
menuEntry_s* me = (menuEntry_s*)malloc(sizeof(menuEntry_s));
|
||||
menuEntryInit(me, type);
|
||||
return me;
|
||||
}
|
||||
|
||||
static void menuDeleteEntry(menuEntry_s* me) {
|
||||
menuEntryFree(me);
|
||||
void menuDeleteEntry(menuEntry_s* me, bool skip_icongfx) {
|
||||
menuEntryFree(me, skip_icongfx);
|
||||
free(me);
|
||||
}
|
||||
|
||||
static void menuAddEntry(menuEntry_s* me) {
|
||||
menu_s* m = &s_menu[!s_curMenu];
|
||||
static void _menuAddEntry(menu_s *m, menuEntry_s* me) {
|
||||
me->menu = m;
|
||||
if (m->lastEntry)
|
||||
{
|
||||
@ -30,20 +34,54 @@ static void menuAddEntry(menuEntry_s* me) {
|
||||
m->firstEntry = me;
|
||||
m->lastEntry = me;
|
||||
}
|
||||
m->xPos = 0;
|
||||
m->slideSpeed = 0;
|
||||
m->nEntries ++;
|
||||
}
|
||||
|
||||
static void menuClear(void) {
|
||||
static void menuAddEntry(menuEntry_s* me) {
|
||||
_menuAddEntry(&s_menu[!s_curMenu], me);
|
||||
}
|
||||
|
||||
void menuFileassocAddEntry(menuEntry_s* me) {
|
||||
_menuAddEntry(&s_menuFileassoc[!s_curMenuFileassoc], me);
|
||||
}
|
||||
|
||||
static void menuAddEntryToFront(menuEntry_s* me) {
|
||||
menu_s* m = &s_menu[!s_curMenu];
|
||||
me->menu = m;
|
||||
if (m->lastEntry)
|
||||
{
|
||||
me->next = m->firstEntry;
|
||||
m->firstEntry = me;
|
||||
} else
|
||||
{
|
||||
m->firstEntry = me;
|
||||
m->lastEntry = me;
|
||||
}
|
||||
m->xPos = 0;
|
||||
m->slideSpeed = 0;
|
||||
m->nEntries ++;
|
||||
}
|
||||
|
||||
static void _menuClear(menu_s* m) {
|
||||
menuEntry_s *cur, *next;
|
||||
for (cur = m->firstEntry; cur; cur = next)
|
||||
{
|
||||
next = cur->next;
|
||||
menuDeleteEntry(cur);
|
||||
menuDeleteEntry(cur, 0);
|
||||
}
|
||||
memset(m, 0, sizeof(*m));
|
||||
}
|
||||
|
||||
static void menuClear(void) {
|
||||
_menuClear(&s_menu[!s_curMenu]);
|
||||
}
|
||||
|
||||
static void menuFileassocClear(void) {
|
||||
_menuClear(&s_menuFileassoc[!s_curMenuFileassoc]);
|
||||
}
|
||||
|
||||
static int menuEntryCmp(const void *p1, const void *p2) {
|
||||
const menuEntry_s* lhs = *(menuEntry_s**)p1;
|
||||
const menuEntry_s* rhs = *(menuEntry_s**)p2;
|
||||
@ -60,20 +98,34 @@ static void menuSort(void) {
|
||||
menu_s* m = &s_menu[!s_curMenu];
|
||||
int nEntries = m->nEntries;
|
||||
if (nEntries==0) return;
|
||||
int nEntriesStar = 0, nEntriesNoStar = 0;
|
||||
|
||||
menuEntry_s** list = (menuEntry_s**)calloc(nEntries, sizeof(menuEntry_s*));
|
||||
if(list == NULL) return;
|
||||
menuEntry_s** listStar = (menuEntry_s**)calloc(nEntries, sizeof(menuEntry_s*));
|
||||
if(listStar == NULL) {
|
||||
free(list);
|
||||
return;
|
||||
}
|
||||
|
||||
menuEntry_s* p = m->firstEntry;
|
||||
for(i = 0; i < nEntries; ++i) {
|
||||
list[i] = p;
|
||||
if (p->starred)
|
||||
listStar[nEntriesStar++] = p;
|
||||
else
|
||||
list[nEntriesNoStar++] = p;
|
||||
p = p->next;
|
||||
}
|
||||
|
||||
qsort(list, nEntries, sizeof(menuEntry_s*), menuEntryCmp);
|
||||
qsort(listStar, nEntriesStar, sizeof(menuEntry_s*), menuEntryCmp);
|
||||
qsort(list, nEntriesNoStar, sizeof(menuEntry_s*), menuEntryCmp);
|
||||
|
||||
menuEntry_s** pp = &m->firstEntry;
|
||||
for(i = 0; i < nEntries; ++i) {
|
||||
for(i = 0; i < nEntriesStar; ++i) {
|
||||
*pp = listStar[i];
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
for(i = 0; i < nEntriesNoStar; ++i) {
|
||||
*pp = list[i];
|
||||
pp = &(*pp)->next;
|
||||
}
|
||||
@ -81,13 +133,33 @@ static void menuSort(void) {
|
||||
*pp = NULL;
|
||||
|
||||
free(list);
|
||||
free(listStar);
|
||||
}
|
||||
|
||||
void menuReorder (void) {
|
||||
s_curMenu = !s_curMenu;
|
||||
menuSort();
|
||||
s_curMenu = !s_curMenu;
|
||||
menuClear();
|
||||
}
|
||||
|
||||
int menuScan(const char* target) {
|
||||
int pos;
|
||||
char dirsep[8];
|
||||
|
||||
if (chdir(target) < 0) return 1;
|
||||
if (getcwd(s_menu[!s_curMenu].dirname, PATH_MAX+1) == NULL)
|
||||
return 1;
|
||||
|
||||
memset(dirsep, 0, sizeof(dirsep));
|
||||
dirsep[0] = '/';
|
||||
|
||||
//While cwd will not have '/' at the end normally, it will have it when cwd is the root dir ("sdmc:/"). Don't add '/' to the path below when it's already present.
|
||||
pos = strlen(s_menu[!s_curMenu].dirname);
|
||||
if (pos > 0) {
|
||||
if (s_menu[!s_curMenu].dirname[pos-1] == '/') dirsep[0] = 0;
|
||||
}
|
||||
|
||||
DIR* dir;
|
||||
struct dirent* dp;
|
||||
char tmp_path[PATH_MAX+1];
|
||||
@ -104,13 +176,12 @@ int menuScan(const char* target) {
|
||||
bool entrytype=0;
|
||||
|
||||
memset(tmp_path, 0, sizeof(tmp_path));
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s%s", s_menu[!s_curMenu].dirname, dp->d_name);
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s%s%s", s_menu[!s_curMenu].dirname, dirsep, dp->d_name);
|
||||
|
||||
#ifdef SWITCH
|
||||
fsdev_dir_t* dirSt = (fsdev_dir_t*)dir->dirData->dirStruct;
|
||||
FsDirectoryEntry* entry = &dirSt->entry_data[dirSt->index];
|
||||
|
||||
entrytype = entry->type == ENTRYTYPE_DIR;
|
||||
#ifdef _DIRENT_HAVE_D_TYPE
|
||||
if (dp->d_type == DT_UNKNOWN)
|
||||
continue;
|
||||
entrytype = dp->d_type != DT_REG;
|
||||
#else
|
||||
struct stat tmpstat;
|
||||
|
||||
@ -127,16 +198,21 @@ int menuScan(const char* target) {
|
||||
const char* ext = getExtension(dp->d_name);
|
||||
if (strcasecmp(ext, ".nro")==0/* || (shortcut = strcasecmp(ext, ".xml")==0)*/)
|
||||
me = menuCreateEntry(ENTRY_TYPE_FILE);
|
||||
|
||||
if (!me)
|
||||
me = menuCreateEntry(ENTRY_TYPE_FILE_OTHER);
|
||||
}
|
||||
|
||||
if (!me)
|
||||
continue;
|
||||
|
||||
strncpy(me->path, tmp_path, sizeof(me->path)-1);
|
||||
if (menuEntryLoad(me, dp->d_name, shortcut))
|
||||
me->path[sizeof(me->path)-1] = 0;
|
||||
|
||||
if (menuEntryLoad(me, dp->d_name, shortcut, true))
|
||||
menuAddEntry(me);
|
||||
else
|
||||
menuDeleteEntry(me);
|
||||
menuDeleteEntry(me, 0);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
@ -148,3 +224,105 @@ int menuScan(const char* target) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int themeMenuScan(const char* target) {
|
||||
menuClear();
|
||||
if (chdir(target) < 0) return 1;
|
||||
if (getcwd(s_menu[!s_curMenu].dirname, PATH_MAX+1) == NULL)
|
||||
return 1;
|
||||
DIR* dir;
|
||||
struct dirent* dp;
|
||||
char tmp_path[PATH_MAX+1];
|
||||
dir = opendir(s_menu[!s_curMenu].dirname);
|
||||
if (!dir) return 2;
|
||||
|
||||
while ((dp = readdir(dir)))
|
||||
{
|
||||
menuEntry_s* me = NULL;
|
||||
|
||||
bool shortcut = false;
|
||||
if (dp->d_name[0]=='.')
|
||||
continue;
|
||||
|
||||
memset(tmp_path, 0, sizeof(tmp_path));
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s/%s", s_menu[!s_curMenu].dirname, dp->d_name);
|
||||
|
||||
bool entrytype=0;
|
||||
|
||||
#ifdef _DIRENT_HAVE_D_TYPE
|
||||
if (dp->d_type == DT_UNKNOWN)
|
||||
continue;
|
||||
entrytype = dp->d_type != DT_REG;
|
||||
#else
|
||||
struct stat tmpstat;
|
||||
|
||||
if(stat(tmp_path, &tmpstat)==-1)
|
||||
continue;
|
||||
|
||||
entrytype = (tmpstat.st_mode & S_IFMT) != S_IFREG;
|
||||
#endif
|
||||
|
||||
const char* ext = getExtension(dp->d_name);
|
||||
if (entrytype || strcasecmp(ext, ".cfg")==0 || strcasecmp(ext, ".romfs")==0 || strcasecmp(ext, ".zip")==0)
|
||||
me = menuCreateEntry(ENTRY_TYPE_THEME);
|
||||
|
||||
if (!me)
|
||||
continue;
|
||||
|
||||
strncpy(me->path, tmp_path, sizeof(me->path)-1);
|
||||
me->path[sizeof(me->path)-1] = 0;
|
||||
if (menuEntryLoad(me, dp->d_name, shortcut, true))
|
||||
menuAddEntry(me);
|
||||
else
|
||||
menuDeleteEntry(me, 0);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
menuSort();
|
||||
|
||||
menuEntry_s* me = menuCreateEntry(ENTRY_TYPE_THEME);
|
||||
|
||||
if(me) {
|
||||
if(menuEntryLoad(me, textGetString(StrId_DefaultThemeName), false, false))//Create Default theme Menu Entry
|
||||
menuAddEntryToFront(me);
|
||||
else
|
||||
menuDeleteEntry(me, 0);
|
||||
}
|
||||
// Swap the menu and clear the previous menu
|
||||
s_curMenu = !s_curMenu;
|
||||
menuClear();
|
||||
return 0;
|
||||
}
|
||||
|
||||
int menuFileassocScan(const char* target) {
|
||||
menuFileassocClear();
|
||||
if (chdir(target) < 0) return 1;
|
||||
if (getcwd(s_menuFileassoc[!s_curMenuFileassoc].dirname, PATH_MAX+1) == NULL)
|
||||
return 1;
|
||||
DIR* dir;
|
||||
struct dirent* dp;
|
||||
char tmp_path[PATH_MAX+1];
|
||||
dir = opendir(s_menuFileassoc[!s_curMenuFileassoc].dirname);
|
||||
if (!dir) return 2;
|
||||
|
||||
while ((dp = readdir(dir)))
|
||||
{
|
||||
if (dp->d_name[0]=='.')
|
||||
continue;
|
||||
|
||||
memset(tmp_path, 0, sizeof(tmp_path));
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s/%s", s_menuFileassoc[!s_curMenuFileassoc].dirname, dp->d_name);
|
||||
|
||||
const char* ext = getExtension(dp->d_name);
|
||||
if (strcasecmp(ext, ".cfg")!=0)
|
||||
continue;
|
||||
|
||||
menuEntryFileassocLoad(tmp_path);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
|
||||
// Swap the menu and clear the previous menu
|
||||
s_curMenuFileassoc = !s_curMenuFileassoc;
|
||||
menuFileassocClear();
|
||||
return 0;
|
||||
}
|
||||
|
693
common/menu.c
@ -1,24 +1,189 @@
|
||||
#include <time.h>
|
||||
#include "common.h"
|
||||
#include "netloader.h"
|
||||
|
||||
#include "invalid_icon_bin.h"
|
||||
#include "folder_icon_bin.h"
|
||||
#ifndef __SWITCH__
|
||||
#include "switch/runtime/nxlink.h"
|
||||
#endif
|
||||
|
||||
void launchMenuEntryTask(menuEntry_s* arg)
|
||||
{
|
||||
double menuTimer;
|
||||
|
||||
char rootPathBase[PATH_MAX];
|
||||
char rootPath[PATH_MAX+8];
|
||||
|
||||
uint8_t *folder_icon_large, *folder_icon_small;
|
||||
uint8_t *invalid_icon_large, *invalid_icon_small;
|
||||
uint8_t *theme_icon_large, *theme_icon_small;
|
||||
|
||||
void computeFrontGradient(color_t baseColor, int height);
|
||||
|
||||
void menuLoadFileassoc(void);
|
||||
|
||||
char *menuGetRootPath(void) {
|
||||
return rootPath;
|
||||
}
|
||||
|
||||
char *menuGetRootBasePath(void) {
|
||||
return rootPathBase;
|
||||
}
|
||||
|
||||
void launchMenuEntryTask(menuEntry_s* arg) {
|
||||
menuEntry_s* me = arg;
|
||||
if (me->type == ENTRY_TYPE_FOLDER)
|
||||
menuScan(me->path);
|
||||
//changeDirTask(me->path);
|
||||
else if(me->type == ENTRY_TYPE_THEME)
|
||||
launchApplyThemeTask(me);
|
||||
else
|
||||
launchMenuEntry(me);
|
||||
}
|
||||
|
||||
typedef enum
|
||||
void toggleStarState(menuEntry_s* arg) {
|
||||
menuEntry_s* me = arg;
|
||||
if (me->starred) {
|
||||
if (fileExists(me->starpath))
|
||||
remove(me->starpath);
|
||||
} else {
|
||||
if (!fileExists(me->starpath)) {
|
||||
FILE* f = fopen(me->starpath, "w");
|
||||
if (f) fclose(f);
|
||||
}
|
||||
}
|
||||
me->starred = fileExists(me->starpath);
|
||||
//todo: error handling/message?
|
||||
|
||||
menuReorder();
|
||||
menu_s* menu = menuGetCurrent();
|
||||
menuEntry_s* meSearch = menu->firstEntry;
|
||||
menu->curEntry = -1;
|
||||
int i = 0;
|
||||
while (menu->curEntry < 0) {
|
||||
if (me == meSearch)
|
||||
menu->curEntry = i;
|
||||
else {
|
||||
meSearch = meSearch->next;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static enum
|
||||
{
|
||||
IMAGE_MODE_RGB24,
|
||||
IMAGE_MODE_RGBA32
|
||||
} ImageMode;
|
||||
HBMENU_DEFAULT,
|
||||
HBMENU_NETLOADER_ACTIVE,
|
||||
HBMENU_NETLOADER_ERROR,
|
||||
HBMENU_NETLOADER_SUCCESS,
|
||||
HBMENU_THEME_MENU,
|
||||
} hbmenu_state = HBMENU_DEFAULT;
|
||||
|
||||
void launchMenuNetloaderTask() {
|
||||
if(hbmenu_state == HBMENU_DEFAULT)
|
||||
workerSchedule(netloaderTask, NULL);
|
||||
}
|
||||
|
||||
void launchMenuBackTask() {
|
||||
if(hbmenu_state == HBMENU_NETLOADER_ACTIVE) {
|
||||
netloaderSignalExit();
|
||||
}
|
||||
else if(hbmenu_state == HBMENU_THEME_MENU) {
|
||||
hbmenu_state = HBMENU_DEFAULT;
|
||||
menuScan(rootPath);
|
||||
}
|
||||
else {
|
||||
menuScan("..");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void menuHandleAButton(void) {
|
||||
menu_s* menu = menuGetCurrent();
|
||||
|
||||
if (hbmenu_state != HBMENU_NETLOADER_ACTIVE && menuIsMsgBoxOpen()) {
|
||||
menuCloseMsgBox();
|
||||
}
|
||||
else if (menu->nEntries > 0 && (hbmenu_state == HBMENU_DEFAULT || hbmenu_state == HBMENU_THEME_MENU))
|
||||
{
|
||||
int i;
|
||||
menuEntry_s* me;
|
||||
for (i = 0, me = menu->firstEntry; i != menu->curEntry; i ++, me = me->next);
|
||||
launchMenuEntryTask(me);
|
||||
//workerSchedule(launchMenuEntryTask, me);
|
||||
}
|
||||
}
|
||||
|
||||
void menuHandleXButton(void) {
|
||||
menu_s* menu = menuGetCurrent();
|
||||
|
||||
if (menu->nEntries > 0 && hbmenu_state == HBMENU_DEFAULT) {
|
||||
int i;
|
||||
menuEntry_s* me;
|
||||
for (i = 0, me = menu->firstEntry; i != menu->curEntry; i ++, me = me->next);
|
||||
toggleStarState(me);
|
||||
}
|
||||
}
|
||||
|
||||
void menuStartupCommon(void) {
|
||||
free(folder_icon_large);
|
||||
free(folder_icon_small);
|
||||
free(invalid_icon_large);
|
||||
free(invalid_icon_small);
|
||||
free(theme_icon_large);
|
||||
free(theme_icon_small);
|
||||
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryIcon];
|
||||
ThemeLayoutObject *layoutobj2 = &themeCurrent.layoutObjects[ThemeLayoutId_MenuListIcon];
|
||||
assetsDataEntry *data = NULL;
|
||||
|
||||
assetsGetData(AssetId_folder_icon, &data);
|
||||
folder_icon_large = downscaleImg(data->buffer, layoutobj->imageSize[0], layoutobj->imageSize[1], layoutobj->size[0], layoutobj->size[1], data->imageMode);
|
||||
folder_icon_small = downscaleImg(data->buffer, layoutobj->imageSize[0], layoutobj->imageSize[1], layoutobj2->size[0], layoutobj2->size[1], data->imageMode);
|
||||
assetsGetData(AssetId_invalid_icon, &data);
|
||||
invalid_icon_large = downscaleImg(data->buffer, layoutobj->imageSize[0], layoutobj->imageSize[1], layoutobj->size[0], layoutobj->size[1], data->imageMode);
|
||||
invalid_icon_small = downscaleImg(data->buffer, layoutobj->imageSize[0], layoutobj->imageSize[1], layoutobj2->size[0], layoutobj2->size[1], data->imageMode);
|
||||
if(themeGlobalPreset == THEME_PRESET_DARK)
|
||||
assetsGetData(AssetId_theme_icon_dark, &data);
|
||||
else
|
||||
assetsGetData(AssetId_theme_icon_light, &data);
|
||||
theme_icon_large = downscaleImg(data->buffer, layoutobj->imageSize[0], layoutobj->imageSize[1], layoutobj->size[0], layoutobj->size[1], data->imageMode);
|
||||
theme_icon_small = downscaleImg(data->buffer, layoutobj->imageSize[0], layoutobj->imageSize[1], layoutobj2->size[0], layoutobj2->size[1], data->imageMode);
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_FrontWave];
|
||||
computeFrontGradient(themeCurrent.frontWaveColor, layoutobj->size[1]);
|
||||
}
|
||||
|
||||
void menuThemeSelectCurrentEntry(void) {
|
||||
menu_s* menu = menuGetCurrent();
|
||||
char themePath[PATH_MAX] = {0};
|
||||
GetThemePathFromConfig(themePath, PATH_MAX);
|
||||
if (themePath[0]==0) menu->curEntry = 0;
|
||||
else {
|
||||
int i;
|
||||
menuEntry_s* me;
|
||||
for (i = 0, me = menu->firstEntry; me != NULL; i ++, me = me->next) {
|
||||
if (strcmp(me->path, themePath)==0) {
|
||||
menu->curEntry = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void launchApplyThemeTask(menuEntry_s* arg) {
|
||||
const char* themePath = arg->path;
|
||||
menu_s* menu = menuGetCurrent();
|
||||
SetThemePathToConfig(themePath);
|
||||
themeStartup(themeGlobalPreset);
|
||||
menuStartupCommon();
|
||||
menuLoadFileassoc();
|
||||
if (hbmenu_state == HBMENU_THEME_MENU) { // Normally this should never be used outside of theme-menu.
|
||||
themeMenuScan(menu->dirname);
|
||||
menuThemeSelectCurrentEntry();
|
||||
} else menuScan(menu->dirname);
|
||||
}
|
||||
|
||||
bool menuIsNetloaderActive(void) {
|
||||
return hbmenu_state == HBMENU_NETLOADER_ACTIVE;
|
||||
}
|
||||
|
||||
//Draws an RGB888 or RGBA8888 image.
|
||||
static void drawImage(int x, int y, int width, int height, const uint8_t *image, ImageMode mode) {
|
||||
@ -45,25 +210,38 @@ static void drawImage(int x, int y, int width, int height, const uint8_t *image,
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t *folder_icon_small;
|
||||
uint8_t *invalid_icon_small;
|
||||
double timer;
|
||||
//Draws an RGBA8888 image masked by the passed color.
|
||||
static void drawIcon(int x, int y, int width, int height, const uint8_t *image, color_t color) {
|
||||
int tmpx, tmpy;
|
||||
int pos;
|
||||
color_t current_color;
|
||||
|
||||
for (tmpy=0; tmpy<height; tmpy++) {
|
||||
for (tmpx=0; tmpx<width; tmpx++) {
|
||||
pos = ((tmpy*width) + tmpx) * 4;
|
||||
current_color = MakeColor(color.r, color.g, color.b, image[pos+3]);
|
||||
DrawPixel(x+tmpx, y+tmpy, current_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void drawEntry(menuEntry_s* me, int off_x, int is_active) {
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuList];
|
||||
int x, y;
|
||||
int start_y = 720 - 100 - 145;//*(n % 2);
|
||||
int end_y = start_y + 140 + 32;
|
||||
int start_y = layoutobj->posStart[1];//*(n % 2);
|
||||
int end_y = start_y + layoutobj->size[1];
|
||||
int start_x = off_x;//(n / 2);
|
||||
int end_x = start_x + 140;
|
||||
int end_x = start_x + layoutobj->size[0];
|
||||
int j;
|
||||
|
||||
const uint8_t *smallimg = NULL;
|
||||
const uint8_t *largeimg = NULL;
|
||||
char *strptr = NULL;
|
||||
char tmpstr[1024];
|
||||
|
||||
int border_start_x, border_end_x;
|
||||
int border_start_y, border_end_y;
|
||||
color_t border_color = MakeColor(255, 255, 255, 255);
|
||||
color_t border_color = themeCurrent.borderColor;
|
||||
|
||||
int shadow_start_y, shadow_y;
|
||||
int shadow_inset;
|
||||
@ -73,8 +251,8 @@ static void drawEntry(menuEntry_s* me, int off_x, int is_active) {
|
||||
int shadow_size = 4;
|
||||
|
||||
if (is_active) {
|
||||
highlight_multiplier = fmax(0.0, fabs(fmod(timer, 1.0) - 0.5) / 0.5);
|
||||
border_color = MakeColor(themeCurrent.highlightColor.r + (255 - themeCurrent.highlightColor.r) * highlight_multiplier, themeCurrent.highlightColor.g + (255 - themeCurrent.highlightColor.g) * highlight_multiplier, themeCurrent.highlightColor.b + (255 - themeCurrent.highlightColor.b) * highlight_multiplier, 255);
|
||||
highlight_multiplier = fmax(0.0, fabs(fmod(menuTimer, 1.0) - 0.5) / 0.5);
|
||||
border_color = MakeColor(themeCurrent.highlightColor.r + (themeCurrent.highlightGradientEdgeColor.r - themeCurrent.highlightColor.r) * highlight_multiplier, themeCurrent.highlightColor.g + (themeCurrent.highlightGradientEdgeColor.g - themeCurrent.highlightColor.g) * highlight_multiplier, themeCurrent.highlightColor.b + (themeCurrent.highlightGradientEdgeColor.b - themeCurrent.highlightColor.b) * highlight_multiplier, 255);
|
||||
border_start_x = start_x-6;
|
||||
border_end_x = end_x+6;
|
||||
border_start_y = start_y-5;
|
||||
@ -142,7 +320,7 @@ static void drawEntry(menuEntry_s* me, int off_x, int is_active) {
|
||||
|
||||
for (y=start_y; y<end_y; y++) {
|
||||
for (x=start_x; x<end_x; x+=4) {
|
||||
Draw4PixelsRaw(x, y, MakeColor(255, 255, 255, 255));
|
||||
Draw4PixelsRaw(x, y, themeCurrent.borderColor);
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,23 +330,29 @@ static void drawEntry(menuEntry_s* me, int off_x, int is_active) {
|
||||
}
|
||||
else if (me->type == ENTRY_TYPE_FOLDER) {
|
||||
smallimg = folder_icon_small;
|
||||
largeimg = folder_icon_bin;
|
||||
largeimg = folder_icon_large;
|
||||
}
|
||||
else if (me->type == ENTRY_TYPE_THEME){
|
||||
smallimg = theme_icon_small;
|
||||
largeimg = theme_icon_large;
|
||||
}
|
||||
else {
|
||||
smallimg = invalid_icon_small;
|
||||
largeimg = invalid_icon_bin;
|
||||
largeimg = invalid_icon_large;
|
||||
}
|
||||
|
||||
if (smallimg) {
|
||||
drawImage(start_x, start_y + 32, 140, 140, smallimg, IMAGE_MODE_RGB24);
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuListIcon];
|
||||
drawImage(start_x + layoutobj->posStart[0], start_y + layoutobj->posStart[1], layoutobj->size[0], layoutobj->size[1], smallimg, IMAGE_MODE_RGB24);
|
||||
}
|
||||
|
||||
if (is_active && largeimg) {
|
||||
drawImage(117, 100, 256, 256, largeimg, IMAGE_MODE_RGB24);
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryIcon];
|
||||
if (is_active && largeimg && layoutobj->visible) {
|
||||
drawImage(layoutobj->posStart[0], layoutobj->posStart[1], layoutobj->size[0], layoutobj->size[1], largeimg, IMAGE_MODE_RGB24);
|
||||
|
||||
shadow_start_y = 100+256;
|
||||
border_start_x = 117;
|
||||
border_end_x = 117+256;
|
||||
shadow_start_y = layoutobj->posStart[1]+layoutobj->size[1];
|
||||
border_start_x = layoutobj->posStart[0];
|
||||
border_end_x = layoutobj->posStart[0]+layoutobj->size[0];
|
||||
|
||||
for (shadow_y=shadow_start_y; shadow_y <shadow_start_y+shadow_size; shadow_y++) {
|
||||
for (x=border_start_x; x<border_end_x; x++) {
|
||||
@ -182,21 +366,31 @@ static void drawEntry(menuEntry_s* me, int off_x, int is_active) {
|
||||
}
|
||||
}
|
||||
|
||||
DrawTextTruncate(interuiregular14, start_x + 4, start_y + 4, MakeColor(64, 64, 64, 255), me->name, 140 - 32, "...");
|
||||
if (me->type != ENTRY_TYPE_THEME)
|
||||
strptr = me->starred ? themeCurrent.labelStarOnText : "";
|
||||
else
|
||||
strptr = "";
|
||||
|
||||
memset(tmpstr, 0, sizeof(tmpstr));
|
||||
snprintf(tmpstr, sizeof(tmpstr)-1, "%s%s", strptr, me->name);
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuListName];
|
||||
DrawTextTruncate(layoutobj->font, start_x + layoutobj->posStart[0], start_y + layoutobj->posStart[1], themeCurrent.borderTextColor, tmpstr, layoutobj->size[0], "...");
|
||||
|
||||
if (is_active) {
|
||||
start_x = 1280 - 790;
|
||||
start_y = 135;
|
||||
|
||||
DrawTextTruncate(interuimedium30, start_x, start_y, themeCurrent.textColor, me->name, 1280 - start_x - 120 ,"...");
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryName];
|
||||
if (layoutobj->visible) DrawTextTruncate(layoutobj->font, layoutobj->posStart[0], layoutobj->posStart[1], themeCurrent.textColor, tmpstr, layoutobj->size[0], "...");
|
||||
|
||||
if (me->type != ENTRY_TYPE_FOLDER) {
|
||||
memset(tmpstr, 0, sizeof(tmpstr));
|
||||
snprintf(tmpstr, sizeof(tmpstr)-1, "%s: %s", textGetString(StrId_AppInfo_Author), me->author);
|
||||
DrawText(interuiregular14, start_x, start_y + 28 + 30, themeCurrent.textColor, tmpstr);
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryAuthor];
|
||||
if (layoutobj->visible) DrawText(layoutobj->font, layoutobj->posStart[0], layoutobj->posStart[1], themeCurrent.textColor, tmpstr);
|
||||
|
||||
memset(tmpstr, 0, sizeof(tmpstr));
|
||||
snprintf(tmpstr, sizeof(tmpstr)-1, "%s: %s", textGetString(StrId_AppInfo_Version), me->version);
|
||||
DrawText(interuiregular14, start_x, start_y + 28 + 30 + 18 + 6, themeCurrent.textColor, tmpstr);
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryVersion];
|
||||
if (layoutobj->visible) DrawText(layoutobj->font, layoutobj->posStart[0], layoutobj->posStart[1], themeCurrent.textColor, tmpstr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -209,6 +403,8 @@ void computeFrontGradient(color_t baseColor, int height) {
|
||||
float dark_mult, dark_sub = 75;
|
||||
color_t color;
|
||||
|
||||
if (height < 0 || height > 720) return;
|
||||
|
||||
for (y=0; y<720; y++) {
|
||||
alpha = y - (720 - height);
|
||||
|
||||
@ -223,24 +419,69 @@ void computeFrontGradient(color_t baseColor, int height) {
|
||||
}
|
||||
}
|
||||
|
||||
void menuStartup() {
|
||||
const char *path;
|
||||
void menuStartupPath(void) {
|
||||
char tmp_path[PATH_MAX+28];
|
||||
|
||||
#ifdef SWITCH
|
||||
path = "sdmc:/switch";
|
||||
#ifdef __SWITCH__
|
||||
strncpy(rootPathBase, "sdmc:", sizeof(rootPathBase)-1);
|
||||
#else
|
||||
path = "switch";
|
||||
getcwd(rootPathBase, sizeof(rootPathBase));
|
||||
#endif
|
||||
snprintf(rootPath, sizeof(rootPath)-1, "%s%s%s", rootPathBase, DIRECTORY_SEPARATOR, "switch");
|
||||
|
||||
menuScan(path);
|
||||
struct stat st = {0};
|
||||
|
||||
folder_icon_small = downscaleIcon(folder_icon_bin);
|
||||
invalid_icon_small = downscaleIcon(invalid_icon_bin);
|
||||
computeFrontGradient(themeCurrent.frontWaveColor, 280);
|
||||
if (stat(rootPath, &st) == -1) {
|
||||
mkdir(rootPath, 0755);
|
||||
}
|
||||
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s/config/nx-hbmenu/themes", rootPathBase);
|
||||
if (stat(tmp_path, &st) == -1) {
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s/config", rootPathBase);
|
||||
mkdir(tmp_path, 0755);
|
||||
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s/config/nx-hbmenu", rootPathBase);
|
||||
mkdir(tmp_path, 0755);
|
||||
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s/config/nx-hbmenu/themes", rootPathBase);
|
||||
mkdir(tmp_path, 0755);
|
||||
}
|
||||
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s/config/nx-hbmenu/fileassoc", rootPathBase);
|
||||
if (stat(tmp_path, &st) == -1) {
|
||||
mkdir(tmp_path, 0755);
|
||||
}
|
||||
}
|
||||
|
||||
void menuLoadFileassoc(void) {
|
||||
char tmp_path[PATH_MAX+28];
|
||||
|
||||
memset(tmp_path, 0, sizeof(tmp_path)-1);
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s/config/nx-hbmenu/fileassoc", rootPathBase);
|
||||
menuFileassocScan(tmp_path);
|
||||
}
|
||||
|
||||
void menuStartup(void) {
|
||||
menuLoadFileassoc();
|
||||
|
||||
menuScan(rootPath);
|
||||
|
||||
menuStartupCommon();
|
||||
}
|
||||
|
||||
void themeMenuStartup(void) {
|
||||
if(hbmenu_state != HBMENU_DEFAULT) return;
|
||||
hbmenu_state = HBMENU_THEME_MENU;
|
||||
char tmp_path[PATH_MAX+25];
|
||||
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s%s%s%s%s%s%s", rootPathBase, DIRECTORY_SEPARATOR, "config", DIRECTORY_SEPARATOR, "nx-hbmenu" , DIRECTORY_SEPARATOR, "themes");
|
||||
|
||||
themeMenuScan(tmp_path);
|
||||
menuThemeSelectCurrentEntry();
|
||||
}
|
||||
|
||||
color_t waveBlendAdd(color_t a, color_t b, float alpha) {
|
||||
return MakeColor(a.r+(b.r*alpha), a.g+b.g*alpha, a.b + b.b*alpha, 255);
|
||||
return MakeColor(a.r*(1.0f-alpha) + b.r*alpha, a.g*(1.0f-alpha) + b.g*alpha, a.b*(1.0f-alpha) + b.b*alpha, 255);
|
||||
}
|
||||
|
||||
void drawWave(int id, float timer, color_t color, int height, float phase, float speed) {
|
||||
@ -248,6 +489,7 @@ void drawWave(int id, float timer, color_t color, int height, float phase, float
|
||||
float wave_top_y, alpha, one_minus_alpha;
|
||||
color_t existing_color, new_color;
|
||||
|
||||
if (height < 0 || height > 720) return;
|
||||
height = 720 - height;
|
||||
|
||||
for (x=0; x<1280; x++) {
|
||||
@ -283,107 +525,321 @@ void drawWave(int id, float timer, color_t color, int height, float phase, float
|
||||
}
|
||||
}
|
||||
|
||||
void drawTime() {
|
||||
void drawCharge() {
|
||||
char chargeString[5];
|
||||
uint32_t batteryCharge;
|
||||
bool isCharging;
|
||||
bool validPower;
|
||||
|
||||
char timeString[9];
|
||||
validPower = powerGetDetails(&batteryCharge, &isCharging);
|
||||
|
||||
if (validPower)
|
||||
{
|
||||
batteryCharge = (batteryCharge > 100) ? 100 : batteryCharge;
|
||||
|
||||
sprintf(chargeString, "%d%%", batteryCharge);
|
||||
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_BatteryCharge];
|
||||
|
||||
if (layoutobj->visible) {
|
||||
int tmpX = GetTextXCoordinate(layoutobj->font, layoutobj->posStart[0], chargeString, 'r');
|
||||
DrawText(layoutobj->font, tmpX, layoutobj->posStart[1], themeCurrent.textColor, chargeString);
|
||||
}
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_BatteryIcon];
|
||||
assetsDataEntry *data = NULL;
|
||||
assetsGetData(AssetId_battery_icon, &data);
|
||||
if (layoutobj->visible) drawIcon(layoutobj->posStart[0], layoutobj->posStart[1], data->imageSize[0], data->imageSize[1], data->buffer, themeCurrent.textColor);
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ChargingIcon];
|
||||
assetsGetData(AssetId_charging_icon, &data);
|
||||
if (isCharging && layoutobj->visible)
|
||||
drawIcon(layoutobj->posStart[0], layoutobj->posStart[1], data->imageSize[0], data->imageSize[1], data->buffer, themeCurrent.textColor);
|
||||
}
|
||||
}
|
||||
|
||||
void drawNetwork(int tmpX, AssetId id) {
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_NetworkIcon];
|
||||
assetsDataEntry *data = NULL;
|
||||
assetsGetData(id, &data);
|
||||
if (layoutobj->visible) drawIcon(layoutobj->posType ? tmpX + layoutobj->posStart[0] : layoutobj->posStart[0], layoutobj->posStart[1], data->imageSize[0], data->imageSize[1], data->buffer, themeCurrent.textColor);
|
||||
}
|
||||
|
||||
u32 drawStatus() {
|
||||
bool netstatusFlag=0;
|
||||
bool temperatureFlag=0;
|
||||
s32 temperature=0;
|
||||
AssetId id;
|
||||
|
||||
char tmpstr[32];
|
||||
|
||||
time_t unixTime = time(NULL);
|
||||
struct tm* timeStruct = gmtime((const time_t *)&unixTime);
|
||||
struct tm* timeStruct = localtime((const time_t *)&unixTime);
|
||||
|
||||
int hours = timeStruct->tm_hour;
|
||||
int minutes = timeStruct->tm_min;
|
||||
int seconds = timeStruct->tm_sec;
|
||||
|
||||
sprintf(timeString, "%02d:%02d:%02d", hours, minutes, seconds);
|
||||
snprintf(tmpstr, sizeof(tmpstr)-1, "%02d:%02d:%02d", hours, minutes, seconds);
|
||||
|
||||
DrawText(interuimedium20, 1280 - (9 * 16) - 30, 30, MakeColor(255, 255, 255, 255), timeString);
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_Status];
|
||||
|
||||
}
|
||||
u32 tmpX = GetTextXCoordinate(layoutobj->font, layoutobj->posStart[0], tmpstr, 'r');
|
||||
|
||||
void drawBackBtn(menu_s* menu, bool emptyDir) {
|
||||
int x_image = 1280 - 252 - 30 - 32;
|
||||
int x_text = 1280 - 216 - 30 - 32;
|
||||
if (layoutobj->visible) DrawText(layoutobj->font, tmpX, layoutobj->posStart[1], themeCurrent.textColor, tmpstr);
|
||||
|
||||
if(emptyDir) {
|
||||
x_image = 1280 - 126 - 30 - 32;
|
||||
x_text = 1280 - 90 - 30 - 32;
|
||||
drawCharge();
|
||||
|
||||
if (statusGet(&netstatusFlag, &id, &temperatureFlag, &temperature)) {
|
||||
if (netstatusFlag) drawNetwork(tmpX, id);
|
||||
if (temperatureFlag) {
|
||||
snprintf(tmpstr, sizeof(tmpstr)-1, "%d°C", temperature);
|
||||
DrawTextFromLayout(ThemeLayoutId_Temperature, themeCurrent.textColor, tmpstr);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SWITCH
|
||||
return tmpX;
|
||||
}
|
||||
|
||||
void drawButtons(menu_s* menu, bool emptyDir, int *out_basePos) {
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonA];
|
||||
int basePos[2]={0};
|
||||
|
||||
basePos[0] = layoutobj->posStart[0];
|
||||
basePos[1] = layoutobj->posStart[1];
|
||||
|
||||
#ifdef __SWITCH__
|
||||
if (strcmp( menu->dirname, "sdmc:/") != 0)
|
||||
#else
|
||||
if (strcmp( menu->dirname, "/") != 0)
|
||||
#endif
|
||||
{
|
||||
drawImage(x_image, 720 - 48, 32, 32, themeCurrent.buttonBImage, IMAGE_MODE_RGBA32);
|
||||
DrawText(interuiregular18, x_text, 720 - 47, themeCurrent.textColor, textGetString(StrId_Actions_Back));
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonBText];
|
||||
DrawTextFromLayoutRelative(ThemeLayoutId_ButtonBText, basePos[0], basePos[1], !emptyDir ? layoutobj->posStart : layoutobj->posEnd, basePos, themeCurrent.textColor, textGetString(StrId_Actions_Back), 'l');
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonB];
|
||||
DrawTextFromLayoutRelative(ThemeLayoutId_ButtonB, basePos[0], basePos[1], layoutobj->posStart, basePos, themeCurrent.textColor, themeCurrent.buttonBText, 'l');
|
||||
}
|
||||
|
||||
if(hbmenu_state == HBMENU_DEFAULT)
|
||||
{
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonYText];
|
||||
DrawTextFromLayoutRelative(ThemeLayoutId_ButtonYText, basePos[0], basePos[1], layoutobj->posStart, basePos, themeCurrent.textColor, textGetString(StrId_NetLoader), 'r');
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonY];
|
||||
DrawTextFromLayoutRelative(ThemeLayoutId_ButtonY, basePos[0], basePos[1], layoutobj->posStart, basePos, themeCurrent.textColor, themeCurrent.buttonYText, 'l');
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonMText];
|
||||
DrawTextFromLayoutRelative(ThemeLayoutId_ButtonMText, basePos[0], basePos[1], layoutobj->posStart, basePos, themeCurrent.textColor, textGetString(StrId_ThemeMenu), 'r');
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonM];
|
||||
DrawTextFromLayoutRelative(ThemeLayoutId_ButtonM, basePos[0], basePos[1], layoutobj->posStart, basePos, themeCurrent.textColor, themeCurrent.buttonMText, 'l');
|
||||
}
|
||||
|
||||
out_basePos[0] = basePos[0];
|
||||
out_basePos[1] = basePos[1];
|
||||
}
|
||||
|
||||
void menuLoop() {
|
||||
menuEntry_s* me;
|
||||
menu_s* menu = menuGetCurrent();
|
||||
int i;
|
||||
int x, y;
|
||||
void menuUpdateNetloader(netloaderState *netloader_state) {
|
||||
bool enable_progress = 0;
|
||||
float progress = 0;
|
||||
char netloader_displaytext[260];
|
||||
char textbody[256];
|
||||
|
||||
for (y=0; y<450; y++) {
|
||||
memset(netloader_displaytext, 0, sizeof(netloader_displaytext));
|
||||
memset(textbody, 0, sizeof(textbody));
|
||||
|
||||
u32 ip = gethostid();
|
||||
|
||||
if (ip == INADDR_LOOPBACK)
|
||||
snprintf(textbody, sizeof(textbody)-1, "%s", textGetString(StrId_NetLoaderOffline));
|
||||
else {
|
||||
if (!netloader_state->sock_connected)
|
||||
snprintf(textbody, sizeof(textbody)-1, textGetString(StrId_NetLoaderActive), ip&0xFF, (ip>>8)&0xFF, (ip>>16)&0xFF, (ip>>24)&0xFF, NXLINK_SERVER_PORT);
|
||||
else {
|
||||
enable_progress = 1;
|
||||
progress = (float)netloader_state->filetotal / netloader_state->filelen;
|
||||
snprintf(textbody, sizeof(textbody)-1, textGetString(StrId_NetLoaderTransferring), netloader_state->filetotal/1024, netloader_state->filelen/1024);
|
||||
}
|
||||
}
|
||||
|
||||
snprintf(netloader_displaytext, sizeof(netloader_displaytext)-1, "%s\n\n\n%s", textGetString(StrId_NetLoader), textbody);
|
||||
|
||||
menuMsgBoxSetNetloaderState(1, netloader_displaytext, enable_progress, progress);
|
||||
}
|
||||
|
||||
void menuLoop(void) {
|
||||
menuEntry_s* me;
|
||||
menu_s* menu = NULL;
|
||||
int i;
|
||||
int x, y, endy = 720;
|
||||
int curPos[2]={0};
|
||||
netloaderState netloader_state;
|
||||
ThemeLayoutObject *layoutobj = NULL;
|
||||
|
||||
for (i=0; i<3; i++) {
|
||||
if (i==2) layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_BackWave];
|
||||
if (i==1) layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MiddleWave];
|
||||
if (i==0) layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_FrontWave];
|
||||
if (layoutobj->visible && layoutobj->size[1] >= 0 && layoutobj->size[1] <= 720-10) {
|
||||
endy = 720 - layoutobj->size[1] + 10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (y=0; y<endy; y++) {
|
||||
for (x=0; x<1280; x+=4) {// don't draw bottom pixels as they are covered by the waves
|
||||
Draw4PixelsRaw(x, y, themeCurrent.backgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
drawWave(0, timer, themeCurrent.backWaveColor, 295, 0.0, 3.0);
|
||||
drawWave(1, timer, themeCurrent.middleWaveColor, 290, 2.0, 3.5);
|
||||
drawWave(2, timer, themeCurrent.frontWaveColor, 280, 4.0, -2.5);
|
||||
timer += 0.05;
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_BackgroundImage];
|
||||
assetsDataEntry *data = NULL;
|
||||
assetsGetData(AssetId_background_image, &data);
|
||||
if (layoutobj->visible && data) drawImage(layoutobj->posStart[0], layoutobj->posStart[1], data->imageSize[0], endy < data->imageSize[1] ? endy : data->imageSize[1], data->buffer, data->imageMode);
|
||||
|
||||
drawImage(40, 20, 140, 60, themeCurrent.hbmenuLogoImage, IMAGE_MODE_RGBA32);
|
||||
DrawText(interuiregular14, 180, 46, themeCurrent.textColor, VERSION);
|
||||
DrawTextTruncate(interuiregular18, 40, 720 - 47, themeCurrent.textColor, menu->dirname, 918, "...");
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_BackWave];
|
||||
if (layoutobj->visible) drawWave(0, menuTimer, themeCurrent.backWaveColor, layoutobj->size[1], 0.0, 3.0);
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MiddleWave];
|
||||
if (layoutobj->visible) drawWave(1, menuTimer, themeCurrent.middleWaveColor, layoutobj->size[1], 2.0, 3.5);
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_FrontWave];
|
||||
if (layoutobj->visible) drawWave(2, menuTimer, themeCurrent.frontWaveColor, layoutobj->size[1], 4.0, -2.5);
|
||||
menuTimer += 0.05;
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_Logo];
|
||||
if(themeGlobalPreset == THEME_PRESET_DARK)
|
||||
assetsGetData(AssetId_hbmenu_logo_dark, &data);
|
||||
else assetsGetData(AssetId_hbmenu_logo_light, &data);
|
||||
|
||||
if (layoutobj->visible) {
|
||||
if (!themeCurrent.logoColor_set)
|
||||
drawImage(layoutobj->posStart[0], layoutobj->posStart[1], data->imageSize[0], data->imageSize[1], data->buffer, data->imageMode);
|
||||
else {
|
||||
drawIcon(layoutobj->posStart[0], layoutobj->posStart[1], data->imageSize[0], data->imageSize[1], data->buffer, themeCurrent.logoColor);
|
||||
}
|
||||
}
|
||||
|
||||
DrawTextFromLayout(ThemeLayoutId_HbmenuVersion, themeCurrent.textColor, VERSION);
|
||||
u32 statusXPos = drawStatus();
|
||||
|
||||
#ifdef __SWITCH__
|
||||
AppletType at = appletGetAppletType();
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_AttentionText];
|
||||
if (at != AppletType_Application && at != AppletType_SystemApplication && layoutobj->visible) {
|
||||
const char* appletMode = textGetString(StrId_AppletMode);
|
||||
u32 x_pos = GetTextXCoordinate(layoutobj->font, statusXPos, appletMode, 'r');
|
||||
DrawText(layoutobj->font, layoutobj->posType ? x_pos + layoutobj->posStart[0] : layoutobj->posStart[0], layoutobj->posStart[1], themeCurrent.attentionTextColor, appletMode);
|
||||
}
|
||||
const char* loaderInfo = envGetLoaderInfo();
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_LoaderInfo];
|
||||
if (loaderInfo && layoutobj->visible) {
|
||||
u32 x_pos = layoutobj->posStart[0];
|
||||
char* spacePos = strchr(loaderInfo, ' ');
|
||||
if (spacePos) {
|
||||
char tempbuf[64] = {0};
|
||||
size_t tempsize = spacePos - loaderInfo + 1;
|
||||
if (tempsize > sizeof(tempbuf)-1) tempsize = sizeof(tempbuf)-1;
|
||||
memcpy(tempbuf, loaderInfo, tempsize);
|
||||
x_pos = GetTextXCoordinate(layoutobj->font, layoutobj->posEnd[0], tempbuf, 'r');
|
||||
}
|
||||
DrawText(layoutobj->font, x_pos, layoutobj->posStart[1], themeCurrent.textColor, loaderInfo);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PERF_LOG_DRAW//Seperate from the PERF_LOG define since this might affect perf.
|
||||
extern u64 g_tickdiff_vsync;
|
||||
extern u64 g_tickdiff_frame;
|
||||
|
||||
char tmpstr[64];
|
||||
|
||||
snprintf(tmpstr, sizeof(tmpstr)-1, "%lu", g_tickdiff_vsync);
|
||||
DrawText(interuiregular14, 180 + 256, 46, themeCurrent.textColor, tmpstr);
|
||||
|
||||
snprintf(tmpstr, sizeof(tmpstr)-1, "%lu", g_tickdiff_frame);
|
||||
DrawText(interuiregular14, 180 + 256, 46 + 16, themeCurrent.textColor, tmpstr);
|
||||
DrawTextFromLayout(ThemeLayoutId_LogInfo, themeCurrent.textColor, tmpstr);
|
||||
#endif
|
||||
|
||||
//drawTime();
|
||||
memset(&netloader_state, 0, sizeof(netloader_state));
|
||||
netloaderGetState(&netloader_state);
|
||||
|
||||
if (menu->nEntries==0)
|
||||
if(hbmenu_state == HBMENU_DEFAULT && netloader_state.activated) {
|
||||
hbmenu_state = HBMENU_NETLOADER_ACTIVE;
|
||||
|
||||
menuCloseMsgBox();
|
||||
menuCreateMsgBox(780,300, "");
|
||||
} else if(hbmenu_state == HBMENU_NETLOADER_ACTIVE && !netloader_state.activated && !netloader_state.launch_app) {
|
||||
hbmenu_state = HBMENU_DEFAULT;
|
||||
menuScan(".");//Reload the menu since netloader may have deleted the NRO if the transfer aborted.
|
||||
|
||||
menuCloseMsgBox();
|
||||
menuMsgBoxSetNetloaderState(0, NULL, 0, 0);
|
||||
}
|
||||
|
||||
if (netloader_state.errormsg[0]) {
|
||||
menuCloseMsgBox();
|
||||
menuCreateMsgBox(780,300, netloader_state.errormsg);
|
||||
}
|
||||
|
||||
if(hbmenu_state == HBMENU_NETLOADER_ACTIVE) {
|
||||
menuUpdateNetloader(&netloader_state);
|
||||
}
|
||||
|
||||
menu = menuGetCurrent();
|
||||
|
||||
if (menu->nEntries==0 || hbmenu_state == HBMENU_NETLOADER_ACTIVE)
|
||||
{
|
||||
DrawText(interuiregular14, 64, 128, themeCurrent.textColor, textGetString(StrId_NoAppsFound_Msg));
|
||||
drawBackBtn(menu, true);
|
||||
if (hbmenu_state == HBMENU_NETLOADER_ACTIVE) {
|
||||
if (netloader_state.launch_app) {
|
||||
hbmenu_state = HBMENU_DEFAULT;
|
||||
menuCloseMsgBox();
|
||||
menuMsgBoxSetNetloaderState(0, NULL, 0, 0);
|
||||
menuCreateMsgBox(240,240, textGetString(StrId_Loading));
|
||||
launchMenuEntryTask(netloader_state.me);
|
||||
}
|
||||
} else {
|
||||
DrawTextFromLayout(ThemeLayoutId_InfoMsg, themeCurrent.textColor, textGetString(StrId_NoAppsFound_Msg));
|
||||
}
|
||||
drawButtons(menu, true, curPos);
|
||||
}
|
||||
else
|
||||
{
|
||||
static int x = 0;
|
||||
static int v = 0;
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuListTiles];
|
||||
int entries_count = layoutobj->posEnd[0];
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuList];
|
||||
|
||||
if (menu->nEntries > 7) {
|
||||
int wanted_x = clamp(-menu->curEntry * (140 + 30), -(menu->nEntries - 7) * (140 + 30), 0);
|
||||
x += v;
|
||||
v += (wanted_x - x) / 3;
|
||||
// Gentle Realign only when not manually moving
|
||||
if (menu->slideSpeed == 0) {
|
||||
if (menu->nEntries > entries_count) {
|
||||
int wanted_x = clamp(-menu->curEntry * layoutobj->posEnd[0], -(menu->nEntries - entries_count) * layoutobj->posEnd[0], 0);
|
||||
menu->xPos += v;
|
||||
v += (wanted_x - menu->xPos) / 3;
|
||||
v /= 2;
|
||||
}
|
||||
else {
|
||||
x = v = 0;
|
||||
menu->xPos = v = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
menu->xPos += menu->slideSpeed;
|
||||
|
||||
if (abs(menu->slideSpeed) > 2) {
|
||||
// Slow down way faster when outside the normal bounds
|
||||
if (menu->xPos > 0 || menu->xPos < -(menu->nEntries) * layoutobj->posEnd[0]) {
|
||||
menu->slideSpeed *= .5f;
|
||||
}
|
||||
else {
|
||||
menu->slideSpeed *= .9f;
|
||||
}
|
||||
}
|
||||
else {
|
||||
menu->slideSpeed = 0;
|
||||
}
|
||||
|
||||
menu->curEntry = clamp(roundf(-((float) menu->xPos / layoutobj->posEnd[0])), 0, menu->nEntries);
|
||||
}
|
||||
|
||||
menuEntry_s *active_entry = NULL;
|
||||
|
||||
// Draw menu entries
|
||||
for (me = menu->firstEntry, i = 0; me; me = me->next, i ++) {
|
||||
int entry_start_x = 29 + i * (140 + 30);
|
||||
int entry_start_x = layoutobj->posStart[0] + i * layoutobj->posEnd[0];
|
||||
int entry_draw_x = entry_start_x + menu->xPos;
|
||||
|
||||
int screen_width = 1280;
|
||||
if (entry_start_x >= (screen_width - x))
|
||||
if (entry_start_x >= (screen_width - menu->xPos))
|
||||
break;
|
||||
|
||||
int is_active = i==menu->curEntry;
|
||||
@ -391,20 +847,61 @@ void menuLoop() {
|
||||
if (is_active)
|
||||
active_entry = me;
|
||||
|
||||
drawEntry(me, entry_start_x + x, is_active);
|
||||
if (!is_active && entry_draw_x < -(layoutobj->posStart[0] + layoutobj->posEnd[0]))
|
||||
continue;
|
||||
|
||||
drawEntry(me, entry_draw_x, is_active);
|
||||
}
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuTypeMsg];
|
||||
int getX=0;
|
||||
|
||||
if (layoutobj->visible) {
|
||||
getX = GetTextXCoordinate(layoutobj->font, layoutobj->posStart[0], textGetString(StrId_ThemeMenu), 'r');
|
||||
|
||||
if(hbmenu_state == HBMENU_THEME_MENU) {
|
||||
DrawText(layoutobj->font, getX, layoutobj->posStart[1], themeCurrent.textColor, textGetString(StrId_ThemeMenu));
|
||||
} else {
|
||||
//DrawText(interuiregular18, getX, 30 + 26 + 32 + 10, themeCurrent.textColor, textGetString(StrId_ThemeMenu));
|
||||
//DrawText(fontscale7, getX - 40, 30 + 26 + 32 + 10, themeCurrent.textColor, themeCurrent.buttonMText);
|
||||
}
|
||||
}
|
||||
|
||||
if(active_entry != NULL) {
|
||||
if (active_entry->type != ENTRY_TYPE_FOLDER) {
|
||||
drawImage(1280 - 126 - 30 - 32, 720 - 48, 32, 32, themeCurrent.buttonAImage, IMAGE_MODE_RGBA32);
|
||||
DrawText(interuiregular18, 1280 - 90 - 30 - 32, 720 - 47, themeCurrent.textColor, textGetString(StrId_Actions_Launch));
|
||||
}
|
||||
else {
|
||||
drawImage(1280 - 126 - 30 - 32, 720 - 48, 32, 32, themeCurrent.buttonAImage, IMAGE_MODE_RGBA32);
|
||||
DrawText(interuiregular18, 1280 - 90 - 30 - 32, 720 - 47, themeCurrent.textColor, textGetString(StrId_Actions_Open));
|
||||
}
|
||||
const char *buttonstr = "";
|
||||
|
||||
if (active_entry->type == ENTRY_TYPE_THEME)
|
||||
buttonstr = textGetString(StrId_Actions_Apply);
|
||||
else if (active_entry->type != ENTRY_TYPE_FOLDER)
|
||||
buttonstr = textGetString(StrId_Actions_Launch);
|
||||
else
|
||||
buttonstr = textGetString(StrId_Actions_Open);
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonAText];
|
||||
DrawTextFromLayoutRelative(ThemeLayoutId_ButtonAText, curPos[0], curPos[1], layoutobj->posStart, curPos, themeCurrent.textColor, buttonstr, 'l');
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonA];
|
||||
DrawTextFromLayoutRelative(ThemeLayoutId_ButtonA, curPos[0], curPos[1], layoutobj->posStart, curPos, themeCurrent.textColor, themeCurrent.buttonAText, 'l');
|
||||
}
|
||||
|
||||
drawBackBtn(menu, false);
|
||||
drawButtons(menu, false, curPos);
|
||||
|
||||
if (active_entry && active_entry->type != ENTRY_TYPE_THEME) {
|
||||
const char *buttonstr = "";
|
||||
if (active_entry->starred)
|
||||
buttonstr = textGetString(StrId_Actions_Unstar);
|
||||
else
|
||||
buttonstr = textGetString(StrId_Actions_Star);
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonXText];
|
||||
DrawTextFromLayoutRelative(ThemeLayoutId_ButtonXText, curPos[0], curPos[1], layoutobj->posStart, curPos, themeCurrent.textColor, buttonstr, 'r');
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_ButtonX];
|
||||
DrawTextFromLayoutRelative(ThemeLayoutId_ButtonX, curPos[0], curPos[1], layoutobj->posStart, curPos, themeCurrent.textColor, themeCurrent.buttonXText, 'l');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuPath];
|
||||
if (layoutobj->visible) DrawTextTruncate(layoutobj->font, layoutobj->posStart[0], layoutobj->posStart[1], themeCurrent.textColor, menu->dirname, layoutobj->size[0], "...");
|
||||
|
||||
menuDrawMsgBox();
|
||||
}
|
||||
|
@ -1,5 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <arpa/inet.h>
|
||||
#else
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#undef DrawText
|
||||
#undef MessageBox
|
||||
#endif
|
||||
|
||||
#define ENTRY_NAMELENGTH 0x200
|
||||
#define ENTRY_AUTHORLENGTH 0x100
|
||||
#define ENTRY_VERLENGTH 0x10
|
||||
@ -9,6 +19,9 @@ typedef enum
|
||||
{
|
||||
ENTRY_TYPE_FILE,
|
||||
ENTRY_TYPE_FOLDER,
|
||||
ENTRY_TYPE_THEME,
|
||||
ENTRY_TYPE_FILEASSOC,
|
||||
ENTRY_TYPE_FILE_OTHER,
|
||||
} MenuEntryType;
|
||||
|
||||
typedef struct menuEntry_s_tag menuEntry_s;
|
||||
@ -19,6 +32,8 @@ struct menu_s_tag
|
||||
menuEntry_s *firstEntry, *lastEntry;
|
||||
int nEntries;
|
||||
int curEntry;
|
||||
int xPos;
|
||||
int slideSpeed;
|
||||
|
||||
char dirname[PATH_MAX+1];
|
||||
};
|
||||
@ -27,6 +42,7 @@ typedef struct
|
||||
{
|
||||
char* dst;
|
||||
uint32_t buf[ENTRY_ARGBUFSIZE/sizeof(uint32_t)];
|
||||
struct in_addr nxlink_host;
|
||||
} argData_s;
|
||||
|
||||
struct menuEntry_s_tag
|
||||
@ -35,9 +51,13 @@ struct menuEntry_s_tag
|
||||
menuEntry_s* next;
|
||||
MenuEntryType type;
|
||||
|
||||
char path[PATH_MAX+1];
|
||||
char path[PATH_MAX+8];
|
||||
char starpath[PATH_MAX+8];
|
||||
argData_s args;
|
||||
|
||||
bool fileassoc_type;//0=file_extension, 1 = filename
|
||||
char fileassoc_str[PATH_MAX+1];//file_extension/filename
|
||||
|
||||
char name[ENTRY_NAMELENGTH+1];
|
||||
char author[ENTRY_AUTHORLENGTH+1];
|
||||
char version[ENTRY_VERLENGTH+1];
|
||||
@ -47,19 +67,61 @@ struct menuEntry_s_tag
|
||||
uint8_t *icon_gfx;
|
||||
uint8_t *icon_gfx_small;
|
||||
|
||||
bool starred;
|
||||
|
||||
NacpStruct *nacp;
|
||||
};
|
||||
|
||||
typedef enum
|
||||
{
|
||||
IMAGE_MODE_RGB24,
|
||||
IMAGE_MODE_RGBA32
|
||||
} ImageMode;
|
||||
|
||||
extern double menuTimer;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void menuEntryInit(menuEntry_s* me, MenuEntryType type);
|
||||
void menuEntryFree(menuEntry_s* me);
|
||||
void menuEntryFree(menuEntry_s* me, bool skip_icongfx);
|
||||
bool fileExists(const char* path);
|
||||
bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut);
|
||||
bool menuEntryLoad(menuEntry_s* me, const char* name, bool shortcut, bool check_exists);
|
||||
void menuEntryParseIcon(menuEntry_s* me);
|
||||
uint8_t *downscaleIcon(const uint8_t *image);
|
||||
uint8_t *downscaleImg(const uint8_t *image, int srcWidth, int srcHeight, int destWidth, int destHeight, ImageMode mode);
|
||||
void menuEntryParseNacp(menuEntry_s* me);
|
||||
|
||||
void menuEntryFileassocLoad(const char* filepath);
|
||||
|
||||
menuEntry_s* menuCreateEntry(MenuEntryType type);
|
||||
|
||||
void menuFileassocAddEntry(menuEntry_s* me);
|
||||
void menuDeleteEntry(menuEntry_s* me, bool skip_icongfx);
|
||||
|
||||
menu_s* menuGetCurrent(void);
|
||||
menu_s* menuFileassocGetCurrent(void);
|
||||
void menuReorder (void);
|
||||
int menuScan(const char* target);
|
||||
int themeMenuScan(const char* target);
|
||||
int menuFileassocScan(const char* target);
|
||||
|
||||
void launchMenuEntryTask(menuEntry_s* arg);
|
||||
void toggleStarState(menuEntry_s* arg);
|
||||
void launchApplyThemeTask(menuEntry_s* arg);
|
||||
void launchMenuBackTask();
|
||||
void launchMenuNetloaderTask();
|
||||
char *menuGetRootPath(void);
|
||||
char *menuGetRootBasePath(void);
|
||||
|
||||
void menuHandleAButton(void);
|
||||
void menuHandleXButton(void);
|
||||
|
||||
bool menuIsNetloaderActive(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline char* getExtension(const char* str)
|
||||
{
|
||||
|
219
common/message-box.c
Normal file
@ -0,0 +1,219 @@
|
||||
#include "common.h"
|
||||
#include "message-box.h"
|
||||
|
||||
MessageBox currMsgBox;
|
||||
|
||||
static bool msgboxNetloaderEnabled;
|
||||
static char msgboxNetloaderText[256];
|
||||
static bool msgboxNetloaderProgressEnabled;
|
||||
static float msgboxNetloaderProgress;
|
||||
|
||||
void drawMsgBoxBgToBuff(color_t *buff, int width, int height) {
|
||||
int x, y;
|
||||
int off;
|
||||
int circle_center_x, circle_center_y;
|
||||
int corner_size = 0;
|
||||
float rad, alpha;
|
||||
color_t base_color = themeCurrent.backgroundColor;
|
||||
color_t color;
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MsgBoxSeparator];
|
||||
|
||||
for (y=0; y<height; y++) {
|
||||
for (x=0; x<width; x++) {
|
||||
if (corner_size > 0) {
|
||||
if (x<corner_size && y<corner_size) { // top left corner
|
||||
circle_center_x = corner_size-1;
|
||||
circle_center_y = corner_size-1;
|
||||
}
|
||||
else if (x>width-corner_size && y<corner_size) { // top right corner
|
||||
circle_center_x = width-corner_size;
|
||||
circle_center_y = corner_size-1;
|
||||
}
|
||||
else if (x<corner_size && y>height-corner_size) { // bottom left corner
|
||||
circle_center_x = corner_size-1;
|
||||
circle_center_y = height-corner_size;
|
||||
}
|
||||
else if (x>width-corner_size && y>height-corner_size) { // bottom right corner
|
||||
circle_center_x = width-corner_size;
|
||||
circle_center_y = height-corner_size;
|
||||
}
|
||||
else {
|
||||
circle_center_x = -1;
|
||||
circle_center_y = -1;
|
||||
}
|
||||
|
||||
if (circle_center_x == -1 && circle_center_y == -1) {
|
||||
color = base_color;
|
||||
}
|
||||
else {
|
||||
rad = sqrt(pow(circle_center_x - x, 2) + pow(circle_center_y - y, 2));
|
||||
alpha = (float)corner_size - rad;
|
||||
|
||||
if (rad < corner_size) {
|
||||
if (alpha < 1.0) {
|
||||
color = MakeColor(base_color.r, base_color.g, base_color.b, base_color.a * alpha);
|
||||
}
|
||||
else
|
||||
color = base_color;
|
||||
}
|
||||
else
|
||||
color = MakeColor(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
else
|
||||
color = base_color;
|
||||
|
||||
if (y == height + layoutobj->posStart[1]) {
|
||||
color = themeCurrent.separatorColor;
|
||||
}
|
||||
|
||||
off = (y * width + x);
|
||||
*((uint32_t *)&buff[off]) = color.r | (color.g<<8) | (color.b<<16) | (color.a<<24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void menuDrawMsgBox() {
|
||||
if (!menuIsMsgBoxOpen())
|
||||
return;
|
||||
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MsgBoxSeparator];
|
||||
int off;
|
||||
int x, y;
|
||||
int start_x = 1280 / 2 - currMsgBox.width / 2;
|
||||
int start_y = 720 / 2 - currMsgBox.height / 2;
|
||||
int end_x = start_x + currMsgBox.width;
|
||||
uint32_t text_width, text_height;
|
||||
color_t curr_color;
|
||||
|
||||
color_t border_color;
|
||||
int sep_start_y = currMsgBox.height + layoutobj->posStart[1];
|
||||
int border_thickness = 6;
|
||||
|
||||
int shadow_start_y, shadow_y;
|
||||
int shadow_inset;
|
||||
int shadow_size = 4;
|
||||
float highlight_multiplier = highlight_multiplier = fmax(0.0, fabs(fmod(menuTimer, 1.0) - 0.5) / 0.5);
|
||||
color_t shadow_color;
|
||||
uint8_t shadow_alpha_base = 80;
|
||||
|
||||
const char* textptr = currMsgBox.text;
|
||||
|
||||
int progress_width = (int)(msgboxNetloaderProgress*currMsgBox.width);
|
||||
char progress_text[32];
|
||||
|
||||
border_color = MakeColor(themeCurrent.highlightColor.r + (255 - themeCurrent.highlightColor.r) * highlight_multiplier, themeCurrent.highlightColor.g + (255 - themeCurrent.highlightColor.g) * highlight_multiplier, themeCurrent.highlightColor.b + (255 - themeCurrent.highlightColor.b) * highlight_multiplier, 255);
|
||||
|
||||
// Darken the background
|
||||
for (y=0; y<720; y++) {
|
||||
for (x=0; x<1280; x++) {
|
||||
DrawPixel(x, y, MakeColor(0, 0, 0, 100));
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the message box background
|
||||
for (y=0; y<currMsgBox.height; y++) {
|
||||
for (x=0; x<currMsgBox.width; x++) {
|
||||
off = (y * currMsgBox.width + x);
|
||||
curr_color = currMsgBox.bg[off];
|
||||
|
||||
if (!msgboxNetloaderEnabled) {
|
||||
if (((x<border_thickness || x>=currMsgBox.width-border_thickness) && y>sep_start_y) ||
|
||||
(y>sep_start_y && y<=sep_start_y+border_thickness) || (y>=currMsgBox.height-border_thickness)) {
|
||||
curr_color = border_color;
|
||||
}
|
||||
}
|
||||
else if (msgboxNetloaderProgressEnabled && y > sep_start_y && x < progress_width) {
|
||||
curr_color = themeCurrent.progressBarColor;
|
||||
}
|
||||
|
||||
DrawPixel(start_x+x, start_y+y, curr_color);
|
||||
}
|
||||
}
|
||||
|
||||
if (msgboxNetloaderEnabled) textptr = msgboxNetloaderText;
|
||||
|
||||
GetTextDimensions(interuiregular18, textptr, &text_width, &text_height);
|
||||
x = GetTextXCoordinate(interuiregular18, start_x + (currMsgBox.width / 2), textptr, 'c');
|
||||
|
||||
if (text_width < currMsgBox.width && text_height < sep_start_y) {
|
||||
DrawText(interuiregular18, x, start_y + (sep_start_y - text_height) / 2, themeCurrent.textColor, textptr);
|
||||
}
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MsgBoxBottomText];
|
||||
y = start_y + currMsgBox.height + layoutobj->posStart[1];
|
||||
|
||||
if (!msgboxNetloaderEnabled) {
|
||||
x = GetTextXCoordinate(interuimedium20, start_x + (currMsgBox.width / 2), textGetString(StrId_MsgBox_OK), 'c');
|
||||
DrawText(interuimedium20, x, y, themeCurrent.textColor, textGetString(StrId_MsgBox_OK));
|
||||
}
|
||||
|
||||
if (msgboxNetloaderEnabled && msgboxNetloaderProgressEnabled) {
|
||||
memset(progress_text, 0, sizeof(progress_text));
|
||||
snprintf(progress_text, sizeof(progress_text)-1, "%.02f%%", msgboxNetloaderProgress*100);
|
||||
x = GetTextXCoordinate(interuiregular18, start_x + (currMsgBox.width / 2), progress_text, 'c');
|
||||
DrawText(interuiregular18, x, y, themeCurrent.textColor, progress_text);
|
||||
}
|
||||
|
||||
shadow_start_y = start_y + currMsgBox.height;
|
||||
|
||||
for (shadow_y=shadow_start_y; shadow_y <shadow_start_y+shadow_size; shadow_y++) {
|
||||
for (x=start_x; x<end_x; x++) {
|
||||
shadow_color = MakeColor(0, 0, 0, shadow_alpha_base * (1.0 - (float)(shadow_y - shadow_start_y) / ((float)shadow_size)));
|
||||
shadow_inset =(shadow_y-shadow_start_y);
|
||||
|
||||
if (x >= start_x + shadow_inset && x <= end_x - shadow_inset) {
|
||||
DrawPixel(x, shadow_y, shadow_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void menuCreateMsgBox(int width, int height, const char *text) {
|
||||
if (menuIsMsgBoxOpen())
|
||||
return;
|
||||
|
||||
char *new_text = strdup(text);
|
||||
if (new_text==NULL)
|
||||
return;
|
||||
|
||||
currMsgBox = (MessageBox) { width, height, NULL, new_text };
|
||||
|
||||
currMsgBox.bg = malloc(currMsgBox.width*currMsgBox.height*4);
|
||||
|
||||
if (currMsgBox.bg) {
|
||||
drawMsgBoxBgToBuff(currMsgBox.bg, currMsgBox.width, currMsgBox.height);
|
||||
}
|
||||
}
|
||||
|
||||
bool menuIsMsgBoxOpen() {
|
||||
return currMsgBox.width != 0 || currMsgBox.height != 0 || currMsgBox.bg || currMsgBox.text;
|
||||
}
|
||||
|
||||
void menuCloseMsgBox() {
|
||||
if (currMsgBox.bg) {
|
||||
free(currMsgBox.bg);
|
||||
currMsgBox.bg = NULL;
|
||||
}
|
||||
|
||||
currMsgBox.width = currMsgBox.height = 0;
|
||||
|
||||
if (currMsgBox.text) {
|
||||
free(currMsgBox.text);
|
||||
currMsgBox.text = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
MessageBox menuGetCurrentMsgBox() {
|
||||
return currMsgBox;
|
||||
}
|
||||
|
||||
void menuMsgBoxSetNetloaderState(bool enabled, const char *text, bool enable_progress, float progress) {
|
||||
msgboxNetloaderEnabled = enabled;
|
||||
|
||||
memset(msgboxNetloaderText, 0, sizeof(msgboxNetloaderText));
|
||||
if (text) strncpy(msgboxNetloaderText, text, sizeof(msgboxNetloaderText)-1);
|
||||
|
||||
msgboxNetloaderProgressEnabled = enable_progress;
|
||||
msgboxNetloaderProgress = progress;
|
||||
}
|
16
common/message-box.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct
|
||||
{
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
color_t *bg;
|
||||
char *text;
|
||||
} MessageBox;
|
||||
|
||||
void menuCreateMsgBox(int width, int height, const char *text);
|
||||
void menuCloseMsgBox();
|
||||
bool menuIsMsgBoxOpen();
|
||||
void menuDrawMsgBox(void);
|
||||
MessageBox menuGetCurrentMsgBox();
|
||||
void menuMsgBoxSetNetloaderState(bool enabled, const char *text, bool enable_progress, float progress);
|
@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct {
|
||||
char name[0x200];
|
||||
char author[0x100];
|
||||
} NacpLanguageEntry;
|
||||
|
||||
typedef struct {
|
||||
NacpLanguageEntry lang[12];
|
||||
NacpLanguageEntry lang_unk[4];//?
|
||||
|
||||
u8 x3000_unk[0x24];////Normally all-zero?
|
||||
u32 x3024_unk;
|
||||
u32 x3028_unk;
|
||||
u32 x302C_unk;
|
||||
u32 x3030_unk;
|
||||
u32 x3034_unk;
|
||||
u64 titleid0;
|
||||
|
||||
u8 x3040_unk[0x20];
|
||||
char version[0x10];
|
||||
|
||||
u64 titleid_dlcbase;
|
||||
u64 titleid1;
|
||||
|
||||
u32 x3080_unk;
|
||||
u32 x3084_unk;
|
||||
u32 x3088_unk;
|
||||
u8 x308C_unk[0x24];//zeros?
|
||||
|
||||
u64 titleid2;
|
||||
u64 titleids[7];//"Array of application titleIDs, normally the same as the above app-titleIDs. Only set for game-updates?"
|
||||
|
||||
u32 x30F0_unk;
|
||||
u32 x30F4_unk;
|
||||
|
||||
u64 titleid3;//"Application titleID. Only set for game-updates?"
|
||||
|
||||
char bcat_passphrase[0x40];
|
||||
u8 x3140_unk[0xEC0];//Normally all-zero?
|
||||
} NacpStruct;
|
@ -1,916 +0,0 @@
|
||||
// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder
|
||||
// version 1.3.5 (2016-11-14)
|
||||
// Copyright (c) 2009-2016 Martin J. Fiedler <martin.fiedler@gmx.net>
|
||||
// published under the terms of the MIT license
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to
|
||||
// deal in the Software without restriction, including without limitation the
|
||||
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
// sell copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// DOCUMENTATION SECTION //
|
||||
// read this if you want to know what this is all about //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// INTRODUCTION
|
||||
// ============
|
||||
//
|
||||
// This is a minimal decoder for baseline JPEG images. It accepts memory dumps
|
||||
// of JPEG files as input and generates either 8-bit grayscale or packed 24-bit
|
||||
// RGB images as output. It does not parse JFIF or Exif headers; all JPEG files
|
||||
// are assumed to be either grayscale or YCbCr. CMYK or other color spaces are
|
||||
// not supported. All YCbCr subsampling schemes with power-of-two ratios are
|
||||
// supported, as are restart intervals. Progressive or lossless JPEG is not
|
||||
// supported.
|
||||
// Summed up, NanoJPEG should be able to decode all images from digital cameras
|
||||
// and most common forms of other non-progressive JPEG images.
|
||||
// The decoder is not optimized for speed, it's optimized for simplicity and
|
||||
// small code. Image quality should be at a reasonable level. A bicubic chroma
|
||||
// upsampling filter ensures that subsampled YCbCr images are rendered in
|
||||
// decent quality. The decoder is not meant to deal with broken JPEG files in
|
||||
// a graceful manner; if anything is wrong with the bitstream, decoding will
|
||||
// simply fail.
|
||||
// The code should work with every modern C compiler without problems and
|
||||
// should not emit any warnings. It uses only (at least) 32-bit integer
|
||||
// arithmetic and is supposed to be endianness independent and 64-bit clean.
|
||||
// However, it is not thread-safe.
|
||||
|
||||
|
||||
// COMPILE-TIME CONFIGURATION
|
||||
// ==========================
|
||||
//
|
||||
// The following aspects of NanoJPEG can be controlled with preprocessor
|
||||
// defines:
|
||||
//
|
||||
// _NJ_EXAMPLE_PROGRAM = Compile a main() function with an example
|
||||
// program.
|
||||
// _NJ_INCLUDE_HEADER_ONLY = Don't compile anything, just act as a header
|
||||
// file for NanoJPEG. Example:
|
||||
// #define _NJ_INCLUDE_HEADER_ONLY
|
||||
// #include "nanojpeg.c"
|
||||
// int main(void) {
|
||||
// njInit();
|
||||
// // your code here
|
||||
// njDone();
|
||||
// }
|
||||
// NJ_USE_LIBC=1 = Use the malloc(), free(), memset() and memcpy()
|
||||
// functions from the standard C library (default).
|
||||
// NJ_USE_LIBC=0 = Don't use the standard C library. In this mode,
|
||||
// external functions njAlloc(), njFreeMem(),
|
||||
// njFillMem() and njCopyMem() need to be defined
|
||||
// and implemented somewhere.
|
||||
// NJ_USE_WIN32=0 = Normal mode (default).
|
||||
// NJ_USE_WIN32=1 = If compiling with MSVC for Win32 and
|
||||
// NJ_USE_LIBC=0, NanoJPEG will use its own
|
||||
// implementations of the required C library
|
||||
// functions (default if compiling with MSVC and
|
||||
// NJ_USE_LIBC=0).
|
||||
// NJ_CHROMA_FILTER=1 = Use the bicubic chroma upsampling filter
|
||||
// (default).
|
||||
// NJ_CHROMA_FILTER=0 = Use simple pixel repetition for chroma upsampling
|
||||
// (bad quality, but faster and less code).
|
||||
|
||||
|
||||
// API
|
||||
// ===
|
||||
//
|
||||
// For API documentation, read the "header section" below.
|
||||
|
||||
|
||||
// EXAMPLE
|
||||
// =======
|
||||
//
|
||||
// A few pages below, you can find an example program that uses NanoJPEG to
|
||||
// convert JPEG files into PGM or PPM. To compile it, use something like
|
||||
// gcc -O3 -D_NJ_EXAMPLE_PROGRAM -o nanojpeg nanojpeg.c
|
||||
// You may also add -std=c99 -Wall -Wextra -pedantic -Werror, if you want :)
|
||||
// The only thing you might need is -Wno-shift-negative-value, because this
|
||||
// code relies on the target machine using two's complement arithmetic, but
|
||||
// the C standard does not, even though *any* practically useful machine
|
||||
// nowadays uses two's complement.
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// HEADER SECTION //
|
||||
// copy and pase this into nanojpeg.h if you want //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _NANOJPEG_H
|
||||
#define _NANOJPEG_H
|
||||
|
||||
// nj_result_t: Result codes for njDecode().
|
||||
typedef enum _nj_result {
|
||||
NJ_OK = 0, // no error, decoding successful
|
||||
NJ_NO_JPEG, // not a JPEG file
|
||||
NJ_UNSUPPORTED, // unsupported format
|
||||
NJ_OUT_OF_MEM, // out of memory
|
||||
NJ_INTERNAL_ERR, // internal error
|
||||
NJ_SYNTAX_ERROR, // syntax error
|
||||
__NJ_FINISHED, // used internally, will never be reported
|
||||
} nj_result_t;
|
||||
|
||||
// njInit: Initialize NanoJPEG.
|
||||
// For safety reasons, this should be called at least one time before using
|
||||
// using any of the other NanoJPEG functions.
|
||||
void njInit(void);
|
||||
|
||||
// njDecode: Decode a JPEG image.
|
||||
// Decodes a memory dump of a JPEG file into internal buffers.
|
||||
// Parameters:
|
||||
// jpeg = The pointer to the memory dump.
|
||||
// size = The size of the JPEG file.
|
||||
// Return value: The error code in case of failure, or NJ_OK (zero) on success.
|
||||
nj_result_t njDecode(const void* jpeg, const int size);
|
||||
|
||||
// njGetWidth: Return the width (in pixels) of the most recently decoded
|
||||
// image. If njDecode() failed, the result of njGetWidth() is undefined.
|
||||
int njGetWidth(void);
|
||||
|
||||
// njGetHeight: Return the height (in pixels) of the most recently decoded
|
||||
// image. If njDecode() failed, the result of njGetHeight() is undefined.
|
||||
int njGetHeight(void);
|
||||
|
||||
// njIsColor: Return 1 if the most recently decoded image is a color image
|
||||
// (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result
|
||||
// of njGetWidth() is undefined.
|
||||
int njIsColor(void);
|
||||
|
||||
// njGetImage: Returns the decoded image data.
|
||||
// Returns a pointer to the most recently image. The memory layout it byte-
|
||||
// oriented, top-down, without any padding between lines. Pixels of color
|
||||
// images will be stored as three consecutive bytes for the red, green and
|
||||
// blue channels. This data format is thus compatible with the PGM or PPM
|
||||
// file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8.
|
||||
// If njDecode() failed, the result of njGetImage() is undefined.
|
||||
unsigned char* njGetImage(void);
|
||||
|
||||
// njGetImageSize: Returns the size (in bytes) of the image data returned
|
||||
// by njGetImage(). If njDecode() failed, the result of njGetImageSize() is
|
||||
// undefined.
|
||||
int njGetImageSize(void);
|
||||
|
||||
// njDone: Uninitialize NanoJPEG.
|
||||
// Resets NanoJPEG's internal state and frees all memory that has been
|
||||
// allocated at run-time by NanoJPEG. It is still possible to decode another
|
||||
// image after a njDone() call.
|
||||
void njDone(void);
|
||||
|
||||
#endif//_NANOJPEG_H
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// CONFIGURATION SECTION //
|
||||
// adjust the default settings for the NJ_ defines here //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef NJ_USE_LIBC
|
||||
#define NJ_USE_LIBC 1
|
||||
#endif
|
||||
|
||||
#ifndef NJ_USE_WIN32
|
||||
#ifdef _MSC_VER
|
||||
#define NJ_USE_WIN32 (!NJ_USE_LIBC)
|
||||
#else
|
||||
#define NJ_USE_WIN32 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef NJ_CHROMA_FILTER
|
||||
#define NJ_CHROMA_FILTER 1
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// EXAMPLE PROGRAM //
|
||||
// just define _NJ_EXAMPLE_PROGRAM to compile this (requires NJ_USE_LIBC) //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef _NJ_EXAMPLE_PROGRAM
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int size;
|
||||
char *buf;
|
||||
FILE *f;
|
||||
|
||||
if (argc < 2) {
|
||||
printf("Usage: %s <input.jpg> [<output.ppm>]\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
f = fopen(argv[1], "rb");
|
||||
if (!f) {
|
||||
printf("Error opening the input file.\n");
|
||||
return 1;
|
||||
}
|
||||
fseek(f, 0, SEEK_END);
|
||||
size = (int) ftell(f);
|
||||
buf = (char*) malloc(size);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
size = (int) fread(buf, 1, size, f);
|
||||
fclose(f);
|
||||
|
||||
njInit();
|
||||
if (njDecode(buf, size)) {
|
||||
free((void*)buf);
|
||||
printf("Error decoding the input file.\n");
|
||||
return 1;
|
||||
}
|
||||
free((void*)buf);
|
||||
|
||||
f = fopen((argc > 2) ? argv[2] : (njIsColor() ? "nanojpeg_out.ppm" : "nanojpeg_out.pgm"), "wb");
|
||||
if (!f) {
|
||||
printf("Error opening the output file.\n");
|
||||
return 1;
|
||||
}
|
||||
fprintf(f, "P%d\n%d %d\n255\n", njIsColor() ? 6 : 5, njGetWidth(), njGetHeight());
|
||||
fwrite(njGetImage(), 1, njGetImageSize(), f);
|
||||
fclose(f);
|
||||
njDone();
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// IMPLEMENTATION SECTION //
|
||||
// you may stop reading here //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _NJ_INCLUDE_HEADER_ONLY
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define NJ_INLINE static __inline
|
||||
#define NJ_FORCE_INLINE static __forceinline
|
||||
#else
|
||||
#define NJ_INLINE static inline
|
||||
#define NJ_FORCE_INLINE static inline
|
||||
#endif
|
||||
|
||||
#if NJ_USE_LIBC
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#define njAllocMem malloc
|
||||
#define njFreeMem free
|
||||
#define njFillMem memset
|
||||
#define njCopyMem memcpy
|
||||
#elif NJ_USE_WIN32
|
||||
#include <windows.h>
|
||||
#define njAllocMem(size) ((void*) LocalAlloc(LMEM_FIXED, (SIZE_T)(size)))
|
||||
#define njFreeMem(block) ((void) LocalFree((HLOCAL) block))
|
||||
NJ_INLINE void njFillMem(void* block, unsigned char value, int count) { __asm {
|
||||
mov edi, block
|
||||
mov al, value
|
||||
mov ecx, count
|
||||
rep stosb
|
||||
} }
|
||||
NJ_INLINE void njCopyMem(void* dest, const void* src, int count) { __asm {
|
||||
mov edi, dest
|
||||
mov esi, src
|
||||
mov ecx, count
|
||||
rep movsb
|
||||
} }
|
||||
#else
|
||||
extern void* njAllocMem(int size);
|
||||
extern void njFreeMem(void* block);
|
||||
extern void njFillMem(void* block, unsigned char byte, int size);
|
||||
extern void njCopyMem(void* dest, const void* src, int size);
|
||||
#endif
|
||||
|
||||
typedef struct _nj_code {
|
||||
unsigned char bits, code;
|
||||
} nj_vlc_code_t;
|
||||
|
||||
typedef struct _nj_cmp {
|
||||
int cid;
|
||||
int ssx, ssy;
|
||||
int width, height;
|
||||
int stride;
|
||||
int qtsel;
|
||||
int actabsel, dctabsel;
|
||||
int dcpred;
|
||||
unsigned char *pixels;
|
||||
} nj_component_t;
|
||||
|
||||
typedef struct _nj_ctx {
|
||||
nj_result_t error;
|
||||
const unsigned char *pos;
|
||||
int size;
|
||||
int length;
|
||||
int width, height;
|
||||
int mbwidth, mbheight;
|
||||
int mbsizex, mbsizey;
|
||||
int ncomp;
|
||||
nj_component_t comp[3];
|
||||
int qtused, qtavail;
|
||||
unsigned char qtab[4][64];
|
||||
nj_vlc_code_t vlctab[4][65536];
|
||||
int buf, bufbits;
|
||||
int block[64];
|
||||
int rstinterval;
|
||||
unsigned char *rgb;
|
||||
} nj_context_t;
|
||||
|
||||
static nj_context_t nj;
|
||||
|
||||
static const char njZZ[64] = { 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18,
|
||||
11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35,
|
||||
42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45,
|
||||
38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 };
|
||||
|
||||
NJ_FORCE_INLINE unsigned char njClip(const int x) {
|
||||
return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char) x);
|
||||
}
|
||||
|
||||
#define W1 2841
|
||||
#define W2 2676
|
||||
#define W3 2408
|
||||
#define W5 1609
|
||||
#define W6 1108
|
||||
#define W7 565
|
||||
|
||||
NJ_INLINE void njRowIDCT(int* blk) {
|
||||
int x0, x1, x2, x3, x4, x5, x6, x7, x8;
|
||||
if (!((x1 = blk[4] << 11)
|
||||
| (x2 = blk[6])
|
||||
| (x3 = blk[2])
|
||||
| (x4 = blk[1])
|
||||
| (x5 = blk[7])
|
||||
| (x6 = blk[5])
|
||||
| (x7 = blk[3])))
|
||||
{
|
||||
blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] = blk[7] = blk[0] << 3;
|
||||
return;
|
||||
}
|
||||
x0 = (blk[0] << 11) + 128;
|
||||
x8 = W7 * (x4 + x5);
|
||||
x4 = x8 + (W1 - W7) * x4;
|
||||
x5 = x8 - (W1 + W7) * x5;
|
||||
x8 = W3 * (x6 + x7);
|
||||
x6 = x8 - (W3 - W5) * x6;
|
||||
x7 = x8 - (W3 + W5) * x7;
|
||||
x8 = x0 + x1;
|
||||
x0 -= x1;
|
||||
x1 = W6 * (x3 + x2);
|
||||
x2 = x1 - (W2 + W6) * x2;
|
||||
x3 = x1 + (W2 - W6) * x3;
|
||||
x1 = x4 + x6;
|
||||
x4 -= x6;
|
||||
x6 = x5 + x7;
|
||||
x5 -= x7;
|
||||
x7 = x8 + x3;
|
||||
x8 -= x3;
|
||||
x3 = x0 + x2;
|
||||
x0 -= x2;
|
||||
x2 = (181 * (x4 + x5) + 128) >> 8;
|
||||
x4 = (181 * (x4 - x5) + 128) >> 8;
|
||||
blk[0] = (x7 + x1) >> 8;
|
||||
blk[1] = (x3 + x2) >> 8;
|
||||
blk[2] = (x0 + x4) >> 8;
|
||||
blk[3] = (x8 + x6) >> 8;
|
||||
blk[4] = (x8 - x6) >> 8;
|
||||
blk[5] = (x0 - x4) >> 8;
|
||||
blk[6] = (x3 - x2) >> 8;
|
||||
blk[7] = (x7 - x1) >> 8;
|
||||
}
|
||||
|
||||
NJ_INLINE void njColIDCT(const int* blk, unsigned char *out, int stride) {
|
||||
int x0, x1, x2, x3, x4, x5, x6, x7, x8;
|
||||
if (!((x1 = blk[8*4] << 8)
|
||||
| (x2 = blk[8*6])
|
||||
| (x3 = blk[8*2])
|
||||
| (x4 = blk[8*1])
|
||||
| (x5 = blk[8*7])
|
||||
| (x6 = blk[8*5])
|
||||
| (x7 = blk[8*3])))
|
||||
{
|
||||
x1 = njClip(((blk[0] + 32) >> 6) + 128);
|
||||
for (x0 = 8; x0; --x0) {
|
||||
*out = (unsigned char) x1;
|
||||
out += stride;
|
||||
}
|
||||
return;
|
||||
}
|
||||
x0 = (blk[0] << 8) + 8192;
|
||||
x8 = W7 * (x4 + x5) + 4;
|
||||
x4 = (x8 + (W1 - W7) * x4) >> 3;
|
||||
x5 = (x8 - (W1 + W7) * x5) >> 3;
|
||||
x8 = W3 * (x6 + x7) + 4;
|
||||
x6 = (x8 - (W3 - W5) * x6) >> 3;
|
||||
x7 = (x8 - (W3 + W5) * x7) >> 3;
|
||||
x8 = x0 + x1;
|
||||
x0 -= x1;
|
||||
x1 = W6 * (x3 + x2) + 4;
|
||||
x2 = (x1 - (W2 + W6) * x2) >> 3;
|
||||
x3 = (x1 + (W2 - W6) * x3) >> 3;
|
||||
x1 = x4 + x6;
|
||||
x4 -= x6;
|
||||
x6 = x5 + x7;
|
||||
x5 -= x7;
|
||||
x7 = x8 + x3;
|
||||
x8 -= x3;
|
||||
x3 = x0 + x2;
|
||||
x0 -= x2;
|
||||
x2 = (181 * (x4 + x5) + 128) >> 8;
|
||||
x4 = (181 * (x4 - x5) + 128) >> 8;
|
||||
*out = njClip(((x7 + x1) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x3 + x2) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x0 + x4) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x8 + x6) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x8 - x6) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x0 - x4) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x3 - x2) >> 14) + 128); out += stride;
|
||||
*out = njClip(((x7 - x1) >> 14) + 128);
|
||||
}
|
||||
|
||||
#define njThrow(e) do { nj.error = e; return; } while (0)
|
||||
#define njCheckError() do { if (nj.error) return; } while (0)
|
||||
|
||||
static int njShowBits(int bits) {
|
||||
unsigned char newbyte;
|
||||
if (!bits) return 0;
|
||||
while (nj.bufbits < bits) {
|
||||
if (nj.size <= 0) {
|
||||
nj.buf = (nj.buf << 8) | 0xFF;
|
||||
nj.bufbits += 8;
|
||||
continue;
|
||||
}
|
||||
newbyte = *nj.pos++;
|
||||
nj.size--;
|
||||
nj.bufbits += 8;
|
||||
nj.buf = (nj.buf << 8) | newbyte;
|
||||
if (newbyte == 0xFF) {
|
||||
if (nj.size) {
|
||||
unsigned char marker = *nj.pos++;
|
||||
nj.size--;
|
||||
switch (marker) {
|
||||
case 0x00:
|
||||
case 0xFF:
|
||||
break;
|
||||
case 0xD9: nj.size = 0; break;
|
||||
default:
|
||||
if ((marker & 0xF8) != 0xD0)
|
||||
nj.error = NJ_SYNTAX_ERROR;
|
||||
else {
|
||||
nj.buf = (nj.buf << 8) | marker;
|
||||
nj.bufbits += 8;
|
||||
}
|
||||
}
|
||||
} else
|
||||
nj.error = NJ_SYNTAX_ERROR;
|
||||
}
|
||||
}
|
||||
return (nj.buf >> (nj.bufbits - bits)) & ((1 << bits) - 1);
|
||||
}
|
||||
|
||||
NJ_INLINE void njSkipBits(int bits) {
|
||||
if (nj.bufbits < bits)
|
||||
(void) njShowBits(bits);
|
||||
nj.bufbits -= bits;
|
||||
}
|
||||
|
||||
NJ_INLINE int njGetBits(int bits) {
|
||||
int res = njShowBits(bits);
|
||||
njSkipBits(bits);
|
||||
return res;
|
||||
}
|
||||
|
||||
NJ_INLINE void njByteAlign(void) {
|
||||
nj.bufbits &= 0xF8;
|
||||
}
|
||||
|
||||
static void njSkip(int count) {
|
||||
nj.pos += count;
|
||||
nj.size -= count;
|
||||
nj.length -= count;
|
||||
if (nj.size < 0) nj.error = NJ_SYNTAX_ERROR;
|
||||
}
|
||||
|
||||
NJ_INLINE unsigned short njDecode16(const unsigned char *pos) {
|
||||
return (pos[0] << 8) | pos[1];
|
||||
}
|
||||
|
||||
static void njDecodeLength(void) {
|
||||
if (nj.size < 2) njThrow(NJ_SYNTAX_ERROR);
|
||||
nj.length = njDecode16(nj.pos);
|
||||
if (nj.length > nj.size) njThrow(NJ_SYNTAX_ERROR);
|
||||
njSkip(2);
|
||||
}
|
||||
|
||||
NJ_INLINE void njSkipMarker(void) {
|
||||
njDecodeLength();
|
||||
njSkip(nj.length);
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeSOF(void) {
|
||||
int i, ssxmax = 0, ssymax = 0;
|
||||
nj_component_t* c;
|
||||
njDecodeLength();
|
||||
njCheckError();
|
||||
if (nj.length < 9) njThrow(NJ_SYNTAX_ERROR);
|
||||
if (nj.pos[0] != 8) njThrow(NJ_UNSUPPORTED);
|
||||
nj.height = njDecode16(nj.pos+1);
|
||||
nj.width = njDecode16(nj.pos+3);
|
||||
if (!nj.width || !nj.height) njThrow(NJ_SYNTAX_ERROR);
|
||||
nj.ncomp = nj.pos[5];
|
||||
njSkip(6);
|
||||
switch (nj.ncomp) {
|
||||
case 1:
|
||||
case 3:
|
||||
break;
|
||||
default:
|
||||
njThrow(NJ_UNSUPPORTED);
|
||||
}
|
||||
if (nj.length < (nj.ncomp * 3)) njThrow(NJ_SYNTAX_ERROR);
|
||||
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
|
||||
c->cid = nj.pos[0];
|
||||
if (!(c->ssx = nj.pos[1] >> 4)) njThrow(NJ_SYNTAX_ERROR);
|
||||
if (c->ssx & (c->ssx - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two
|
||||
if (!(c->ssy = nj.pos[1] & 15)) njThrow(NJ_SYNTAX_ERROR);
|
||||
if (c->ssy & (c->ssy - 1)) njThrow(NJ_UNSUPPORTED); // non-power of two
|
||||
if ((c->qtsel = nj.pos[2]) & 0xFC) njThrow(NJ_SYNTAX_ERROR);
|
||||
njSkip(3);
|
||||
nj.qtused |= 1 << c->qtsel;
|
||||
if (c->ssx > ssxmax) ssxmax = c->ssx;
|
||||
if (c->ssy > ssymax) ssymax = c->ssy;
|
||||
}
|
||||
if (nj.ncomp == 1) {
|
||||
c = nj.comp;
|
||||
c->ssx = c->ssy = ssxmax = ssymax = 1;
|
||||
}
|
||||
nj.mbsizex = ssxmax << 3;
|
||||
nj.mbsizey = ssymax << 3;
|
||||
nj.mbwidth = (nj.width + nj.mbsizex - 1) / nj.mbsizex;
|
||||
nj.mbheight = (nj.height + nj.mbsizey - 1) / nj.mbsizey;
|
||||
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
|
||||
c->width = (nj.width * c->ssx + ssxmax - 1) / ssxmax;
|
||||
c->height = (nj.height * c->ssy + ssymax - 1) / ssymax;
|
||||
c->stride = nj.mbwidth * c->ssx << 3;
|
||||
if (((c->width < 3) && (c->ssx != ssxmax)) || ((c->height < 3) && (c->ssy != ssymax))) njThrow(NJ_UNSUPPORTED);
|
||||
if (!(c->pixels = (unsigned char*) njAllocMem(c->stride * nj.mbheight * c->ssy << 3))) njThrow(NJ_OUT_OF_MEM);
|
||||
}
|
||||
if (nj.ncomp == 3) {
|
||||
nj.rgb = (unsigned char*) njAllocMem(nj.width * nj.height * nj.ncomp);
|
||||
if (!nj.rgb) njThrow(NJ_OUT_OF_MEM);
|
||||
}
|
||||
njSkip(nj.length);
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeDHT(void) {
|
||||
int codelen, currcnt, remain, spread, i, j;
|
||||
nj_vlc_code_t *vlc;
|
||||
static unsigned char counts[16];
|
||||
njDecodeLength();
|
||||
njCheckError();
|
||||
while (nj.length >= 17) {
|
||||
i = nj.pos[0];
|
||||
if (i & 0xEC) njThrow(NJ_SYNTAX_ERROR);
|
||||
if (i & 0x02) njThrow(NJ_UNSUPPORTED);
|
||||
i = (i | (i >> 3)) & 3; // combined DC/AC + tableid value
|
||||
for (codelen = 1; codelen <= 16; ++codelen)
|
||||
counts[codelen - 1] = nj.pos[codelen];
|
||||
njSkip(17);
|
||||
vlc = &nj.vlctab[i][0];
|
||||
remain = spread = 65536;
|
||||
for (codelen = 1; codelen <= 16; ++codelen) {
|
||||
spread >>= 1;
|
||||
currcnt = counts[codelen - 1];
|
||||
if (!currcnt) continue;
|
||||
if (nj.length < currcnt) njThrow(NJ_SYNTAX_ERROR);
|
||||
remain -= currcnt << (16 - codelen);
|
||||
if (remain < 0) njThrow(NJ_SYNTAX_ERROR);
|
||||
for (i = 0; i < currcnt; ++i) {
|
||||
register unsigned char code = nj.pos[i];
|
||||
for (j = spread; j; --j) {
|
||||
vlc->bits = (unsigned char) codelen;
|
||||
vlc->code = code;
|
||||
++vlc;
|
||||
}
|
||||
}
|
||||
njSkip(currcnt);
|
||||
}
|
||||
while (remain--) {
|
||||
vlc->bits = 0;
|
||||
++vlc;
|
||||
}
|
||||
}
|
||||
if (nj.length) njThrow(NJ_SYNTAX_ERROR);
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeDQT(void) {
|
||||
int i;
|
||||
unsigned char *t;
|
||||
njDecodeLength();
|
||||
njCheckError();
|
||||
while (nj.length >= 65) {
|
||||
i = nj.pos[0];
|
||||
if (i & 0xFC) njThrow(NJ_SYNTAX_ERROR);
|
||||
nj.qtavail |= 1 << i;
|
||||
t = &nj.qtab[i][0];
|
||||
for (i = 0; i < 64; ++i)
|
||||
t[i] = nj.pos[i + 1];
|
||||
njSkip(65);
|
||||
}
|
||||
if (nj.length) njThrow(NJ_SYNTAX_ERROR);
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeDRI(void) {
|
||||
njDecodeLength();
|
||||
njCheckError();
|
||||
if (nj.length < 2) njThrow(NJ_SYNTAX_ERROR);
|
||||
nj.rstinterval = njDecode16(nj.pos);
|
||||
njSkip(nj.length);
|
||||
}
|
||||
|
||||
static int njGetVLC(nj_vlc_code_t* vlc, unsigned char* code) {
|
||||
int value = njShowBits(16);
|
||||
int bits = vlc[value].bits;
|
||||
if (!bits) { nj.error = NJ_SYNTAX_ERROR; return 0; }
|
||||
njSkipBits(bits);
|
||||
value = vlc[value].code;
|
||||
if (code) *code = (unsigned char) value;
|
||||
bits = value & 15;
|
||||
if (!bits) return 0;
|
||||
value = njGetBits(bits);
|
||||
if (value < (1 << (bits - 1)))
|
||||
value += ((-1) << bits) + 1;
|
||||
return value;
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeBlock(nj_component_t* c, unsigned char* out) {
|
||||
unsigned char code = 0;
|
||||
int value, coef = 0;
|
||||
njFillMem(nj.block, 0, sizeof(nj.block));
|
||||
c->dcpred += njGetVLC(&nj.vlctab[c->dctabsel][0], NULL);
|
||||
nj.block[0] = (c->dcpred) * nj.qtab[c->qtsel][0];
|
||||
do {
|
||||
value = njGetVLC(&nj.vlctab[c->actabsel][0], &code);
|
||||
if (!code) break; // EOB
|
||||
if (!(code & 0x0F) && (code != 0xF0)) njThrow(NJ_SYNTAX_ERROR);
|
||||
coef += (code >> 4) + 1;
|
||||
if (coef > 63) njThrow(NJ_SYNTAX_ERROR);
|
||||
nj.block[(int) njZZ[coef]] = value * nj.qtab[c->qtsel][coef];
|
||||
} while (coef < 63);
|
||||
for (coef = 0; coef < 64; coef += 8)
|
||||
njRowIDCT(&nj.block[coef]);
|
||||
for (coef = 0; coef < 8; ++coef)
|
||||
njColIDCT(&nj.block[coef], &out[coef], c->stride);
|
||||
}
|
||||
|
||||
NJ_INLINE void njDecodeScan(void) {
|
||||
int i, mbx, mby, sbx, sby;
|
||||
int rstcount = nj.rstinterval, nextrst = 0;
|
||||
nj_component_t* c;
|
||||
njDecodeLength();
|
||||
njCheckError();
|
||||
if (nj.length < (4 + 2 * nj.ncomp)) njThrow(NJ_SYNTAX_ERROR);
|
||||
if (nj.pos[0] != nj.ncomp) njThrow(NJ_UNSUPPORTED);
|
||||
njSkip(1);
|
||||
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
|
||||
if (nj.pos[0] != c->cid) njThrow(NJ_SYNTAX_ERROR);
|
||||
if (nj.pos[1] & 0xEE) njThrow(NJ_SYNTAX_ERROR);
|
||||
c->dctabsel = nj.pos[1] >> 4;
|
||||
c->actabsel = (nj.pos[1] & 1) | 2;
|
||||
njSkip(2);
|
||||
}
|
||||
if (nj.pos[0] || (nj.pos[1] != 63) || nj.pos[2]) njThrow(NJ_UNSUPPORTED);
|
||||
njSkip(nj.length);
|
||||
for (mbx = mby = 0;;) {
|
||||
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c)
|
||||
for (sby = 0; sby < c->ssy; ++sby)
|
||||
for (sbx = 0; sbx < c->ssx; ++sbx) {
|
||||
njDecodeBlock(c, &c->pixels[((mby * c->ssy + sby) * c->stride + mbx * c->ssx + sbx) << 3]);
|
||||
njCheckError();
|
||||
}
|
||||
if (++mbx >= nj.mbwidth) {
|
||||
mbx = 0;
|
||||
if (++mby >= nj.mbheight) break;
|
||||
}
|
||||
if (nj.rstinterval && !(--rstcount)) {
|
||||
njByteAlign();
|
||||
i = njGetBits(16);
|
||||
if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst)) njThrow(NJ_SYNTAX_ERROR);
|
||||
nextrst = (nextrst + 1) & 7;
|
||||
rstcount = nj.rstinterval;
|
||||
for (i = 0; i < 3; ++i)
|
||||
nj.comp[i].dcpred = 0;
|
||||
}
|
||||
}
|
||||
nj.error = __NJ_FINISHED;
|
||||
}
|
||||
|
||||
#if NJ_CHROMA_FILTER
|
||||
|
||||
#define CF4A (-9)
|
||||
#define CF4B (111)
|
||||
#define CF4C (29)
|
||||
#define CF4D (-3)
|
||||
#define CF3A (28)
|
||||
#define CF3B (109)
|
||||
#define CF3C (-9)
|
||||
#define CF3X (104)
|
||||
#define CF3Y (27)
|
||||
#define CF3Z (-3)
|
||||
#define CF2A (139)
|
||||
#define CF2B (-11)
|
||||
#define CF(x) njClip(((x) + 64) >> 7)
|
||||
|
||||
NJ_INLINE void njUpsampleH(nj_component_t* c) {
|
||||
const int xmax = c->width - 3;
|
||||
unsigned char *out, *lin, *lout;
|
||||
int x, y;
|
||||
out = (unsigned char*) njAllocMem((c->width * c->height) << 1);
|
||||
if (!out) njThrow(NJ_OUT_OF_MEM);
|
||||
lin = c->pixels;
|
||||
lout = out;
|
||||
for (y = c->height; y; --y) {
|
||||
lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]);
|
||||
lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]);
|
||||
lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]);
|
||||
for (x = 0; x < xmax; ++x) {
|
||||
lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] + CF4C * lin[x + 2] + CF4D * lin[x + 3]);
|
||||
lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] + CF4B * lin[x + 2] + CF4A * lin[x + 3]);
|
||||
}
|
||||
lin += c->stride;
|
||||
lout += c->width << 1;
|
||||
lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]);
|
||||
lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]);
|
||||
lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]);
|
||||
}
|
||||
c->width <<= 1;
|
||||
c->stride = c->width;
|
||||
njFreeMem((void*)c->pixels);
|
||||
c->pixels = out;
|
||||
}
|
||||
|
||||
NJ_INLINE void njUpsampleV(nj_component_t* c) {
|
||||
const int w = c->width, s1 = c->stride, s2 = s1 + s1;
|
||||
unsigned char *out, *cin, *cout;
|
||||
int x, y;
|
||||
out = (unsigned char*) njAllocMem((c->width * c->height) << 1);
|
||||
if (!out) njThrow(NJ_OUT_OF_MEM);
|
||||
for (x = 0; x < w; ++x) {
|
||||
cin = &c->pixels[x];
|
||||
cout = &out[x];
|
||||
*cout = CF(CF2A * cin[0] + CF2B * cin[s1]); cout += w;
|
||||
*cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]); cout += w;
|
||||
*cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]); cout += w;
|
||||
cin += s1;
|
||||
for (y = c->height - 3; y; --y) {
|
||||
*cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] + CF4D * cin[s2]); cout += w;
|
||||
*cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] + CF4A * cin[s2]); cout += w;
|
||||
cin += s1;
|
||||
}
|
||||
cin += s1;
|
||||
*cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]); cout += w;
|
||||
*cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]); cout += w;
|
||||
*cout = CF(CF2A * cin[0] + CF2B * cin[-s1]);
|
||||
}
|
||||
c->height <<= 1;
|
||||
c->stride = c->width;
|
||||
njFreeMem((void*) c->pixels);
|
||||
c->pixels = out;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
NJ_INLINE void njUpsample(nj_component_t* c) {
|
||||
int x, y, xshift = 0, yshift = 0;
|
||||
unsigned char *out, *lin, *lout;
|
||||
while (c->width < nj.width) { c->width <<= 1; ++xshift; }
|
||||
while (c->height < nj.height) { c->height <<= 1; ++yshift; }
|
||||
out = (unsigned char*) njAllocMem(c->width * c->height);
|
||||
if (!out) njThrow(NJ_OUT_OF_MEM);
|
||||
lin = c->pixels;
|
||||
lout = out;
|
||||
for (y = 0; y < c->height; ++y) {
|
||||
lin = &c->pixels[(y >> yshift) * c->stride];
|
||||
for (x = 0; x < c->width; ++x)
|
||||
lout[x] = lin[x >> xshift];
|
||||
lout += c->width;
|
||||
}
|
||||
c->stride = c->width;
|
||||
njFreeMem((void*) c->pixels);
|
||||
c->pixels = out;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
NJ_INLINE void njConvert(void) {
|
||||
int i;
|
||||
nj_component_t* c;
|
||||
for (i = 0, c = nj.comp; i < nj.ncomp; ++i, ++c) {
|
||||
#if NJ_CHROMA_FILTER
|
||||
while ((c->width < nj.width) || (c->height < nj.height)) {
|
||||
if (c->width < nj.width) njUpsampleH(c);
|
||||
njCheckError();
|
||||
if (c->height < nj.height) njUpsampleV(c);
|
||||
njCheckError();
|
||||
}
|
||||
#else
|
||||
if ((c->width < nj.width) || (c->height < nj.height))
|
||||
njUpsample(c);
|
||||
#endif
|
||||
if ((c->width < nj.width) || (c->height < nj.height)) njThrow(NJ_INTERNAL_ERR);
|
||||
}
|
||||
if (nj.ncomp == 3) {
|
||||
// convert to RGB
|
||||
int x, yy;
|
||||
unsigned char *prgb = nj.rgb;
|
||||
const unsigned char *py = nj.comp[0].pixels;
|
||||
const unsigned char *pcb = nj.comp[1].pixels;
|
||||
const unsigned char *pcr = nj.comp[2].pixels;
|
||||
for (yy = nj.height; yy; --yy) {
|
||||
for (x = 0; x < nj.width; ++x) {
|
||||
register int y = py[x] << 8;
|
||||
register int cb = pcb[x] - 128;
|
||||
register int cr = pcr[x] - 128;
|
||||
*prgb++ = njClip((y + 359 * cr + 128) >> 8);
|
||||
*prgb++ = njClip((y - 88 * cb - 183 * cr + 128) >> 8);
|
||||
*prgb++ = njClip((y + 454 * cb + 128) >> 8);
|
||||
}
|
||||
py += nj.comp[0].stride;
|
||||
pcb += nj.comp[1].stride;
|
||||
pcr += nj.comp[2].stride;
|
||||
}
|
||||
} else if (nj.comp[0].width != nj.comp[0].stride) {
|
||||
// grayscale -> only remove stride
|
||||
unsigned char *pin = &nj.comp[0].pixels[nj.comp[0].stride];
|
||||
unsigned char *pout = &nj.comp[0].pixels[nj.comp[0].width];
|
||||
int y;
|
||||
for (y = nj.comp[0].height - 1; y; --y) {
|
||||
njCopyMem(pout, pin, nj.comp[0].width);
|
||||
pin += nj.comp[0].stride;
|
||||
pout += nj.comp[0].width;
|
||||
}
|
||||
nj.comp[0].stride = nj.comp[0].width;
|
||||
}
|
||||
}
|
||||
|
||||
void njInit(void) {
|
||||
njFillMem(&nj, 0, sizeof(nj_context_t));
|
||||
}
|
||||
|
||||
void njDone(void) {
|
||||
int i;
|
||||
for (i = 0; i < 3; ++i)
|
||||
if (nj.comp[i].pixels) njFreeMem((void*) nj.comp[i].pixels);
|
||||
if (nj.rgb) njFreeMem((void*) nj.rgb);
|
||||
njInit();
|
||||
}
|
||||
|
||||
nj_result_t njDecode(const void* jpeg, const int size) {
|
||||
njDone();
|
||||
nj.pos = (const unsigned char*) jpeg;
|
||||
nj.size = size & 0x7FFFFFFF;
|
||||
if (nj.size < 2) return NJ_NO_JPEG;
|
||||
if ((nj.pos[0] ^ 0xFF) | (nj.pos[1] ^ 0xD8)) return NJ_NO_JPEG;
|
||||
njSkip(2);
|
||||
while (!nj.error) {
|
||||
if ((nj.size < 2) || (nj.pos[0] != 0xFF)) return NJ_SYNTAX_ERROR;
|
||||
njSkip(2);
|
||||
switch (nj.pos[-1]) {
|
||||
case 0xC0: njDecodeSOF(); break;
|
||||
case 0xC4: njDecodeDHT(); break;
|
||||
case 0xDB: njDecodeDQT(); break;
|
||||
case 0xDD: njDecodeDRI(); break;
|
||||
case 0xDA: njDecodeScan(); break;
|
||||
case 0xFE: njSkipMarker(); break;
|
||||
default:
|
||||
if ((nj.pos[-1] & 0xF0) == 0xE0)
|
||||
njSkipMarker();
|
||||
else
|
||||
return NJ_UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
if (nj.error != __NJ_FINISHED) return nj.error;
|
||||
nj.error = NJ_OK;
|
||||
njConvert();
|
||||
return nj.error;
|
||||
}
|
||||
|
||||
int njGetWidth(void) { return nj.width; }
|
||||
int njGetHeight(void) { return nj.height; }
|
||||
int njIsColor(void) { return (nj.ncomp != 1); }
|
||||
unsigned char* njGetImage(void) { return (nj.ncomp == 1) ? nj.comp[0].pixels : nj.rgb; }
|
||||
int njGetImageSize(void) { return nj.width * nj.height * nj.ncomp; }
|
||||
|
||||
#endif // _NJ_INCLUDE_HEADER_ONLY
|
@ -1,66 +0,0 @@
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// HEADER SECTION //
|
||||
// copy and pase this into nanojpeg.h if you want //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _NANOJPEG_H
|
||||
#define _NANOJPEG_H
|
||||
|
||||
// nj_result_t: Result codes for njDecode().
|
||||
typedef enum _nj_result {
|
||||
NJ_OK = 0, // no error, decoding successful
|
||||
NJ_NO_JPEG, // not a JPEG file
|
||||
NJ_UNSUPPORTED, // unsupported format
|
||||
NJ_OUT_OF_MEM, // out of memory
|
||||
NJ_INTERNAL_ERR, // internal error
|
||||
NJ_SYNTAX_ERROR, // syntax error
|
||||
__NJ_FINISHED, // used internally, will never be reported
|
||||
} nj_result_t;
|
||||
|
||||
// njInit: Initialize NanoJPEG.
|
||||
// For safety reasons, this should be called at least one time before using
|
||||
// using any of the other NanoJPEG functions.
|
||||
void njInit(void);
|
||||
|
||||
// njDecode: Decode a JPEG image.
|
||||
// Decodes a memory dump of a JPEG file into internal buffers.
|
||||
// Parameters:
|
||||
// jpeg = The pointer to the memory dump.
|
||||
// size = The size of the JPEG file.
|
||||
// Return value: The error code in case of failure, or NJ_OK (zero) on success.
|
||||
nj_result_t njDecode(const void* jpeg, const int size);
|
||||
|
||||
// njGetWidth: Return the width (in pixels) of the most recently decoded
|
||||
// image. If njDecode() failed, the result of njGetWidth() is undefined.
|
||||
int njGetWidth(void);
|
||||
|
||||
// njGetHeight: Return the height (in pixels) of the most recently decoded
|
||||
// image. If njDecode() failed, the result of njGetHeight() is undefined.
|
||||
int njGetHeight(void);
|
||||
|
||||
// njIsColor: Return 1 if the most recently decoded image is a color image
|
||||
// (RGB) or 0 if it is a grayscale image. If njDecode() failed, the result
|
||||
// of njGetWidth() is undefined.
|
||||
int njIsColor(void);
|
||||
|
||||
// njGetImage: Returns the decoded image data.
|
||||
// Returns a pointer to the most recently image. The memory layout it byte-
|
||||
// oriented, top-down, without any padding between lines. Pixels of color
|
||||
// images will be stored as three consecutive bytes for the red, green and
|
||||
// blue channels. This data format is thus compatible with the PGM or PPM
|
||||
// file formats and the OpenGL texture formats GL_LUMINANCE8 or GL_RGB8.
|
||||
// If njDecode() failed, the result of njGetImage() is undefined.
|
||||
unsigned char* njGetImage(void);
|
||||
|
||||
// njGetImageSize: Returns the size (in bytes) of the image data returned
|
||||
// by njGetImage(). If njDecode() failed, the result of njGetImageSize() is
|
||||
// undefined.
|
||||
int njGetImageSize(void);
|
||||
|
||||
// njDone: Uninitialize NanoJPEG.
|
||||
// Resets NanoJPEG's internal state and frees all memory that has been
|
||||
// allocated at run-time by NanoJPEG. It is still possible to decode another
|
||||
// image after a njDone() call.
|
||||
void njDone(void);
|
||||
|
||||
#endif//_NANOJPEG_H
|
813
common/netloader.c
Normal file
@ -0,0 +1,813 @@
|
||||
#include "common.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <zlib.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#ifndef __WIN32__
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#else
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
typedef int socklen_t;
|
||||
typedef uint32_t in_addr_t;
|
||||
|
||||
#undef DrawText
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#include "netloader.h"
|
||||
|
||||
#define PING_ENABLED 1
|
||||
|
||||
#ifndef __SWITCH__
|
||||
#include "switch/runtime/nxlink.h"
|
||||
#endif
|
||||
|
||||
#define ZLIB_CHUNK (16 * 1024)
|
||||
#define FILE_BUFFER_SIZE (128*1024)
|
||||
|
||||
static int netloader_listenfd = -1;
|
||||
static int netloader_datafd = -1;
|
||||
#if PING_ENABLED
|
||||
static int netloader_udpfd = -1;
|
||||
#endif
|
||||
static unsigned char in[ZLIB_CHUNK];
|
||||
static unsigned char out[ZLIB_CHUNK];
|
||||
|
||||
static mtx_t netloader_mtx;
|
||||
|
||||
static menuEntry_s netloader_me;
|
||||
static volatile bool netloader_initialized = 0;
|
||||
static volatile bool netloader_exitflag = 0;
|
||||
static volatile bool netloader_activated = 0, netloader_launchapp = 0;
|
||||
static volatile size_t netloader_filelen, netloader_filetotal;
|
||||
static volatile char netloader_errortext[1024];
|
||||
|
||||
static bool netloaderGetExit(void);
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
static void netloader_error(const char *func, int err) {
|
||||
//---------------------------------------------------------------------------------
|
||||
if (!netloader_initialized || netloaderGetExit()) return;
|
||||
|
||||
mtx_lock(&netloader_mtx);
|
||||
if (netloader_errortext[0] == 0) {
|
||||
memset((char*)netloader_errortext, 0, sizeof(netloader_errortext));
|
||||
snprintf((char*)netloader_errortext, sizeof(netloader_errortext)-1, "%s: err=%d\n %s\n", func, err, strerror(errno));
|
||||
}
|
||||
mtx_unlock(&netloader_mtx);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
static void netloader_socket_error(const char *func) {
|
||||
//---------------------------------------------------------------------------------
|
||||
int errcode;
|
||||
#ifdef __WIN32__
|
||||
errcode = WSAGetLastError();
|
||||
#else
|
||||
errcode = errno;
|
||||
#endif
|
||||
netloader_error(func,errcode);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
void shutdownSocket(int socket) {
|
||||
//---------------------------------------------------------------------------------
|
||||
#ifdef __WIN32__
|
||||
shutdown (socket, SD_SEND);
|
||||
closesocket (socket);
|
||||
#else
|
||||
close(socket);
|
||||
#endif
|
||||
}
|
||||
|
||||
static const char DIRECTORY_THIS[] = ".";
|
||||
static const char DIRECTORY_PARENT[] = "..";
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
static bool isDirectorySeparator(int c) {
|
||||
//---------------------------------------------------------------------------------
|
||||
return c == DIRECTORY_SEPARATOR_CHAR;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
static void sanitisePath(char *path) {
|
||||
//---------------------------------------------------------------------------------
|
||||
char *tmpPath = strdup(path);
|
||||
tmpPath[0] = 0;
|
||||
|
||||
char *dirStart = path;
|
||||
char *curPath = tmpPath;
|
||||
|
||||
#ifdef _WIN32
|
||||
while(dirStart[0]) {
|
||||
if (dirStart[0] == '/') dirStart[0] =DIRECTORY_SEPARATOR_CHAR;
|
||||
dirStart++;
|
||||
}
|
||||
#endif
|
||||
|
||||
dirStart = path;
|
||||
|
||||
while(isDirectorySeparator(dirStart[0])) dirStart++;
|
||||
|
||||
|
||||
do {
|
||||
char *dirEnd = strchr(dirStart, DIRECTORY_SEPARATOR_CHAR);
|
||||
if (dirEnd) {
|
||||
dirEnd++;
|
||||
if(!strncmp(DIRECTORY_PARENT,dirStart,strlen(DIRECTORY_PARENT))) {
|
||||
/* move back one directory */
|
||||
size_t pathlen = strlen(tmpPath);
|
||||
if(tmpPath[pathlen-1] == DIRECTORY_SEPARATOR_CHAR) tmpPath[pathlen-1] = 0;
|
||||
char *prev = strrchr(tmpPath,DIRECTORY_SEPARATOR_CHAR);
|
||||
if (prev) {
|
||||
curPath = prev + 1;
|
||||
} else {
|
||||
curPath = tmpPath;
|
||||
}
|
||||
|
||||
|
||||
dirStart = dirEnd;
|
||||
} else if (!strncmp(DIRECTORY_THIS,dirStart,strlen(DIRECTORY_THIS))) {
|
||||
/* strip this entry */
|
||||
dirStart = dirEnd;
|
||||
} else {
|
||||
size_t dirSize = dirEnd - dirStart;
|
||||
strncpy(curPath,dirStart,dirSize);
|
||||
curPath[dirSize] = 0;
|
||||
curPath += dirSize;
|
||||
dirStart += dirSize;
|
||||
}
|
||||
} else {
|
||||
strcpy(curPath,dirStart);
|
||||
dirStart += strlen(dirStart);
|
||||
}
|
||||
} while(dirStart[0]);
|
||||
|
||||
strcpy(path, tmpPath);
|
||||
free(tmpPath);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
static int set_socket_nonblocking(int sock) {
|
||||
//---------------------------------------------------------------------------------
|
||||
|
||||
#ifndef __WIN32__
|
||||
int flags = fcntl(sock, F_GETFL);
|
||||
|
||||
if(flags == -1) return -1;
|
||||
|
||||
int rc = fcntl(sock, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
if(rc != 0) return -1;
|
||||
|
||||
#else
|
||||
u_long opt = 1;
|
||||
ioctlsocket(sock, FIONBIO, &opt);
|
||||
#endif
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
static int recvall(int sock, void *buffer, int size, int flags) {
|
||||
//---------------------------------------------------------------------------------
|
||||
int len, sizeleft = size;
|
||||
bool blockflag=0;
|
||||
|
||||
while (sizeleft) {
|
||||
|
||||
len = recv(sock,buffer,sizeleft,flags);
|
||||
|
||||
if (len == 0) {
|
||||
size = 0;
|
||||
break;
|
||||
};
|
||||
|
||||
if (len != -1) {
|
||||
sizeleft -=len;
|
||||
buffer +=len;
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
int errcode = WSAGetLastError();
|
||||
if (errcode != WSAEWOULDBLOCK) {
|
||||
netloader_error("win socket error",errcode);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
blockflag = 1;
|
||||
}
|
||||
#else
|
||||
if ( errno != EWOULDBLOCK && errno != EAGAIN) {
|
||||
netloader_socket_error("recv");
|
||||
break;
|
||||
}
|
||||
else {
|
||||
blockflag = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (blockflag && netloaderGetExit()) return 0;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
static int sendall(int sock, void *buffer, int size, int flags) {
|
||||
//---------------------------------------------------------------------------------
|
||||
int len, sizeleft = size;
|
||||
bool blockflag=0;
|
||||
|
||||
while (sizeleft) {
|
||||
|
||||
len = send(sock,buffer,sizeleft,flags);
|
||||
|
||||
if (len == 0) {
|
||||
size = 0;
|
||||
break;
|
||||
};
|
||||
|
||||
if (len != -1) {
|
||||
sizeleft -=len;
|
||||
buffer +=len;
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
int errcode = WSAGetLastError();
|
||||
if (errcode != WSAEWOULDBLOCK) {
|
||||
netloader_error("win socket error",errcode);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
blockflag = 1;
|
||||
}
|
||||
#else
|
||||
if ( errno != EWOULDBLOCK && errno != EAGAIN) {
|
||||
netloader_socket_error("recv");
|
||||
break;
|
||||
}
|
||||
else {
|
||||
blockflag = 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (blockflag && netloaderGetExit()) return 0;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
static int decompress(int sock, FILE *fh, size_t filesize) {
|
||||
//---------------------------------------------------------------------------------
|
||||
int ret;
|
||||
unsigned have;
|
||||
z_stream strm;
|
||||
uint32_t chunksize=0;
|
||||
|
||||
/* allocate inflate state */
|
||||
strm.zalloc = Z_NULL;
|
||||
strm.zfree = Z_NULL;
|
||||
strm.opaque = Z_NULL;
|
||||
strm.avail_in = 0;
|
||||
strm.next_in = Z_NULL;
|
||||
ret = inflateInit(&strm);
|
||||
if (ret != Z_OK) {
|
||||
netloader_error("inflateInit failed.",ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
/* decompress until deflate stream ends or end of file */
|
||||
do {
|
||||
if (netloaderGetExit()) {
|
||||
ret = Z_DATA_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
int len = recvall(sock, &chunksize, 4, 0);
|
||||
|
||||
if (len != 4) {
|
||||
(void)inflateEnd(&strm);
|
||||
netloader_error("Error getting chunk size",len);
|
||||
return Z_DATA_ERROR;
|
||||
}
|
||||
|
||||
if (chunksize > sizeof(in)) {
|
||||
(void)inflateEnd(&strm);
|
||||
netloader_error("Invalid chunk size",chunksize);
|
||||
return Z_DATA_ERROR;
|
||||
}
|
||||
|
||||
strm.avail_in = recvall(sock,in,chunksize,0);
|
||||
|
||||
if (strm.avail_in == 0) {
|
||||
(void)inflateEnd(&strm);
|
||||
netloader_error("remote closed socket.",0);
|
||||
return Z_DATA_ERROR;
|
||||
}
|
||||
|
||||
strm.next_in = in;
|
||||
|
||||
/* run inflate() on input until output buffer not full */
|
||||
do {
|
||||
strm.avail_out = ZLIB_CHUNK;
|
||||
strm.next_out = out;
|
||||
ret = inflate(&strm, Z_NO_FLUSH);
|
||||
|
||||
switch (ret) {
|
||||
|
||||
case Z_NEED_DICT:
|
||||
ret = Z_DATA_ERROR; /* and fall through */
|
||||
|
||||
case Z_DATA_ERROR:
|
||||
case Z_MEM_ERROR:
|
||||
case Z_STREAM_ERROR:
|
||||
(void)inflateEnd(&strm);
|
||||
netloader_error("inflate error",ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
have = ZLIB_CHUNK - strm.avail_out;
|
||||
|
||||
if (fwrite(out, 1, have, fh) != have || ferror(fh)) {
|
||||
(void)inflateEnd(&strm);
|
||||
netloader_error("file write error",0);
|
||||
return Z_ERRNO;
|
||||
}
|
||||
|
||||
total += have;
|
||||
mtx_lock(&netloader_mtx);
|
||||
netloader_filetotal = total;
|
||||
mtx_unlock(&netloader_mtx);
|
||||
//printf("%zu (%zd%%)",total, (100 * total) / filesize);
|
||||
} while (strm.avail_out == 0);
|
||||
|
||||
/* done when inflate() says it's done */
|
||||
} while (ret != Z_STREAM_END);
|
||||
|
||||
/* clean up and return */
|
||||
(void)inflateEnd(&strm);
|
||||
return ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
int loadnro(menuEntry_s *me, int sock, struct in_addr remote) {
|
||||
//---------------------------------------------------------------------------------
|
||||
int len, namelen, filelen;
|
||||
char filepath[PATH_MAX+1];
|
||||
len = recvall(sock, &namelen, 4, 0);
|
||||
|
||||
if (len != 4) {
|
||||
netloader_error("Error getting name length", errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (namelen >= sizeof(filepath)-1) {
|
||||
netloader_error("File-path length is too large",errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
len = recvall(sock, filepath, namelen, 0);
|
||||
|
||||
if (len != namelen) {
|
||||
netloader_error("Error getting file-path", errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
filepath[namelen] = 0;
|
||||
|
||||
len = recvall(sock, &filelen, 4, 0);
|
||||
|
||||
if (len != 4) {
|
||||
netloader_error("Error getting file length",errno);
|
||||
return -1;
|
||||
}
|
||||
|
||||
mtx_lock(&netloader_mtx);
|
||||
netloader_filelen = filelen;
|
||||
mtx_unlock(&netloader_mtx);
|
||||
|
||||
int response = 0;
|
||||
|
||||
sanitisePath(filepath);
|
||||
|
||||
snprintf(me->path, sizeof(me->path)-1, "%s%s%s", menuGetRootPath(), DIRECTORY_SEPARATOR, filepath);
|
||||
// make sure it's terminated
|
||||
me->path[sizeof(me->path)-1] = 0;
|
||||
strncpy(filepath, me->path, sizeof(filepath)-1); // menuEntryLoad() below will overwrite me->path, so copy me->path to filepath and use that instead.
|
||||
filepath[sizeof(filepath)-1] = 0;
|
||||
|
||||
argData_s* ad = &me->args;
|
||||
ad->dst = (char*)&ad->buf[1];
|
||||
ad->nxlink_host = remote;
|
||||
|
||||
const char* ext = getExtension(me->path);
|
||||
if (ext && strcasecmp(ext, ".nro")==0)
|
||||
launchAddArg(ad, me->path);
|
||||
else {
|
||||
me->type = ENTRY_TYPE_FILE_OTHER; // Handle fileassoc when extension isn't .nro.
|
||||
if (!menuEntryLoad(me, "", false, false)) {
|
||||
response = -3;
|
||||
errno = EINVAL;
|
||||
netloader_error("File-extension/filename not recognized",0);
|
||||
}
|
||||
menuEntryFree(me, false); // We don't need any of the buffers which may have been allocated.
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
if (response == 0) {
|
||||
int fd = open(filepath,O_CREAT|O_WRONLY, ACCESSPERMS);
|
||||
|
||||
if (fd < 0) {
|
||||
response = -1;
|
||||
netloader_error("open", errno);
|
||||
} else {
|
||||
if (ftruncate(fd,filelen) == -1) {
|
||||
response = -2;
|
||||
netloader_error("ftruncate",errno);
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
FILE *file = NULL;
|
||||
|
||||
if (response == 0) {
|
||||
file = fopen(filepath,"wb");
|
||||
if(file == NULL) {
|
||||
perror("file");
|
||||
response = -1;
|
||||
}
|
||||
}
|
||||
|
||||
send(sock,(char *)&response,sizeof(response),0);
|
||||
|
||||
char *writebuffer = NULL;
|
||||
if (response == 0 ) {
|
||||
writebuffer = malloc(FILE_BUFFER_SIZE);
|
||||
if (writebuffer==NULL) {
|
||||
netloader_error("Failed to allocate memory",ENOMEM);
|
||||
response = -1;
|
||||
}
|
||||
else {
|
||||
memset(writebuffer, 0, FILE_BUFFER_SIZE);
|
||||
setvbuf(file,writebuffer,_IOFBF, FILE_BUFFER_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (response == 0 ) {
|
||||
//printf("transferring %s\n%d bytes.\n", filepath, filelen);
|
||||
|
||||
if (decompress(sock,file,filelen)==Z_OK) {
|
||||
int netloaded_cmdlen = 0;
|
||||
len = sendall(sock,(char *)&response,sizeof(response),0);
|
||||
|
||||
if (len != sizeof(response)) {
|
||||
netloader_error("Error sending response",errno);
|
||||
response = -1;
|
||||
}
|
||||
|
||||
//printf("\ntransferring command line\n");
|
||||
|
||||
if (response == 0 ) {
|
||||
len = recvall(sock,(char*)&netloaded_cmdlen,4,0);
|
||||
|
||||
if (len != 4) {
|
||||
netloader_error("Error getting netloaded_cmdlen",errno);
|
||||
response = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (response == 0 ) {
|
||||
if ((me->args.dst+netloaded_cmdlen) >= (char*)(me->args.buf + sizeof(me->args.buf))) netloaded_cmdlen = (uintptr_t)me->args.buf + sizeof(me->args.buf)-1 - (uintptr_t)me->args.dst;
|
||||
|
||||
len = recvall(sock,me->args.dst, netloaded_cmdlen,0);
|
||||
|
||||
if (len != netloaded_cmdlen) {
|
||||
netloader_error("Error getting args",errno);
|
||||
response = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (response == 0 ) {
|
||||
while(netloaded_cmdlen) {
|
||||
size_t len = strlen(me->args.dst) + 1;
|
||||
ad->dst += len;
|
||||
ad->buf[0]++;
|
||||
netloaded_cmdlen -= len;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
response = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (file) {
|
||||
fflush(file);
|
||||
fclose(file);
|
||||
}
|
||||
if (response == -1) unlink(filepath);
|
||||
free(writebuffer);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
int netloader_activate(void) {
|
||||
//---------------------------------------------------------------------------------
|
||||
struct sockaddr_in serv_addr;
|
||||
|
||||
memset(&serv_addr, 0, sizeof(serv_addr));
|
||||
serv_addr.sin_family = AF_INET;
|
||||
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
|
||||
serv_addr.sin_port = htons(NXLINK_SERVER_PORT);
|
||||
|
||||
#if PING_ENABLED
|
||||
// create udp socket for broadcast ping
|
||||
netloader_udpfd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (netloader_udpfd < 0)
|
||||
{
|
||||
netloader_socket_error("udp socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(bind(netloader_udpfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) < 0) {
|
||||
netloader_socket_error("bind udp socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (set_socket_nonblocking(netloader_udpfd) == -1)
|
||||
{
|
||||
netloader_socket_error("listen fcntl");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
// create listening socket on all addresses on NXLINK_SERVER_PORT
|
||||
|
||||
netloader_listenfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if(netloader_listenfd < 0)
|
||||
{
|
||||
netloader_socket_error("socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t tmpval=1;
|
||||
int rc = setsockopt(netloader_listenfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&tmpval, sizeof(tmpval));
|
||||
if(rc != 0)
|
||||
{
|
||||
netloader_socket_error("setsockopt");
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = bind(netloader_listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
|
||||
if(rc != 0)
|
||||
{
|
||||
netloader_socket_error("bind");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (set_socket_nonblocking(netloader_listenfd) == -1)
|
||||
{
|
||||
netloader_socket_error("listen fcntl");
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = listen(netloader_listenfd, 10);
|
||||
if(rc != 0)
|
||||
{
|
||||
netloader_socket_error("listen");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
int netloader_deactivate(void) {
|
||||
//---------------------------------------------------------------------------------
|
||||
// close all remaining sockets and allow mainloop to return to main menu
|
||||
if(netloader_listenfd >= 0)
|
||||
{
|
||||
shutdownSocket(netloader_listenfd);
|
||||
netloader_listenfd = -1;
|
||||
}
|
||||
|
||||
if(netloader_datafd >= 0)
|
||||
{
|
||||
shutdownSocket(netloader_datafd);
|
||||
netloader_datafd = -1;
|
||||
}
|
||||
|
||||
#if PING_ENABLED
|
||||
if(netloader_udpfd >= 0)
|
||||
{
|
||||
shutdownSocket(netloader_udpfd);
|
||||
netloader_udpfd = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------
|
||||
int netloader_loop(struct sockaddr_in *sa_remote) {
|
||||
//---------------------------------------------------------------------------------
|
||||
|
||||
#if PING_ENABLED
|
||||
char recvbuf[256];
|
||||
socklen_t fromlen = sizeof(struct sockaddr_in);
|
||||
|
||||
int len = recvfrom(netloader_udpfd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr*) sa_remote, &fromlen);
|
||||
|
||||
if (len!=-1) {
|
||||
if (strncmp(recvbuf,"nxboot",strlen("nxboot")) == 0) {
|
||||
sa_remote->sin_family=AF_INET;
|
||||
sa_remote->sin_port=htons(NXLINK_CLIENT_PORT);
|
||||
sendto(netloader_udpfd, "bootnx", strlen("bootnx"), 0, (struct sockaddr*) sa_remote,sizeof(struct sockaddr_in));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if(netloader_listenfd >= 0 && netloader_datafd < 0) {
|
||||
socklen_t addrlen = sizeof(struct sockaddr_in);
|
||||
netloader_datafd = accept(netloader_listenfd, (struct sockaddr*)sa_remote, &addrlen);
|
||||
if(netloader_datafd < 0)
|
||||
{
|
||||
|
||||
#ifdef _WIN32
|
||||
int errcode = WSAGetLastError();
|
||||
if (errcode != WSAEWOULDBLOCK) {
|
||||
netloader_error("accept", errcode);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
if ( errno != EWOULDBLOCK && errno != EAGAIN) {
|
||||
netloader_error("accept", errno);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if (set_socket_nonblocking(netloader_datafd) == -1)
|
||||
{
|
||||
netloader_socket_error("set_socket_nonblocking(netloader_datafd)");
|
||||
return -1;
|
||||
}
|
||||
|
||||
close(netloader_listenfd);
|
||||
netloader_listenfd = -1;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void netloaderGetState(netloaderState *state) {
|
||||
if(state==NULL)return;
|
||||
mtx_lock(&netloader_mtx);
|
||||
|
||||
state->activated = netloader_activated;
|
||||
state->launch_app = netloader_launchapp;
|
||||
state->me = &netloader_me;
|
||||
|
||||
state->transferring = (netloader_datafd >= 0 && netloader_filelen);
|
||||
state->sock_connected = netloader_datafd >= 0;
|
||||
state->filelen = netloader_filelen;
|
||||
state->filetotal = netloader_filetotal;
|
||||
|
||||
memset(state->errormsg, 0, sizeof(state->errormsg));
|
||||
if(netloader_errortext[0]) {
|
||||
strncpy(state->errormsg, (char*)netloader_errortext, sizeof(state->errormsg)-1);
|
||||
memset((char*)netloader_errortext, 0, sizeof(netloader_errortext));
|
||||
}
|
||||
|
||||
mtx_unlock(&netloader_mtx);
|
||||
}
|
||||
|
||||
static bool netloaderGetExit(void) {
|
||||
bool flag;
|
||||
mtx_lock(&netloader_mtx);
|
||||
flag = netloader_exitflag;
|
||||
mtx_unlock(&netloader_mtx);
|
||||
return flag;
|
||||
}
|
||||
|
||||
void netloaderSignalExit(void) {
|
||||
if (!netloader_initialized) return;
|
||||
|
||||
mtx_lock(&netloader_mtx);
|
||||
netloader_exitflag = 1;
|
||||
mtx_unlock(&netloader_mtx);
|
||||
}
|
||||
|
||||
Result netloaderInit(void) {
|
||||
Result rc=0;
|
||||
if (netloader_initialized) return 0;
|
||||
|
||||
if (mtx_init(&netloader_mtx, mtx_plain) != thrd_success) return 1;
|
||||
|
||||
#ifdef __SWITCH__
|
||||
rc = socketInitializeDefault();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = nifmInitialize(NifmServiceType_User);
|
||||
if (R_FAILED(rc)) socketExit();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef __WIN32__
|
||||
WSADATA wsa_data;
|
||||
if (WSAStartup (MAKEWORD(2,2), &wsa_data)) {
|
||||
//netloader_error("WSAStartup failed\n",1);
|
||||
rc = 2;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (rc) {
|
||||
mtx_destroy(&netloader_mtx);
|
||||
return rc;
|
||||
}
|
||||
|
||||
netloader_initialized = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void netloaderExit(void) {
|
||||
if (!netloader_initialized) return;
|
||||
netloader_initialized = 0;
|
||||
|
||||
mtx_destroy(&netloader_mtx);
|
||||
|
||||
#ifdef __SWITCH__
|
||||
nifmExit();
|
||||
socketExit();
|
||||
#endif
|
||||
|
||||
#ifdef __WIN32__
|
||||
WSACleanup ();
|
||||
#endif
|
||||
}
|
||||
|
||||
void netloaderTask(void* arg) {
|
||||
int ret=0;
|
||||
struct sockaddr_in sa_remote;
|
||||
struct timespec duration = {.tv_nsec = 100000000};
|
||||
menuEntryInit(&netloader_me,ENTRY_TYPE_FILE);
|
||||
|
||||
mtx_lock(&netloader_mtx);
|
||||
netloader_exitflag = 0;
|
||||
netloader_activated = 0;
|
||||
netloader_launchapp = 0;
|
||||
netloader_filelen = 0;
|
||||
netloader_filetotal = 0;
|
||||
mtx_unlock(&netloader_mtx);
|
||||
|
||||
if(netloader_activate() == 0) {
|
||||
mtx_lock(&netloader_mtx);
|
||||
netloader_activated = 1;
|
||||
mtx_unlock(&netloader_mtx);
|
||||
}
|
||||
else {
|
||||
netloader_deactivate();
|
||||
return;
|
||||
}
|
||||
|
||||
while((ret = netloader_loop(&sa_remote)) == 0 && !netloaderGetExit()) {
|
||||
thrd_sleep(&duration, NULL);
|
||||
}
|
||||
|
||||
if(ret == 1 && !netloaderGetExit()) {
|
||||
int result = loadnro(&netloader_me, netloader_datafd,sa_remote.sin_addr);
|
||||
if (result== 0) {
|
||||
ret = 1;
|
||||
} else {
|
||||
ret = -1;
|
||||
}
|
||||
}
|
||||
|
||||
netloader_deactivate();
|
||||
mtx_lock(&netloader_mtx);
|
||||
if (ret==1 && !netloader_exitflag) netloader_launchapp = 1;//Access netloader_exitflag directly since the mutex is already locked.
|
||||
netloader_exitflag = 0;
|
||||
netloader_activated = 0;
|
||||
mtx_unlock(&netloader_mtx);
|
||||
}
|
||||
|
23
common/netloader.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct {
|
||||
bool activated;
|
||||
bool launch_app;
|
||||
bool transferring;
|
||||
bool sock_connected;
|
||||
menuEntry_s *me;
|
||||
size_t filelen, filetotal;
|
||||
char errormsg[1025];
|
||||
} netloaderState;
|
||||
|
||||
int netloader_activate(void);
|
||||
int netloader_deactivate(void);
|
||||
int netloader_loop(struct sockaddr_in *sa_remote);
|
||||
|
||||
Result netloaderInit(void);
|
||||
void netloaderExit(void);
|
||||
|
||||
void netloaderTask(void* arg);
|
||||
|
||||
void netloaderGetState(netloaderState *state);
|
||||
void netloaderSignalExit(void);
|
5
common/netstatus.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
bool netstatusGetDetails(AssetId *id);
|
||||
|
54
common/nro.h
@ -1,54 +0,0 @@
|
||||
/**
|
||||
* @file nro.h
|
||||
* @brief NRO headers.
|
||||
* @copyright libnx Authors
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define NROHEADER_MAGIC 0x304f524e
|
||||
|
||||
#define NROASSETHEADER_MAGIC 0x54455341
|
||||
#define NROASSETHEADER_VERSION 0
|
||||
|
||||
/// Entry for each segment in the codebin.
|
||||
typedef struct {
|
||||
u32 file_off;
|
||||
u32 size;
|
||||
} NroSegment;
|
||||
|
||||
/// Offset 0x0 in the NRO.
|
||||
typedef struct {
|
||||
u32 unused;
|
||||
u32 mod_offset;
|
||||
u8 padding[8];
|
||||
} NroStart;
|
||||
|
||||
/// This follows NroStart, the actual nro-header.
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u32 unk1;
|
||||
u32 size;
|
||||
u32 unk2;
|
||||
NroSegment segments[3];
|
||||
u32 bss_size;
|
||||
u32 unk3;
|
||||
u8 build_id[0x20];
|
||||
u8 padding[0x20];
|
||||
} NroHeader;
|
||||
|
||||
/// Custom asset section.
|
||||
typedef struct {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
} NroAssetSection;
|
||||
|
||||
/// Custom asset header.
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
NroAssetSection icon;
|
||||
NroAssetSection nacp;
|
||||
NroAssetSection romfs;
|
||||
} NroAssetHeader;
|
||||
|
8
common/power.h
Normal file
@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
void powerInit(void);
|
||||
|
||||
bool powerGetDetails(uint32_t *batteryCharge, bool *isCharging);
|
||||
|
||||
void powerExit(void);
|
130
common/status.c
Normal file
@ -0,0 +1,130 @@
|
||||
#include "status.h"
|
||||
|
||||
static bool s_statusInitialized = 0;
|
||||
static thrd_t s_statusThread;
|
||||
static cnd_t s_statusCdn;
|
||||
static mtx_t s_statusMtx;
|
||||
static mtx_t s_statusAccessMtx;
|
||||
static bool s_statusExit;
|
||||
|
||||
static bool s_statusReady;
|
||||
static bool s_statusNetFlag;
|
||||
static AssetId s_statusNetAssetId;
|
||||
static bool s_statusTemperatureFlag;
|
||||
static s32 s_statusTemperature;
|
||||
|
||||
// This uses netstatusGetDetails from a dedicated thread, since nifmGetInternetConnectionStatus can block for a few seconds.
|
||||
|
||||
static int statusThreadProc(void* unused)
|
||||
{
|
||||
mtx_lock(&s_statusMtx);
|
||||
|
||||
struct timespec timeout = {0};
|
||||
bool tmpflag=0;
|
||||
bool thermalflag=0;
|
||||
bool thermal_initialized=0;
|
||||
AssetId tmpid;
|
||||
s32 temperature;
|
||||
|
||||
thermal_initialized = thermalstatusInit();
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &timeout);
|
||||
timeout.tv_sec++;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
cnd_timedwait(&s_statusCdn, &s_statusMtx, &timeout);
|
||||
|
||||
if (s_statusExit)
|
||||
break;
|
||||
|
||||
tmpflag = netstatusGetDetails(&tmpid);
|
||||
if (thermal_initialized) thermalflag = thermalstatusGetDetails(&temperature);
|
||||
|
||||
mtx_lock(&s_statusAccessMtx);
|
||||
|
||||
s_statusNetFlag = tmpflag;
|
||||
s_statusNetAssetId = tmpid;
|
||||
|
||||
s_statusTemperatureFlag = thermalflag;
|
||||
s_statusTemperature = temperature;
|
||||
|
||||
s_statusReady = 1;
|
||||
|
||||
mtx_unlock(&s_statusAccessMtx);
|
||||
|
||||
clock_gettime(CLOCK_MONOTONIC, &timeout);
|
||||
timeout.tv_sec++;
|
||||
}
|
||||
|
||||
mtx_unlock(&s_statusMtx);
|
||||
|
||||
if (thermal_initialized) thermalstatusExit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool statusGet(bool *netstatusFlag, AssetId *netstatusAssetId, bool *temperatureFlag, s32 *temperature) {
|
||||
if (!s_statusReady) return 0;
|
||||
|
||||
mtx_lock(&s_statusAccessMtx);
|
||||
|
||||
*netstatusFlag = s_statusNetFlag;
|
||||
*netstatusAssetId = s_statusNetAssetId;
|
||||
|
||||
*temperatureFlag = s_statusTemperatureFlag;
|
||||
*temperature = s_statusTemperature;
|
||||
|
||||
mtx_unlock(&s_statusAccessMtx);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool statusInit(void)
|
||||
{
|
||||
if (s_statusInitialized) return 1;
|
||||
|
||||
if (cnd_init(&s_statusCdn) != thrd_success) return 0;
|
||||
if (mtx_init(&s_statusMtx, mtx_plain) != thrd_success) {
|
||||
cnd_destroy(&s_statusCdn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mtx_init(&s_statusAccessMtx, mtx_plain) != thrd_success) {
|
||||
mtx_destroy(&s_statusMtx);
|
||||
cnd_destroy(&s_statusCdn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (thrd_create(&s_statusThread, statusThreadProc, 0) != thrd_success) {
|
||||
mtx_destroy(&s_statusAccessMtx);
|
||||
mtx_destroy(&s_statusMtx);
|
||||
cnd_destroy(&s_statusCdn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
s_statusInitialized = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void statusExit(void)
|
||||
{
|
||||
int res=0;
|
||||
|
||||
if (!s_statusInitialized) return;
|
||||
s_statusInitialized = 0;
|
||||
|
||||
mtx_lock(&s_statusMtx);
|
||||
s_statusExit = true;
|
||||
cnd_signal(&s_statusCdn);
|
||||
mtx_unlock(&s_statusMtx);
|
||||
|
||||
thrd_join(s_statusThread, &res);
|
||||
|
||||
s_statusReady = 0;
|
||||
|
||||
mtx_destroy(&s_statusAccessMtx);
|
||||
mtx_destroy(&s_statusMtx);
|
||||
cnd_destroy(&s_statusCdn);
|
||||
}
|
||||
|
7
common/status.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
bool statusInit(void);
|
||||
void statusExit(void);
|
||||
bool statusGet(bool *netstatusFlag, AssetId *netstatusAssetId, bool *temperatureFlag, s32 *temperature);
|
||||
|
@ -1,26 +1,30 @@
|
||||
#include "text.h"
|
||||
|
||||
#ifdef SWITCH
|
||||
static u64 s_textLanguageCode = 0;
|
||||
|
||||
#ifdef __SWITCH__
|
||||
static int s_textLang = SetLanguage_ENUS;
|
||||
#else
|
||||
static int s_textLang = 1;
|
||||
#endif
|
||||
|
||||
void textInit(void) {
|
||||
#ifdef SWITCH
|
||||
//u64 LanguageCode=0;
|
||||
//s32 Language=0;
|
||||
Result textInit(void) {
|
||||
#ifdef __SWITCH__
|
||||
SetLanguage Language=SetLanguage_ENUS;
|
||||
|
||||
s_textLang = SetLanguage_ENUS;
|
||||
//TODO: Re-enable this once the font supports all used languages.
|
||||
/*Result rc = setInitialize();
|
||||
if (R_SUCCEEDED(rc)) rc = setGetSystemLanguage(&LanguageCode);
|
||||
if (R_SUCCEEDED(rc)) rc = setMakeLanguage(LanguageCode, &Language);
|
||||
if (R_SUCCEEDED(rc) && Language < 16) s_textLang = Language;
|
||||
setExit();*/
|
||||
s_textLang = Language;
|
||||
|
||||
Result rc = setInitialize();
|
||||
if (R_SUCCEEDED(rc)) rc = setGetSystemLanguage(&s_textLanguageCode);
|
||||
if (R_SUCCEEDED(rc)) rc = setMakeLanguage(s_textLanguageCode, &Language);
|
||||
//if (R_SUCCEEDED(rc) && Language < 17) s_textLang = Language;//TODO: Re-enable this once language.c supports all used languages.
|
||||
setExit();
|
||||
if (R_FAILED(rc)) return rc;
|
||||
#else
|
||||
s_textLang = 1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int textGetLang(void) {
|
||||
@ -29,10 +33,14 @@ int textGetLang(void) {
|
||||
|
||||
const char* textGetString(StrId id) {
|
||||
const char* str = g_strings[id][s_textLang];
|
||||
#ifdef SWITCH
|
||||
#ifdef __SWITCH__
|
||||
if (!str) str = g_strings[id][SetLanguage_ENUS];
|
||||
#else
|
||||
if (!str) str = g_strings[id][1];
|
||||
#endif
|
||||
return str;
|
||||
}
|
||||
|
||||
u64 textGetLanguageCode(void) {
|
||||
return s_textLanguageCode;
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
#include "common.h"
|
||||
#include "language.h"
|
||||
|
||||
void textInit(void);
|
||||
Result textInit(void);
|
||||
int textGetLang(void);
|
||||
const char* textGetString(StrId id);
|
||||
u64 textGetLanguageCode(void);
|
||||
|
736
common/theme.c
@ -1,41 +1,717 @@
|
||||
#include "theme.h"
|
||||
#include "button_a_light_bin.h"
|
||||
#include "button_a_dark_bin.h"
|
||||
#include "button_b_light_bin.h"
|
||||
#include "button_b_dark_bin.h"
|
||||
#include "hbmenu_logo_light_bin.h"
|
||||
#include "hbmenu_logo_dark_bin.h"
|
||||
#include <physfs.h>
|
||||
|
||||
theme_t themeCurrent;
|
||||
ThemePreset themeGlobalPreset;
|
||||
|
||||
bool colorFromSetting(config_setting_t *rgba, color_t *col) {
|
||||
if(rgba == NULL)
|
||||
return false;
|
||||
*col = MakeColor(config_setting_get_int_elem(rgba, 0), config_setting_get_int_elem(rgba, 1), config_setting_get_int_elem(rgba, 2), config_setting_get_int_elem(rgba, 3));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool intElemFromSetting(config_setting_t *setting, int *out, size_t count) {
|
||||
if (!setting || config_setting_length(setting) < count)
|
||||
return false;
|
||||
|
||||
for (size_t i=0; i<count; i++) {
|
||||
out[i] = config_setting_get_int_elem(setting, i);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool layoutObjectFromSetting(config_setting_t *layout_setting, ThemeLayoutObject *obj, bool ignore_cfg_visible) {
|
||||
int tmp=0;
|
||||
ThemeLayoutObject tmpobj={0};
|
||||
if (!layout_setting)
|
||||
return false;
|
||||
|
||||
memcpy(tmpobj.posStart, obj->posStart, sizeof(obj->posStart));
|
||||
memcpy(tmpobj.posEnd, obj->posEnd, sizeof(obj->posEnd));
|
||||
memcpy(tmpobj.size, obj->size, sizeof(obj->size));
|
||||
|
||||
if (config_setting_lookup_bool(layout_setting, "visible", &tmp)==CONFIG_TRUE)
|
||||
tmpobj.visible = tmp;
|
||||
else
|
||||
tmpobj.visible = obj->visible;
|
||||
if (config_setting_lookup_bool(layout_setting, "posType", &tmp)==CONFIG_TRUE)
|
||||
tmpobj.posType = tmp;
|
||||
else
|
||||
tmpobj.posType = obj->posType;
|
||||
|
||||
intElemFromSetting(config_setting_lookup(layout_setting, "posStart"), tmpobj.posStart, 2);
|
||||
intElemFromSetting(config_setting_lookup(layout_setting, "posEnd"), tmpobj.posEnd, 2);
|
||||
intElemFromSetting(config_setting_lookup(layout_setting, "size"), tmpobj.size, 2);
|
||||
|
||||
if (!tmpobj.posType && (tmpobj.posStart[0] < 0 || tmpobj.posStart[1] < 0 || tmpobj.posEnd[0] < 0 || tmpobj.posEnd[1] < 0))
|
||||
return false;
|
||||
if (tmpobj.size[0] < 0 || tmpobj.size[1] < 0)
|
||||
return false;
|
||||
|
||||
obj->posStart[0] = tmpobj.posStart[0];
|
||||
obj->posStart[1] = tmpobj.posStart[1];
|
||||
obj->posEnd[0] = tmpobj.posEnd[0];
|
||||
obj->posEnd[1] = tmpobj.posEnd[1];
|
||||
|
||||
if (!ignore_cfg_visible) obj->visible = tmpobj.visible;
|
||||
obj->posType = tmpobj.posType;
|
||||
obj->size[0] = tmpobj.size[0];
|
||||
obj->size[1] = tmpobj.size[1];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool assetObjectFromSetting(config_setting_t *asset_setting, AssetId id, ThemeLayoutObject *layoutobj) {
|
||||
int imageSize[2]={0};
|
||||
const char *path = NULL;
|
||||
char tmp_path[PATH_MAX];
|
||||
if (!asset_setting)
|
||||
return false;
|
||||
|
||||
if (config_setting_lookup_string(asset_setting, "path", &path)==CONFIG_FALSE)
|
||||
return false;
|
||||
|
||||
if (!intElemFromSetting(config_setting_lookup(asset_setting, "imageSize"), imageSize, 2))
|
||||
return false;
|
||||
|
||||
if (imageSize[0] <= 0 || imageSize[1] <= 0 || imageSize[0] > 1280 || imageSize[1] > 720)
|
||||
return false;
|
||||
|
||||
if (layoutobj && (imageSize[0] != layoutobj->imageSize[0] || imageSize[1] != layoutobj->imageSize[1]))
|
||||
return false;
|
||||
|
||||
memset(tmp_path, 0, sizeof(tmp_path));
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "theme/%s", path);
|
||||
|
||||
return assetsLoadData(id, tmp_path, imageSize);
|
||||
}
|
||||
|
||||
void themeStartup(ThemePreset preset) {
|
||||
themeGlobalPreset = preset;
|
||||
theme_t themeLight = (theme_t) {
|
||||
.textColor = MakeColor(0, 0, 0, 255),
|
||||
.attentionTextColor = MakeColor(255, 0, 0, 255),
|
||||
.frontWaveColor = MakeColor(100, 212, 250, 255),
|
||||
.middleWaveColor = MakeColor(100, 153, 255, 255),
|
||||
.backWaveColor = MakeColor(154, 171, 255, 255),
|
||||
.backgroundColor = MakeColor(233, 236, 241, 255),
|
||||
.highlightColor = MakeColor(91, 237, 224, 255),
|
||||
.highlightGradientEdgeColor = MakeColor(91,176,224,255),
|
||||
.separatorColor = MakeColor(219, 218, 219, 255),
|
||||
.borderColor = MakeColor(255,255,255,255),
|
||||
.borderTextColor = MakeColor(64,64,64,255),
|
||||
.progressBarColor = MakeColor(0,224,0,255),
|
||||
.enableWaveBlending = 0,
|
||||
.buttonAText = "\uE0E0",
|
||||
.buttonBText = "\uE0E1",
|
||||
.buttonXText = "\uE0E2",
|
||||
.buttonYText = "\uE0E3",
|
||||
.buttonPText = "\uE0EF",
|
||||
.buttonMText = "\uE0F0",
|
||||
.labelStarOnText = "\u2605",
|
||||
.labelStarOffText = "\u2606",
|
||||
};
|
||||
|
||||
theme_t themeDark = (theme_t) {
|
||||
.textColor = MakeColor(255, 255, 255, 255),
|
||||
.attentionTextColor = MakeColor(255, 0, 0, 255),
|
||||
.frontWaveColor = MakeColor(96, 204, 204, 255),
|
||||
.middleWaveColor = MakeColor(66, 154, 159, 255),
|
||||
.backWaveColor = MakeColor(73, 103, 169, 255),
|
||||
.backgroundColor = MakeColor(45, 45, 50, 255),
|
||||
.highlightColor = MakeColor(91, 237, 224, 255),
|
||||
.highlightGradientEdgeColor = MakeColor(91,176,224,255),
|
||||
.separatorColor = MakeColor(219, 218, 219, 255),
|
||||
.borderColor = MakeColor(255,255,255,255),
|
||||
.borderTextColor = MakeColor(64,64,64,255),
|
||||
.progressBarColor = MakeColor(0,224,0,255),
|
||||
.enableWaveBlending = 0,
|
||||
.buttonAText = "\uE0A0",
|
||||
.buttonBText = "\uE0A1",
|
||||
.buttonXText = "\uE0A2",
|
||||
.buttonYText = "\uE0A3",
|
||||
.buttonPText = "\uE0B3",
|
||||
.buttonMText = "\uE0B4",
|
||||
.labelStarOnText = "\u2605",
|
||||
.labelStarOffText = "\u2606",
|
||||
};
|
||||
|
||||
theme_t themeCommon = {
|
||||
.layoutObjects = {
|
||||
[ThemeLayoutId_Logo] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {40, 20},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_HbmenuVersion] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {184, 46 + 18},
|
||||
.font = interuiregular14,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_LoaderInfo] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {43, 46 + 18 + 20},
|
||||
.posEnd = {184},
|
||||
.font = interuiregular14,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_AttentionText] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {-32, 46 + 18},
|
||||
.font = interuimedium30,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_LogInfo] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {180 + 256, 46 + 16 + 18},
|
||||
.font = interuiregular14,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_InfoMsg] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {64, 128 + 18},
|
||||
.font = interuiregular14,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MenuPath] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {40, 720 - 47 + 24},
|
||||
.size = {380},
|
||||
.font = interuiregular18,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MenuTypeMsg] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1180, 30 + 26 + 32 + 20},
|
||||
.font = interuiregular18,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MsgBoxSeparator] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {0, -80},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MsgBoxBottomText] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {0, -29},
|
||||
},
|
||||
|
||||
// ThemeLayoutId_BackgroundImage is not set with the defaults.
|
||||
|
||||
[ThemeLayoutId_BackWave] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.size = {0, 295},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MiddleWave] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.size = {0, 290},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_FrontWave] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.size = {0, 280},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ButtonA] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1280 - 126 - 30 - 32, 720 - 47 + 24},
|
||||
.touchSize = {36, 25},
|
||||
.font = fontscale7,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ButtonAText] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1280 - 90 - 30 - 32, 720 - 47 + 24},
|
||||
.touchSize = {0, 25},
|
||||
.font = interuiregular18,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ButtonB] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {-36, 0},
|
||||
.posEnd = {0},
|
||||
.touchSize = {36, 25},
|
||||
.font = fontscale7,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ButtonBText] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {-90, 0},
|
||||
.touchSize = {0, 32},
|
||||
.font = interuiregular18,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ButtonY] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {-36, 0},
|
||||
.font = fontscale7,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ButtonYText] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {-32, 0},
|
||||
.font = interuiregular18,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ButtonM] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {-36, 0},
|
||||
.font = fontscale7,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ButtonMText] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {-32, 0},
|
||||
.font = interuiregular18,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ButtonX] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {-36, 0},
|
||||
.touchSize = {36, 25},
|
||||
.font = fontscale7,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ButtonXText] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {-40 + 8, 0},
|
||||
.touchSize = {0, 25},
|
||||
.font = interuiregular18,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_NetworkIcon] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {0, 0 + 47 + 10 + 3},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_BatteryCharge] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1180 - 10 - 24 - 8, 0 + 47 + 10 + 21 + 4},
|
||||
.font = interuiregular14,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_BatteryIcon] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1180 - 8 - 24 - 8, 0 + 47 + 10 + 6},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_ChargingIcon] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1180 - 20, 0 + 47 + 10 + 6},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_Status] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1180, 0 + 47 + 10},
|
||||
.font = interuimedium20,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_Temperature] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1180 + 4, 0 + 47 + 10 + + 21 + 6},
|
||||
.font = interuiregular14,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MenuList] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {29, 720 - 100 - 145},
|
||||
.posEnd = {140 + 30, 0},
|
||||
.size = {140, 140 + 32},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MenuListTiles] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posEnd = {7, 0},
|
||||
.size = {0, 0},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MenuListIcon] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {0, 32},
|
||||
.size = {140, 140},
|
||||
.imageSize = {256, 256},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MenuListName] = {
|
||||
.visible = true,
|
||||
.posType = true,
|
||||
.posStart = {4, 4 + 18},
|
||||
.size = {140 - 32, 0},
|
||||
.font = interuiregular14,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MenuActiveEntryIcon] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {117, 100+10},
|
||||
.size = {256, 256},
|
||||
.imageSize = {256, 256},
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MenuActiveEntryName] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1280 - 790, 135+10 + 39},
|
||||
.size = {790 - 120, 0},
|
||||
.font = interuimedium30,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MenuActiveEntryAuthor] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1280 - 790, 135+10 + 28 + 30 + 18},
|
||||
.font = interuiregular14,
|
||||
},
|
||||
|
||||
[ThemeLayoutId_MenuActiveEntryVersion] = {
|
||||
.visible = true,
|
||||
.posType = false,
|
||||
.posStart = {1280 - 790, 135+10 + 28 + 30 + 18 + 6 + 18},
|
||||
.font = interuiregular14,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
char themePath[PATH_MAX] = {0};
|
||||
GetThemePathFromConfig(themePath, PATH_MAX);
|
||||
|
||||
theme_t *themeDefault;
|
||||
config_t cfg = {0};
|
||||
config_init(&cfg);
|
||||
config_setting_t *theme = NULL, *layout = NULL, *assets = NULL;
|
||||
color_t text, logoColor={0}, attentionText, frontWave, middleWave, backWave, background, highlight, highlightGradientEdgeColor, separator, borderColor, borderTextColor, progressBarColor;
|
||||
int waveBlending;
|
||||
const char *AText, *BText, *XText, *YText, *PText, *MText, *starOnText, *starOffText;
|
||||
bool logoColor_set = false;
|
||||
bool good_cfg = false;
|
||||
#ifdef __SWITCH__
|
||||
bool is_romfs = false;
|
||||
#endif
|
||||
bool is_archive = false;
|
||||
const char* theme_archive_path = NULL;
|
||||
|
||||
assetsClearTheme();
|
||||
|
||||
if(themePath[0]!=0) {
|
||||
const char* cfg_path = themePath;
|
||||
const char* ext = getExtension(themePath);
|
||||
#ifdef __SWITCH__
|
||||
if (strcasecmp(ext, ".romfs")==0) {
|
||||
if (R_FAILED(romfsMountFromFsdev(themePath, 0, "theme")))
|
||||
cfg_path = NULL;
|
||||
else {
|
||||
is_romfs = true;
|
||||
cfg_path = "theme:/theme.cfg";
|
||||
theme_archive_path = "theme:/";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (strcasecmp(ext, ".romfs")!=0 && strcasecmp(ext, ".cfg")!=0) {
|
||||
theme_archive_path = themePath;
|
||||
}
|
||||
if (theme_archive_path) {
|
||||
if (!PHYSFS_mount(theme_archive_path, "theme", 0)) cfg_path = NULL;
|
||||
else {
|
||||
is_archive = true;
|
||||
cfg_path = "theme/theme.cfg";
|
||||
}
|
||||
}
|
||||
|
||||
if (cfg_path) {
|
||||
if (!is_archive) good_cfg = config_read_file(&cfg, cfg_path);
|
||||
else {
|
||||
u8 *cfg_buf = NULL;
|
||||
good_cfg = assetsPhysfsReadFile(cfg_path, &cfg_buf, NULL, true);
|
||||
if (good_cfg) good_cfg = config_read_string(&cfg, (char*)cfg_buf);
|
||||
free(cfg_buf);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (preset) {
|
||||
case THEME_PRESET_LIGHT:
|
||||
themeCurrent = (theme_t) {
|
||||
textColor: MakeColor(0, 0, 0, 255),
|
||||
frontWaveColor: MakeColor(100, 212, 250, 255),
|
||||
middleWaveColor: MakeColor(100, 153, 255, 255),
|
||||
backWaveColor: MakeColor(154, 171, 255, 255),
|
||||
backgroundColor: MakeColor(233, 236, 241, 255),
|
||||
highlightColor: MakeColor(91, 237, 224, 255),
|
||||
enableWaveBlending: 0,
|
||||
buttonAImage: button_a_light_bin,
|
||||
buttonBImage: button_b_light_bin,
|
||||
hbmenuLogoImage: hbmenu_logo_light_bin
|
||||
};
|
||||
default:
|
||||
themeDefault = &themeLight;
|
||||
if (good_cfg)
|
||||
theme = config_lookup(&cfg, "lightTheme");
|
||||
break;
|
||||
|
||||
case THEME_PRESET_DARK:
|
||||
themeCurrent = (theme_t) {
|
||||
textColor: MakeColor(255, 255, 255, 255),
|
||||
frontWaveColor: MakeColor(96, 204, 204, 255),
|
||||
middleWaveColor: MakeColor(66, 154, 159, 255),
|
||||
backWaveColor: MakeColor(73, 103, 169, 255),
|
||||
backgroundColor: MakeColor(45, 45, 50, 255),
|
||||
highlightColor: MakeColor(91, 237, 224, 255),
|
||||
enableWaveBlending: 0,
|
||||
buttonAImage: button_a_dark_bin,
|
||||
buttonBImage: button_b_dark_bin,
|
||||
hbmenuLogoImage: hbmenu_logo_dark_bin
|
||||
};
|
||||
themeDefault = &themeDark;
|
||||
if (good_cfg)
|
||||
theme = config_lookup(&cfg, "darkTheme");
|
||||
break;
|
||||
}
|
||||
|
||||
if (good_cfg) {
|
||||
if (theme != NULL) {
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "textColor"), &text))
|
||||
text = themeDefault->textColor;
|
||||
if (colorFromSetting(config_setting_lookup(theme, "logoColor"), &logoColor))
|
||||
logoColor_set = true;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "attentionTextColor"), &attentionText))
|
||||
attentionText = themeDefault->attentionTextColor;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "frontWaveColor"), &frontWave))
|
||||
frontWave = themeDefault->frontWaveColor;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "middleWaveColor"), &middleWave))
|
||||
middleWave = themeDefault->middleWaveColor;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "backWaveColor"), &backWave))
|
||||
backWave = themeDefault->backWaveColor;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "backgroundColor"), &background))
|
||||
background = themeDefault->backgroundColor;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "highlightColor"), &highlight))
|
||||
highlight = themeDefault->highlightColor;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "highlightGradientEdgeColor"), &highlightGradientEdgeColor))
|
||||
highlightGradientEdgeColor = themeDefault->highlightGradientEdgeColor;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "separatorColor"), &separator))
|
||||
separator = themeDefault->separatorColor;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "borderColor"), &borderColor))
|
||||
borderColor = themeDefault->borderColor;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "borderTextColor"), &borderTextColor))
|
||||
borderTextColor = themeDefault->borderTextColor;
|
||||
if (!colorFromSetting(config_setting_lookup(theme, "progressBarColor"), &progressBarColor))
|
||||
progressBarColor = themeDefault->progressBarColor;
|
||||
if (!config_setting_lookup_int(theme, "enableWaveBlending", &waveBlending))
|
||||
waveBlending = themeDefault->enableWaveBlending;
|
||||
if (!config_setting_lookup_string(theme, "buttonAText", &AText))
|
||||
AText = themeDefault->buttonAText;
|
||||
if (!config_setting_lookup_string(theme, "buttonBText", &BText))
|
||||
BText = themeDefault->buttonBText;
|
||||
if (!config_setting_lookup_string(theme, "buttonXText", &XText))
|
||||
XText = themeDefault->buttonXText;
|
||||
if (!config_setting_lookup_string(theme, "buttonYText", &YText))
|
||||
YText = themeDefault->buttonYText;
|
||||
if (!config_setting_lookup_string(theme, "buttonPText", &PText))
|
||||
PText = themeDefault->buttonPText;
|
||||
if (!config_setting_lookup_string(theme, "buttonMText", &MText))
|
||||
MText = themeDefault->buttonMText;
|
||||
if (!config_setting_lookup_string(theme, "labelStarOnText", &starOnText))
|
||||
starOnText = themeDefault->labelStarOnText;
|
||||
if (!config_setting_lookup_string(theme, "labelStarOffText", &starOffText))
|
||||
starOffText = themeDefault->labelStarOffText;
|
||||
themeCurrent = (theme_t) {
|
||||
.textColor = text,
|
||||
.logoColor = logoColor,
|
||||
.attentionTextColor = attentionText,
|
||||
.frontWaveColor = frontWave,
|
||||
.middleWaveColor = middleWave,
|
||||
.backWaveColor = backWave,
|
||||
.backgroundColor = background,
|
||||
.highlightColor = highlight,
|
||||
.highlightGradientEdgeColor = highlightGradientEdgeColor,
|
||||
.separatorColor = separator,
|
||||
.borderColor = borderColor,
|
||||
.borderTextColor = borderTextColor,
|
||||
.progressBarColor = progressBarColor,
|
||||
.logoColor_set = logoColor_set,
|
||||
.enableWaveBlending = waveBlending,
|
||||
};
|
||||
strncpy(themeCurrent.buttonAText, AText, sizeof(themeCurrent.buttonAText));
|
||||
themeCurrent.buttonAText[sizeof(themeCurrent.buttonAText)-1] = 0;
|
||||
strncpy(themeCurrent.buttonBText, BText, sizeof(themeCurrent.buttonBText));
|
||||
themeCurrent.buttonBText[sizeof(themeCurrent.buttonBText)-1] = 0;
|
||||
strncpy(themeCurrent.buttonXText, XText, sizeof(themeCurrent.buttonXText));
|
||||
themeCurrent.buttonXText[sizeof(themeCurrent.buttonXText)-1] = 0;
|
||||
strncpy(themeCurrent.buttonYText, YText, sizeof(themeCurrent.buttonYText));
|
||||
themeCurrent.buttonYText[sizeof(themeCurrent.buttonYText)-1] = 0;
|
||||
strncpy(themeCurrent.buttonPText, PText, sizeof(themeCurrent.buttonPText));
|
||||
themeCurrent.buttonPText[sizeof(themeCurrent.buttonPText)-1] = 0;
|
||||
strncpy(themeCurrent.buttonMText, MText, sizeof(themeCurrent.buttonMText));
|
||||
themeCurrent.buttonMText[sizeof(themeCurrent.buttonMText)-1] = 0;
|
||||
strncpy(themeCurrent.labelStarOnText, starOnText, sizeof(themeCurrent.labelStarOnText));
|
||||
themeCurrent.labelStarOnText[sizeof(themeCurrent.labelStarOnText)-1] = 0;
|
||||
strncpy(themeCurrent.labelStarOffText, starOffText, sizeof(themeCurrent.labelStarOffText));
|
||||
themeCurrent.labelStarOffText[sizeof(themeCurrent.labelStarOffText)-1] = 0;
|
||||
} else {
|
||||
themeCurrent = *themeDefault;
|
||||
}
|
||||
|
||||
memcpy(themeCurrent.layoutObjects, themeCommon.layoutObjects, sizeof(themeCommon.layoutObjects));
|
||||
|
||||
layout = config_lookup(&cfg, "layout");
|
||||
|
||||
if (layout != NULL) {
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "logo"), &themeCurrent.layoutObjects[ThemeLayoutId_Logo], true);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "hbmenuVersion"), &themeCurrent.layoutObjects[ThemeLayoutId_HbmenuVersion], true);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "loaderInfo"), &themeCurrent.layoutObjects[ThemeLayoutId_LoaderInfo], true);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "attentionText"), &themeCurrent.layoutObjects[ThemeLayoutId_AttentionText], true);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "logInfo"), &themeCurrent.layoutObjects[ThemeLayoutId_LogInfo], true);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "infoMsg"), &themeCurrent.layoutObjects[ThemeLayoutId_InfoMsg], true);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "menuPath"), &themeCurrent.layoutObjects[ThemeLayoutId_MenuPath], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "menuTypeMsg"), &themeCurrent.layoutObjects[ThemeLayoutId_MenuTypeMsg], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "msgBoxSeparator"), &themeCurrent.layoutObjects[ThemeLayoutId_MsgBoxSeparator], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "msgBoxBottomText"), &themeCurrent.layoutObjects[ThemeLayoutId_MsgBoxBottomText], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "backgroundImage"), &themeCurrent.layoutObjects[ThemeLayoutId_BackgroundImage], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "backWave"), &themeCurrent.layoutObjects[ThemeLayoutId_BackWave], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "middleWave"), &themeCurrent.layoutObjects[ThemeLayoutId_MiddleWave], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "frontWave"), &themeCurrent.layoutObjects[ThemeLayoutId_FrontWave], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "buttonA"), &themeCurrent.layoutObjects[ThemeLayoutId_ButtonA], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "buttonAText"), &themeCurrent.layoutObjects[ThemeLayoutId_ButtonAText], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "buttonB"), &themeCurrent.layoutObjects[ThemeLayoutId_ButtonB], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "buttonBText"), &themeCurrent.layoutObjects[ThemeLayoutId_ButtonBText], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "buttonY"), &themeCurrent.layoutObjects[ThemeLayoutId_ButtonY], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "buttonYText"), &themeCurrent.layoutObjects[ThemeLayoutId_ButtonYText], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "buttonM"), &themeCurrent.layoutObjects[ThemeLayoutId_ButtonM], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "buttonMText"), &themeCurrent.layoutObjects[ThemeLayoutId_ButtonMText], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "buttonX"), &themeCurrent.layoutObjects[ThemeLayoutId_ButtonX], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "buttonXText"), &themeCurrent.layoutObjects[ThemeLayoutId_ButtonXText], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "networkIcon"), &themeCurrent.layoutObjects[ThemeLayoutId_NetworkIcon], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "batteryCharge"), &themeCurrent.layoutObjects[ThemeLayoutId_BatteryCharge], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "batteryIcon"), &themeCurrent.layoutObjects[ThemeLayoutId_BatteryIcon], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "chargingIcon"), &themeCurrent.layoutObjects[ThemeLayoutId_ChargingIcon], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "status"), &themeCurrent.layoutObjects[ThemeLayoutId_Status], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "temperature"), &themeCurrent.layoutObjects[ThemeLayoutId_Temperature], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "menuList"), &themeCurrent.layoutObjects[ThemeLayoutId_MenuList], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "menuListTiles"), &themeCurrent.layoutObjects[ThemeLayoutId_MenuListTiles], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "menuListIcon"), &themeCurrent.layoutObjects[ThemeLayoutId_MenuListIcon], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "menuListName"), &themeCurrent.layoutObjects[ThemeLayoutId_MenuListName], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "menuActiveEntryIcon"), &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryIcon], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "menuActiveEntryName"), &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryName], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "menuActiveEntryAuthor"), &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryAuthor], false);
|
||||
layoutObjectFromSetting(config_setting_lookup(layout, "menuActiveEntryVersion"), &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryVersion], false);
|
||||
}
|
||||
|
||||
if (is_archive) assets = config_lookup(&cfg, "assets");
|
||||
if (is_archive && assets) {
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "battery_icon"), AssetId_battery_icon, NULL);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "charging_icon"), AssetId_charging_icon, NULL);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "folder_icon"), AssetId_folder_icon, &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryIcon]);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "invalid_icon"), AssetId_invalid_icon, &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryIcon]);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "theme_icon_dark"), AssetId_theme_icon_dark, &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryIcon]);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "theme_icon_light"), AssetId_theme_icon_light, &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryIcon]);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "airplane_icon"), AssetId_airplane_icon, NULL);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "wifi_none_icon"), AssetId_wifi_none_icon, NULL);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "wifi1_icon"), AssetId_wifi1_icon, NULL);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "wifi2_icon"), AssetId_wifi2_icon, NULL);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "wifi3_icon"), AssetId_wifi3_icon, NULL);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "eth_icon"), AssetId_eth_icon, NULL);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "eth_none_icon"), AssetId_eth_none_icon, NULL);
|
||||
assetObjectFromSetting(config_setting_lookup(assets, "background_image"), AssetId_background_image, NULL);
|
||||
}
|
||||
} else {
|
||||
themeCurrent = *themeDefault;
|
||||
memcpy(themeCurrent.layoutObjects, themeCommon.layoutObjects, sizeof(themeCommon.layoutObjects));
|
||||
}
|
||||
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuListTiles];
|
||||
if (layoutobj->posEnd[0] < 1) layoutobj->posEnd[0] = 1;
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuListIcon];
|
||||
if (layoutobj->size[0] <= 0 || layoutobj->size[1] <= 0 || layoutobj->size[0] > layoutobj->imageSize[0] || layoutobj->size[1] > layoutobj->imageSize[1]) {
|
||||
layoutobj->size[0] = layoutobj->imageSize[0];
|
||||
layoutobj->size[1] = layoutobj->imageSize[1];
|
||||
}
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuActiveEntryIcon];
|
||||
if (layoutobj->size[0] <= 0 || layoutobj->size[1] <= 0 || layoutobj->size[0] > layoutobj->imageSize[0] || layoutobj->size[1] > layoutobj->imageSize[1]) {
|
||||
layoutobj->size[0] = layoutobj->imageSize[0];
|
||||
layoutobj->size[1] = layoutobj->imageSize[1];
|
||||
}
|
||||
|
||||
config_destroy(&cfg);
|
||||
|
||||
if (is_archive) PHYSFS_unmount(theme_archive_path);
|
||||
#ifdef __SWITCH__
|
||||
if (is_romfs) romfsUnmount("theme");
|
||||
#endif
|
||||
}
|
||||
|
||||
void GetThemePathFromConfig(char* themePath, size_t size) {
|
||||
const char* tmpThemePath = "";
|
||||
config_t cfg = {0};
|
||||
config_setting_t *settings = NULL;
|
||||
char tmp_path[PATH_MAX+1] = {0};
|
||||
char tmp_path_theme[PATH_MAX+1] = {0};
|
||||
|
||||
snprintf(tmp_path, sizeof(tmp_path)-1, "%s/config/nx-hbmenu/settings.cfg", menuGetRootBasePath());
|
||||
snprintf(tmp_path_theme, sizeof(tmp_path_theme)-1, "%s/config/nx-hbmenu/themes/", menuGetRootBasePath());
|
||||
bool good_cfg = config_read_file(&cfg, tmp_path);
|
||||
|
||||
if(good_cfg) {
|
||||
settings = config_lookup(&cfg, "settings");
|
||||
if(settings != NULL) {
|
||||
if(config_setting_lookup_string(settings, "themePath", &tmpThemePath))
|
||||
snprintf(themePath, size-1, "%s%s", tmp_path_theme, tmpThemePath);
|
||||
}
|
||||
}
|
||||
|
||||
config_destroy(&cfg);
|
||||
}
|
||||
|
||||
void SetThemePathToConfig(const char* themePath) {
|
||||
config_t cfg = {0};
|
||||
config_init(&cfg);
|
||||
|
||||
char settingPath[PATH_MAX] = {0};
|
||||
config_setting_t *root = NULL,
|
||||
*group = NULL,
|
||||
*settings = NULL;
|
||||
|
||||
themePath = getSlash(themePath);
|
||||
if(themePath[0] == '/') themePath++;
|
||||
|
||||
#ifdef __SWITCH__
|
||||
settingPath[0] = '/';
|
||||
#endif
|
||||
|
||||
snprintf(settingPath, sizeof(settingPath)-1, "%s/config/nx-hbmenu/settings.cfg", menuGetRootBasePath());
|
||||
bool good_cfg = config_read_file(&cfg, settingPath);
|
||||
|
||||
if(good_cfg) {
|
||||
group = config_lookup(&cfg, "settings");
|
||||
if(group != NULL)
|
||||
settings = config_setting_lookup(group, "themePath");
|
||||
if(settings != NULL)
|
||||
config_setting_set_string(settings, themePath);
|
||||
} else {
|
||||
root = config_root_setting(&cfg);
|
||||
if(root != NULL)
|
||||
group = config_setting_add(root, "settings", CONFIG_TYPE_GROUP);
|
||||
if(group != NULL)
|
||||
settings = config_setting_add(group, "themePath", CONFIG_TYPE_STRING);
|
||||
if(settings != NULL)
|
||||
config_setting_set_string(settings, themePath);
|
||||
}
|
||||
|
||||
if(!config_write_file(&cfg, settingPath)) {
|
||||
menuCreateMsgBox(780, 300, textGetString(StrId_ThemeNotApplied));
|
||||
}
|
||||
|
||||
config_destroy(&cfg);
|
||||
}
|
@ -1,20 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
color_t textColor;
|
||||
color_t frontWaveColor;
|
||||
color_t middleWaveColor;
|
||||
color_t backWaveColor;
|
||||
color_t backgroundColor;
|
||||
color_t highlightColor;
|
||||
bool enableWaveBlending;
|
||||
const uint8_t *buttonAImage;
|
||||
const uint8_t *buttonBImage;
|
||||
const uint8_t *hbmenuLogoImage;
|
||||
} theme_t;
|
||||
#include <libconfig.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
@ -22,6 +9,54 @@ typedef enum
|
||||
THEME_PRESET_DARK,
|
||||
} ThemePreset;
|
||||
|
||||
void themeStartup(ThemePreset preset);
|
||||
typedef struct
|
||||
{
|
||||
bool visible;
|
||||
bool posType; // false = absolute, true = relative
|
||||
int posStart[2];
|
||||
int posEnd[2];
|
||||
int size[2]; // width/height
|
||||
int imageSize[2]; // width/height for the actual image data
|
||||
int touchSize[2];
|
||||
int posFinal[2];
|
||||
uint32_t textSize[2];
|
||||
u32 font;
|
||||
} ThemeLayoutObject;
|
||||
|
||||
theme_t themeCurrent;
|
||||
typedef struct
|
||||
{
|
||||
color_t textColor;
|
||||
color_t logoColor;
|
||||
color_t attentionTextColor;
|
||||
color_t frontWaveColor;
|
||||
color_t middleWaveColor;
|
||||
color_t backWaveColor;
|
||||
color_t backgroundColor;
|
||||
color_t highlightColor;
|
||||
color_t highlightGradientEdgeColor;
|
||||
color_t separatorColor;
|
||||
color_t borderColor;
|
||||
color_t borderTextColor;
|
||||
color_t progressBarColor;
|
||||
bool logoColor_set;
|
||||
bool enableWaveBlending;
|
||||
char buttonAText[32];
|
||||
char buttonBText[32];
|
||||
char buttonXText[32];
|
||||
char buttonYText[32];
|
||||
char buttonPText[32];
|
||||
char buttonMText[32];
|
||||
char labelStarOnText[32];
|
||||
char labelStarOffText[32];
|
||||
|
||||
ThemeLayoutObject layoutObjects[ThemeLayoutId_Total];
|
||||
} theme_t;
|
||||
|
||||
bool colorFromSetting(config_setting_t *rgba, color_t *col);
|
||||
void themeStartup(ThemePreset preset);
|
||||
void GetThemePathFromConfig(char* themePath, size_t size);
|
||||
void SetThemePathToConfig(const char* themePath);
|
||||
|
||||
extern theme_t themeCurrent;
|
||||
|
||||
extern ThemePreset themeGlobalPreset;
|
||||
|
7
common/thermalstatus.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
bool thermalstatusInit(void);
|
||||
void thermalstatusExit(void);
|
||||
bool thermalstatusGetDetails(s32 *temperature);
|
||||
|
82
common/worker.c
Normal file
@ -0,0 +1,82 @@
|
||||
#include "worker.h"
|
||||
|
||||
static bool s_workerInitialized = 0;
|
||||
static thrd_t s_workerThread;
|
||||
static cnd_t s_workerCdn;
|
||||
static mtx_t s_workerMtx;
|
||||
|
||||
static volatile struct
|
||||
{
|
||||
workerThreadFunc func;
|
||||
void* data;
|
||||
|
||||
bool exit;
|
||||
} s_workerParam;
|
||||
|
||||
static int workerThreadProc(void* unused)
|
||||
{
|
||||
mtx_lock(&s_workerMtx);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
cnd_wait(&s_workerCdn, &s_workerMtx);
|
||||
|
||||
if (s_workerParam.exit)
|
||||
break;
|
||||
|
||||
s_workerParam.func(s_workerParam.data);
|
||||
}
|
||||
|
||||
mtx_unlock(&s_workerMtx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool workerInit(void)
|
||||
{
|
||||
if (s_workerInitialized) return 1;
|
||||
|
||||
if (cnd_init(&s_workerCdn) != thrd_success) return 0;
|
||||
if (mtx_init(&s_workerMtx, mtx_plain) != thrd_success) {
|
||||
cnd_destroy(&s_workerCdn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (thrd_create(&s_workerThread, workerThreadProc, 0) != thrd_success) {
|
||||
mtx_destroy(&s_workerMtx);
|
||||
cnd_destroy(&s_workerCdn);
|
||||
return 0;
|
||||
}
|
||||
|
||||
s_workerInitialized = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void workerExit(void)
|
||||
{
|
||||
int res=0;
|
||||
|
||||
if (!s_workerInitialized) return;
|
||||
s_workerInitialized = 0;
|
||||
|
||||
mtx_lock(&s_workerMtx);
|
||||
s_workerParam.exit = true;
|
||||
cnd_signal(&s_workerCdn);
|
||||
mtx_unlock(&s_workerMtx);
|
||||
|
||||
thrd_join(s_workerThread, &res);
|
||||
mtx_destroy(&s_workerMtx);
|
||||
cnd_destroy(&s_workerCdn);
|
||||
}
|
||||
|
||||
void workerSchedule(workerThreadFunc func, void* data)
|
||||
{
|
||||
if (!s_workerInitialized) return;
|
||||
|
||||
mtx_lock(&s_workerMtx);
|
||||
s_workerParam.func = func;
|
||||
s_workerParam.data = data;
|
||||
cnd_signal(&s_workerCdn);
|
||||
mtx_unlock(&s_workerMtx);
|
||||
}
|
||||
|
6
common/worker.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
|
||||
bool workerInit(void);
|
||||
void workerExit(void);
|
||||
void workerSchedule(workerThreadFunc func, void* data);
|
@ -1,8 +1,10 @@
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "../common/common.h"
|
||||
|
||||
static char argBuf[ENTRY_ARGBUFSIZE];
|
||||
|
||||
static void init_args(char *dst, size_t dst_maxsize, u32 *in_args, size_t size)
|
||||
static char *init_args(char *dst, size_t dst_maxsize, u32 *in_args, size_t size)
|
||||
{
|
||||
size_t tmplen;
|
||||
u32 argi;
|
||||
@ -36,6 +38,7 @@ static void init_args(char *dst, size_t dst_maxsize, u32 *in_args, size_t size)
|
||||
dst_maxsize--;
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
static bool init(void)
|
||||
@ -50,13 +53,42 @@ static void deinit(void)
|
||||
|
||||
static void launchFile(const char* path, argData_s* args)
|
||||
{
|
||||
char msg[256];
|
||||
/*if (strncmp(path, "sdmc:/",6) == 0)
|
||||
path += 5;*/
|
||||
memset(argBuf, 0, sizeof(argBuf));
|
||||
|
||||
uint32_t remote = args->nxlink_host.s_addr;
|
||||
|
||||
if (remote) {
|
||||
char nxlinked[17];
|
||||
sprintf(nxlinked,"%08" PRIx32 "_NXLINK_",remote);
|
||||
launchAddArg(args, nxlinked);
|
||||
}
|
||||
|
||||
init_args(argBuf, sizeof(argBuf)-1, args->buf, sizeof(args->buf));
|
||||
|
||||
struct stat st;
|
||||
|
||||
if (stat(path, &st) == -1) {
|
||||
memset(msg, 0, sizeof(msg));
|
||||
snprintf(msg, sizeof(msg)-1, textGetString(StrId_NroNotFound), path);
|
||||
|
||||
menuCreateMsgBox(780, 300, msg);
|
||||
menuScan(".");
|
||||
}
|
||||
else {
|
||||
Result rc = envSetNextLoad(path, argBuf);
|
||||
if(R_FAILED(rc)) fatalSimple(rc);//TODO: How should failing be handled?
|
||||
if(R_FAILED(rc)) {
|
||||
memset(msg, 0, sizeof(msg));
|
||||
snprintf(msg, sizeof(msg)-1, "%s\n2%03d-%04d", textGetString(StrId_AppLaunchError), R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
|
||||
menuCreateMsgBox(780, 300, msg);
|
||||
}
|
||||
else {
|
||||
uiExitLoop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const loaderFuncs_s loader_builtin =
|
||||
|
262
nx_main/main.c
@ -1,118 +1,256 @@
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <physfs.h>
|
||||
|
||||
#include "../common/common.h"
|
||||
#include "nx_graphics.h"
|
||||
#include "nx_touch.h"
|
||||
|
||||
// Define the desired framebuffer resolution (here we set it to 720p).
|
||||
#define FB_WIDTH 1280
|
||||
#define FB_HEIGHT 720
|
||||
|
||||
uint8_t* g_framebuf;
|
||||
u32 g_framebuf_width;
|
||||
|
||||
PadState g_pad;
|
||||
PadRepeater g_pad_repeater;
|
||||
|
||||
bool menuUpdateErrorScreen(void);
|
||||
|
||||
#ifdef PERF_LOG
|
||||
u64 g_tickdiff_vsync=0;
|
||||
u64 g_tickdiff_frame=0;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_AUDIO
|
||||
void audio_initialize(void);
|
||||
void audio_exit(void);
|
||||
#endif
|
||||
|
||||
extern u32 __nx_applet_exit_mode;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
bool error_screen=0;
|
||||
Result lastret=0;
|
||||
Result rc=0;
|
||||
char msg[256];
|
||||
char errormsg[256];//Can't use StrId for these error messages since it would be unavailable if textInit fails.
|
||||
|
||||
#ifdef PERF_LOG
|
||||
u64 start_tick=0;
|
||||
#endif
|
||||
|
||||
gfxInitDefault();
|
||||
padConfigureInput(8, HidNpadStyleSet_NpadStandard);
|
||||
padInitializeAny(&g_pad);
|
||||
padRepeaterInitialize(&g_pad_repeater, 20, 10);
|
||||
hidSetNpadHandheldActivationMode(HidNpadHandheldActivationMode_Single);
|
||||
touchInit();
|
||||
|
||||
appletSetScreenShotPermission(1);
|
||||
memset(errormsg, 0, sizeof(errormsg));
|
||||
|
||||
ColorSetId theme;
|
||||
setsysInitialize();
|
||||
appletLockExit();
|
||||
appletSetScreenShotPermission(AppletScreenShotPermission_Enable);
|
||||
|
||||
ColorSetId theme = ColorSetId_Light;
|
||||
rc = setsysInitialize();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
setsysGetColorSetId(&theme);
|
||||
themeStartup((ThemePreset)theme);
|
||||
textInit();
|
||||
menuStartup();
|
||||
setsysExit();
|
||||
}
|
||||
|
||||
launchInit();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = textInit();
|
||||
if (R_FAILED(rc)) {
|
||||
snprintf(errormsg, sizeof(errormsg)-1, "Error: textInit() failed: 2%03d-%04d", R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef PERF_LOG
|
||||
gfxWaitForVsync();
|
||||
if (R_SUCCEEDED(rc)) menuStartupPath();
|
||||
|
||||
start_tick = svcGetSystemTick();
|
||||
gfxWaitForVsync();
|
||||
g_tickdiff_vsync = svcGetSystemTick() - start_tick;
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (!PHYSFS_init(argv[0])) {
|
||||
rc = 1;
|
||||
snprintf(errormsg, sizeof(errormsg)-1, "Error: PHYSFS_init() failed: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
|
||||
}
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = assetsInit();
|
||||
if (R_FAILED(rc)) {
|
||||
snprintf(errormsg, sizeof(errormsg)-1, "Error: assetsInit() failed: 2%03d-%04d", R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
}
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) themeStartup((ThemePreset)theme);
|
||||
|
||||
if (R_SUCCEEDED(rc)) powerInit();
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = netloaderInit();
|
||||
if (R_FAILED(rc)) {
|
||||
snprintf(errormsg, sizeof(errormsg)-1, "Error: netloaderInit() failed: 2%03d-%04d", R_MODULE(rc), R_DESCRIPTION(rc));
|
||||
}
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc) && !workerInit()) {
|
||||
rc = 1;
|
||||
snprintf(errormsg, sizeof(errormsg)-1, "Error: workerInit() failed.");
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc) && !statusInit()) {
|
||||
rc = 1;
|
||||
snprintf(errormsg, sizeof(errormsg)-1, "Error: statusInit() failed.");
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) menuStartup();
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
if (!launchInit()) {
|
||||
rc = 2;
|
||||
snprintf(errormsg, sizeof(errormsg)-1, "Error: launchInit() failed.");
|
||||
}
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc) && !fontInitialize()) {
|
||||
rc = 3;
|
||||
snprintf(errormsg, sizeof(errormsg)-1, "Error: fontInitialize() failed.");
|
||||
}
|
||||
|
||||
#ifdef ENABLE_AUDIO
|
||||
if (R_SUCCEEDED(rc)) audio_initialize();
|
||||
#endif
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
lastret = envGetLastLoadResult();
|
||||
|
||||
if (R_FAILED(lastret)) {
|
||||
memset(msg, 0, sizeof(msg));
|
||||
snprintf(msg, sizeof(msg)-1, "%s\n2%03d-%04d", textGetString(StrId_LastLoadResult), R_MODULE(lastret), R_DESCRIPTION(lastret));
|
||||
|
||||
menuCreateMsgBox(780, 300, msg);
|
||||
}
|
||||
}
|
||||
|
||||
if (errormsg[0]) error_screen = 1;
|
||||
|
||||
if (!error_screen) {
|
||||
graphicsInit(FB_WIDTH, FB_HEIGHT);
|
||||
}
|
||||
else {
|
||||
consoleInit(NULL);
|
||||
printf("%s\n", errormsg);
|
||||
printf("Press the + button to exit.\n");
|
||||
}
|
||||
|
||||
while (appletMainLoop())
|
||||
{
|
||||
#ifdef PERF_LOG
|
||||
start_tick = svcGetSystemTick();
|
||||
#endif
|
||||
// Scan the gamepad. This should be done once for each frame
|
||||
padUpdate(&g_pad);
|
||||
padRepeaterUpdate(&g_pad_repeater, padGetButtons(&g_pad) & (
|
||||
HidNpadButton_AnyLeft | HidNpadButton_AnyUp | HidNpadButton_AnyRight | HidNpadButton_AnyDown
|
||||
));
|
||||
|
||||
//Scan all the inputs. This should be done once for each frame
|
||||
hidScanInput();
|
||||
|
||||
g_framebuf = gfxGetFramebuffer(&g_framebuf_width, NULL);
|
||||
memset(g_framebuf, 237, gfxGetFramebufferSize());
|
||||
if (!error_screen) {
|
||||
if (!uiUpdate()) break;
|
||||
menuLoop();
|
||||
|
||||
gfxFlushBuffers();
|
||||
|
||||
g_framebuf = graphicsFrameBegin(&g_framebuf_width);
|
||||
#ifdef PERF_LOG
|
||||
g_tickdiff_frame = svcGetSystemTick() - start_tick;
|
||||
start_tick = armGetSystemTick();
|
||||
#endif
|
||||
|
||||
gfxSwapBuffers();
|
||||
gfxWaitForVsync();
|
||||
memset(g_framebuf, 237, g_framebuf_width * FB_HEIGHT);
|
||||
menuLoop();
|
||||
}
|
||||
else {
|
||||
if (menuUpdateErrorScreen()) break;
|
||||
}
|
||||
|
||||
launchExit();
|
||||
setsysExit();
|
||||
if (!error_screen) {
|
||||
graphicsFrameEnd();
|
||||
|
||||
#ifdef PERF_LOG
|
||||
g_tickdiff_frame = armGetSystemTick() - start_tick;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
if (!error_screen) {
|
||||
graphicsExit();
|
||||
}
|
||||
else {
|
||||
consoleExit(NULL);
|
||||
__nx_applet_exit_mode = 1;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_AUDIO
|
||||
audio_exit();
|
||||
#endif
|
||||
|
||||
fontExit();
|
||||
launchExit();
|
||||
netloaderSignalExit();
|
||||
statusExit();
|
||||
workerExit();
|
||||
netloaderExit();
|
||||
powerExit();
|
||||
assetsExit();
|
||||
PHYSFS_deinit();
|
||||
|
||||
appletUnlockExit();
|
||||
|
||||
gfxExit();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void launchMenuEntryTask(menuEntry_s* arg);
|
||||
u64 menuGetKeysDown(void) {
|
||||
u64 keys = padGetButtonsDown(&g_pad);
|
||||
keys |= padRepeaterGetButtons(&g_pad_repeater);
|
||||
return keys;
|
||||
}
|
||||
|
||||
//This is implemented here due to the hid code.
|
||||
bool menuUpdate(void) {
|
||||
bool exitflag = 0;
|
||||
menu_s* menu = menuGetCurrent();
|
||||
u32 down = hidKeysDown(CONTROLLER_P1_AUTO);
|
||||
u64 down = menuGetKeysDown();
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuListTiles];
|
||||
int entries_count = layoutobj->posEnd[0];
|
||||
|
||||
if (down & KEY_A)
|
||||
handleTouch(menu);
|
||||
|
||||
if (down & HidNpadButton_Y)
|
||||
{
|
||||
if (menu->nEntries > 0)
|
||||
launchMenuNetloaderTask();
|
||||
}
|
||||
else if (down & HidNpadButton_X)
|
||||
{
|
||||
int i;
|
||||
menuEntry_s* me;
|
||||
for (i = 0, me = menu->firstEntry; i != menu->curEntry; i ++, me = me->next);
|
||||
launchMenuEntryTask(me);
|
||||
//workerSchedule(launchMenuEntryTask, me);
|
||||
menuHandleXButton();
|
||||
}
|
||||
}
|
||||
else if (down & KEY_B)
|
||||
else if (down & HidNpadButton_A)
|
||||
{
|
||||
if (strcmp( menu->dirname, "sdmc:/") != 0)
|
||||
menuHandleAButton();
|
||||
}
|
||||
else if (down & HidNpadButton_B)
|
||||
{
|
||||
//workerSchedule(changeDirTask, "..");
|
||||
menuScan("..");
|
||||
launchMenuBackTask();
|
||||
}
|
||||
else if(down & HidNpadButton_Minus){
|
||||
themeMenuStartup();
|
||||
}
|
||||
else if (down & KEY_PLUS)
|
||||
else if (down & HidNpadButton_Plus)
|
||||
{
|
||||
exitflag = 1;
|
||||
}
|
||||
/*else if (down & KEY_Y)
|
||||
{
|
||||
workerSchedule(netloaderTask, NULL);
|
||||
}*/
|
||||
else if (menu->nEntries > 0)
|
||||
{
|
||||
int move = 0;
|
||||
|
||||
if (down & KEY_LEFT) move--;
|
||||
if (down & KEY_RIGHT) move++;
|
||||
if (down & KEY_DOWN) move-=7;
|
||||
if (down & KEY_UP) move+=7;
|
||||
if (down & HidNpadButton_AnyLeft) move--;
|
||||
if (down & HidNpadButton_AnyRight) move++;
|
||||
if (down & HidNpadButton_AnyDown) move-=entries_count;
|
||||
if (down & HidNpadButton_AnyUp) move+=entries_count;
|
||||
|
||||
int newEntry = menu->curEntry + move;
|
||||
if (newEntry < 0) newEntry = 0;
|
||||
@ -122,3 +260,15 @@ bool menuUpdate(void) {
|
||||
|
||||
return exitflag;
|
||||
}
|
||||
|
||||
bool menuUpdateErrorScreen(void) {
|
||||
bool exitflag = 0;
|
||||
u64 down = menuGetKeysDown();
|
||||
|
||||
if (down & HidNpadButton_Plus)
|
||||
{
|
||||
exitflag = 1;
|
||||
}
|
||||
|
||||
return exitflag;
|
||||
}
|
||||
|
166
nx_main/nx_audio.c
Normal file
@ -0,0 +1,166 @@
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "../common/common.h"
|
||||
|
||||
#ifdef ENABLE_AUDIO
|
||||
#error "Audio is not supported currently."
|
||||
|
||||
#define SAMPLERATE 48000
|
||||
#define BYTESPERSAMPLE 2
|
||||
#define CHANNELCOUNT 2
|
||||
|
||||
static u8* raw_data, *raw_data2;
|
||||
static u8 *audio_data;
|
||||
static size_t audio_data_size;
|
||||
static size_t audio_data_loopoffset;
|
||||
static AudioOutBuffer source_buffer[2];
|
||||
|
||||
static Thread audio_thread;
|
||||
static bool audio_thread_exitflag = 0;
|
||||
static bool audio_thread_started = 0;
|
||||
|
||||
static void audio_playback_thread(void* arg)
|
||||
{
|
||||
bool playing = 0;
|
||||
bool data_ready=0;
|
||||
int j, count;
|
||||
int bufi=0;
|
||||
|
||||
u64 offset=0;
|
||||
u64 tmpsize=0;
|
||||
u64 totalsize = audio_data_size;
|
||||
|
||||
AudioOutBuffer *released_buffer = NULL;
|
||||
AudioOutBuffer *src_buf = NULL;
|
||||
u32 released_count=0;
|
||||
|
||||
while (!audio_thread_exitflag)
|
||||
{
|
||||
if (!playing)
|
||||
{
|
||||
count = 2;
|
||||
if (data_ready && released_count<2) count = 1;
|
||||
|
||||
for (j=0; j<count; j++)
|
||||
{
|
||||
tmpsize = source_buffer[0].buffer_size;
|
||||
if (tmpsize > totalsize - offset) tmpsize = totalsize - offset;
|
||||
|
||||
if (!data_ready || released_count==2) {
|
||||
src_buf = &source_buffer[bufi];
|
||||
}
|
||||
else {
|
||||
src_buf = released_buffer;
|
||||
}
|
||||
|
||||
src_buf->data_size = tmpsize;
|
||||
|
||||
memcpy(src_buf->buffer, &audio_data[offset], tmpsize);
|
||||
|
||||
offset+= tmpsize;
|
||||
if (offset >= totalsize) offset = audio_data_loopoffset;
|
||||
|
||||
audoutAppendAudioOutBuffer(src_buf);
|
||||
|
||||
bufi = 1-bufi;
|
||||
}
|
||||
|
||||
if (!data_ready) data_ready = 1;
|
||||
|
||||
playing = 1;
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(audoutWaitPlayFinish(&released_buffer, &released_count, UINT64_MAX)))
|
||||
playing = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void audio_initialize(void)
|
||||
{
|
||||
Result rc=0;
|
||||
|
||||
u8 *audio_intro, *audio_loop;
|
||||
size_t audio_intro_size, audio_loop_size;
|
||||
|
||||
audio_intro = (u8*)audio_intro_bin;
|
||||
audio_intro_size = audio_intro_bin_size;
|
||||
|
||||
audio_loop = (u8*)audio_loop_bin;
|
||||
audio_loop_size = audio_loop_bin_size;
|
||||
|
||||
audio_data_loopoffset = audio_intro_size;
|
||||
audio_data_size = audio_intro_size + audio_loop_size;
|
||||
|
||||
u32 SAMPLESPERBUF = SAMPLERATE/4;
|
||||
|
||||
u32 raw_data_size = (SAMPLESPERBUF * CHANNELCOUNT * BYTESPERSAMPLE);
|
||||
u32 raw_data_size_aligned = (raw_data_size + 0xfff) & ~0xfff;
|
||||
|
||||
audio_data = (u8*)malloc(audio_data_size);
|
||||
|
||||
raw_data = (u8*)memalign(0x1000, raw_data_size_aligned);
|
||||
raw_data2 = (u8*)memalign(0x1000, raw_data_size_aligned);
|
||||
|
||||
if (audio_data==NULL || raw_data == NULL || raw_data2==NULL) {
|
||||
free(audio_data);//free() checks NULL.
|
||||
free(raw_data);
|
||||
free(raw_data2);
|
||||
|
||||
audio_data = NULL;
|
||||
raw_data = NULL;
|
||||
raw_data2 = NULL;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
memset(audio_data, 0, audio_data_size);
|
||||
|
||||
memset(raw_data, 0, raw_data_size_aligned);
|
||||
memset(raw_data2, 0, raw_data_size_aligned);
|
||||
|
||||
memcpy(audio_data, audio_intro, audio_intro_size);
|
||||
memcpy(&audio_data[audio_data_loopoffset], audio_loop, audio_loop_size);
|
||||
|
||||
source_buffer[0].next = 0;
|
||||
source_buffer[0].buffer = raw_data;
|
||||
source_buffer[0].buffer_size = raw_data_size;
|
||||
source_buffer[0].data_size = raw_data_size;
|
||||
source_buffer[0].data_offset = 0;
|
||||
|
||||
memcpy(&source_buffer[1], &source_buffer[0], sizeof(AudioOutBuffer));
|
||||
source_buffer[1].buffer = raw_data2;
|
||||
|
||||
if (R_SUCCEEDED(rc)) rc = audoutInitialize();
|
||||
if (R_SUCCEEDED(rc)) rc = audoutStartAudioOut();
|
||||
|
||||
audio_thread_started = 0;
|
||||
|
||||
if (R_SUCCEEDED(rc)) rc = threadCreate(&audio_thread, audio_playback_thread, 0, 0x4000, 28, -2);
|
||||
|
||||
if (R_SUCCEEDED(rc)) rc = threadStart(&audio_thread);
|
||||
|
||||
if (R_SUCCEEDED(rc)) audio_thread_started = 1;
|
||||
}
|
||||
|
||||
void audio_exit(void)
|
||||
{
|
||||
if (audio_thread_started) {
|
||||
audio_thread_exitflag = 1;
|
||||
threadWaitForExit(&audio_thread);
|
||||
threadClose(&audio_thread);
|
||||
}
|
||||
|
||||
audoutStopAudioOut();
|
||||
audoutExit();
|
||||
|
||||
free(audio_data);
|
||||
free(raw_data);
|
||||
free(raw_data2);
|
||||
audio_data = NULL;
|
||||
raw_data = NULL;
|
||||
raw_data2 = NULL;
|
||||
}
|
||||
#endif
|
||||
|
141
nx_main/nx_graphics.c
Normal file
@ -0,0 +1,141 @@
|
||||
#include <switch.h>
|
||||
#include <deko3d.h>
|
||||
|
||||
#include "nx_graphics.h"
|
||||
|
||||
#define FB_NUM 2
|
||||
#define CMDMEMSIZE 0x1000
|
||||
|
||||
static u32 s_fbWidth, s_fbHeight;
|
||||
|
||||
static DkDevice s_device;
|
||||
static DkMemBlock s_fbMemBlock, s_workMemBlock, s_cmdMemBlock;
|
||||
static DkSwapchain s_swapchain;
|
||||
static DkCmdBuf s_cmdBuf;
|
||||
static DkCmdList s_cmdLists[FB_NUM];
|
||||
static DkFence s_fence;
|
||||
static DkQueue s_queue;
|
||||
|
||||
void graphicsInit(u32 width, u32 height)
|
||||
{
|
||||
DkImageLayoutMaker imgLayoutMaker;
|
||||
DkMemBlockMaker memBlockMaker;
|
||||
|
||||
// Create the device, which is the root object
|
||||
DkDeviceMaker deviceMaker;
|
||||
dkDeviceMakerDefaults(&deviceMaker);
|
||||
s_device = dkDeviceCreate(&deviceMaker);
|
||||
|
||||
// Calculate layout for the framebuffers
|
||||
DkImageLayout fbLayout;
|
||||
dkImageLayoutMakerDefaults(&imgLayoutMaker, s_device);
|
||||
imgLayoutMaker.flags = DkImageFlags_UsagePresent;
|
||||
imgLayoutMaker.format = DkImageFormat_RGBA8_Unorm;
|
||||
imgLayoutMaker.dimensions[0] = s_fbWidth = width;
|
||||
imgLayoutMaker.dimensions[1] = s_fbHeight = height;
|
||||
dkImageLayoutInitialize(&fbLayout, &imgLayoutMaker);
|
||||
|
||||
// Retrieve necessary size and alignment for the framebuffers
|
||||
uint32_t fbSize = dkImageLayoutGetSize(&fbLayout);
|
||||
uint32_t fbAlign = dkImageLayoutGetAlignment(&fbLayout);
|
||||
fbSize = (fbSize + fbAlign - 1) &~ (fbAlign - 1);
|
||||
|
||||
// Create a memory block that will host the framebuffers
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, s_device, FB_NUM*fbSize);
|
||||
memBlockMaker.flags = DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image;
|
||||
s_fbMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
|
||||
// Initialize the framebuffers with the layout and backing memory we've just created
|
||||
DkImage fbImages[FB_NUM];
|
||||
DkImage const* swapchainImages[FB_NUM];
|
||||
for (unsigned i = 0; i < FB_NUM; i ++)
|
||||
{
|
||||
swapchainImages[i] = &fbImages[i];
|
||||
dkImageInitialize(&fbImages[i], &fbLayout, s_fbMemBlock, i*fbSize);
|
||||
}
|
||||
|
||||
// Create a swapchain out of the framebuffers we've just initialized
|
||||
DkSwapchainMaker swapchainMaker;
|
||||
dkSwapchainMakerDefaults(&swapchainMaker, s_device, nwindowGetDefault(), swapchainImages, FB_NUM);
|
||||
s_swapchain = dkSwapchainCreate(&swapchainMaker);
|
||||
|
||||
// Create a memory block for the linear framebuffer
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, s_device, width*height*4);
|
||||
memBlockMaker.flags = DkMemBlockFlags_CpuCached | DkMemBlockFlags_GpuUncached;
|
||||
s_workMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
|
||||
// Create a memory block for the command lists
|
||||
dkMemBlockMakerDefaults(&memBlockMaker, s_device, CMDMEMSIZE);
|
||||
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
|
||||
s_cmdMemBlock = dkMemBlockCreate(&memBlockMaker);
|
||||
|
||||
// Create a command buffer
|
||||
DkCmdBufMaker cmdBufMaker;
|
||||
dkCmdBufMakerDefaults(&cmdBufMaker, s_device);
|
||||
s_cmdBuf = dkCmdBufCreate(&cmdBufMaker);
|
||||
dkCmdBufAddMemory(s_cmdBuf, s_cmdMemBlock, 0, CMDMEMSIZE);
|
||||
|
||||
// Define source for linear framebuffer copies
|
||||
const DkCopyBuf linearSrc = {
|
||||
.addr = dkMemBlockGetGpuAddr(s_workMemBlock),
|
||||
.rowLength = 0,
|
||||
.imageHeight = 0,
|
||||
};
|
||||
|
||||
// Define rectangle for the copies
|
||||
const DkImageRect copyRect = {
|
||||
.x = 0, .y = 0, .z = 0,
|
||||
.width = width, .height = height, .depth = 1,
|
||||
};
|
||||
|
||||
// Record command lists for the copies
|
||||
for (unsigned i = 0; i < FB_NUM; i ++) {
|
||||
DkImageView tiledDst;
|
||||
dkImageViewDefaults(&tiledDst, &fbImages[i]);
|
||||
dkCmdBufCopyBufferToImage(s_cmdBuf, &linearSrc, &tiledDst, ©Rect, 0);
|
||||
dkCmdBufSignalFence(s_cmdBuf, &s_fence, false);
|
||||
s_cmdLists[i] = dkCmdBufFinishList(s_cmdBuf);
|
||||
}
|
||||
|
||||
// Create a queue, to which we will submit our command lists
|
||||
DkQueueMaker queueMaker;
|
||||
dkQueueMakerDefaults(&queueMaker, s_device);
|
||||
queueMaker.flags = 0; // we will only use this queue for transferring
|
||||
s_queue = dkQueueCreate(&queueMaker);
|
||||
}
|
||||
|
||||
void graphicsExit(void)
|
||||
{
|
||||
// Make sure the queue is idle before destroying anything
|
||||
dkQueueWaitIdle(s_queue);
|
||||
|
||||
// Destroy all the resources we've created
|
||||
dkQueueDestroy(s_queue);
|
||||
dkCmdBufDestroy(s_cmdBuf);
|
||||
dkMemBlockDestroy(s_cmdMemBlock);
|
||||
dkMemBlockDestroy(s_workMemBlock);
|
||||
dkSwapchainDestroy(s_swapchain);
|
||||
dkMemBlockDestroy(s_fbMemBlock);
|
||||
dkDeviceDestroy(s_device);
|
||||
}
|
||||
|
||||
void* graphicsFrameBegin(u32* out_stride)
|
||||
{
|
||||
// Ensure the GPU is not reading from the framebuffer
|
||||
dkFenceWait(&s_fence, -1);
|
||||
|
||||
// Return information
|
||||
if (out_stride) *out_stride = s_fbWidth*4;
|
||||
return dkMemBlockGetCpuAddr(s_workMemBlock);
|
||||
}
|
||||
|
||||
void graphicsFrameEnd(void)
|
||||
{
|
||||
// Flush the linear framebuffer
|
||||
dkMemBlockFlushCpuCache(s_workMemBlock, 0, s_fbWidth*s_fbHeight*4);
|
||||
|
||||
// Present a frame
|
||||
int slot = dkQueueAcquireImage(s_queue, s_swapchain);
|
||||
dkQueueSubmitCommands(s_queue, s_cmdLists[slot]);
|
||||
dkQueuePresentImage(s_queue, s_swapchain, slot);
|
||||
}
|
9
nx_main/nx_graphics.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
void graphicsInit(u32 width, u32 height);
|
||||
void graphicsExit(void);
|
||||
|
||||
void* graphicsFrameBegin(u32* out_stride);
|
||||
void graphicsFrameEnd(void);
|
@ -2,25 +2,27 @@
|
||||
|
||||
static const loaderFuncs_s* s_loader;
|
||||
|
||||
void launchInit(void) {
|
||||
bool launchInit(void) {
|
||||
#define ADD_LOADER(_name) do \
|
||||
{ \
|
||||
extern const loaderFuncs_s _name; \
|
||||
if (_name.init()) \
|
||||
{ \
|
||||
s_loader = &_name; \
|
||||
return; \
|
||||
return 1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
ADD_LOADER(loader_builtin);
|
||||
|
||||
// Shouldn't happen
|
||||
fatalSimple(-1);//TODO: What value should be used for this?
|
||||
s_loader = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void launchExit(void) {
|
||||
s_loader->deinit();
|
||||
if (s_loader) s_loader->deinit();
|
||||
s_loader = NULL;
|
||||
}
|
||||
|
||||
const loaderFuncs_s* launchGetLoader(void) {
|
||||
@ -71,5 +73,6 @@ void launchMenuEntry(menuEntry_s* me) {
|
||||
descriptorScanFile(&me->descriptor, me->path);*/
|
||||
|
||||
// Launch it
|
||||
if (s_loader == NULL) return;
|
||||
s_loader->launchFile(me->path, &me->args);
|
||||
}
|
||||
|
36
nx_main/nx_netstatus.c
Normal file
@ -0,0 +1,36 @@
|
||||
#include "../common/common.h"
|
||||
|
||||
bool netstatusGetDetails(AssetId *id) {
|
||||
Result rc=0;
|
||||
NifmInternetConnectionType contype;
|
||||
u32 wifiStrength=0;
|
||||
NifmInternetConnectionStatus connectionStatus;
|
||||
|
||||
rc = nifmGetInternetConnectionStatus(&contype, &wifiStrength, &connectionStatus);
|
||||
if (R_FAILED(rc)) {
|
||||
*id = AssetId_airplane_icon;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (contype == NifmInternetConnectionType_Ethernet) {
|
||||
if (connectionStatus != NifmInternetConnectionStatus_Connected)
|
||||
*id = AssetId_eth_none_icon;
|
||||
else
|
||||
*id = AssetId_eth_icon;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (wifiStrength==0) {
|
||||
*id = AssetId_wifi_none_icon;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (wifiStrength==3)
|
||||
*id = AssetId_wifi3_icon;
|
||||
if (wifiStrength==2)
|
||||
*id = AssetId_wifi2_icon;
|
||||
else
|
||||
*id = AssetId_wifi1_icon;
|
||||
|
||||
return true;
|
||||
}
|
74
nx_main/nx_power.c
Normal file
@ -0,0 +1,74 @@
|
||||
#include <switch.h>
|
||||
#include "../common/common.h"
|
||||
|
||||
static bool powerInitialized;
|
||||
static bool powerCacheInitialized;
|
||||
static uint32_t powerCacheCharge;
|
||||
static bool powerCacheIsCharging;
|
||||
static PsmSession powerSession;
|
||||
|
||||
bool powerGetDetails(uint32_t *batteryCharge, bool *isCharging) {
|
||||
PsmChargerType charger = PsmChargerType_Unconnected;
|
||||
bool hwReadsSucceeded = false;
|
||||
bool use_cache = false;
|
||||
Result rc = 0;
|
||||
|
||||
*isCharging = false;
|
||||
*batteryCharge = 0;
|
||||
|
||||
if (powerInitialized) {
|
||||
if (powerCacheInitialized) {
|
||||
rc = psmWaitStateChangeEvent(&powerSession, 0);
|
||||
|
||||
if (R_FAILED(rc)) use_cache = true;
|
||||
}
|
||||
|
||||
rc = psmGetBatteryChargePercentage(batteryCharge);
|
||||
hwReadsSucceeded = R_SUCCEEDED(rc);
|
||||
if (use_cache) {
|
||||
*isCharging = powerCacheIsCharging;
|
||||
}
|
||||
else {
|
||||
rc = psmGetChargerType(&charger);
|
||||
hwReadsSucceeded &= R_SUCCEEDED(rc);
|
||||
*isCharging = (charger != PsmChargerType_Unconnected);
|
||||
}
|
||||
|
||||
powerCacheCharge = *batteryCharge;
|
||||
powerCacheIsCharging = *isCharging;
|
||||
powerCacheInitialized = true;
|
||||
}
|
||||
|
||||
return hwReadsSucceeded;
|
||||
}
|
||||
|
||||
void powerInit(void) {
|
||||
uint32_t charge=0;
|
||||
bool isCharging=0;
|
||||
|
||||
powerCacheInitialized = false;
|
||||
powerCacheCharge = 0;
|
||||
powerCacheIsCharging = false;
|
||||
|
||||
if (!powerInitialized) {
|
||||
Result rc = psmInitialize();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
rc = psmBindStateChangeEvent(&powerSession, 1, 1, 1);
|
||||
|
||||
if (R_FAILED(rc)) psmExit();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
powerInitialized = true;
|
||||
powerGetDetails(&charge, &isCharging);//Init the cache.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void powerExit(void) {
|
||||
if (powerInitialized) {
|
||||
psmUnbindStateChangeEvent(&powerSession);
|
||||
psmExit();
|
||||
powerInitialized = false;
|
||||
powerCacheInitialized = false;
|
||||
}
|
||||
}
|
14
nx_main/nx_thermalstatus.c
Normal file
@ -0,0 +1,14 @@
|
||||
#include "../common/common.h"
|
||||
|
||||
bool thermalstatusInit(void) {
|
||||
return R_SUCCEEDED(tsInitialize());
|
||||
}
|
||||
|
||||
void thermalstatusExit(void) {
|
||||
tsExit();
|
||||
}
|
||||
|
||||
bool thermalstatusGetDetails(s32 *temperature) {
|
||||
return R_SUCCEEDED(tsGetTemperature(TsLocation_Internal, temperature));
|
||||
}
|
||||
|
168
nx_main/nx_touch.c
Normal file
@ -0,0 +1,168 @@
|
||||
#include "nx_touch.h"
|
||||
|
||||
#define TAP_MOVEMENT_GAP 20
|
||||
#define VERTICAL_SWIPE_HORIZONTAL_PLAY 250
|
||||
#define VERTICAL_SWIPE_MINIMUM_DISTANCE 300
|
||||
#define HORIZONTAL_SWIPE_VERTICAL_PLAY 250
|
||||
#define HORIZONTAL_SWIPE_MINIMUM_DISTANCE 300
|
||||
|
||||
#define distance(x1, y1, x2, y2) (int) sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)))
|
||||
|
||||
struct touchInfo_s touchInfo;
|
||||
|
||||
void touchInit(void) {
|
||||
touchInfo.gestureInProgress = false;
|
||||
touchInfo.isTap = true;
|
||||
touchInfo.initMenuXPos = 0;
|
||||
touchInfo.initMenuIndex = 0;
|
||||
touchInfo.lastSlideSpeed = 0;
|
||||
hidInitializeTouchScreen();
|
||||
}
|
||||
|
||||
void handleTappingOnApp(menu_s* menu, int px) {
|
||||
int i = 0;
|
||||
menuEntry_s *me = NULL;
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuList];
|
||||
|
||||
for (me = menu->firstEntry, i = 0; me; me = me->next, i ++) {
|
||||
int entry_start_x = layoutobj->posStart[0] + i * layoutobj->posEnd[0];
|
||||
|
||||
int screen_width = 1280;
|
||||
if (entry_start_x >= (screen_width - menu->xPos))
|
||||
break;
|
||||
|
||||
if (px >= (entry_start_x + menu->xPos) && px <= (entry_start_x + menu->xPos) + layoutobj->size[0]) {
|
||||
launchMenuEntryTask(me);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handleTappingOnOpenLaunch(menu_s* menu) {
|
||||
if (menu->nEntries > 0)
|
||||
{
|
||||
int i;
|
||||
menuEntry_s* me;
|
||||
for (i = 0, me = menu->firstEntry; i != menu->curEntry; i ++, me = me->next);
|
||||
launchMenuEntryTask(me);
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool checkInsideTextLayoutObject(ThemeLayoutId id, int x, int y) {
|
||||
ThemeLayoutObject *layoutobj = &themeCurrent.layoutObjects[id];
|
||||
if (!layoutobj->visible) return false;
|
||||
|
||||
return x > layoutobj->posFinal[0] && x < layoutobj->posFinal[0]+(layoutobj->touchSize[0] ? layoutobj->touchSize[0] : layoutobj->textSize[0]) && y > layoutobj->posFinal[1]-layoutobj->touchSize[1] && y < layoutobj->posFinal[1];
|
||||
}
|
||||
|
||||
void handleTouch(menu_s* menu) {
|
||||
ThemeLayoutObject *layoutobj = NULL;
|
||||
HidTouchScreenState touch = {0};
|
||||
hidGetTouchScreenStates(&touch, 1);
|
||||
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuListTiles];
|
||||
int entries_count = layoutobj->posEnd[0];
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MenuList];
|
||||
|
||||
// On touch start.
|
||||
if (touch.count == 1 && !touchInfo.gestureInProgress) {
|
||||
touchInfo.gestureInProgress = true;
|
||||
touchInfo.firstTouch = touch.touches[0];
|
||||
touchInfo.prevTouch = touch.touches[0];
|
||||
touchInfo.isTap = true;
|
||||
touchInfo.initMenuXPos = menu->xPos;
|
||||
touchInfo.initMenuIndex = menu->curEntry;
|
||||
touchInfo.lastSlideSpeed = 0;
|
||||
menu->slideSpeed = 0;
|
||||
}
|
||||
// On touch moving.
|
||||
else if (touch.count >= 1 && touchInfo.gestureInProgress) {
|
||||
touchInfo.lastSlideSpeed = ((int)(touch.touches[0].x - touchInfo.prevTouch.x));
|
||||
|
||||
touchInfo.prevTouch = touch.touches[0];
|
||||
|
||||
if (touchInfo.isTap && (abs(touchInfo.firstTouch.x - touch.touches[0].x) > TAP_MOVEMENT_GAP || abs(touchInfo.firstTouch.y - touch.touches[0].y) > TAP_MOVEMENT_GAP)) {
|
||||
touchInfo.isTap = false;
|
||||
}
|
||||
if (!menuIsMsgBoxOpen() && touchInfo.firstTouch.y > layoutobj->posStart[1] && touchInfo.firstTouch.y < layoutobj->posStart[1]+layoutobj->size[1] && !touchInfo.isTap && menu->nEntries > entries_count) {
|
||||
|
||||
if (!touchInfo.isTap) {
|
||||
menu->slideSpeed = touchInfo.lastSlideSpeed;
|
||||
}
|
||||
}
|
||||
}
|
||||
// On touch end.
|
||||
else if (touchInfo.gestureInProgress) {
|
||||
int x1 = touchInfo.firstTouch.x;
|
||||
int y1 = touchInfo.firstTouch.y;
|
||||
int x2 = touchInfo.prevTouch.x;
|
||||
int y2 = touchInfo.prevTouch.y;
|
||||
|
||||
if (!touchInfo.isTap) {
|
||||
menu->slideSpeed = touchInfo.lastSlideSpeed;
|
||||
}
|
||||
|
||||
bool netloader_active = menuIsNetloaderActive();
|
||||
|
||||
if (menuIsMsgBoxOpen() && !netloader_active) {
|
||||
layoutobj = &themeCurrent.layoutObjects[ThemeLayoutId_MsgBoxSeparator];
|
||||
MessageBox currMsgBox = menuGetCurrentMsgBox();
|
||||
int start_x = 1280 / 2 - currMsgBox.width / 2;
|
||||
int start_y = (720 / 2 - currMsgBox.height / 2) + currMsgBox.height;
|
||||
int end_x = start_x + currMsgBox.width;
|
||||
int end_y = start_y;
|
||||
start_y+= layoutobj->posStart[1];
|
||||
|
||||
if (x1 > start_x && x1 < end_x && y1 > start_y && y1 < end_y && touchInfo.isTap) {
|
||||
menuCloseMsgBox();
|
||||
}
|
||||
} else if (touchInfo.isTap && !netloader_active) {
|
||||
// App Icons
|
||||
if (y1 > layoutobj->posStart[1] && y1 < layoutobj->posStart[1]+layoutobj->size[1]) {
|
||||
handleTappingOnApp(menu, touchInfo.prevTouch.x);
|
||||
}
|
||||
// Bottom Buttons
|
||||
else {
|
||||
// Back Button
|
||||
if (checkInsideTextLayoutObject(ThemeLayoutId_ButtonB, x1, y1) || checkInsideTextLayoutObject(ThemeLayoutId_ButtonBText, x1, y1)) {
|
||||
launchMenuBackTask();
|
||||
}
|
||||
// Open/Launch Button
|
||||
else if (menu->nEntries != 0 && (checkInsideTextLayoutObject(ThemeLayoutId_ButtonA, x1, y1) || checkInsideTextLayoutObject(ThemeLayoutId_ButtonAText, x1, y1))) {
|
||||
handleTappingOnOpenLaunch(menu);
|
||||
}
|
||||
// Star Button
|
||||
else if (menu->nEntries != 0) {
|
||||
int i;
|
||||
menuEntry_s* me;
|
||||
for (i = 0, me = menu->firstEntry; i != menu->curEntry; i ++, me = me->next);
|
||||
if (me->type != ENTRY_TYPE_THEME && (checkInsideTextLayoutObject(ThemeLayoutId_ButtonX, x1, y1) || checkInsideTextLayoutObject(ThemeLayoutId_ButtonXText, x1, y1))) {
|
||||
menuHandleXButton();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Vertical Swipe
|
||||
else if (abs(x1 - x2) < VERTICAL_SWIPE_HORIZONTAL_PLAY && distance(x1, y1, x2, y2) > VERTICAL_SWIPE_MINIMUM_DISTANCE) {
|
||||
// Swipe up to go back
|
||||
if (y1 - y2 > 0) {
|
||||
launchMenuBackTask();
|
||||
}
|
||||
// Swipe down to go into netloader
|
||||
else if (y1 - y2 < 0) {
|
||||
launchMenuNetloaderTask();
|
||||
}
|
||||
}
|
||||
// Horizontal Swipe
|
||||
else if (y1 < layoutobj->posStart[1] && y2 < layoutobj->posStart[1]) {
|
||||
if (abs(y1 - y2) < HORIZONTAL_SWIPE_VERTICAL_PLAY && distance(x1, y1, x2, y2) > HORIZONTAL_SWIPE_MINIMUM_DISTANCE) {
|
||||
// Swipe left to go into theme-menu
|
||||
if (x1 - x2 > 0) {
|
||||
themeMenuStartup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
touchInfo.gestureInProgress = false;
|
||||
}
|
||||
}
|
17
nx_main/nx_touch.h
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "../common/common.h"
|
||||
|
||||
struct touchInfo_s {
|
||||
bool gestureInProgress;
|
||||
HidTouchState firstTouch;
|
||||
HidTouchState prevTouch;
|
||||
bool isTap;
|
||||
int initMenuXPos;
|
||||
int initMenuIndex;
|
||||
int lastSlideSpeed;
|
||||
};
|
||||
|
||||
void touchInit(void);
|
||||
void handleTouch(menu_s* menu);
|
@ -1,6 +1,7 @@
|
||||
#include <SFML/Graphics.hpp>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <physfs.h>
|
||||
|
||||
extern "C" {
|
||||
|
||||
@ -10,13 +11,20 @@ extern "C" {
|
||||
|
||||
color_t pixels[720][1280];
|
||||
|
||||
int main()
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
sf::RenderWindow window(sf::VideoMode(1280, 720), "Test");
|
||||
window.setFramerateLimit(60);
|
||||
|
||||
menuStartupPath();
|
||||
PHYSFS_init(argv[0]);
|
||||
assetsInit();
|
||||
themeStartup(THEME_PRESET_LIGHT);
|
||||
textInit();
|
||||
fontInitialize();
|
||||
netloaderInit();
|
||||
workerInit();
|
||||
statusInit();
|
||||
menuStartup();
|
||||
|
||||
while (window.isOpen())
|
||||
@ -50,11 +58,17 @@ int main()
|
||||
window.display();
|
||||
}
|
||||
|
||||
netloaderSignalExit();
|
||||
statusExit();
|
||||
workerExit();
|
||||
netloaderExit();
|
||||
fontExit();
|
||||
assetsExit();
|
||||
PHYSFS_deinit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" void launchMenuEntryTask(menuEntry_s* arg);
|
||||
|
||||
extern "C" bool menuUpdate(void) {
|
||||
//This is implemented here due to the hid code.
|
||||
menu_s* menu = menuGetCurrent();
|
||||
@ -63,21 +77,33 @@ extern "C" bool menuUpdate(void) {
|
||||
int new_esc_state = sf::Keyboard::isKeyPressed(sf::Keyboard::Escape);
|
||||
static int return_state = 0;
|
||||
int new_return_state = sf::Keyboard::isKeyPressed(sf::Keyboard::Return);
|
||||
static int x_state = 0;
|
||||
int new_x_state = sf::Keyboard::isKeyPressed(sf::Keyboard::X);
|
||||
static int y_state = 0;
|
||||
int new_y_state = sf::Keyboard::isKeyPressed(sf::Keyboard::Y);
|
||||
static int t_state = 0;
|
||||
int new_t_state = sf::Keyboard::isKeyPressed(sf::Keyboard::T);
|
||||
|
||||
if(!new_y_state && y_state)
|
||||
{
|
||||
launchMenuNetloaderTask();
|
||||
}
|
||||
|
||||
if(!new_x_state && x_state)
|
||||
{
|
||||
menuHandleXButton();
|
||||
}
|
||||
|
||||
if (!new_esc_state && esc_state)
|
||||
{
|
||||
menuScan("..");
|
||||
launchMenuBackTask();
|
||||
}
|
||||
else if(!new_t_state && t_state){
|
||||
themeMenuStartup();
|
||||
}
|
||||
else if (!new_return_state && return_state)
|
||||
{
|
||||
if (menu->nEntries > 0)
|
||||
{
|
||||
int i;
|
||||
menuEntry_s* me;
|
||||
for (i = 0, me = menu->firstEntry; i != menu->curEntry; i ++, me = me->next);
|
||||
launchMenuEntryTask(me);
|
||||
//workerSchedule(launchMenuEntryTask, me);
|
||||
}
|
||||
menuHandleAButton();
|
||||
}
|
||||
else if (menu->nEntries > 0)
|
||||
{
|
||||
@ -103,6 +129,8 @@ extern "C" bool menuUpdate(void) {
|
||||
|
||||
esc_state = new_esc_state;
|
||||
return_state = new_return_state;
|
||||
y_state = new_y_state;
|
||||
t_state = new_t_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
|
||||
static const loaderFuncs_s* s_loader;
|
||||
|
||||
void launchInit(void) {
|
||||
|
||||
bool launchInit(void) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void launchExit(void) {
|
||||
|
6
pc_main/pc_netstatus.c
Normal file
@ -0,0 +1,6 @@
|
||||
#include "../common/common.h"
|
||||
|
||||
bool netstatusGetDetails(AssetId *id) {
|
||||
*id = AssetId_wifi3_icon;
|
||||
return true;
|
||||
}
|
15
pc_main/pc_power.c
Normal file
@ -0,0 +1,15 @@
|
||||
#include "../common/common.h"
|
||||
|
||||
void powerInit(void) {
|
||||
|
||||
}
|
||||
|
||||
void powerExit(void) {
|
||||
|
||||
}
|
||||
|
||||
bool powerGetDetails(uint32_t *batteryCharge, bool *isCharging) {
|
||||
*isCharging = false;
|
||||
*batteryCharge = 100;
|
||||
return false;
|
||||
}
|
15
pc_main/pc_thermalstatus.c
Normal file
@ -0,0 +1,15 @@
|
||||
#include "../common/common.h"
|
||||
|
||||
bool thermalstatusInit(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void thermalstatusExit(void) {
|
||||
|
||||
}
|
||||
|
||||
bool thermalstatusGetDetails(s32 *temperature) {
|
||||
*temperature = 0;
|
||||
return false;
|
||||
}
|
||||
|
BIN
resources/airplane_icon.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
resources/battery_icon.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
resources/charging_icon.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
resources/eth_icon.png
Normal file
After Width: | Height: | Size: 405 B |
BIN
resources/eth_none_icon.png
Normal file
After Width: | Height: | Size: 481 B |
BIN
resources/theme_icon_dark.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
resources/theme_icon_light.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
resources/wifi1_icon.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
resources/wifi2_icon.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
resources/wifi3_icon.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
resources/wifi_none_icon.png
Normal file
After Width: | Height: | Size: 4.5 KiB |