Add Borealis GUI for patch extraction on Switch.
All checks were successful
Build NRO / build (push) Successful in 1m48s
All checks were successful
Build NRO / build (push) Successful in 1m48s
Replace the console UI with a Borealis-based flow, bundle ROMFS assets and borealis as a submodule, and apply small upstream patches at build time. Self-delete runs after romfsExit on quit so the NRO can be removed like the old console build.
This commit is contained in:
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@@ -18,9 +18,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: make
|
run: |
|
||||||
|
patch -d library/borealis -p1 -N < patches/borealis-swkbd-libnx.patch || true
|
||||||
|
patch -d library/borealis -p1 -N < patches/borealis-applet-frame-hints.patch || true
|
||||||
|
make
|
||||||
|
|
||||||
- name: Upload NRO
|
- name: Upload NRO
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
build/
|
build/
|
||||||
|
resources/shaders/
|
||||||
*.nro
|
*.nro
|
||||||
*.nacp
|
*.nacp
|
||||||
*.elf
|
*.elf
|
||||||
|
|||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "library/borealis"]
|
||||||
|
path = library/borealis
|
||||||
|
url = https://github.com/natinusala/borealis.git
|
||||||
80
Makefile
80
Makefile
@@ -21,6 +21,10 @@ SOURCES := source
|
|||||||
DATA := data
|
DATA := data
|
||||||
INCLUDES := include
|
INCLUDES := include
|
||||||
|
|
||||||
|
ROMFS := resources
|
||||||
|
BOREALIS_PATH := library/borealis
|
||||||
|
OUT_SHADERS := shaders
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
# options for code generation
|
# options for code generation
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
@@ -31,7 +35,7 @@ CFLAGS := -g -Wall -O2 -ffunction-sections \
|
|||||||
|
|
||||||
CFLAGS += $(INCLUDE) -D__SWITCH__
|
CFLAGS += $(INCLUDE) -D__SWITCH__
|
||||||
|
|
||||||
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
|
CXXFLAGS := $(CFLAGS) -std=c++1z -O2 -Wno-volatile -include optional
|
||||||
|
|
||||||
ASFLAGS := -g $(ARCH)
|
ASFLAGS := -g $(ARCH)
|
||||||
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
|
||||||
@@ -41,10 +45,14 @@ LIBS := -lminizip -lz -lnx
|
|||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
LIBDIRS := $(PORTLIBS) $(LIBNX)
|
LIBDIRS := $(PORTLIBS) $(LIBNX)
|
||||||
|
|
||||||
|
include $(TOPDIR)/$(BOREALIS_PATH)/library/borealis.mk
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
ifneq ($(BUILD),$(notdir $(CURDIR)))
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := all
|
||||||
|
|
||||||
export OUTPUT := $(CURDIR)/$(TARGET)
|
export OUTPUT := $(CURDIR)/$(TARGET)
|
||||||
export TOPDIR := $(CURDIR)
|
export TOPDIR := $(CURDIR)
|
||||||
|
|
||||||
@@ -56,6 +64,7 @@ export DEPSDIR := $(CURDIR)/$(BUILD)
|
|||||||
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
|
||||||
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
|
||||||
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
|
||||||
|
GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl)))
|
||||||
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
@@ -75,6 +84,18 @@ export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
|
|||||||
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
|
||||||
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
|
||||||
|
|
||||||
|
ifneq ($(strip $(ROMFS)),)
|
||||||
|
ROMFS_TARGETS :=
|
||||||
|
ROMFS_FOLDERS :=
|
||||||
|
ifneq ($(strip $(OUT_SHADERS)),)
|
||||||
|
ROMFS_SHADERS := $(ROMFS)/$(OUT_SHADERS)
|
||||||
|
ROMFS_TARGETS += $(patsubst %.glsl, $(ROMFS_SHADERS)/%.dksh, $(GLSLFILES))
|
||||||
|
ROMFS_FOLDERS += $(ROMFS_SHADERS)
|
||||||
|
endif
|
||||||
|
|
||||||
|
export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file))
|
||||||
|
endif
|
||||||
|
|
||||||
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
|
||||||
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
|
||||||
-I$(CURDIR)/$(BUILD)
|
-I$(CURDIR)/$(BUILD)
|
||||||
@@ -123,22 +144,63 @@ ifneq ($(ROMFS),)
|
|||||||
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: $(BUILD) clean all
|
.PHONY: $(BUILD) clean all patch-borealis
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
all: $(BUILD)
|
patch-borealis:
|
||||||
|
@if ! grep -q 'SWKBD_CONFIG_SET_STRING_LEN_MAX_EXT' $(BOREALIS_PATH)/library/lib/platforms/switch/swkbd.cpp 2>/dev/null; then \
|
||||||
|
patch -d $(BOREALIS_PATH) -p1 -N -i $(CURDIR)/patches/borealis-swkbd-libnx.patch >/dev/null 2>&1 || true; \
|
||||||
|
fi
|
||||||
|
@if ! grep -q 'footer_hint_right' $(BOREALIS_PATH)/library/lib/views/applet_frame.cpp 2>/dev/null; then \
|
||||||
|
patch -d $(BOREALIS_PATH) -p1 -N -i $(CURDIR)/patches/borealis-applet-frame-hints.patch >/dev/null 2>&1 || true; \
|
||||||
|
fi
|
||||||
|
|
||||||
$(BUILD):
|
all: $(ROMFS_TARGETS) | $(BUILD)
|
||||||
|
|
||||||
|
$(BUILD): patch-borealis
|
||||||
@[ -d $@ ] || mkdir -p $@
|
@[ -d $@ ] || mkdir -p $@
|
||||||
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
@MSYS2_ARG_CONV_EXCL="-D;$(MSYS2_ARG_CONV_EXCL)" $(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
|
||||||
|
|
||||||
|
ifneq ($(strip $(ROMFS_TARGETS)),)
|
||||||
|
|
||||||
|
$(ROMFS_TARGETS): | $(ROMFS_FOLDERS)
|
||||||
|
|
||||||
|
$(ROMFS_FOLDERS):
|
||||||
|
@mkdir -p $@
|
||||||
|
|
||||||
|
$(ROMFS_SHADERS)/%_vsh.dksh: %_vsh.glsl
|
||||||
|
@echo {vert} $(notdir $<)
|
||||||
|
@uam -s vert -o $@ $<
|
||||||
|
|
||||||
|
$(ROMFS_SHADERS)/%_tcsh.dksh: %_tcsh.glsl
|
||||||
|
@echo {tess_ctrl} $(notdir $<)
|
||||||
|
@uam -s tess_ctrl -o $@ $<
|
||||||
|
|
||||||
|
$(ROMFS_SHADERS)/%_tesh.dksh: %_tesh.glsl
|
||||||
|
@echo {tess_eval} $(notdir $<)
|
||||||
|
@uam -s tess_eval -o $@ $<
|
||||||
|
|
||||||
|
$(ROMFS_SHADERS)/%_gsh.dksh: %_gsh.glsl
|
||||||
|
@echo {geom} $(notdir $<)
|
||||||
|
@uam -s geom -o $@ $<
|
||||||
|
|
||||||
|
$(ROMFS_SHADERS)/%_fsh.dksh: %_fsh.glsl
|
||||||
|
@echo {frag} $(notdir $<)
|
||||||
|
@uam -s frag -o $@ $<
|
||||||
|
|
||||||
|
$(ROMFS_SHADERS)/%.dksh: %.glsl
|
||||||
|
@echo {comp} $(notdir $<)
|
||||||
|
@uam -s comp -o $@ $<
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
#---------------------------------------------------------------------------------
|
#---------------------------------------------------------------------------------
|
||||||
clean:
|
clean:
|
||||||
@echo clean ...
|
@echo clean ...
|
||||||
ifeq ($(strip $(APP_JSON)),)
|
ifeq ($(strip $(APP_JSON)),)
|
||||||
@rm -fr $(BUILD) $(TARGET).nro $(TARGET).nacp $(TARGET).elf
|
@rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nro $(TARGET).nacp $(TARGET).elf
|
||||||
else
|
else
|
||||||
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf
|
@rm -fr $(BUILD) $(ROMFS_FOLDERS) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
||||||
@@ -154,9 +216,9 @@ ifeq ($(strip $(APP_JSON)),)
|
|||||||
all : $(OUTPUT).nro
|
all : $(OUTPUT).nro
|
||||||
|
|
||||||
ifeq ($(strip $(NO_NACP)),)
|
ifeq ($(strip $(NO_NACP)),)
|
||||||
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
|
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS)
|
||||||
else
|
else
|
||||||
$(OUTPUT).nro : $(OUTPUT).elf
|
$(OUTPUT).nro : $(OUTPUT).elf $(ROMFS_DEPS)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Nintendo Switch Homebrew App für [OmniNX OC](https://git.niklascfw.de/OmniNX/Om
|
|||||||
1. Sucht nach `sd:/SaltySD/plugins/FPSLocker/patches.zip`
|
1. Sucht nach `sd:/SaltySD/plugins/FPSLocker/patches.zip`
|
||||||
2. Entpackt alle Patches in das gleiche Verzeichnis (überschreibt existierende Dateien)
|
2. Entpackt alle Patches in das gleiche Verzeichnis (überschreibt existierende Dateien)
|
||||||
3. Löscht `patches.zip` nach erfolgreichem Entpacken
|
3. Löscht `patches.zip` nach erfolgreichem Entpacken
|
||||||
4. Löscht sich selbst (`sd:/switch/PatchExtractor.nro`)
|
4. Löscht `sd:/switch/PatchExtractor.nro` beim Beenden (nach `romfsExit`, da eingebettetes ROMFS die Datei sonst offen hält)
|
||||||
|
|
||||||
## Nutzung
|
## Nutzung
|
||||||
|
|
||||||
@@ -15,14 +15,17 @@ Die App ist bereits in OmniNX OC enthalten und kann direkt aus Sphaira (hbmenu)
|
|||||||
|
|
||||||
## Selber bauen
|
## Selber bauen
|
||||||
|
|
||||||
Benötigt [devkitPro](https://devkitpro.org/) mit libnx, zlib und minizip.
|
Benötigt [devkitPro](https://devkitpro.org/) mit libnx, zlib, minizip und [borealis](https://github.com/natinusala/borealis) (als Git-Submodul).
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
git submodule update --init --recursive
|
||||||
export DEVKITPRO=/opt/devkitpro
|
export DEVKITPRO=/opt/devkitpro
|
||||||
export PATH=$DEVKITPRO/devkitA64/bin:$DEVKITPRO/tools/bin:$PATH
|
export PATH=$DEVKITPRO/devkitA64/bin:$DEVKITPRO/tools/bin:$PATH
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Die Oberfläche nutzt die Borealis UI-Bibliothek (Switch-Systemdesign).
|
||||||
|
|
||||||
## Lizenz
|
## Lizenz
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
1
library/borealis
Submodule
1
library/borealis
Submodule
Submodule library/borealis added at 20e2d33b6c
383
patches/borealis-applet-frame-hints.patch
Normal file
383
patches/borealis-applet-frame-hints.patch
Normal file
@@ -0,0 +1,383 @@
|
|||||||
|
diff --git a/library/include/borealis/views/applet_frame.hpp b/library/include/borealis/views/applet_frame.hpp
|
||||||
|
index 1402928..beacd1a 100644
|
||||||
|
--- a/library/include/borealis/views/applet_frame.hpp
|
||||||
|
+++ b/library/include/borealis/views/applet_frame.hpp
|
||||||
|
@@ -19,6 +19,7 @@
|
||||||
|
|
||||||
|
#include <borealis/core/bind.hpp>
|
||||||
|
#include <borealis/core/box.hpp>
|
||||||
|
+#include <borealis/core/event.hpp>
|
||||||
|
#include <borealis/views/image.hpp>
|
||||||
|
#include <borealis/views/label.hpp>
|
||||||
|
|
||||||
|
@@ -30,6 +31,7 @@ class AppletFrame : public Box
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AppletFrame();
|
||||||
|
+ ~AppletFrame() override;
|
||||||
|
|
||||||
|
void handleXMLElement(tinyxml2::XMLElement* element) override;
|
||||||
|
|
||||||
|
@@ -48,8 +50,20 @@ class AppletFrame : public Box
|
||||||
|
static View* create();
|
||||||
|
|
||||||
|
private:
|
||||||
|
+ void updateFooterHints();
|
||||||
|
+ void clearHintBox(Box* box);
|
||||||
|
+ void addHintItem(Box* box, const Action& action, float spacingAfter);
|
||||||
|
+
|
||||||
|
BRLS_BIND(Label, title, "brls/applet_frame/title_label");
|
||||||
|
BRLS_BIND(Image, icon, "brls/applet_frame/title_icon");
|
||||||
|
+ BRLS_BIND(Box, footerHintRight, "brls/applet_frame/footer_hint_right");
|
||||||
|
+ BRLS_BIND(Box, footerHintLeft, "brls/applet_frame/footer_hint_left");
|
||||||
|
+
|
||||||
|
+ GenericEvent::Subscription focusSubscription;
|
||||||
|
+ VoidEvent::Subscription hintsSubscription;
|
||||||
|
+
|
||||||
|
+ int switchFont = FONT_INVALID;
|
||||||
|
+ int regularFont = FONT_INVALID;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
View* contentView = nullptr;
|
||||||
|
diff --git a/library/include/borealis/views/label.hpp b/library/include/borealis/views/label.hpp
|
||||||
|
index 3e1ccef..797d328 100644
|
||||||
|
--- a/library/include/borealis/views/label.hpp
|
||||||
|
+++ b/library/include/borealis/views/label.hpp
|
||||||
|
@@ -85,6 +85,7 @@ class Label : public View
|
||||||
|
void setVerticalAlign(VerticalAlign align);
|
||||||
|
|
||||||
|
void setFontSize(float value);
|
||||||
|
+ void setFontFace(int fontFace);
|
||||||
|
void setLineHeight(float value);
|
||||||
|
void setTextColor(NVGcolor color);
|
||||||
|
|
||||||
|
diff --git a/library/lib/views/applet_frame.cpp b/library/lib/views/applet_frame.cpp
|
||||||
|
index f9f47dc..11b8cd6 100644
|
||||||
|
--- a/library/lib/views/applet_frame.cpp
|
||||||
|
+++ b/library/lib/views/applet_frame.cpp
|
||||||
|
@@ -15,6 +15,11 @@
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
+#include <algorithm>
|
||||||
|
+#include <set>
|
||||||
|
+
|
||||||
|
+#include <borealis/core/application.hpp>
|
||||||
|
+#include <borealis/core/font.hpp>
|
||||||
|
#include <borealis/core/logger.hpp>
|
||||||
|
#include <borealis/core/util.hpp>
|
||||||
|
#include <borealis/views/applet_frame.hpp>
|
||||||
|
@@ -22,6 +27,73 @@
|
||||||
|
namespace brls
|
||||||
|
{
|
||||||
|
|
||||||
|
+namespace
|
||||||
|
+{
|
||||||
|
+
|
||||||
|
+constexpr float HINT_FONT_SIZE = 22.0f;
|
||||||
|
+constexpr float HINT_SPACING = 30.0f;
|
||||||
|
+constexpr float HINT_ICON_GAP = 4.0f;
|
||||||
|
+
|
||||||
|
+const char* getButtonIcon(ControllerButton button)
|
||||||
|
+{
|
||||||
|
+ switch (button)
|
||||||
|
+ {
|
||||||
|
+ case BUTTON_A:
|
||||||
|
+ return "\uE0E0";
|
||||||
|
+ case BUTTON_B:
|
||||||
|
+ return "\uE0E1";
|
||||||
|
+ case BUTTON_X:
|
||||||
|
+ return "\uE0E2";
|
||||||
|
+ case BUTTON_Y:
|
||||||
|
+ return "\uE0E3";
|
||||||
|
+ case BUTTON_START:
|
||||||
|
+ return "\uE0EF";
|
||||||
|
+ case BUTTON_BACK:
|
||||||
|
+ return "\uE0F0";
|
||||||
|
+ case BUTTON_UP:
|
||||||
|
+ return "\uE0EB";
|
||||||
|
+ case BUTTON_DOWN:
|
||||||
|
+ return "\uE0EC";
|
||||||
|
+ case BUTTON_LEFT:
|
||||||
|
+ return "\uE0ED";
|
||||||
|
+ case BUTTON_RIGHT:
|
||||||
|
+ return "\uE0EE";
|
||||||
|
+ default:
|
||||||
|
+ return "\uE152";
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+std::string getButtonHintText(const Action& action)
|
||||||
|
+{
|
||||||
|
+ if (!action.hintText.empty())
|
||||||
|
+ return action.hintText;
|
||||||
|
+
|
||||||
|
+ switch (action.button)
|
||||||
|
+ {
|
||||||
|
+ case BUTTON_A:
|
||||||
|
+ return "Ok";
|
||||||
|
+ case BUTTON_B:
|
||||||
|
+ return "Zurück";
|
||||||
|
+ case BUTTON_START:
|
||||||
|
+ return "Beenden";
|
||||||
|
+ default:
|
||||||
|
+ return "";
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+bool actionsSort(const Action& a, const Action& b)
|
||||||
|
+{
|
||||||
|
+ if (a.button == BUTTON_START)
|
||||||
|
+ return true;
|
||||||
|
+ if (b.button == BUTTON_A)
|
||||||
|
+ return true;
|
||||||
|
+ if (b.button == BUTTON_B && a.button != BUTTON_A)
|
||||||
|
+ return true;
|
||||||
|
+ return false;
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+} // namespace
|
||||||
|
+
|
||||||
|
const std::string appletFrameXML = R"xml(
|
||||||
|
<brls:Box
|
||||||
|
width="auto"
|
||||||
|
@@ -36,9 +108,9 @@ const std::string appletFrameXML = R"xml(
|
||||||
|
axis="row"
|
||||||
|
paddingTop="@style/brls/applet_frame/header_padding_top_bottom"
|
||||||
|
paddingBottom="@style/brls/applet_frame/header_padding_top_bottom"
|
||||||
|
- paddingLeft="@style/brls/applet_frame/header_padding_sides"
|
||||||
|
+ paddingLeft="26px"
|
||||||
|
paddingRight="@style/brls/applet_frame/header_padding_sides"
|
||||||
|
- marginLeft="@style/brls/applet_frame/padding_sides"
|
||||||
|
+ marginLeft="22px"
|
||||||
|
marginRight="@style/brls/applet_frame/padding_sides"
|
||||||
|
lineColor="@theme/brls/applet_frame/separator"
|
||||||
|
lineBottom="1px">
|
||||||
|
@@ -61,16 +133,12 @@ const std::string appletFrameXML = R"xml(
|
||||||
|
|
||||||
|
<!-- Content will be injected here with grow="1.0" -->
|
||||||
|
|
||||||
|
- <!--
|
||||||
|
- Footer
|
||||||
|
- Direction inverted so that the bottom left text can be
|
||||||
|
- set to visibility="gone" without affecting the hint
|
||||||
|
- -->
|
||||||
|
+ <!-- Footer -->
|
||||||
|
<brls:Box
|
||||||
|
width="auto"
|
||||||
|
height="@style/brls/applet_frame/footer_height"
|
||||||
|
axis="row"
|
||||||
|
- direction="rightToLeft"
|
||||||
|
+ direction="leftToRight"
|
||||||
|
paddingLeft="@style/brls/applet_frame/footer_padding_sides"
|
||||||
|
paddingRight="@style/brls/applet_frame/footer_padding_sides"
|
||||||
|
paddingTop="@style/brls/applet_frame/footer_padding_top_bottom"
|
||||||
|
@@ -81,15 +149,25 @@ const std::string appletFrameXML = R"xml(
|
||||||
|
lineTop="1px"
|
||||||
|
justifyContent="spaceBetween" >
|
||||||
|
|
||||||
|
- <brls:Rectangle
|
||||||
|
- width="272px"
|
||||||
|
+ <brls:Box
|
||||||
|
+ id="brls/applet_frame/footer_hint_left"
|
||||||
|
+ width="auto"
|
||||||
|
height="auto"
|
||||||
|
- color="#FF0000" />
|
||||||
|
+ axis="row"
|
||||||
|
+ direction="leftToRight"
|
||||||
|
+ justifyContent="flexStart"
|
||||||
|
+ alignItems="center"
|
||||||
|
+ visibility="gone" />
|
||||||
|
|
||||||
|
- <brls:Rectangle
|
||||||
|
- width="75px"
|
||||||
|
+ <brls:Box
|
||||||
|
+ id="brls/applet_frame/footer_hint_right"
|
||||||
|
+ width="auto"
|
||||||
|
height="auto"
|
||||||
|
- color="#FF00FF" />
|
||||||
|
+ grow="1.0"
|
||||||
|
+ axis="row"
|
||||||
|
+ direction="leftToRight"
|
||||||
|
+ justifyContent="flexEnd"
|
||||||
|
+ alignItems="center" />
|
||||||
|
|
||||||
|
</brls:Box>
|
||||||
|
|
||||||
|
@@ -109,6 +187,141 @@ AppletFrame::AppletFrame()
|
||||||
|
});
|
||||||
|
|
||||||
|
this->forwardXMLAttribute("iconInterpolation", this->icon, "interpolation");
|
||||||
|
+
|
||||||
|
+ this->switchFont = Application::getFont(FONT_SWITCH_ICONS);
|
||||||
|
+ this->regularFont = Application::getFont(FONT_REGULAR);
|
||||||
|
+
|
||||||
|
+ this->focusSubscription = Application::getGlobalFocusChangeEvent()->subscribe([this](View*) {
|
||||||
|
+ this->updateFooterHints();
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ this->hintsSubscription = Application::getGlobalHintsUpdateEvent()->subscribe([this]() {
|
||||||
|
+ this->updateFooterHints();
|
||||||
|
+ });
|
||||||
|
+
|
||||||
|
+ this->updateFooterHints();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+AppletFrame::~AppletFrame()
|
||||||
|
+{
|
||||||
|
+ Application::getGlobalFocusChangeEvent()->unsubscribe(this->focusSubscription);
|
||||||
|
+ Application::getGlobalHintsUpdateEvent()->unsubscribe(this->hintsSubscription);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void AppletFrame::clearHintBox(Box* box)
|
||||||
|
+{
|
||||||
|
+ if (!box)
|
||||||
|
+ return;
|
||||||
|
+
|
||||||
|
+ std::vector<View*> children = box->getChildren();
|
||||||
|
+ while (!children.empty())
|
||||||
|
+ {
|
||||||
|
+ View* child = children.back();
|
||||||
|
+ box->removeView(child);
|
||||||
|
+ children.pop_back();
|
||||||
|
+ }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void AppletFrame::addHintItem(Box* box, const Action& action, float spacingAfter)
|
||||||
|
+{
|
||||||
|
+ const std::string hintText = getButtonHintText(action);
|
||||||
|
+ if (hintText.empty())
|
||||||
|
+ return;
|
||||||
|
+
|
||||||
|
+ Box* item = new Box(Axis::ROW);
|
||||||
|
+ item->setDirection(Direction::LEFT_TO_RIGHT);
|
||||||
|
+ item->setAlignItems(AlignItems::CENTER);
|
||||||
|
+ if (spacingAfter > 0.0f)
|
||||||
|
+ item->setMarginRight(spacingAfter);
|
||||||
|
+
|
||||||
|
+ Label* icon = new Label();
|
||||||
|
+ icon->setText(getButtonIcon(action.button));
|
||||||
|
+ icon->setFontSize(HINT_FONT_SIZE);
|
||||||
|
+ icon->setSingleLine(true);
|
||||||
|
+ if (this->switchFont != FONT_INVALID)
|
||||||
|
+ icon->setFontFace(this->switchFont);
|
||||||
|
+
|
||||||
|
+ Label* text = new Label();
|
||||||
|
+ text->setText(hintText);
|
||||||
|
+ text->setFontSize(HINT_FONT_SIZE);
|
||||||
|
+ text->setSingleLine(true);
|
||||||
|
+ text->setMarginLeft(HINT_ICON_GAP);
|
||||||
|
+ if (this->regularFont != FONT_INVALID)
|
||||||
|
+ text->setFontFace(this->regularFont);
|
||||||
|
+
|
||||||
|
+ item->addView(icon);
|
||||||
|
+ item->addView(text);
|
||||||
|
+ box->addView(item);
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+void AppletFrame::updateFooterHints()
|
||||||
|
+{
|
||||||
|
+ this->clearHintBox(this->footerHintRight);
|
||||||
|
+ this->clearHintBox(this->footerHintLeft);
|
||||||
|
+
|
||||||
|
+ std::set<ControllerButton> addedKeys;
|
||||||
|
+ std::vector<Action> rightActions;
|
||||||
|
+ std::vector<Action> leftActions;
|
||||||
|
+
|
||||||
|
+ View* focusParent = Application::getCurrentFocus();
|
||||||
|
+ if (!focusParent)
|
||||||
|
+ focusParent = this->contentView;
|
||||||
|
+
|
||||||
|
+ while (focusParent)
|
||||||
|
+ {
|
||||||
|
+ for (const Action& action : focusParent->getActions())
|
||||||
|
+ {
|
||||||
|
+ if (action.hidden || !action.available)
|
||||||
|
+ continue;
|
||||||
|
+
|
||||||
|
+ if (addedKeys.find(action.button) != addedKeys.end())
|
||||||
|
+ continue;
|
||||||
|
+
|
||||||
|
+ addedKeys.insert(action.button);
|
||||||
|
+
|
||||||
|
+ if (action.button == BUTTON_START)
|
||||||
|
+ leftActions.push_back(action);
|
||||||
|
+ else
|
||||||
|
+ rightActions.push_back(action);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ focusParent = focusParent->getParent();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ std::stable_sort(rightActions.begin(), rightActions.end(), actionsSort);
|
||||||
|
+ std::stable_sort(leftActions.begin(), leftActions.end(), actionsSort);
|
||||||
|
+
|
||||||
|
+ for (size_t i = 0; i < rightActions.size(); i++)
|
||||||
|
+ {
|
||||||
|
+ float spacing = (i + 1 < rightActions.size()) ? HINT_SPACING : 0.0f;
|
||||||
|
+ this->addHintItem(this->footerHintRight, rightActions[i], spacing);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ for (size_t i = 0; i < leftActions.size(); i++)
|
||||||
|
+ {
|
||||||
|
+ float spacing = (i + 1 < leftActions.size()) ? HINT_SPACING : 0.0f;
|
||||||
|
+ this->addHintItem(this->footerHintLeft, leftActions[i], spacing);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ std::string* commonFooter = Application::getCommonFooter();
|
||||||
|
+ if (commonFooter && !commonFooter->empty())
|
||||||
|
+ {
|
||||||
|
+ Label* text = new Label();
|
||||||
|
+ text->setText(*commonFooter);
|
||||||
|
+ text->setFontSize(HINT_FONT_SIZE);
|
||||||
|
+ text->setSingleLine(true);
|
||||||
|
+ if (!leftActions.empty())
|
||||||
|
+ text->setMarginLeft(HINT_SPACING);
|
||||||
|
+ if (this->regularFont != FONT_INVALID)
|
||||||
|
+ text->setFontFace(this->regularFont);
|
||||||
|
+ this->footerHintLeft->addView(text);
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ const bool hasRight = !this->footerHintRight->getChildren().empty();
|
||||||
|
+ const bool hasLeft = !this->footerHintLeft->getChildren().empty();
|
||||||
|
+
|
||||||
|
+ this->footerHintRight->setVisibility(hasRight ? Visibility::VISIBLE : Visibility::GONE);
|
||||||
|
+ this->footerHintLeft->setVisibility(hasLeft ? Visibility::VISIBLE : Visibility::GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppletFrame::setIconFromRes(std::string name)
|
||||||
|
@@ -132,7 +345,6 @@ void AppletFrame::setContentView(View* view)
|
||||||
|
{
|
||||||
|
if (this->contentView)
|
||||||
|
{
|
||||||
|
- // Remove the node
|
||||||
|
this->removeView(this->contentView);
|
||||||
|
this->contentView = nullptr;
|
||||||
|
}
|
||||||
|
@@ -146,6 +358,7 @@ void AppletFrame::setContentView(View* view)
|
||||||
|
this->contentView->setGrow(1.0f);
|
||||||
|
|
||||||
|
this->addView(this->contentView, 1);
|
||||||
|
+ this->updateFooterHints();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppletFrame::handleXMLElement(tinyxml2::XMLElement* element)
|
||||||
|
diff --git a/library/lib/views/label.cpp b/library/lib/views/label.cpp
|
||||||
|
index c31d904..25e4bdf 100644
|
||||||
|
--- a/library/lib/views/label.cpp
|
||||||
|
+++ b/library/lib/views/label.cpp
|
||||||
|
@@ -316,6 +316,14 @@ void Label::setFontSize(float value)
|
||||||
|
this->invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
+void Label::setFontFace(int fontFace)
|
||||||
|
+{
|
||||||
|
+ if (fontFace != FONT_INVALID)
|
||||||
|
+ this->font = fontFace;
|
||||||
|
+
|
||||||
|
+ this->invalidate();
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
void Label::setLineHeight(float value)
|
||||||
|
{
|
||||||
|
this->lineHeight = value;
|
||||||
12
patches/borealis-swkbd-libnx.patch
Normal file
12
patches/borealis-swkbd-libnx.patch
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
--- a/library/lib/platforms/switch/swkbd.cpp
|
||||||
|
+++ b/library/lib/platforms/switch/swkbd.cpp
|
||||||
|
@@ -39,7 +39,9 @@ static SwkbdConfig createSwkbdBaseConfig(std::string headerText, std::string sub
|
||||||
|
swkbdConfigSetSubText(&config, subText.c_str());
|
||||||
|
swkbdConfigSetStringLenMax(&config, maxStringLength);
|
||||||
|
swkbdConfigSetInitialText(&config, initialText.c_str());
|
||||||
|
+#if defined(SWKBD_CONFIG_SET_STRING_LEN_MAX_EXT)
|
||||||
|
swkbdConfigSetStringLenMaxExt(&config, 1);
|
||||||
|
+#endif
|
||||||
|
swkbdConfigSetBlurBackground(&config, true);
|
||||||
|
|
||||||
|
return config;
|
||||||
15
resources/i18n/de/brls.json
Normal file
15
resources/i18n/de/brls.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"hints": {
|
||||||
|
"ok": "Ok",
|
||||||
|
"back": "Zurück",
|
||||||
|
"exit": "Beenden"
|
||||||
|
},
|
||||||
|
|
||||||
|
"crash_frame": {
|
||||||
|
"button": "Ok"
|
||||||
|
},
|
||||||
|
|
||||||
|
"thumbnail_sidebar": {
|
||||||
|
"save": "Speichern"
|
||||||
|
}
|
||||||
|
}
|
||||||
15
resources/i18n/en-US/brls.json
Normal file
15
resources/i18n/en-US/brls.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"hints": {
|
||||||
|
"ok": "Ok",
|
||||||
|
"back": "Back",
|
||||||
|
"exit": "Exit"
|
||||||
|
},
|
||||||
|
|
||||||
|
"crash_frame": {
|
||||||
|
"button": "Ok"
|
||||||
|
},
|
||||||
|
|
||||||
|
"thumbnail_sidebar": {
|
||||||
|
"save": "Save"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
resources/img/omninx.png
Normal file
BIN
resources/img/omninx.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
1
resources/inter
Symbolic link
1
resources/inter
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../library/borealis/resources/inter
|
||||||
1
resources/material
Symbolic link
1
resources/material
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../library/borealis/resources/material
|
||||||
134
source/extractor.cpp
Normal file
134
source/extractor.cpp
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
#include "extractor.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
#include <switch.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static constexpr size_t READ_BUF_SIZE = 8192;
|
||||||
|
|
||||||
|
int PatchExtractor::mkdirs(const char* path) {
|
||||||
|
char tmp[512];
|
||||||
|
snprintf(tmp, sizeof(tmp), "%s", path);
|
||||||
|
size_t len = strlen(tmp);
|
||||||
|
if (len == 0) return 0;
|
||||||
|
if (tmp[len - 1] == '/') tmp[len - 1] = '\0';
|
||||||
|
|
||||||
|
for (char* p = tmp + 1; *p; p++) {
|
||||||
|
if (*p == '/') {
|
||||||
|
*p = '\0';
|
||||||
|
mkdir(tmp, 0755);
|
||||||
|
*p = '/';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mkdir(tmp, 0755);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PatchExtractor::open() {
|
||||||
|
zip = unzOpen(PATCHES_ZIP);
|
||||||
|
if (!zip) return false;
|
||||||
|
|
||||||
|
unz_global_info gi{};
|
||||||
|
if (unzGetGlobalInfo(zip, &gi) != UNZ_OK) {
|
||||||
|
close();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
total = gi.number_entry;
|
||||||
|
extracted = 0;
|
||||||
|
skipped = 0;
|
||||||
|
finished = false;
|
||||||
|
err = unzGoToFirstFile(zip);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PatchExtractor::close() {
|
||||||
|
if (zip) {
|
||||||
|
unzClose(zip);
|
||||||
|
zip = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int PatchExtractor::getProgressPercent() const {
|
||||||
|
if (total == 0) return 100;
|
||||||
|
return static_cast<int>((extracted * 100) / total);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PatchExtractor::step() {
|
||||||
|
if (!zip || finished) return false;
|
||||||
|
|
||||||
|
if (err != UNZ_OK) {
|
||||||
|
finished = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char filename[512];
|
||||||
|
char fullpath[1024];
|
||||||
|
unz_file_info fi{};
|
||||||
|
|
||||||
|
unzGetCurrentFileInfo(zip, &fi, filename, sizeof(filename), nullptr, 0, nullptr, 0);
|
||||||
|
snprintf(fullpath, sizeof(fullpath), "%s%s", EXTRACT_DIR, filename);
|
||||||
|
currentFile = filename;
|
||||||
|
|
||||||
|
size_t flen = strlen(filename);
|
||||||
|
if (flen > 0 && filename[flen - 1] == '/') {
|
||||||
|
mkdirs(fullpath);
|
||||||
|
} else {
|
||||||
|
char dirpart[1024];
|
||||||
|
snprintf(dirpart, sizeof(dirpart), "%s", fullpath);
|
||||||
|
char* last_slash = strrchr(dirpart, '/');
|
||||||
|
if (last_slash) {
|
||||||
|
*last_slash = '\0';
|
||||||
|
mkdirs(dirpart);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unzOpenCurrentFile(zip) == UNZ_OK) {
|
||||||
|
FILE* out = fopen(fullpath, "wb");
|
||||||
|
if (out) {
|
||||||
|
unsigned char buf[READ_BUF_SIZE];
|
||||||
|
int bytes;
|
||||||
|
while ((bytes = unzReadCurrentFile(zip, buf, READ_BUF_SIZE)) > 0) {
|
||||||
|
fwrite(buf, 1, static_cast<size_t>(bytes), out);
|
||||||
|
}
|
||||||
|
fclose(out);
|
||||||
|
} else {
|
||||||
|
skipped++;
|
||||||
|
}
|
||||||
|
unzCloseCurrentFile(zip);
|
||||||
|
} else {
|
||||||
|
skipped++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extracted++;
|
||||||
|
err = unzGoToNextFile(zip);
|
||||||
|
if (err != UNZ_OK) finished = true;
|
||||||
|
|
||||||
|
return !finished;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PatchExtractor::cleanup() {
|
||||||
|
cleanupOk_ = true;
|
||||||
|
|
||||||
|
if (remove(PATCHES_ZIP) != 0)
|
||||||
|
cleanupOk_ = false;
|
||||||
|
|
||||||
|
// Cannot delete our own NRO while the app is still running; try anyway for edge cases.
|
||||||
|
remove(SELF_NRO);
|
||||||
|
|
||||||
|
remove("sdmc:/switch/.PatchExtractor.nro.star");
|
||||||
|
remove("sdmc:/switch/.packages/boot_package.ini");
|
||||||
|
|
||||||
|
return cleanupOk_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PatchExtractor::tryDeleteSelfOnExit() {
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
// Embedded ROMFS keeps the .nro open on SD; unmount before unlink (console build had no ROMFS).
|
||||||
|
romfsExit();
|
||||||
|
#endif
|
||||||
|
remove(SELF_NRO);
|
||||||
|
}
|
||||||
43
source/extractor.hpp
Normal file
43
source/extractor.hpp
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <minizip/unzip.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class PatchExtractor {
|
||||||
|
public:
|
||||||
|
static constexpr const char* PATCHES_ZIP = "sdmc:/SaltySD/plugins/FPSLocker/patches.zip";
|
||||||
|
static constexpr const char* EXTRACT_DIR = "sdmc:/SaltySD/plugins/FPSLocker/";
|
||||||
|
static constexpr const char* SELF_NRO = "sdmc:/switch/PatchExtractor.nro";
|
||||||
|
|
||||||
|
bool open();
|
||||||
|
void close();
|
||||||
|
|
||||||
|
/** Process one zip entry. Returns false when finished or on fatal error. */
|
||||||
|
bool step();
|
||||||
|
|
||||||
|
bool isOpen() const { return zip != nullptr; }
|
||||||
|
unsigned long getTotal() const { return total; }
|
||||||
|
unsigned long getExtracted() const { return extracted; }
|
||||||
|
unsigned long getSkipped() const { return skipped; }
|
||||||
|
int getProgressPercent() const;
|
||||||
|
const std::string& getCurrentFile() const { return currentFile; }
|
||||||
|
bool isFinished() const { return finished; }
|
||||||
|
|
||||||
|
bool cleanup();
|
||||||
|
bool cleanupOk() const { return cleanupOk_; }
|
||||||
|
|
||||||
|
/** Call after the UI has shut down; releases romfs so the NRO file can be unlinked. */
|
||||||
|
static void tryDeleteSelfOnExit();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static int mkdirs(const char* path);
|
||||||
|
|
||||||
|
unzFile zip = nullptr;
|
||||||
|
unsigned long total = 0;
|
||||||
|
unsigned long extracted = 0;
|
||||||
|
unsigned long skipped = 0;
|
||||||
|
int err = UNZ_OK;
|
||||||
|
bool finished = false;
|
||||||
|
bool cleanupOk_ = true;
|
||||||
|
std::string currentFile;
|
||||||
|
};
|
||||||
202
source/main.c
202
source/main.c
@@ -1,202 +0,0 @@
|
|||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <errno.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <switch.h>
|
|
||||||
#include <minizip/unzip.h>
|
|
||||||
|
|
||||||
#define PATCHES_ZIP "sdmc:/SaltySD/plugins/FPSLocker/patches.zip"
|
|
||||||
#define EXTRACT_DIR "sdmc:/SaltySD/plugins/FPSLocker/"
|
|
||||||
#define SELF_NRO "sdmc:/switch/PatchExtractor.nro"
|
|
||||||
#define READ_BUF_SIZE 8192
|
|
||||||
|
|
||||||
#define STATUS_ROW 11
|
|
||||||
|
|
||||||
static PrintConsole *con;
|
|
||||||
|
|
||||||
static void status(const char *fmt, ...) {
|
|
||||||
printf("\x1b[%d;0H\x1b[2K", STATUS_ROW);
|
|
||||||
va_list ap;
|
|
||||||
va_start(ap, fmt);
|
|
||||||
vprintf(fmt, ap);
|
|
||||||
va_end(ap);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int mkdirs(const char *path) {
|
|
||||||
char tmp[512];
|
|
||||||
snprintf(tmp, sizeof(tmp), "%s", path);
|
|
||||||
size_t len = strlen(tmp);
|
|
||||||
if (len == 0) return 0;
|
|
||||||
if (tmp[len - 1] == '/') tmp[len - 1] = '\0';
|
|
||||||
|
|
||||||
for (char *p = tmp + 1; *p; p++) {
|
|
||||||
if (*p == '/') {
|
|
||||||
*p = '\0';
|
|
||||||
mkdir(tmp, 0755);
|
|
||||||
*p = '/';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mkdir(tmp, 0755);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
|
||||||
con = consoleInit(NULL);
|
|
||||||
|
|
||||||
padConfigureInput(1, HidNpadStyleSet_NpadStandard);
|
|
||||||
PadState pad;
|
|
||||||
padInitializeDefault(&pad);
|
|
||||||
|
|
||||||
printf("\n");
|
|
||||||
printf(" ========================================\n");
|
|
||||||
printf(" PatchExtractor - OmniNX OC\n");
|
|
||||||
printf(" SaltyNX / FPSLocker Patch Installer\n");
|
|
||||||
printf(" ========================================\n\n");
|
|
||||||
|
|
||||||
unzFile zip = unzOpen(PATCHES_ZIP);
|
|
||||||
if (!zip) {
|
|
||||||
printf(" \x1b[31mFEHLER:\x1b[0m patches.zip nicht gefunden!\n\n");
|
|
||||||
printf(" Pfad: %s\n\n", PATCHES_ZIP);
|
|
||||||
printf(" Bist du sicher, dass du OmniNX OC installiert hast?\n\n");
|
|
||||||
printf(" Die Datei patches.zip muss unter\n");
|
|
||||||
printf(" sd:/SaltySD/plugins/FPSLocker/ liegen.\n\n");
|
|
||||||
printf(" ----------------------------------------\n");
|
|
||||||
printf(" Druecke + zum Beenden.\n");
|
|
||||||
|
|
||||||
while (appletMainLoop()) {
|
|
||||||
padUpdate(&pad);
|
|
||||||
if (padGetButtonsDown(&pad) & HidNpadButton_Plus) break;
|
|
||||||
consoleUpdate(NULL);
|
|
||||||
}
|
|
||||||
consoleExit(NULL);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
unz_global_info gi;
|
|
||||||
unzGetGlobalInfo(zip, &gi);
|
|
||||||
unsigned long total = gi.number_entry;
|
|
||||||
printf(" patches.zip gefunden! (%lu Eintraege)\n", total);
|
|
||||||
printf(" Druecke A zum Entpacken, + zum Abbrechen.\n\n");
|
|
||||||
consoleUpdate(NULL);
|
|
||||||
|
|
||||||
while (appletMainLoop()) {
|
|
||||||
padUpdate(&pad);
|
|
||||||
u64 kDown = padGetButtonsDown(&pad);
|
|
||||||
if (kDown & HidNpadButton_A) break;
|
|
||||||
if (kDown & HidNpadButton_Plus) {
|
|
||||||
unzClose(zip);
|
|
||||||
consoleExit(NULL);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
consoleUpdate(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
printf("\x1b[10;0H \x1b[93mBitte nicht beenden, bis der Vorgang abgeschlossen ist!\x1b[0m\n");
|
|
||||||
consoleUpdate(NULL);
|
|
||||||
|
|
||||||
char filename[512];
|
|
||||||
char fullpath[1024];
|
|
||||||
unsigned char buf[READ_BUF_SIZE];
|
|
||||||
unsigned long extracted = 0;
|
|
||||||
unsigned long skipped = 0;
|
|
||||||
int err = unzGoToFirstFile(zip);
|
|
||||||
|
|
||||||
while (err == UNZ_OK) {
|
|
||||||
unz_file_info fi;
|
|
||||||
unzGetCurrentFileInfo(zip, &fi, filename, sizeof(filename), NULL, 0, NULL, 0);
|
|
||||||
|
|
||||||
snprintf(fullpath, sizeof(fullpath), "%s%s", EXTRACT_DIR, filename);
|
|
||||||
unsigned long progress = (unsigned long)((extracted * 100) / total);
|
|
||||||
|
|
||||||
size_t flen = strlen(filename);
|
|
||||||
if (flen > 0 && filename[flen - 1] == '/') {
|
|
||||||
mkdirs(fullpath);
|
|
||||||
status(" [%3lu%%] DIR %s", progress, filename);
|
|
||||||
} else {
|
|
||||||
char dirpart[1024];
|
|
||||||
snprintf(dirpart, sizeof(dirpart), "%s", fullpath);
|
|
||||||
char *last_slash = strrchr(dirpart, '/');
|
|
||||||
if (last_slash) {
|
|
||||||
*last_slash = '\0';
|
|
||||||
mkdirs(dirpart);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unzOpenCurrentFile(zip) != UNZ_OK) {
|
|
||||||
status(" [%3lu%%] \x1b[31mERR\x1b[0m %s", progress, filename);
|
|
||||||
skipped++;
|
|
||||||
extracted++;
|
|
||||||
err = unzGoToNextFile(zip);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
FILE *out = fopen(fullpath, "wb");
|
|
||||||
if (!out) {
|
|
||||||
status(" [%3lu%%] \x1b[31mERR\x1b[0m %s", progress, filename);
|
|
||||||
unzCloseCurrentFile(zip);
|
|
||||||
skipped++;
|
|
||||||
extracted++;
|
|
||||||
err = unzGoToNextFile(zip);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int bytes;
|
|
||||||
while ((bytes = unzReadCurrentFile(zip, buf, READ_BUF_SIZE)) > 0) {
|
|
||||||
fwrite(buf, 1, bytes, out);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose(out);
|
|
||||||
unzCloseCurrentFile(zip);
|
|
||||||
status(" [%3lu%%] FILE %s", progress, filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
extracted++;
|
|
||||||
consoleUpdate(NULL);
|
|
||||||
err = unzGoToNextFile(zip);
|
|
||||||
}
|
|
||||||
|
|
||||||
unzClose(zip);
|
|
||||||
|
|
||||||
status(" [100%%] Fertig! %lu / %lu entpackt.", extracted - skipped, total);
|
|
||||||
consoleUpdate(NULL);
|
|
||||||
|
|
||||||
int cleanup_ok = 1;
|
|
||||||
|
|
||||||
printf("\x1b[%d;0H", STATUS_ROW + 2);
|
|
||||||
if (remove(PATCHES_ZIP) == 0) {
|
|
||||||
printf(" patches.zip geloescht.\n");
|
|
||||||
} else {
|
|
||||||
printf(" \x1b[31mpatches.zip konnte nicht geloescht werden.\x1b[0m\n");
|
|
||||||
cleanup_ok = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remove(SELF_NRO) == 0) {
|
|
||||||
printf(" PatchExtractor.nro geloescht (Selbstreinigung).\n");
|
|
||||||
} else {
|
|
||||||
printf(" \x1b[31mPatchExtractor.nro konnte nicht geloescht werden.\x1b[0m\n");
|
|
||||||
cleanup_ok = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
remove("sdmc:/switch/.PatchExtractor.nro.star");
|
|
||||||
remove("sdmc:/switch/.packages/boot_package.ini");
|
|
||||||
|
|
||||||
printf("\n ========================================\n");
|
|
||||||
if (skipped == 0 && cleanup_ok) {
|
|
||||||
printf(" \x1b[32mAlles erledigt!\x1b[0m\n");
|
|
||||||
} else {
|
|
||||||
printf(" \x1b[32mEntpacken abgeschlossen.\x1b[0m\n");
|
|
||||||
if (skipped > 0)
|
|
||||||
printf(" \x1b[31m%lu Eintraege fehlgeschlagen.\x1b[0m\n", skipped);
|
|
||||||
}
|
|
||||||
printf(" ========================================\n\n");
|
|
||||||
printf(" Druecke + zum Beenden.\n");
|
|
||||||
|
|
||||||
while (appletMainLoop()) {
|
|
||||||
padUpdate(&pad);
|
|
||||||
if (padGetButtonsDown(&pad) & HidNpadButton_Plus) break;
|
|
||||||
consoleUpdate(NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
consoleExit(NULL);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
25
source/main.cpp
Normal file
25
source/main.cpp
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#include <borealis.hpp>
|
||||||
|
|
||||||
|
#include "extractor.hpp"
|
||||||
|
#include "patch_activity.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
(void)argc;
|
||||||
|
(void)argv;
|
||||||
|
|
||||||
|
brls::Logger::setLogLevel(brls::LogLevel::WARNING);
|
||||||
|
|
||||||
|
if (!brls::Application::init()) {
|
||||||
|
brls::Logger::error("Borealis init failed");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
brls::Application::createWindow("PatchExtractor");
|
||||||
|
brls::Application::setGlobalQuit(true);
|
||||||
|
brls::Application::pushActivity(new PatchActivity());
|
||||||
|
|
||||||
|
while (brls::Application::mainLoop()) {}
|
||||||
|
|
||||||
|
PatchExtractor::tryDeleteSelfOnExit();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
201
source/patch_activity.cpp
Normal file
201
source/patch_activity.cpp
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
#include "patch_activity.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
|
||||||
|
PatchActivity::PatchActivity() {
|
||||||
|
extractor = std::make_unique<PatchExtractor>();
|
||||||
|
|
||||||
|
extractTimer.setCallback([this]() { onExtractTick(); });
|
||||||
|
extractTimer.setPeriod(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
PatchActivity::~PatchActivity() {
|
||||||
|
extractTimer.stop();
|
||||||
|
if (extractor) extractor->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
brls::View* PatchActivity::createContentView() {
|
||||||
|
frame = new brls::AppletFrame();
|
||||||
|
frame->setTitle("PatchExtractor");
|
||||||
|
frame->setIconFromRes("img/omninx.png");
|
||||||
|
|
||||||
|
contentBox = new brls::Box(brls::Axis::COLUMN);
|
||||||
|
contentBox->setAlignItems(brls::AlignItems::CENTER);
|
||||||
|
contentBox->setJustifyContent(brls::JustifyContent::CENTER);
|
||||||
|
contentBox->setMargins(40, 40, 40, 40);
|
||||||
|
contentBox->setGrow(1.0f);
|
||||||
|
|
||||||
|
titleLabel = new brls::Label();
|
||||||
|
titleLabel->setText("PatchExtractor");
|
||||||
|
titleLabel->setFontSize(28);
|
||||||
|
titleLabel->setHorizontalAlign(brls::HorizontalAlign::CENTER);
|
||||||
|
titleLabel->setMarginBottom(16);
|
||||||
|
|
||||||
|
messageLabel = new brls::Label();
|
||||||
|
messageLabel->setHorizontalAlign(brls::HorizontalAlign::CENTER);
|
||||||
|
messageLabel->setMarginBottom(12);
|
||||||
|
|
||||||
|
detailLabel = new brls::Label();
|
||||||
|
detailLabel->setHorizontalAlign(brls::HorizontalAlign::CENTER);
|
||||||
|
detailLabel->setFontSize(14);
|
||||||
|
detailLabel->setMarginBottom(24);
|
||||||
|
detailLabel->setVisibility(brls::Visibility::GONE);
|
||||||
|
|
||||||
|
progressLabel = new brls::Label();
|
||||||
|
progressLabel->setHorizontalAlign(brls::HorizontalAlign::CENTER);
|
||||||
|
progressLabel->setFontSize(16);
|
||||||
|
progressLabel->setMarginBottom(8);
|
||||||
|
progressLabel->setVisibility(brls::Visibility::GONE);
|
||||||
|
|
||||||
|
progressTrack = new brls::Box(brls::Axis::ROW);
|
||||||
|
progressTrack->setWidth(600);
|
||||||
|
progressTrack->setHeight(12);
|
||||||
|
progressTrack->setMarginBottom(24);
|
||||||
|
progressTrack->setVisibility(brls::Visibility::GONE);
|
||||||
|
|
||||||
|
progressFill = new brls::Rectangle(nvgRGB(0, 190, 80));
|
||||||
|
progressFill->setHeight(12);
|
||||||
|
progressFill->setWidth(0);
|
||||||
|
progressTrack->addView(progressFill);
|
||||||
|
|
||||||
|
actionButton = new brls::Button();
|
||||||
|
actionButton->setStyle(&brls::BUTTONSTYLE_PRIMARY);
|
||||||
|
actionButton->setWidth(320);
|
||||||
|
actionButton->registerClickAction([this](brls::View* view) { return onExtractClicked(view); });
|
||||||
|
|
||||||
|
contentBox->addView(titleLabel);
|
||||||
|
contentBox->addView(messageLabel);
|
||||||
|
contentBox->addView(detailLabel);
|
||||||
|
contentBox->addView(progressLabel);
|
||||||
|
contentBox->addView(progressTrack);
|
||||||
|
contentBox->addView(actionButton);
|
||||||
|
|
||||||
|
frame->setContentView(contentBox);
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PatchActivity::onContentAvailable() {
|
||||||
|
registerExitAction(brls::BUTTON_START);
|
||||||
|
|
||||||
|
if (extractor->open()) {
|
||||||
|
char detail[128];
|
||||||
|
snprintf(detail, sizeof(detail), "%lu Eintraege in patches.zip", extractor->getTotal());
|
||||||
|
detailLabel->setText(detail);
|
||||||
|
showScreen(Screen::Ready);
|
||||||
|
} else {
|
||||||
|
detailLabel->setText(PatchExtractor::PATCHES_ZIP);
|
||||||
|
detailLabel->setVisibility(brls::Visibility::VISIBLE);
|
||||||
|
showScreen(Screen::Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PatchActivity::showScreen(Screen screen) {
|
||||||
|
currentScreen = screen;
|
||||||
|
|
||||||
|
progressLabel->setVisibility(brls::Visibility::GONE);
|
||||||
|
progressTrack->setVisibility(brls::Visibility::GONE);
|
||||||
|
actionButton->setVisibility(brls::Visibility::VISIBLE);
|
||||||
|
|
||||||
|
switch (screen) {
|
||||||
|
case Screen::Error:
|
||||||
|
titleLabel->setText("Fehler");
|
||||||
|
messageLabel->setText("patches.zip nicht gefunden!");
|
||||||
|
detailLabel->setVisibility(brls::Visibility::VISIBLE);
|
||||||
|
actionButton->setText("Beenden");
|
||||||
|
actionButton->registerClickAction([](brls::View*) {
|
||||||
|
brls::Application::quit();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
brls::Application::giveFocus(actionButton);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Screen::Ready:
|
||||||
|
titleLabel->setText("OmniNX OC");
|
||||||
|
messageLabel->setText("SaltyNX / FPSLocker Patch Installer");
|
||||||
|
detailLabel->setVisibility(brls::Visibility::VISIBLE);
|
||||||
|
actionButton->setText("Entpacken");
|
||||||
|
actionButton->registerClickAction([this](brls::View* view) { return onExtractClicked(view); });
|
||||||
|
brls::Application::giveFocus(actionButton);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Screen::Extracting:
|
||||||
|
titleLabel->setText("Entpacken...");
|
||||||
|
messageLabel->setText("Bitte nicht beenden, bis der Vorgang abgeschlossen ist!");
|
||||||
|
detailLabel->setVisibility(brls::Visibility::VISIBLE);
|
||||||
|
progressLabel->setVisibility(brls::Visibility::VISIBLE);
|
||||||
|
progressTrack->setVisibility(brls::Visibility::VISIBLE);
|
||||||
|
actionButton->setVisibility(brls::Visibility::GONE);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Screen::Done: {
|
||||||
|
titleLabel->setText("Fertig");
|
||||||
|
const unsigned long ok = extractor->getExtracted() - extractor->getSkipped();
|
||||||
|
char msg[128];
|
||||||
|
if (extractor->getSkipped() == 0 && extractor->cleanupOk())
|
||||||
|
snprintf(msg, sizeof(msg), "Alles erledigt! %lu / %lu entpackt.", ok, extractor->getTotal());
|
||||||
|
else
|
||||||
|
snprintf(msg, sizeof(msg), "Entpacken abgeschlossen. %lu / %lu entpackt.", ok, extractor->getTotal());
|
||||||
|
messageLabel->setText(msg);
|
||||||
|
|
||||||
|
if (extractor->getSkipped() > 0) {
|
||||||
|
char err[64];
|
||||||
|
snprintf(err, sizeof(err), "%lu Eintraege fehlgeschlagen.", extractor->getSkipped());
|
||||||
|
detailLabel->setText(err);
|
||||||
|
detailLabel->setVisibility(brls::Visibility::VISIBLE);
|
||||||
|
} else if (!extractor->cleanupOk()) {
|
||||||
|
detailLabel->setText("Aufräumen teilweise fehlgeschlagen.");
|
||||||
|
detailLabel->setVisibility(brls::Visibility::VISIBLE);
|
||||||
|
} else {
|
||||||
|
detailLabel->setVisibility(brls::Visibility::GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
actionButton->setText("Beenden");
|
||||||
|
actionButton->setVisibility(brls::Visibility::VISIBLE);
|
||||||
|
actionButton->registerClickAction([](brls::View*) {
|
||||||
|
brls::Application::quit();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
brls::Application::unblockInputs();
|
||||||
|
brls::Application::giveFocus(actionButton);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PatchActivity::onExtractClicked(brls::View* view) {
|
||||||
|
(void)view;
|
||||||
|
if (currentScreen != Screen::Ready) return false;
|
||||||
|
startExtraction();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PatchActivity::startExtraction() {
|
||||||
|
showScreen(Screen::Extracting);
|
||||||
|
brls::Application::blockInputs();
|
||||||
|
updateProgressUi();
|
||||||
|
extractTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PatchActivity::onExtractTick() {
|
||||||
|
if (!extractor->step()) {
|
||||||
|
extractTimer.stop();
|
||||||
|
extractor->close();
|
||||||
|
extractor->cleanup();
|
||||||
|
updateProgressUi();
|
||||||
|
showScreen(Screen::Done);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateProgressUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PatchActivity::updateProgressUi() {
|
||||||
|
const int pct = extractor->getProgressPercent();
|
||||||
|
progressLabel->setText(std::to_string(pct) + "%");
|
||||||
|
|
||||||
|
constexpr float trackWidth = 600.0f;
|
||||||
|
progressFill->setWidth(trackWidth * static_cast<float>(pct) / 100.0f);
|
||||||
|
|
||||||
|
const std::string& file = extractor->getCurrentFile();
|
||||||
|
if (!file.empty())
|
||||||
|
detailLabel->setText(file);
|
||||||
|
}
|
||||||
39
source/patch_activity.hpp
Normal file
39
source/patch_activity.hpp
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <borealis.hpp>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "extractor.hpp"
|
||||||
|
|
||||||
|
class PatchActivity : public brls::Activity {
|
||||||
|
public:
|
||||||
|
PatchActivity();
|
||||||
|
~PatchActivity() override;
|
||||||
|
|
||||||
|
brls::View* createContentView() override;
|
||||||
|
void onContentAvailable() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class Screen { Error, Ready, Extracting, Done };
|
||||||
|
|
||||||
|
void showScreen(Screen screen);
|
||||||
|
void startExtraction();
|
||||||
|
void onExtractTick();
|
||||||
|
void updateProgressUi();
|
||||||
|
bool onExtractClicked(brls::View* view);
|
||||||
|
|
||||||
|
brls::AppletFrame* frame = nullptr;
|
||||||
|
brls::Box* contentBox = nullptr;
|
||||||
|
|
||||||
|
brls::Label* titleLabel = nullptr;
|
||||||
|
brls::Label* messageLabel = nullptr;
|
||||||
|
brls::Label* detailLabel = nullptr;
|
||||||
|
brls::Label* progressLabel = nullptr;
|
||||||
|
brls::Box* progressTrack = nullptr;
|
||||||
|
brls::Rectangle* progressFill = nullptr;
|
||||||
|
brls::Button* actionButton = nullptr;
|
||||||
|
|
||||||
|
std::unique_ptr<PatchExtractor> extractor;
|
||||||
|
brls::RepeatingTimer extractTimer;
|
||||||
|
Screen currentScreen = Screen::Ready;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user