Compare commits
226 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 | ||
|
dd78d0f8d7 | ||
|
8f734a631d | ||
|
1542a249f0 | ||
|
a614d2fd98 | ||
|
5743c2946d | ||
|
8c1d558f15 | ||
|
6edaef1bc8 | ||
|
c6fe507e82 | ||
|
0cb9f3961f | ||
|
1730ed1889 | ||
|
4ffb8e1355 | ||
|
b4fa1825ae | ||
|
922319b0f3 | ||
|
4211679e0e | ||
|
65d3a19499 | ||
|
114db71711 | ||
|
a9d4fb7830 | ||
|
f26331a828 | ||
|
af90827c1d | ||
|
50ff808184 | ||
|
bb53f73cc7 | ||
|
62e26552d0 | ||
|
5eb782ce8e | ||
|
0981e34b6f | ||
|
d7396de761 | ||
|
db56a250bf | ||
|
b6b061f2b2 | ||
|
5e95934cb6 | ||
|
538b760644 | ||
|
045d126006 | ||
|
814961b84f | ||
|
f98d33ace2 | ||
|
3a2264e4df | ||
|
811bcdda1d | ||
|
f23f133404 | ||
|
8d8a5c8e12 | ||
|
7a991f2f94 | ||
|
3a415ddaf7 | ||
|
72116ed62b | ||
|
725a19605d | ||
|
df8c215d43 | ||
|
1bce371999 | ||
|
ad52da6278 | ||
|
50abc932f9 | ||
|
0cec4acd6b | ||
|
d534aaa834 | ||
|
7cd0e06be2 | ||
|
b8cf331ff9 | ||
|
4ef5c0dd15 | ||
|
5bedb753a8 | ||
|
05410210c3 | ||
|
4653c32646 | ||
|
70a3995081 | ||
|
706b4f3b62 | ||
|
0f93b9c9d5 | ||
|
473adcbdff | ||
|
43f6395491 | ||
|
3461adef0b | ||
|
749bc21898 | ||
|
173aab2c07 | ||
|
287487eb1f | ||
|
88d13d29e2 | ||
|
363f7b67a7 | ||
|
233d70c22c | ||
|
e843cc598f | ||
|
3298bdaaf7 | ||
|
7205d34bc7 | ||
|
ed116e3a23 |
10
.gitignore
vendored
@ -1,6 +1,16 @@
|
||||
.*/
|
||||
*~
|
||||
*.exe
|
||||
*.o
|
||||
test
|
||||
tahoma12.c
|
||||
tahoma24.c
|
||||
*.zip
|
||||
build
|
||||
*.elf
|
||||
*.nso
|
||||
*.pfs0
|
||||
*.nacp
|
||||
*.nro
|
||||
test.*
|
||||
switch
|
||||
|
5
LICENSE.md
Normal file
@ -0,0 +1,5 @@
|
||||
Copyright 2017-2018 nx-hbmenu Authors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
33
Makefile
@ -1,7 +1,30 @@
|
||||
all:
|
||||
make -f Makefile.nx
|
||||
make -f Makefile.pc
|
||||
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
|
||||
|
||||
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: romfs/assets.zip
|
||||
$(MAKE) -f Makefile.nx
|
||||
|
||||
pc: romfs/assets.zip
|
||||
$(MAKE) -f Makefile.pc
|
||||
|
||||
clean:
|
||||
make -f Makefile.pc clean
|
||||
make -f Makefile.nx clean
|
||||
@rm -Rf romfs
|
||||
$(MAKE) -f Makefile.pc clean
|
||||
$(MAKE) -f Makefile.nx clean
|
||||
|
89
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,31 +28,42 @@ 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)_v$(APP_VERSION)
|
||||
|
||||
APP_AUTHOR := switchbrew
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# options for code generation
|
||||
#---------------------------------------------------------------------------------
|
||||
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
|
||||
|
||||
CFLAGS := -g -Wall -O2 \
|
||||
-ffast-math \
|
||||
$(ARCH) $(DEFINES)
|
||||
CFLAGS := -g -Wall -O2 -ffunction-sections \
|
||||
$(ARCH) $(DEFINES) `freetype-config --cflags`
|
||||
|
||||
CFLAGS += $(INCLUDE) -DSWITCH
|
||||
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
|
||||
LIBS := -ldeko3d -lphysfs `freetype-config --libs` -lconfig -lturbojpeg -lpng
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
# list of directories containing libraries, this must be the top level containing
|
||||
@ -95,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) \
|
||||
@ -104,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)
|
||||
@ -131,7 +155,11 @@ ifneq ($(APP_TITLEID),)
|
||||
export NACPFLAGS += --titleid=$(APP_TITLEID)
|
||||
endif
|
||||
|
||||
.PHONY: $(BUILD) clean all
|
||||
ifneq ($(ROMFS),)
|
||||
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
||||
endif
|
||||
|
||||
.PHONY: $(BUILD) clean all dist-bin
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
all: $(BUILD)
|
||||
@ -143,8 +171,17 @@ $(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
|
||||
@mkdir -p $(DIST_PATH)
|
||||
@cp $(OUTPUT).nro $(DIST_PATH)/hbmenu.nro
|
||||
@zip -rj $(DIST_PATH).zip $(DIST_PATH)
|
||||
|
||||
#---------------------------------------------------------------------------------
|
||||
else
|
||||
@ -155,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
|
||||
@ -167,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)
|
||||
|
48
Makefile.pc
@ -1,38 +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 \
|
||||
build_pc/tahoma24.o build_pc/tahoma12.o build_pc/switchicon_questionmark.bin.o build_pc/folder_icon.bin.o
|
||||
gcc -Wall -O2 -g0 $^ -lsfml-graphics -lsfml-window -lsfml-system -lstdc++ -I. -Ibuild_pc -o $@
|
||||
ifeq ($(strip $(HOST_OS)),Darwin)
|
||||
BIN2S_FLAGS := --apple-llvm
|
||||
endif
|
||||
|
||||
build_pc/tahoma12.o : data/tahoma12.nxfnt
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
ifneq (,$(findstring MINGW,$(HOST_OS)))
|
||||
EXTRA_CFLAGS="-D__USE_MINGW_ANSI_STDIO"
|
||||
EXTRA_LDFLAGS="-lws2_32"
|
||||
endif
|
||||
|
||||
build_pc/tahoma24.o : data/tahoma24.nxfnt
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/switchicon_questionmark.bin.o : data/switchicon_questionmark.bin
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
|
||||
build_pc/folder_icon.bin.o : data/folder_icon.bin
|
||||
mkdir -p $(dir $@)
|
||||
@echo $(notdir $<)
|
||||
@$(bin2o)
|
||||
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.*
|
||||
|
31
README.md
@ -1,12 +1,31 @@
|
||||
#### Download
|
||||
### 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
|
||||
The latest release is available from the [releases](https://github.com/switchbrew/nx-hbmenu/releases/latest) page.
|
||||
|
||||
#### Building
|
||||
Build with Makefile.nx directly 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).
|
||||
* The "switchicon-questionmark" icon is based on the icon by [Sweet Farm from the Noun Project](https://thenounproject.com/term/nintendo-switch/694750/).
|
||||
* "folder_icon": [Original](https://www.iconfinder.com/icons/97888/docs_folder_google_icon) icon by Dakirby309, modified by @fincs.
|
||||
* [nanojpeg](svn.emphy.de/nanojpeg/trunk/nanojpeg/nanojpeg.c) is used for handling JPEG icons.
|
||||
* `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/folder_icon.bin
Normal file
BIN
assets/hbmenu_logo_dark.bin
Normal file
BIN
assets/hbmenu_logo_light.bin
Normal file
1
assets/invalid_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
BIN
assets_unused/button_a_dark.bin
Normal file
BIN
assets_unused/button_a_light.bin
Normal file
BIN
assets_unused/button_b_dark.bin
Normal file
BIN
assets_unused/button_b_light.bin
Normal file
BIN
assets_unused/interuimedium20.nxfnt
Normal file
BIN
assets_unused/interuimedium28.nxfnt
Normal file
BIN
assets_unused/interuimedium30.nxfnt
Normal file
BIN
assets_unused/interuimedium42.nxfnt
Normal file
BIN
assets_unused/interuiregular14.nxfnt
Normal file
BIN
assets_unused/interuiregular18.nxfnt
Normal file
BIN
assets_unused/interuiregular20.nxfnt
Normal file
BIN
assets_unused/interuiregular24.nxfnt
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);
|
||||
|
141
common/common.h
@ -10,15 +10,34 @@
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <threads.h>
|
||||
#ifndef __APPLE__
|
||||
#include <malloc.h>
|
||||
#ifdef SWITCH
|
||||
#endif
|
||||
#include <math.h>
|
||||
#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)
|
||||
|
||||
@ -29,17 +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 "nro.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"
|
||||
|
||||
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)
|
||||
{
|
||||
@ -57,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++;
|
||||
@ -74,11 +152,27 @@ 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;
|
||||
g_framebuf[off] = clr.r; off++;
|
||||
g_framebuf[off] = clr.g; off++;
|
||||
g_framebuf[off] = clr.b; off++;
|
||||
g_framebuf[off] = 0xff;
|
||||
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)
|
||||
{
|
||||
if (x >= 1280 || y >= 720 || x > 1280-4)
|
||||
return;
|
||||
|
||||
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;
|
||||
*((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 val = *((u32*)&g_framebuf[off]);
|
||||
u8 r = (u8)val;
|
||||
u8 g = (u8)(val>>8);
|
||||
u8 b = (u8)(val>>16);
|
||||
return MakeColor(r, g, b, 255);
|
||||
}
|
||||
#else
|
||||
extern color_t pixels[720][1280];
|
||||
@ -100,8 +194,25 @@ static inline void DrawPixelRaw(uint32_t x, uint32_t y, color_t clr)
|
||||
pixels[y][x].b = clr.b;
|
||||
pixels[y][x].a = 0xff;
|
||||
}
|
||||
static inline void Draw4PixelsRaw(uint32_t x, uint32_t y, color_t clr)
|
||||
{
|
||||
uint32_t pos;
|
||||
for (pos=0; pos<4; pos++) DrawPixelRaw(x+pos, y, clr);
|
||||
}
|
||||
static inline color_t FetchPixelColor(uint32_t x, uint32_t y)
|
||||
{
|
||||
return pixels[y][x];
|
||||
}
|
||||
#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,11 +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;
|
||||
#define tahoma24 &tahoma24_nxfnt
|
||||
#define tahoma12 &tahoma12_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 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,21 +1,34 @@
|
||||
#include "language.h"
|
||||
|
||||
//TODO: Update this once libnx supports settings get-language.
|
||||
#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_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_ZH_HANT(_str) [SetLanguage_ZHTW] = _str, [SetLanguage_ZHHANT] = _str
|
||||
#else
|
||||
#define STR_JP(_str) [0] = _str
|
||||
#define STR_EN(_str) [1] = _str
|
||||
#define STR_FR(_str) [2] = _str
|
||||
#define STR_DE(_str) [3] = _str
|
||||
#define STR_IT(_str) [4] = _str
|
||||
#define STR_ES(_str) [5] = _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_ZH_HANT(_str) [11] = _str
|
||||
#endif
|
||||
|
||||
#define STR_JP(_str) [/*CFG_LANGUAGE_JP*/0] = _str
|
||||
#define STR_EN(_str) [/*CFG_LANGUAGE_EN*/1] = _str
|
||||
#define STR_FR(_str) [/*CFG_LANGUAGE_FR*/2] = _str
|
||||
#define STR_DE(_str) [/*CFG_LANGUAGE_DE*/3] = _str
|
||||
#define STR_IT(_str) [/*CFG_LANGUAGE_IT*/4] = _str
|
||||
#define STR_ES(_str) [/*CFG_LANGUAGE_ES*/5] = _str
|
||||
#define STR_ZH(_str) [/*CFG_LANGUAGE_ZH*/6] = _str
|
||||
#define STR_KO(_str) [/*CFG_LANGUAGE_KO*/7] = _str
|
||||
#define STR_NL(_str) [/*CFG_LANGUAGE_NL*/8] = _str
|
||||
#define STR_PT(_str) [/*CFG_LANGUAGE_PT*/9] = _str
|
||||
#define STR_RU(_str) [/*CFG_LANGUAGE_RU*/10] = _str
|
||||
#define STR_TW(_str) [/*CFG_LANGUAGE_TW*/11] = _str
|
||||
|
||||
const char* const g_strings[StrId_Max][16] =
|
||||
const char* const g_strings[StrId_Max][17] =
|
||||
{
|
||||
[StrId_Loading] =
|
||||
{
|
||||
@ -29,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] =
|
||||
@ -45,24 +66,8 @@ const char* const g_strings[StrId_Max][16] =
|
||||
STR_NL("Map"),
|
||||
STR_KO("디렉토리"),
|
||||
STR_RU("каталог"),
|
||||
STR_ZH("目录"),
|
||||
STR_TW("資料夾"),
|
||||
},
|
||||
|
||||
[StrId_DefaultVersion] =
|
||||
{
|
||||
STR_EN("1.0.0"),
|
||||
STR_ES("1.0.0"),
|
||||
STR_DE("1.0.0"),
|
||||
STR_FR("1.0.0"),
|
||||
STR_IT("1.0.0"),
|
||||
STR_JP("1.0.0"),
|
||||
STR_PT("1.0.0"),
|
||||
STR_NL("1.0.0"),
|
||||
STR_KO("1.0.0"),
|
||||
STR_RU("1.0.0"),
|
||||
STR_ZH("1.0.0"),
|
||||
STR_TW("1.0.0"),
|
||||
STR_ZH_HANS("目录"),
|
||||
STR_ZH_HANT("資料夾"),
|
||||
},
|
||||
|
||||
/*[StrId_DefaultLongTitle] =
|
||||
@ -77,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] =
|
||||
@ -88,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] =
|
||||
@ -125,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] =
|
||||
@ -141,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] =
|
||||
@ -160,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(
|
||||
@ -177,7 +189,7 @@ const char* const g_strings[StrId_Max][16] =
|
||||
),
|
||||
STR_JP(
|
||||
"SDカードにアプリケーションが見つかりませんでした。\n"
|
||||
"SDカードのルートに「/switch」という名前のフォルダを\n"
|
||||
"SDカードのルートに「switch」という名前のフォルダを\n"
|
||||
"作成してください。"
|
||||
),
|
||||
STR_PT(
|
||||
@ -193,27 +205,212 @@ 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"),
|
||||
STR_ES("Autor"),
|
||||
STR_DE("Autor"),
|
||||
STR_FR("Auteur"),
|
||||
STR_IT("Autore"),
|
||||
STR_JP("作者"),
|
||||
STR_PT("Autor"),
|
||||
STR_NL("Auteur"),
|
||||
STR_KO("개발자"),
|
||||
STR_RU("автор"),
|
||||
STR_ZH_HANS("作者"),
|
||||
STR_ZH_HANT("作者"),
|
||||
},
|
||||
|
||||
[StrId_AppInfo_Version] =
|
||||
{
|
||||
STR_EN("Version"),
|
||||
STR_ES("Versión"),
|
||||
STR_DE("Version"),
|
||||
STR_FR("Version"),
|
||||
STR_IT("Versione"),
|
||||
STR_JP("バージョン"),
|
||||
STR_PT("Versão"),
|
||||
STR_NL("Versie"),
|
||||
STR_KO("버전"),
|
||||
STR_RU("Версия"),
|
||||
STR_ZH_HANS("版本"),
|
||||
STR_ZH_HANT("版本"),
|
||||
},
|
||||
|
||||
[StrId_Actions_Launch] =
|
||||
{
|
||||
STR_EN("Launch"),
|
||||
STR_ES("Lanzamiento"),
|
||||
STR_DE("Starten"),
|
||||
STR_FR("Lancer"),
|
||||
STR_IT("Avvia"),
|
||||
STR_JP("起動"),
|
||||
STR_PT("Lançamento"),
|
||||
STR_NL("Lancering"),
|
||||
STR_KO("실행"),
|
||||
STR_RU("запуск"),
|
||||
STR_ZH_HANS("发射"),
|
||||
STR_ZH_HANT("啟動"),
|
||||
},
|
||||
|
||||
[StrId_Actions_Open] =
|
||||
{
|
||||
STR_EN("Open"),
|
||||
STR_ES("Abrir"),
|
||||
STR_DE("Öffnen"),
|
||||
STR_FR("Ouvrir"),
|
||||
STR_IT("Apri"),
|
||||
STR_JP("開く"),
|
||||
STR_PT("Abrir"),
|
||||
STR_NL("Open"),
|
||||
STR_KO("열기"),
|
||||
STR_RU("открыто"),
|
||||
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] =
|
||||
{
|
||||
STR_EN(
|
||||
@ -248,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 キャンセル"
|
||||
),
|
||||
@ -266,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(
|
||||
@ -277,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"
|
||||
@ -324,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 再起動"
|
||||
),
|
||||
@ -342,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"
|
||||
@ -353,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"
|
||||
@ -371,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("タイトルセレクタ"),
|
||||
@ -379,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"),
|
||||
@ -398,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] =
|
||||
@ -412,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] =
|
||||
@ -470,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 取消"
|
||||
@ -510,7 +707,7 @@ const char* const g_strings[StrId_Max][16] =
|
||||
"Utilizza un exploit diverso."
|
||||
),
|
||||
STR_JP(
|
||||
"この自家製のエクスプロイトは、ターゲットタイトルの\n"
|
||||
"この自作エクスプロイトでは、ターゲットタイトルの\n"
|
||||
"下でアプリを起動することができません。\n"
|
||||
"別のエクスプロイトを使用してください。"
|
||||
),
|
||||
@ -525,8 +722,8 @@ const char* const g_strings[StrId_Max][16] =
|
||||
"Gebruik een andere exploit."
|
||||
),
|
||||
STR_KO(
|
||||
"이 홈브류 익스플로잇은 해당 타이틀에서 어플리케이션을\n"
|
||||
"실행시키는 것을 지원하지 않습니다.\n"
|
||||
"이 홈브류 익스플로잇은 해당 타이틀에서 애플리케이션을\n"
|
||||
"실행하는 것을 지원하지 않습니다.\n"
|
||||
"다른 익스플로잇을 사용해 주십시오."
|
||||
),
|
||||
STR_RU(
|
||||
@ -534,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"
|
||||
"請利用其它漏洞來啓動「自製軟體啓動器」。"
|
||||
),
|
||||
},
|
||||
@ -558,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"
|
||||
@ -570,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"
|
||||
@ -581,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] =
|
||||
@ -619,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] =
|
||||
@ -640,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"
|
||||
),
|
||||
},
|
||||
|
||||
@ -741,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"
|
||||
@ -754,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,17 +1,35 @@
|
||||
#pragma once
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
StrId_Loading = 0,
|
||||
StrId_AppletMode,
|
||||
StrId_Directory,
|
||||
StrId_DefaultVersion,
|
||||
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,
|
||||
|
||||
@ -30,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);
|
||||
|
17
common/math.c
Normal file
@ -0,0 +1,17 @@
|
||||
#include "math.h"
|
||||
|
||||
// Low precision, adapted from http://lab.polygonal.de/2007/07/18/fast-and-accurate-sinecosine-approximation/
|
||||
float approxSin(float x) {
|
||||
float ret;
|
||||
|
||||
// always wrap input angle to -PI..PI
|
||||
x = fmod(x, M_PI*2.0)-M_PI;
|
||||
|
||||
// compute sine
|
||||
if (x<0)
|
||||
ret = 1.27323954 * x + .405284735 * x * x;
|
||||
else
|
||||
ret = 1.27323954 * x - 0.405284735 * x * x;
|
||||
|
||||
return ret;
|
||||
}
|
9
common/math.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
float approxSin(float x);
|
||||
|
||||
static inline float clamp(float x, float min, float max) {
|
||||
return fmin(fmax(x, min), max);
|
||||
}
|
@ -1,20 +1,28 @@
|
||||
#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 (me->icon_gfx) {
|
||||
free(me->icon_gfx);
|
||||
me->icon_gfx = NULL;
|
||||
if (!skip_icongfx) {
|
||||
if (me->icon_gfx) {
|
||||
free(me->icon_gfx);
|
||||
me->icon_gfx = NULL;
|
||||
}
|
||||
|
||||
if (me->icon_gfx_small) {
|
||||
free(me->icon_gfx_small);
|
||||
me->icon_gfx_small = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (me->nacp) {
|
||||
@ -28,9 +36,14 @@ 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;
|
||||
AssetHeader asset_header;
|
||||
NroAssetHeader asset_header;
|
||||
|
||||
FILE* f = fopen(me->path, "rb");
|
||||
if (!f) return false;
|
||||
@ -45,8 +58,8 @@ static bool menuEntryLoadEmbeddedIcon(menuEntry_s* me) {
|
||||
fseek(f, header.size, SEEK_SET);
|
||||
|
||||
if (fread(&asset_header, sizeof(asset_header), 1, f) != 1
|
||||
|| asset_header.magic != ASSETHEADER_MAGICNUM
|
||||
|| asset_header.version > ASSETHEADER_VERSION
|
||||
|| asset_header.magic != NROASSETHEADER_MAGIC
|
||||
|| asset_header.version > NROASSETHEADER_VERSION
|
||||
|| asset_header.icon.offset == 0
|
||||
|| asset_header.icon.size == 0)
|
||||
{
|
||||
@ -68,9 +81,60 @@ 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;
|
||||
AssetHeader asset_header;
|
||||
NroAssetHeader asset_header;
|
||||
|
||||
FILE* f = fopen(me->path, "rb");
|
||||
if (!f) return false;
|
||||
@ -85,8 +149,8 @@ static bool menuEntryLoadEmbeddedNacp(menuEntry_s* me) {
|
||||
fseek(f, header.size, SEEK_SET);
|
||||
|
||||
if (fread(&asset_header, sizeof(asset_header), 1, f) != 1
|
||||
|| asset_header.magic != ASSETHEADER_MAGICNUM
|
||||
|| asset_header.version > ASSETHEADER_VERSION
|
||||
|| asset_header.magic != NROASSETHEADER_MAGIC
|
||||
|| asset_header.version > NROASSETHEADER_VERSION
|
||||
|| asset_header.nacp.offset == 0
|
||||
|| asset_header.nacp.size == 0)
|
||||
{
|
||||
@ -112,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;
|
||||
@ -126,32 +210,91 @@ 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/".
|
||||
tmplen = strlen(menuGetRootPath());
|
||||
if (!found && strncmp(me->path, menuGetRootPath(), tmplen)==0 && me->path[tmplen]=='/') {
|
||||
DIR* dir;
|
||||
struct dirent* dp;
|
||||
u32 nro_count=0;
|
||||
|
||||
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;
|
||||
|
||||
const char* ext = getExtension(dp->d_name);
|
||||
if (strcasecmp(ext, ".nro")==0) {
|
||||
nro_count++;
|
||||
if (nro_count>1) {
|
||||
found = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
snprintf(tempbuf, sizeof(tempbuf)-1, "%.*s/%.*s", (int)sizeof(tempbuf)/2, me->path, (int)sizeof(tempbuf)/2-7, dp->d_name);
|
||||
found = fileExists(tempbuf);
|
||||
}
|
||||
}
|
||||
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);
|
||||
} /*else
|
||||
strcpy(me->name, textGetString(StrId_Directory));*/
|
||||
}
|
||||
}
|
||||
|
||||
if (me->type == ENTRY_TYPE_FILE)
|
||||
{
|
||||
strcpy(me->name, name);
|
||||
strcpy(me->author, textGetString(StrId_DefaultPublisher));
|
||||
strcpy(me->version, textGetString(StrId_DefaultVersion));
|
||||
strncpy(me->author, textGetString(StrId_DefaultPublisher), sizeof(me->author)-1);
|
||||
strncpy(me->version, "1.0.0", sizeof(me->version)-1);
|
||||
|
||||
//shortcut_s sc;
|
||||
|
||||
@ -188,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)
|
||||
@ -196,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;
|
||||
}*/
|
||||
|
||||
@ -261,56 +404,504 @@ 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;
|
||||
free(tmp_gfx);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
me->icon_gfx = (uint8_t*)malloc(imagesize);
|
||||
if (me->icon_gfx == NULL) {
|
||||
njDone();
|
||||
return;
|
||||
if (out == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
memcpy(me->icon_gfx, imageptr, imagesize);
|
||||
if (srcWidth == destWidth && srcHeight == destHeight) {
|
||||
memcpy(out, image, destWidth*destHeight*(mode==IMAGE_MODE_RGBA32 ? 4 : 3));
|
||||
return out;
|
||||
}
|
||||
|
||||
njDone();
|
||||
int tmpx, tmpy;
|
||||
int pos;
|
||||
float sourceX, sourceY;
|
||||
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;
|
||||
|
||||
for (tmpx=0; tmpx<destWidth; tmpx++) {
|
||||
for (tmpy=0; tmpy<destHeight; tmpy++) {
|
||||
sourceX = tmpx * xScale;
|
||||
sourceY = tmpy * yScale;
|
||||
pixelX = (int)sourceX;
|
||||
pixelY = (int)sourceY;
|
||||
|
||||
// get colours from four surrounding pixels
|
||||
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];
|
||||
|
||||
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];
|
||||
|
||||
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];
|
||||
|
||||
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;
|
||||
fx1 = 1.0f - fx;
|
||||
fy1 = 1.0f - fy;
|
||||
|
||||
w1 = (int)(fx1*fy1*256.0);
|
||||
w2 = (int)(fx*fy1*256.0);
|
||||
w3 = (int)(fx1*fy*256.0);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
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];
|
||||
@ -98,19 +170,18 @@ int menuScan(const char* target) {
|
||||
{
|
||||
menuEntry_s* me = NULL;
|
||||
bool shortcut = false;
|
||||
/*if (entry->attributes & FS_ATTRIBUTE_HIDDEN)
|
||||
continue;*/
|
||||
if (dp->d_name[0]=='.')
|
||||
continue;
|
||||
|
||||
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;
|
||||
}
|
||||
|
999
common/menu.c
@ -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];
|
||||
@ -45,19 +65,63 @@ struct menuEntry_s_tag
|
||||
uint8_t *icon;
|
||||
size_t icon_size;
|
||||
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 *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);
|
||||
|
43
common/nro.h
@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#define NROHEADER_MAGICNUM 0x304f524e
|
||||
|
||||
#define ASSETHEADER_MAGICNUM 0x54455341
|
||||
#define ASSETHEADER_VERSION 0
|
||||
|
||||
typedef struct {
|
||||
u32 FileOff;
|
||||
u32 Size;
|
||||
} NsoSegment;
|
||||
|
||||
typedef struct {
|
||||
u32 unused;
|
||||
u32 modOffset;
|
||||
u8 Padding[8];
|
||||
} NroStart;
|
||||
|
||||
typedef struct {
|
||||
u32 Magic;
|
||||
u32 Unk1;
|
||||
u32 size;
|
||||
u32 Unk2;
|
||||
NsoSegment Segments[3];
|
||||
u32 bssSize;
|
||||
u32 Unk3;
|
||||
u8 BuildId[0x20];
|
||||
u8 Padding[0x20];
|
||||
} NroHeader;
|
||||
|
||||
typedef struct {
|
||||
u64 offset;
|
||||
u64 size;
|
||||
} AssetSection;
|
||||
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u32 version;
|
||||
AssetSection icon;
|
||||
AssetSection nacp;
|
||||
AssetSection romfs;
|
||||
} AssetHeader;
|
||||
|
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,8 +1,31 @@
|
||||
#include "text.h"
|
||||
|
||||
//TODO: Update this once libnx supports settings get-language.
|
||||
static u64 s_textLanguageCode = 0;
|
||||
|
||||
static int s_textLang = /*CFG_LANGUAGE_EN*/1;
|
||||
#ifdef __SWITCH__
|
||||
static int s_textLang = SetLanguage_ENUS;
|
||||
#else
|
||||
static int s_textLang = 1;
|
||||
#endif
|
||||
|
||||
Result textInit(void) {
|
||||
#ifdef __SWITCH__
|
||||
SetLanguage Language=SetLanguage_ENUS;
|
||||
|
||||
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) {
|
||||
return s_textLang;
|
||||
@ -10,6 +33,14 @@ int textGetLang(void) {
|
||||
|
||||
const char* textGetString(StrId id) {
|
||||
const char* str = g_strings[id][s_textLang];
|
||||
if (!str) str = g_strings[id][/*CFG_LANGUAGE_EN*/1];
|
||||
#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,5 +2,7 @@
|
||||
#include "common.h"
|
||||
#include "language.h"
|
||||
|
||||
Result textInit(void);
|
||||
int textGetLang(void);
|
||||
const char* textGetString(StrId id);
|
||||
u64 textGetLanguageCode(void);
|
||||
|
717
common/theme.c
Normal file
@ -0,0 +1,717 @@
|
||||
#include "theme.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:
|
||||
default:
|
||||
themeDefault = &themeLight;
|
||||
if (good_cfg)
|
||||
theme = config_lookup(&cfg, "lightTheme");
|
||||
break;
|
||||
|
||||
case THEME_PRESET_DARK:
|
||||
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);
|
||||
}
|
62
common/theme.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
#include <libconfig.h>
|
||||
|
||||
typedef enum
|
||||
{
|
||||
THEME_PRESET_LIGHT,
|
||||
THEME_PRESET_DARK,
|
||||
} ThemePreset;
|
||||
|
||||
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;
|
||||
|
||||
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,6 +1,45 @@
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "../common/common.h"
|
||||
|
||||
static u32 argBuf[ENTRY_ARGBUFSIZE/sizeof(u32)];
|
||||
static char argBuf[ENTRY_ARGBUFSIZE];
|
||||
|
||||
static char *init_args(char *dst, size_t dst_maxsize, u32 *in_args, size_t size)
|
||||
{
|
||||
size_t tmplen;
|
||||
u32 argi;
|
||||
char *in_argdata = (char*)&in_args[1];
|
||||
|
||||
size-= sizeof(u32);
|
||||
|
||||
for (argi=0; argi<in_args[0]; argi++) {
|
||||
if (size < 2) break;
|
||||
|
||||
tmplen = strnlen(in_argdata, size-1);
|
||||
|
||||
if (tmplen+3 > dst_maxsize) break;
|
||||
|
||||
if (dst_maxsize < 3) break;
|
||||
|
||||
*dst++ = '"';
|
||||
dst_maxsize--;
|
||||
|
||||
strncpy(dst, in_argdata, tmplen);
|
||||
in_argdata+= tmplen+1;
|
||||
size-= tmplen+1;
|
||||
dst+= tmplen;
|
||||
dst_maxsize-= tmplen;
|
||||
|
||||
*dst++ = '"';
|
||||
dst_maxsize--;
|
||||
|
||||
if (argi+1 < in_args[0]) {
|
||||
*dst++ = ' ';
|
||||
dst_maxsize--;
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
static bool init(void)
|
||||
{
|
||||
@ -14,12 +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;*/
|
||||
memcpy(argBuf, args->buf, sizeof(args->buf));
|
||||
Result rc = envSetNextLoad(path, (char*)&argBuf[1]);
|
||||
if(R_FAILED(rc)) fatalSimple(rc);//TODO: How should failing be handled?
|
||||
uiExitLoop();
|
||||
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)) {
|
||||
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 =
|
||||
|
261
nx_main/main.c
@ -1,83 +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_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)
|
||||
{
|
||||
gfxInitDefault();
|
||||
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.
|
||||
|
||||
appletSetScreenShotPermission(1);
|
||||
#ifdef PERF_LOG
|
||||
u64 start_tick=0;
|
||||
#endif
|
||||
|
||||
menuStartup();
|
||||
padConfigureInput(8, HidNpadStyleSet_NpadStandard);
|
||||
padInitializeAny(&g_pad);
|
||||
padRepeaterInitialize(&g_pad_repeater, 20, 10);
|
||||
hidSetNpadHandheldActivationMode(HidNpadHandheldActivationMode_Single);
|
||||
touchInit();
|
||||
|
||||
launchInit();
|
||||
memset(errormsg, 0, sizeof(errormsg));
|
||||
|
||||
appletLockExit();
|
||||
appletSetScreenShotPermission(AppletScreenShotPermission_Enable);
|
||||
|
||||
ColorSetId theme = ColorSetId_Light;
|
||||
rc = setsysInitialize();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
setsysGetColorSetId(&theme);
|
||||
setsysExit();
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
if (R_SUCCEEDED(rc)) menuStartupPath();
|
||||
|
||||
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())
|
||||
{
|
||||
//Scan all the inputs. This should be done once for each frame
|
||||
hidScanInput();
|
||||
// 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
|
||||
));
|
||||
|
||||
g_framebuf = gfxGetFramebuffer(&g_framebuf_width, NULL);
|
||||
memset(g_framebuf, 237, gfxGetFramebufferSize());
|
||||
if (!uiUpdate()) break;
|
||||
menuLoop();
|
||||
if (!error_screen) {
|
||||
if (!uiUpdate()) break;
|
||||
g_framebuf = graphicsFrameBegin(&g_framebuf_width);
|
||||
#ifdef PERF_LOG
|
||||
start_tick = armGetSystemTick();
|
||||
#endif
|
||||
memset(g_framebuf, 237, g_framebuf_width * FB_HEIGHT);
|
||||
menuLoop();
|
||||
}
|
||||
else {
|
||||
if (menuUpdateErrorScreen()) break;
|
||||
}
|
||||
|
||||
gfxFlushBuffers();
|
||||
gfxSwapBuffers();
|
||||
gfxWaitForVsync();
|
||||
if (!error_screen) {
|
||||
graphicsFrameEnd();
|
||||
|
||||
#ifdef PERF_LOG
|
||||
g_tickdiff_frame = armGetSystemTick() - start_tick;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
consoleUpdate(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
launchExit();
|
||||
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)
|
||||
{
|
||||
int i;
|
||||
menuEntry_s* me;
|
||||
for (i = 0, me = menu->firstEntry; i != menu->curEntry; i ++, me = me->next);
|
||||
launchMenuEntryTask(me);
|
||||
//workerSchedule(launchMenuEntryTask, me);
|
||||
}
|
||||
launchMenuNetloaderTask();
|
||||
}
|
||||
else if (down & KEY_B)
|
||||
else if (down & HidNpadButton_X)
|
||||
{
|
||||
//workerSchedule(changeDirTask, "..");
|
||||
menuScan("..");
|
||||
menuHandleXButton();
|
||||
}
|
||||
else if (down & KEY_PLUS)
|
||||
else if (down & HidNpadButton_A)
|
||||
{
|
||||
menuHandleAButton();
|
||||
}
|
||||
else if (down & HidNpadButton_B)
|
||||
{
|
||||
launchMenuBackTask();
|
||||
}
|
||||
else if(down & HidNpadButton_Minus){
|
||||
themeMenuStartup();
|
||||
}
|
||||
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-=4;
|
||||
if (down & KEY_UP) move+=4;
|
||||
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;
|
||||
@ -87,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,11 +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())
|
||||
@ -48,9 +58,79 @@ int main()
|
||||
window.display();
|
||||
}
|
||||
|
||||
netloaderSignalExit();
|
||||
statusExit();
|
||||
workerExit();
|
||||
netloaderExit();
|
||||
fontExit();
|
||||
assetsExit();
|
||||
PHYSFS_deinit();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern "C" bool menuUpdate(void) {
|
||||
//This is implemented here due to the hid code.
|
||||
menu_s* menu = menuGetCurrent();
|
||||
|
||||
static int esc_state = 0;
|
||||
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)
|
||||
{
|
||||
launchMenuBackTask();
|
||||
}
|
||||
else if(!new_t_state && t_state){
|
||||
themeMenuStartup();
|
||||
}
|
||||
else if (!new_return_state && return_state)
|
||||
{
|
||||
menuHandleAButton();
|
||||
}
|
||||
else if (menu->nEntries > 0)
|
||||
{
|
||||
int move = 0;
|
||||
|
||||
static int left_state = 0;
|
||||
int new_left_state = sf::Keyboard::isKeyPressed(sf::Keyboard::Left);
|
||||
if (!new_left_state && left_state)
|
||||
move--;
|
||||
left_state = new_left_state;
|
||||
|
||||
static int right_state = 0;
|
||||
int new_right_state = sf::Keyboard::isKeyPressed(sf::Keyboard::Right);
|
||||
if (!new_right_state && right_state)
|
||||
move++;
|
||||
right_state = new_right_state;
|
||||
|
||||
int newEntry = menu->curEntry + move;
|
||||
if (newEntry < 0) newEntry = 0;
|
||||
if (newEntry >= menu->nEntries) newEntry = menu->nEntries-1;
|
||||
menu->curEntry = newEntry;
|
||||
}
|
||||
|
||||
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/folder_icon.png
Normal file
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 4.2 KiB |
BIN
resources/invalid_icon.png
Normal file
After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 8.8 KiB |
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 |