1
0
Fork 0
mirror of https://github.com/HamletDuFromage/aio-switch-updater.git synced 2025-03-02 20:25:49 +00:00

Initial Commit

This commit is contained in:
flb 2020-09-20 02:21:28 +02:00
parent 4c1eee22be
commit 5e9026d5ea
38 changed files with 22814 additions and 0 deletions

10
.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
.vscode/
build/
lib/borealis/*
lib/minizip/*
screenshots/
switch/
*.elf
*.nacp
*.nro
*.zip

247
Makefile Normal file
View file

@ -0,0 +1,247 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# 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
# 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.
# APP_TITLE is the name of the app stored in the .nacp file (Optional)
# APP_AUTHOR is the author of the app stored in the .nacp file (Optional)
# APP_VERSION is the version of the app stored in the .nacp file (Optional)
# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional)
# ICON is the filename of the icon (.jpg), relative to the project folder.
# If not set, it attempts to use one of the following (in this order):
# - <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 := source lib/minizip/source
RESOURCES := resources
DATA := data
INCLUDES := include lib/minizip/include
APP_TITLE := AIO Switch Updater
APP_AUTHOR := HamletDuFromage
APP_VERSION := 1.0
#ROMFS := $(BUILD)/romfs
BOREALIS_PATH := lib/borealis
#BOREALIS_RESOURCES := romfs:/borealis/
#APP_RESOURCES := romfs:/
#---------------------------------------------------------------------------------
# version control constants
#---------------------------------------------------------------------------------
#TARGET_VERSION := $(shell git describe --dirty --always --tags)
#APP_VERSION := $(TARGET_VERSION)
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) -D__SWITCH__ \
-DBOREALIS_RESOURCES="\"$(BOREALIS_RESOURCES)\"" \
-DAPP_VERSION="\"$(APP_VERSION)\"" \
-DAPP_TITLE="\"$(APP_TITLE)\"" -DAPP_TITLE_LOWER="\"$(TARGET)\""
CXXFLAGS := $(CFLAGS) -std=gnu++17 -fexceptions -Wno-reorder
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lm -lcurl -lnx -lz -lmbedtls -lmbedx509 -lmbedcrypto -lstdc++fs `freetype-config --libs`
#LIBS := -ltwili -lm -lcurl -lnx -lz -lmbedtls -lmbedx509 -lmbedcrypto -lstdc++fs `freetype-config --libs`
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(LIBNX)
include $(TOPDIR)/$(BOREALIS_PATH)/library/borealis.mk
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
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) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
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)
ifneq (,$(findstring $(TARGET).jpg,$(icons)))
export APP_ICON := $(TOPDIR)/$(TARGET).jpg
else
ifneq (,$(findstring icon.jpg,$(icons)))
export APP_ICON := $(TOPDIR)/icon.jpg
endif
endif
else
export APP_ICON := $(TOPDIR)/$(ICON)
endif
ifeq ($(strip $(NO_ICON)),)
export NROFLAGS += --icon=$(APP_ICON)
endif
ifeq ($(strip $(NO_NACP)),)
export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp
endif
ifneq ($(APP_TITLEID),)
export NACPFLAGS += --titleid=$(APP_TITLEID)
endif
ifneq ($(ROMFS),)
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
endif
.PHONY: $(BUILD) $(ROMFS) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(ROMFS):
@[ -d $@ ] || mkdir -p $@
@echo Merging ROMFS...
@cp -ruf $(CURDIR)/$(BOREALIS_PATH)/resources/. $(CURDIR)/$(ROMFS)/borealis/
$(BUILD): $(ROMFS)
@[ -d $@ ] || mkdir -p $@
@MSYS2_ARG_CONV_EXCL="-D;$(MSYS2_ARG_CONV_EXCL)" $(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
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
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
ifeq ($(strip $(APP_JSON)),)
all : $(OUTPUT).nro
ifeq ($(strip $(NO_NACP)),)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
else
$(OUTPUT).nro : $(OUTPUT).elf
endif
else
all : $(OUTPUT).nsp
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
$(OUTPUT).nso : $(OUTPUT).elf
endif
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

View file

@ -1,2 +1,36 @@
# AIO-switch-updater
![releases](https://img.shields.io/github/downloads/HamletDuFromage/AIO-switch-updater/total)
All-in-One Nintendo Switch Updater
<p align="center">
<img src = "https://user-images.githubusercontent.com/61667930/93691188-7833f000-fad1-11ea-866d-42e19be54425.jpg"\><br>
</p>
A Nintendo Switch homebrew app download and update CFWs, sigpatches, FWs and cheat codes. Support Atmosphere, ReiNX and SXOS.
## How to install
Copy the `aio-switch-updater/` directory to `/switch/` in your sdcard
## Extras
This app can also reboot to specific payload and change software color scheme of Joy-Cons
## Screenshots
<div class="flex-container" style=" display: flex; flex-direction: row;">
<div><img src = "https://user-images.githubusercontent.com/61667930/93691403-30fb2e80-fad4-11ea-9701-7992a1de53e0.jpg"\></div>
</div>
<div class="flex-container" style=" display: flex; flex-direction: row;">
<div><img src = "https://user-images.githubusercontent.com/61667930/93691404-3193c500-fad4-11ea-9647-927c979960bc.jpg"\></div>
<div><img src = "https://user-images.githubusercontent.com/61667930/93691405-3193c500-fad4-11ea-960d-b68d413aedd4.jpg"\></div>
</div>
<div class="flex-container" style=" display: flex; flex-direction: row;">
<div><img src = "https://user-images.githubusercontent.com/61667930/93691407-322c5b80-fad4-11ea-8879-78047724d9e7.jpg"\></div>
<div><img src = "https://user-images.githubusercontent.com/61667930/93691465-16758500-fad5-11ea-8a5c-c0f9694cfb0e.jpg"\></div>
</div>
## Disclaimer
I do not own, host nor distribute any of the files that can be downloaded with this homebrew tool. At the owner's request, I will immediately remove the ability to download the problematic files.

BIN
icon.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View file

@ -0,0 +1,24 @@
#pragma once
#include <iomanip>
#include <fstream>
#include <sstream>
#include <filesystem>
#include <iostream>
#include <tuple>
#include <switch.h>
#include "constants.hpp"
#include "progress_event.hpp"
#include "json.hpp"
int hexToBGR(std::string hex);
std::string BGRToHex(int v);
bool isHexaAnd3Bytes(std::string str);
int setColor(std::vector<int> colors);
int backupToJSON(nlohmann::json &profiles, const char* path);
void writeJSONToFile(nlohmann::json &profiles, const char* path);
std::tuple<std::vector<std::string>, std::vector<std::vector<int>>> getProfiles(const char* path);
void changeJCColor(std::vector<int> values);
nlohmann::json backupProfile();
void backupJCColor(const char* path);

20
include/JC_page.hpp Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include "utils.hpp"
#include "JC_color_swapper.hpp"
#include "constants.hpp"
#include "confirm_page.hpp"
#include "worker_page.hpp"
class JCPage : public brls::AppletFrame
{
private:
brls::List* list;
brls::Label* label;
std::vector<brls::ListItem*> items;
brls::ListItem* restore;
brls::ListItem* backup;
public:
JCPage();
};

16
include/about_tab.hpp Normal file
View file

@ -0,0 +1,16 @@
#pragma once
#include <borealis.hpp>
#include <borealis.hpp>
class AboutTab : public brls::List
{
public:
AboutTab();
View* getDefaultFocus() override
{
return nullptr;
}
};

41
include/app_page.hpp Normal file
View file

@ -0,0 +1,41 @@
#pragma once
#include <borealis.hpp>
#include <switch.h>
#include <cstring>
#include "utils.hpp"
#include <filesystem>
#include <fstream>
#include <iostream>
#include <algorithm>
#include "worker_page.hpp"
#include "confirm_page.hpp"
#define PROFILE_BADGE "\uE3E0"
typedef char NsApplicationName[0x201];
typedef uint8_t NsApplicationIcon[0x20000];
typedef struct
{
uint64_t tid;
NsApplicationName name;
NsApplicationIcon icon;
brls::ListItem* listItem;
} App;
class AppPage : public brls::AppletFrame
{
private:
brls::List* list;
brls::Label* label;
brls::ListItem* download;
std::vector<App*> apps;
std::set<std::string> titles;
std::vector<brls::ListItem*> items;
public:
AppPage();
};

26
include/confirm_page.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <borealis.hpp>
#include <chrono>
#include "utils.hpp"
#include "download.hpp"
#include "constants.hpp"
#include "main_frame.hpp"
class ConfirmPage : public brls::View
{
private:
brls::Button* button = nullptr;
brls::Label* label = nullptr;
std::chrono::system_clock::time_point start = std::chrono::high_resolution_clock::now();
bool done = false;
public:
ConfirmPage(brls::StagedAppletFrame* frame, std::string text, bool done = false);
~ConfirmPage();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override;
void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override;
brls::View* getDefaultFocus() override;
};

53
include/constants.hpp Normal file
View file

@ -0,0 +1,53 @@
#pragma once
#define ROOT_PATH "/"
#define DOWNLOAD_PATH "/config/aio-switch-updater/"
#define CONFIG_PATH "/config/aio-switch-updater/"
#define APP_URL "https://github.com/HamletDuFromage/sigpatches-updater/releases/latest/download/aio-switch-updater.zip"
#define APP_FILENAME "/config/aio-switch-updater/app.zip"
#define SIGPATCHES_URL "https://hamletdufromage.github.io/sigpatches-updater/sigpatches.html"
#define SIGPATCHES_FILENAME "/config/aio-switch-updater/sigpatches.zip"
#define FIRMWARE_URL "https://hamletdufromage.github.io/switch-fw/firmwares.html"
#define FIRMWARE_FILENAME "/config/aio-switch-updater/firmware.zip"
#define FIRMWARE_PATH "/firmware/"
#define CFW_URL "https://hamletdufromage.github.io/switch-cfw/cfw.html"
#define CFW_FILENAME "/config/aio-switch-updater/cfw.zip"
#define CHEATS_RELEASE_URL "https://github.com/HamletDuFromage/switch-cheats-db/releases/tag/v1.0"
#define CHEATS_URL_TITLES "https://github.com/HamletDuFromage/switch-cheats-db/releases/download/v1.0/titles.zip"
#define CHEATS_URL_CONTENTS "https://github.com/HamletDuFromage/switch-cheats-db/releases/download/v1.0/contents.zip"
#define CHEATS_FILENAME "/config/aio-switch-updater/cheats.zip"
#define CHEATS_EXCLUDE "/config/aio-switch-updater/exclude.txt"
#define UPDATED_TITLES_PATH "/config/aio-switch-updater/updated.dat"
#define AMS_CONTENTS "/atmosphere/contents/"
#define REINX_CONTENTS "/ReiNX/contents/"
#define SXOS_TITLES "/sxos/titles/"
#define AMS_PATH "/atmosphere/"
#define SXOS_PATH "/sxos/"
#define REINX_PATH "/ReiNX/"
#define CONTENTS_PATH "contents/"
#define TITLES_PATH "titles/"
#define COLOR_PROFILES_PATH "/config/aio-switch-updater/jc_profiles.json"
#define PAYLOAD_PATH "/payloads/"
#define BOOTLOADER_PL_PATH "/bootloader/payloads/"
enum archiveType{
sigpatches,
cheats,
fw,
app,
cfw,
};
enum CFW{
rnx,
sxos,
ams,
};

32
include/download.hpp Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#ifndef _DOWNLOAD_H_
#define _DOWNLOAD_H_
#define ON 1
#define OFF 0
#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <math.h>
#include <curl/curl.h>
#include <string>
#include <regex>
#include <iostream>
#include <switch.h>
#include "progress_event.hpp"
void downloadFile(const char *url, const char *output, int api);
std::tuple<std::vector<std::string>, std::vector<std::string>> fetchLinks(const char *url);
std::string fetchTitle(const char *url);
#endif

47
include/extract.hpp Normal file
View file

@ -0,0 +1,47 @@
#pragma once
#include <string>
#include <vector>
#include <switch.h>
#include <sstream>
#include <iomanip>
#include <iostream>
#include <algorithm>
#include <iterator>
#include <string.h>
#include <stdio.h>
#include <filesystem>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <set>
#include <unzipper.h>
#include <borealis.hpp>
#include "progress_event.hpp"
#include "constants.hpp"
#include "utils.hpp"
static constexpr u32 MaxTitleCount = 64000;
typedef struct Title {
std::string id;
std::string name;
bool operator ==(const Title&x) const {
return id == x.id;
}
bool operator <(const Title&x) const {
return id < x.id;
}
} Title;
void extract(const char* filename, const char* workingPath = ROOT_PATH);
std::string formatApplicationId(u64 ApplicationId);
std::vector<Title> getInstalledTitlesNs();
std::vector<Title> excludeTitles(const char* path, std::vector<Title> listedTitles);
void writeTitlesToFile(std::set<std::string> titles);
void extractCheats(const char * zipPath, std::vector<Title> titles, CFW cfw, bool credits = false);
void removeCheats(CFW cfw);

20406
include/json.hpp Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
#pragma once
#include <borealis.hpp>
#include <string>
#include "download.hpp"
#include "extract.hpp"
#include "constants.hpp"
#include "confirm_page.hpp"
#include "worker_page.hpp"
#include "utils.hpp"
class ListDownloadTab : public brls::List
{
private:
std::tuple<std::vector<std::string>, std::vector<std::string>> links;
std::vector<brls::ListItem*> linkItems;
brls::Label *notFound;
brls::Label *description;
public:
ListDownloadTab(archiveType type);
~ListDownloadTab();
};

17
include/main_frame.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include <borealis.hpp>
#include "utils.hpp"
#include "about_tab.hpp"
#include "list_download_tab.hpp"
#include "tools_tab.hpp"
class MainFrame : public brls::TabFrame
{
private:
//RefreshTask *refreshTask;
public:
MainFrame();
};

17
include/payload_page.hpp Normal file
View file

@ -0,0 +1,17 @@
#pragma once
#include "utils.hpp"
#include "reboot_payload.h"
class PayloadPage : public brls::AppletFrame
{
private:
brls::Label* label;
brls::List* list;
std::vector<brls::ListItem*> items;
brls::ListItem* reboot;
brls::ListItem* shutDown;
public:
PayloadPage();
};

View file

@ -0,0 +1,30 @@
#pragma once
class ProgressEvent{
private:
ProgressEvent() {}
int _current = 0;
int _max = 60;
public:
ProgressEvent(const ProgressEvent&) = delete;
ProgressEvent& operator=(const ProgressEvent &) = delete;
ProgressEvent(ProgressEvent &&) = delete;
ProgressEvent & operator=(ProgressEvent &&) = delete;
static auto& instance(){
static ProgressEvent event;
return event;
}
void reset() {
_current = 0;
_max = 60;
}
inline void setTotalSteps(int steps) { _max = steps; }
inline void setStep(int step) { _current = step; }
inline int getStep() { return _current; }
inline bool finished() { return (_current == _max) ; }
inline int getMax() { return _max; }
};

19
include/reboot_payload.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef __REBOOT_PAYLOAD_H__
#define __REBOOT_PAYLOAD_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <string.h>
#include <stdio.h>
#include <stdbool.h>
#include <switch.h>
int reboot_to_payload(const char* path);
#ifdef __cplusplus
}
#endif
#endif

26
include/tools_tab.hpp Normal file
View file

@ -0,0 +1,26 @@
#pragma once
#include <borealis.hpp>
#include "confirm_page.hpp"
#include "worker_page.hpp"
#include "app_page.hpp"
#include "payload_page.hpp"
#include "JC_page.hpp"
#include "extract.hpp"
#include "utils.hpp"
class ToolsTab : public brls::List
{
private:
brls::Label* label;
brls::ListItem* deleteCheats;
brls::ListItem* viewCheats;
brls::ListItem* JCcolor;
brls::ListItem* updateApp;
brls::ListItem* rebootPayload;
brls::StagedAppletFrame* stagedFrame;
public:
ToolsTab();
};

24
include/utils.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include <regex>
#include <iostream>
#include <set>
#include "download.hpp"
#include "extract.hpp"
#include "constants.hpp"
#include "progress_event.hpp"
CFW getCFW();
bool isServiceRunning(const char *serviceName);
std::vector<std::string> htmlProcess(std::string s, std::regex rgx);
void createTree(std::string path);
void clearConsole();
bool isArchive(const char * path);
void downloadArchive(std::string url, archiveType type);
void extractArchive(archiveType type);
void progressTest(std::string url, archiveType type);
std::string formatListItemTitle(const std::string str, size_t maxScore = 140);
std::string formatApplicationId(u64 ApplicationId);
std::set<std::string> readLineByLine(const char * path);
std::vector<std::string> fetchPayloads();
void shut_down(bool reboot = false);

36
include/worker_page.hpp Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include <borealis.hpp>
#include <thread>
#include <string>
#include <functional>
#include "utils.hpp"
#include "download.hpp"
#include "extract.hpp"
#include "constants.hpp"
#include "progress_event.hpp"
typedef std::function<void()> worker_func_t;
class WorkerPage : public brls::View
{
private:
brls::ProgressDisplay* progressDisp = nullptr;
brls::StagedAppletFrame* frame;
brls::Button* button;
brls::Label* label;
int progressValue = 0;
bool workStarted = false;
std::thread* workerThread;
worker_func_t workerFunc;
public:
WorkerPage(brls::StagedAppletFrame* frame, const std::string& text, worker_func_t worker_func);
~WorkerPage();
void draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx) override;
void layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash) override;
void doWork();
brls::View* getDefaultFocus() override;
};

107
jc_profiles.json Normal file
View file

@ -0,0 +1,107 @@
[
{
"L_BTN": "0A1E0A",
"L_JC": "82FF96",
"R_BTN": "0A1E28",
"R_JC": "96F5F5",
"name": "Animal Crossing: New Horizons"
},
{
"L_BTN": "0F0F0F",
"L_JC": "828282",
"R_BTN": "0F0F0F",
"R_JC": "828282",
"name": "Gray"
},
{
"L_BTN": "001E1E",
"L_JC": "0AB9E6",
"R_BTN": "1E0A0A",
"R_JC": "FF3C28",
"name": "Neon Blue (L) & Neon Red (R)"
},
{
"L_BTN": "1E0A0A",
"L_JC": "FF3C28",
"R_BTN": "001E1E",
"R_JC": "0AB9E6",
"name": "Neon Red (L) & Neon Blue (R)"
},
{
"L_BTN": "001E1E",
"L_JC": "0AB9E6",
"R_BTN": "001E1E",
"R_JC": "0AB9E6",
"name": "Neon Blue"
},
{
"L_BTN": "1E0A0A ",
"L_JC": "FF3C28",
"R_BTN": "1E0A0A ",
"R_JC": "FF3C28",
"name": "Neon Red"
},
{
"L_BTN": "142800",
"L_JC": "E6FF00",
"R_BTN": "142800",
"R_JC": "E6FF00",
"name": "Neon Yellow"
},
{
"L_BTN": "28001E",
"L_JC": "FF3278",
"R_BTN": "28001E",
"R_JC": "FF3278",
"name": "Neon Pink"
},
{
"L_BTN": "002800",
"L_JC": "1EDC00",
"R_BTN": "002800",
"R_JC": "1EDC00",
"name": "Neon Green"
},
{
"L_BTN": "280A0A",
"L_JC": "E10F00",
"R_BTN": "280A0A",
"R_JC": "E10F00",
"name": "Red"
},
{
"L_BTN": "00000A",
"L_JC": "4655F5",
"R_BTN": "00000A",
"R_JC": "4655F5",
"name": "Blue"
},
{
"L_BTN": "FFFFF",
"L_JC": "323232",
"R_BTN": "FFFFF",
"R_JC": "323232",
"name": "Pro Black"
},
{
"L_BTN": "281900",
"L_JC": "C88C32",
"R_BTN": "322800",
"R_JC": "FFDC00",
"name": "Pokemon Let's Go! Eevee (L) and Pikachu (R)"
},
{
"L_BTN": "1E1914",
"L_JC": "D7AA73",
"R_BTN": "1E1914",
"R_JC": "D7AA73",
"name": "Nintendo Labo Creators Contest Edition"
},
{
"L_BTN": "0A1E0A",
"L_JC": "82FF96",
"R_BTN": "0A1E28",
"R_JC": "96F5F5",
"name": "Animal Crossing: New Horizons"
}
]

8
pack_release.sh Normal file
View file

@ -0,0 +1,8 @@
#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
cd $DIR
rm -r switch/aio-switch-updater/
mkdir -p switch/aio-switch-updater/
mv *.nro switch/aio-switch-updater/aio-switch-updater.nro
zip -r aio-switch-updater.zip switch/

201
source/JC_color_swapper.cpp Normal file
View file

@ -0,0 +1,201 @@
#include "JC_color_swapper.hpp"
using json = nlohmann::json;
int hexToBGR(std::string hex){
std::string R = hex.substr(0, 2);
std::string G = hex.substr(2, 2);
std::string B = hex.substr(4, 2);
return std::stoi(B + G + R, 0, 16);
}
std::string BGRToHex(int v){
std::stringstream ss;
v = ((v & 0xFF) << 16) + (v & 0xFF00) + (v >> 16) + 256;
ss << std::setfill('0') << std::setw(6) << std::right << std::hex << v;
return ss.str();
}
bool isHexaAnd3Bytes(std::string str){
if(str.size()!=6) return false;
for(char const &c : str){
if(!isxdigit(c)) return false;
}
return true;
}
int setColor(std::vector<int> colors){
Result pads, ljc, rjc;
int res = 0;
s32 nbEntries;
u64 UniquePadIds[2] = {};
pads = hidsysGetUniquePadsFromNpad(CONTROLLER_HANDHELD, UniquePadIds, 2,&nbEntries);
if(R_SUCCEEDED(pads)){
ljc = hiddbgUpdateControllerColor(colors[0], colors[1], UniquePadIds[0]);
if (R_FAILED(ljc)) res +=1;
rjc = hiddbgUpdateControllerColor(colors[2], colors[3], UniquePadIds[1]);
if (R_FAILED(rjc)) res +=2;
}
else{
res +=4;
}
return res;
}
int backupToJSON(json &profiles, const char* path){
HidControllerColors colors;
hidGetControllerColors(CONTROLLER_P1_AUTO, &colors);
std::vector<int> oldBackups;
if (colors.splitSet) {
int i = 0;
for (const auto& x : profiles.items()){
if(x.value()["name"] == "_backup") {
oldBackups.push_back(i);
}
i++;
}
for (auto &k : oldBackups){
profiles.erase(profiles.begin() + k);
}
json newBackup = json::object({
{"name", "_backup"},
{"L_JC", BGRToHex(colors.leftColorBody)},
{"L_BTN", BGRToHex(colors.leftColorButtons)},
{"R_JC", BGRToHex(colors.rightColorBody)},
{"R_BTN", BGRToHex(colors.rightColorButtons)}
});
profiles.push_back(newBackup);
writeJSONToFile(profiles, path);
return 0;
}
else{
return -1;
}
}
json backupProfile(){
json newBackup;
HidControllerColors colors;
hidGetControllerColors(CONTROLLER_P1_AUTO, &colors);
//if(0){
if (colors.splitSet) {
newBackup = json::object({
{"name", "_backup"},
{"L_JC", BGRToHex(colors.leftColorBody)},
{"L_BTN", BGRToHex(colors.leftColorButtons)},
{"R_JC", BGRToHex(colors.rightColorBody)},
{"R_BTN", BGRToHex(colors.rightColorButtons)}
});
}
return newBackup;
}
void writeJSONToFile(json &profiles, const char* path){
std::fstream newProfiles;
newProfiles.open(path, std::fstream::out | std::fstream::trunc);
newProfiles << std::setw(4) << profiles << std::endl;
newProfiles.close();
}
std::tuple<std::vector<std::string>, std::vector<std::vector<int>>> getProfiles(const char* path){
std::vector<std::string> names;
std::vector<std::vector<int>> colorValues;
bool properData;
std::fstream profilesFile;
json profilesJson;
if(std::filesystem::exists(path)){
profilesFile.open(path, std::fstream::in);
profilesFile >> profilesJson;
profilesFile.close();
}
if(profilesJson.empty()){
profilesJson = {{
{"L_BTN", "0A1E0A"},
{"L_JC", "82FF96"},
{"R_BTN", "0A1E28"},
{"R_JC", "96F5F5"},
{"name", "Animal Crossing: New Horizons"}
}};
writeJSONToFile(profilesJson, path);
}
for (const auto& x : profilesJson.items()){
std::string name = x.value()["name"];
std::vector<std::string> values = {
std::string(x.value()["L_JC"]),
std::string(x.value()["L_BTN"]),
std::string(x.value()["R_JC"]),
std::string(x.value()["R_BTN"])
};
properData = true;
for(auto& str : values){
if(!isHexaAnd3Bytes(str)){
properData = false;
}
}
if(properData){
if(name == "") name = "Unamed";
names.push_back(name);
colorValues.push_back({
hexToBGR(values[0]),
hexToBGR(values[1]),
hexToBGR(values[2]),
hexToBGR(values[3])
});
}
}
return std::make_tuple(names, colorValues);
}
void changeJCColor(std::vector<int> values){
hiddbgInitialize();
hidsysInitialize();
ProgressEvent::instance().reset();
ProgressEvent::instance().setStep(1);
int res = -1;
while(res !=0){
res = setColor(values);
}
hiddbgExit();
hidsysExit();
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
}
void backupJCColor(const char* path){
hiddbgInitialize();
hidsysInitialize();
ProgressEvent::instance().reset();
ProgressEvent::instance().setStep(1);
json backup;
json profiles;
std::fstream profilesFile;
if(std::filesystem::exists(path)){
profilesFile.open(path, std::fstream::in);
profilesFile >> profiles;
profilesFile.close();
}
std::vector<int> oldBackups;
int i = 0;
for (const auto& x : profiles.items()){
if(x.value()["name"] == "_backup") {
oldBackups.push_back(i);
}
i++;
}
for (auto &k : oldBackups){
profiles.erase(profiles.begin() + k);
}
while(backup.empty()){
backup = backupProfile();
}
profiles.push_back(backup);
//backup.push_back(profiles);
writeJSONToFile(profiles, path);
hiddbgExit();
hidsysExit();
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
}

55
source/JC_page.cpp Normal file
View file

@ -0,0 +1,55 @@
#include "JC_page.hpp"
JCPage::JCPage() : AppletFrame(true, true)
{
this->setTitle("Joy-Con color swapper");
list = new brls::List();
std::string labelText = "You can change the internal color of your Joy-Cons. Make sure they're docked.\n"\
"Color profiles are stored in '" + std::string(COLOR_PROFILES_PATH) + "'. Go to http://bit.ly/JC-color "\
"to generate your own custom profiles.";
label = new brls::Label(brls::LabelStyle::DESCRIPTION, labelText, true);
list->addView(label);
backup = new brls::ListItem("Backup current color profile");
backup->getClickEvent()->subscribe([&](brls::View* view) {
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
stagedFrame->setTitle("Joy-Con color swapper");
stagedFrame->addStage(
new WorkerPage(stagedFrame, "Backing up the current color profile. Make sure the Joy-Con are docked. "\
"If the process hangs, try docking/undocking the JCs.",
[](){backupJCColor(COLOR_PROFILES_PATH);})
);
stagedFrame->addStage(
new ConfirmPage(stagedFrame, "All done!", true)
);
brls::Application::pushView(stagedFrame);
});
list->addView(backup);
list->addView(new brls::ListItemGroupSpacing(true));
auto profiles = getProfiles(COLOR_PROFILES_PATH);
std::vector<std::string> names = std::get<0>(profiles);
int nbProfiles = names.size();
items.reserve(nbProfiles);
for (int i = nbProfiles - 1; i >= 0; i--){
std::string name = std::get<0>(profiles)[i];
std::vector<int> value = std::get<1>(profiles)[i];
items[i] = new brls::ListItem(names[i]);
items[i]->getClickEvent()->subscribe([&, value](brls::View* view) {
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
stagedFrame->setTitle("Joy-Con color swapper");
stagedFrame->addStage(
new WorkerPage(stagedFrame, "Changing color. Make sure the Joy-Con are docked. "\
"If the process hangs, try docking/undocking the JCs.",
[value](){changeJCColor(value);})
);
stagedFrame->addStage(
new ConfirmPage(stagedFrame, "All done! You may need to dock/undock your Joy-Cons for the change to take effect", true)
);
brls::Application::pushView(stagedFrame);
});
list->addView(items[i]);
}
this->setContentView(list);
}

34
source/about_tab.cpp Normal file
View file

@ -0,0 +1,34 @@
#include "about_tab.hpp"
AboutTab::AboutTab()
{
// Subtitle
brls::Label *subTitle = new brls::Label(
brls::LabelStyle::REGULAR,
"All-in-One Nintendo Switch Updater",
true
);
subTitle->setHorizontalAlign(NVG_ALIGN_CENTER);
this->addView(subTitle);
// Copyright
brls::Label *copyright = new brls::Label(
brls::LabelStyle::DESCRIPTION,
"AIO-switch-updater is licensed under GPL-3.0\n" \
"\u00A9 2020 HamletDuFromage",
true
);
copyright->setHorizontalAlign(NVG_ALIGN_CENTER);
this->addView(copyright);
// Links
this->addView(new brls::Header("Disclaimers"));
brls::Label *links = new brls::Label(
brls::LabelStyle::SMALL,
"\uE016 Aside from cheat codes that are mirrored from the main Gbatemp thread, "\
"HamletDuFromage isn't hosting anything. All credits go to respective owners\n"\
"\uE016 Links are refreshed every three hours. If a link remains broken after 3 hours have passed, please open a Github issue\n",
true
);
this->addView(links);
}

99
source/app_page.cpp Normal file
View file

@ -0,0 +1,99 @@
#include "app_page.hpp"
//TODO: Serialize it in extract.cpp
AppPage::AppPage() : AppletFrame(true, true)
{
this->setTitle("Installed cheats");
list = new brls::List();
label = new brls::Label(
brls::LabelStyle::DESCRIPTION,
"The following titles have had cheats downloaded for last time you used the app. Please note despite having been "\
"downloaded for a game, cheats may not match its current update.",
true
);
list->addView(label);
NsApplicationRecord record;
uint64_t tid;
NsApplicationControlData controlData;
NacpLanguageEntry* langEntry = NULL;
Result rc;
size_t i = 0;
int recordCount = 0;
size_t controlSize = 0;
titles = readLineByLine(UPDATED_TITLES_PATH);
if(!titles.empty()){
while (true)
{
rc = nsListApplicationRecord(&record, sizeof(record), i, &recordCount);
if (R_FAILED(rc)) break;
if(recordCount <= 0)
break;
tid = record.application_id;
rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, &controlData, sizeof(controlData), &controlSize);
if (R_FAILED(rc)) break;
rc = nacpGetLanguageEntry(&controlData.nacp, &langEntry);
if (R_FAILED(rc)) break;
if (!langEntry->name || titles.find(formatApplicationId(tid)) == titles.end())
{
i++;
continue;
}
App* app = (App*) malloc(sizeof(App));
app->tid = tid;
memset(app->name, 0, sizeof(app->name));
strncpy(app->name, langEntry->name, sizeof(app->name)-1);
memcpy(app->icon, controlData.icon, sizeof(app->icon));
// Add the ListItem
brls::ListItem *listItem = new brls::ListItem(formatListItemTitle(std::string(app->name)), "", formatApplicationId(app->tid));
listItem->setThumbnail(app->icon, sizeof(app->icon));
list->addView(listItem);
i++;
}
}
std::string text("Downloading:\nLatest cheat codes\n\nFrom:\n");
std::string url = "";
switch(getCFW()){
case ams:
url += CHEATS_URL_CONTENTS;
break;
case rnx:
url += CHEATS_URL_CONTENTS;
break;
case sxos:
url += CHEATS_URL_CONTENTS;
break;
}
text += url;
download = new brls::ListItem("Download latest cheat codes");
archiveType type = cheats;
download->getClickEvent()->subscribe([&, url, text, type](brls::View* view) {
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
stagedFrame->setTitle("Getting cheat codes");
stagedFrame->addStage(
new ConfirmPage(stagedFrame, text)
);
stagedFrame->addStage(
new WorkerPage(stagedFrame, "Downloading...", [url, type](){downloadArchive(url, type);})
);
stagedFrame->addStage(
new WorkerPage(stagedFrame, "Extracting...", [type](){extractArchive(type);})
);
stagedFrame->addStage(
new ConfirmPage(stagedFrame, "All done!", true)
);
brls::Application::pushView(stagedFrame);
});
list->addView(download);
this->setContentView(list);
}

74
source/confirm_page.cpp Normal file
View file

@ -0,0 +1,74 @@
#include "confirm_page.hpp"
#include <algorithm>
ConfirmPage::ConfirmPage(brls::StagedAppletFrame* frame, std::string text, bool done): done(done)
{
this->button = (new brls::Button(brls::ButtonStyle::BORDERLESS))->setLabel(done ? "Back": "Continue");
this->button->setParent(this);
this->button->getClickEvent()->subscribe([frame, this](View* view) {
if (!frame->isLastStage()) frame->nextStage();
else if (this->done) {
brls::Application::pushView(new MainFrame());
}
});
this->label = new brls::Label(brls::LabelStyle::DIALOG, text, true);
this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
this->label->setParent(this);
this->registerAction("Back", brls::Key::B, [this] {
brls::Application::popView();
return true;
});
}
void ConfirmPage::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{
if(!this->done){
auto end = std::chrono::high_resolution_clock::now();
auto missing = std::max(2l - std::chrono::duration_cast<std::chrono::seconds>(end - start).count(), 0l);
auto text = std::string("Continue");
if (missing > 0) {
this->button->setLabel(text + " (" + std::to_string(missing) + ")");
this->button->setState(brls::ButtonState::DISABLED);
} else {
this->button->setLabel(text);
this->button->setState(brls::ButtonState::ENABLED);
}
this->button->invalidate();
}
this->label->frame(ctx);
this->button->frame(ctx);
}
brls::View* ConfirmPage::getDefaultFocus()
{
return this->button;
}
void ConfirmPage::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
this->label->setWidth(this->width);
this->label->invalidate(true);
// this->label->setBackground(brls::ViewBackground::DEBUG);
this->label->setBoundaries(
this->x + this->width / 2 - this->label->getWidth() / 2,
this->y + (this->height - this->label->getHeight() - this->y - style->CrashFrame.buttonHeight)/2,
this->label->getWidth(),
this->label->getHeight());
this->button->setBoundaries(
this->x + this->width / 2 - style->CrashFrame.buttonWidth / 2,
this->y + (this->height-style->CrashFrame.buttonHeight*3),
style->CrashFrame.buttonWidth,
style->CrashFrame.buttonHeight);
this->button->invalidate();
start = std::chrono::high_resolution_clock::now() + std::chrono::milliseconds(150);
}
ConfirmPage::~ConfirmPage()
{
delete this->label;
delete this->button;
}

191
source/download.cpp Normal file
View file

@ -0,0 +1,191 @@
#include "download.hpp"
#include "utils.hpp"
#include <algorithm>
#define API_AGENT "HamletDuFromage"
#define _1MiB 0x100000
typedef struct
{
char *memory;
size_t size;
} MemoryStruct_t;
typedef struct
{
u_int8_t *data;
size_t data_size;
u_int64_t offset;
FILE *out;
} ntwrk_struct_t;
static size_t WriteMemoryCallback(void *contents, size_t size, size_t num_files, void *userp)
{
ntwrk_struct_t *data_struct = (ntwrk_struct_t *)userp;
size_t realsize = size * num_files;
if (realsize + data_struct->offset >= data_struct->data_size)
{
fwrite(data_struct->data, data_struct->offset, 1, data_struct->out);
data_struct->offset = 0;
}
memcpy(&data_struct->data[data_struct->offset], contents, realsize);
data_struct->offset += realsize;
data_struct->data[data_struct->offset] = 0;
return realsize;
}
int download_progress(void *p, double dltotal, double dlnow, double ultotal, double ulnow)
{
if (dltotal <= 0.0) return 0;
double fractionDownloaded = dlnow / dltotal;
int counter = (int) (fractionDownloaded * ProgressEvent::instance().getMax()); //20 is the number of increments
ProgressEvent::instance().setStep(std::min(ProgressEvent::instance().getMax() - 1, counter));
return 0;
}
void downloadFile(const char *url, const char *output, int api)
{
ProgressEvent::instance().reset();
CURL *curl = curl_easy_init();
if (curl)
{
FILE *fp = fopen(output, "wb");
if (fp)
{
printf("\n");
ntwrk_struct_t chunk = {0};
chunk.data = static_cast<u_int8_t *>(malloc(_1MiB));
chunk.data_size = _1MiB;
chunk.out = fp;
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_USERAGENT, API_AGENT);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
// write calls
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk);
if (api == OFF)
{
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
//curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, &prog);
curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, download_progress);
}
curl_easy_perform(curl);
if (chunk.offset)
fwrite(chunk.data, 1, chunk.offset, fp);
curl_easy_cleanup(curl);
free(chunk.data);
fclose(chunk.out);
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
}
}
}
struct MemoryStruct {
char *memory;
size_t size;
};
static size_t WriteMemoryCallback2(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
char *ptr = static_cast<char *>(realloc(mem->memory, mem->size + realsize + 1));
if(ptr == NULL) {
/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
return 0;
}
mem->memory = ptr;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
std::tuple<std::vector<std::string>, std::vector<std::string>> fetchLinks(const char *url){
CURL *curl_handle;
struct MemoryStruct chunk;
chunk.memory = static_cast<char *>(malloc(1)); /* will be grown as needed by the realloc above */
chunk.size = 0; /* no data at this point */
curl_global_init(CURL_GLOBAL_ALL);
curl_handle = curl_easy_init();
curl_easy_setopt(curl_handle, CURLOPT_URL, url);
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback2);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, API_AGENT);
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_perform(curl_handle);
std::tuple <std::vector<std::string>, std::vector<std::string>> links;
std::string s = std::string(chunk.memory);
//<a href=.*?> links
//>(?:(?!>).)*?</a> titles
auto titles = htmlProcess(s, std::regex(">(?:(?!>).)*?</a>"));
std::transform(titles.begin(), titles.end(), titles.begin(),
[](std::string s) -> std::string {return s.substr(1, s.size() - 1 - 4);});
auto targets = htmlProcess(s, std::regex("<a href=.*?>"));
std::transform(targets.begin(), targets.end(), targets.begin(),
[](std::string s) -> std::string {return s.substr(8, s.size() - 8 - 1);});
links = std::make_tuple(titles, targets);
curl_easy_cleanup(curl_handle);
free(chunk.memory);
curl_global_cleanup();
return links;
}
std::string fetchTitle(const char *url){
CURL *curl_handle;
struct MemoryStruct chunk;
chunk.memory = static_cast<char *>(malloc(1)); /* will be grown as needed by the realloc above */
chunk.size = 0; /* no data at this point */
curl_global_init(CURL_GLOBAL_ALL);
curl_handle = curl_easy_init();
curl_easy_setopt(curl_handle, CURLOPT_URL, url);
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback2);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, API_AGENT);
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_perform(curl_handle);
/* check for errors */
std::string ver = "-1";
std::string s = std::string(chunk.memory);
//std::cout << "start html " << s.substr(0, 199) << std::endl;
std::regex rgx("<title>.+</title>");
std::smatch match;
if (std::regex_search(s, match, rgx)){
//ver = std::stoi(match[0]);
//std::cout << match[0].str().substr(match[0].str().find(" ") + 1, 6) << std::endl;
ver = match[0].str().substr(match[0].str().find(" ") + 1, 5);
}
curl_easy_cleanup(curl_handle);
free(chunk.memory);
curl_global_cleanup();
return ver;
}

287
source/extract.cpp Normal file
View file

@ -0,0 +1,287 @@
#include "extract.hpp"
void extract(const char * filename, const char* workingPath){
ProgressEvent::instance().reset();
chdir(workingPath);
zipper::Unzipper unzipper(filename);
std::vector<zipper::ZipEntry> entries = unzipper.entries();
ProgressEvent::instance().setTotalSteps(entries.size());
for (int i = 0; i < (int) entries.size(); i++){
unzipper.extractEntry(entries[i].name);
ProgressEvent::instance().setStep(i);
}
unzipper.close();
ProgressEvent::instance().setStep(entries.size());
}
std::vector<Title> getInstalledTitlesNs(){
// This function has been cobbled together from the "app_controldata" example in devkitpro.
// Set the rc variable to begin with
Result rc = 0;
// Initialise a vector of tuples, storing the title ID and the title name.
std::vector<Title> titles;
// Initialise an application record array, where the size is MaxTitleCount
NsApplicationRecord *recs = new NsApplicationRecord[MaxTitleCount]();
// Set the buffer to NULL
NsApplicationControlData *buf=NULL;
// Set outsize to 0
u64 outsize=0;
// Set the language entry to NULL
NacpLanguageEntry *langentry = NULL;
// Create a char array to store the name of the title
char name[0x201];
// Set the total records to 0
s32 total = 0;
// Set a failed counter to 0
int totalFailed = 0;
// Fill the recs array with application records
rc = nsListApplicationRecord(recs, MaxTitleCount, 0, &total);
// If filling the recs array was successful
if (R_SUCCEEDED(rc)){
// Loop through records
for (s32 i = 0; i < total; i++){
// Reset varaibles for accessing memory
outsize=0;
buf = (NsApplicationControlData*)malloc(sizeof(NsApplicationControlData));
if (buf==NULL) {
rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory);
} else {
memset(buf, 0, sizeof(NsApplicationControlData));
}
if (R_SUCCEEDED(rc)) {
rc = nsInitialize();
if (R_FAILED(rc)) {
printf("nsInitialize() failed: 0x%x\n", rc);
}
}
// Get the application control data for the current record
rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, recs[i].application_id, buf, sizeof(NsApplicationControlData), &outsize);
if (R_FAILED(rc)) {
totalFailed++;
std::cout << "nsGetApplicationControlData() failed: 0x" << std::hex << rc << " for Title ID: " << formatApplicationId(recs[i].application_id) << std::endl;
}
if (outsize < sizeof(buf->nacp)) {
rc = -1;
}
// If application control data was retrieved successfully
if (R_SUCCEEDED(rc)) {
rc = nacpGetLanguageEntry(&buf->nacp, &langentry);
}
// If nacp language entry was retrieved successfully
if (R_SUCCEEDED(rc)) {
memset(name, 0, sizeof(name));
strncpy(name, langentry->name, sizeof(name)-1); //Don't assume the nacp string is NUL-terminated for safety.
titles.push_back({formatApplicationId(recs[i].application_id), name});
}
nsExit();
}
}
free(buf);
delete[] recs;
std::sort(titles.begin(), titles.end());
return titles;
}
std::vector<Title> excludeTitles(const char* path, std::vector<Title> listedTitles){
// Initialise a vector of titles
std::vector<Title> titles;
// Open a file stream
std::ifstream file(path);
// Declare a total variable and set to 0
int total = 0;
// Declare a name variable
std::string name;
if (file.is_open()) {
std::string line;
// Read each line of the file in
while (std::getline(file, line)) {
name = "No name provided";
// Change the line to uppercase
std::transform(line.begin(), line.end(), line.begin(), ::toupper);
for(int i = 0; i < (int)listedTitles.size(); i++) {
// Iterate through the listedTitles, and compare the id's in the file, to the installed title ids
// When a match is found, it sets the name of the excluded title to the name of the installed title
if(listedTitles.at(i).id == line) {
name = listedTitles.at(i).name;
break;
}
}
// Push a new title to the titles vector and increment the total number of excluded titles
titles.push_back({line, name});
total++;
}
// Close the file
file.close();
}
// Sort the titles list (uses an overloaded < operator to sort based on ID's)
std::sort(titles.begin(), titles.end());
// Create a vector to store the difference between the excluded titles list, and the installed title list
std::vector<Title> diff;
// Populate the difference vector
std::set_difference(listedTitles.begin(), listedTitles.end(), titles.begin(), titles.end(),
std::inserter(diff, diff.begin()));
return diff;
}
bool caselessCompare (const std::string& a, const std::string& b){
return strcasecmp(a.c_str(), b.c_str()) < 0;
}
void extractCheats(const char * zipPath, std::vector<Title> titles, CFW cfw, bool credits){
//TODO: REWRITE WITH SETS INSTEAD OF VECTORS
ProgressEvent::instance().reset();
zipper::Unzipper unzipper(zipPath);
std::vector<zipper::ZipEntry> entries = unzipper.entries();
std::set<std::string> extractedTitles;
int offset = 0;
switch(cfw){
case ams:
offset = std::string(CONTENTS_PATH).length();
createTree(AMS_CONTENTS);
chdir(AMS_PATH);
break;
case rnx:
offset = std::string(CONTENTS_PATH).length();
createTree(REINX_CONTENTS);
chdir(REINX_PATH);
break;
case sxos:
offset = std::string(TITLES_PATH).length();
createTree(SXOS_TITLES);
chdir(SXOS_PATH);
break;
}
std::vector<std::string> entriesNames;
std::vector<int> parentIndexes;
for (size_t i = 1; i < entries.size(); i++){
entriesNames.push_back(entries[i].name);
}
std::sort(entriesNames.begin(), entriesNames.end(), caselessCompare);
std::vector<std::string> parents;
std::vector<std::vector<std::string>> children;
std::vector<std::string> tempChildren;
size_t k = 0;
while(k < entriesNames.size()){
if(entriesNames[k].length() == (size_t) (offset + 17)){
parents.push_back(entriesNames[k]);
k++;
while(entriesNames[k].length() != (size_t) (offset + 17) && k < entriesNames.size()){
if(credits == false){
if(strcasecmp(entriesNames[k].substr(offset + 16, 7).c_str(), "/cheats") == 0){
tempChildren.push_back(entriesNames[k]);
}
}
else{
tempChildren.push_back(entriesNames[k]);
}
k++;
}
children.push_back(tempChildren);
tempChildren.clear ();
}
else{
k++;
}
}
brls::Logger::debug("Titles size:");
std::cout << titles.size() << std::endl;
brls::Logger::debug("Parents size:");
std::cout << parents.size() << std::endl;
size_t lastL = 0;
std::string name;
std::string id;
ProgressEvent::instance().setTotalSteps(titles.size());
for(size_t j = 0; j < titles.size(); j++){
for(size_t l = lastL; l < parents.size(); l++){
if(strcasecmp((titles.at(j).id).c_str(), parents[l].substr(offset, 16).c_str()) == 0){
unzipper.extractEntry(parents[l]);
for(auto& e : children[l]){
unzipper.extractEntry(e);
extractedTitles.insert(id);
ProgressEvent::instance().setStep(j);
name = "No name retrieved.";
id = e.substr(offset, 16);
std::transform(id.begin(), id.end(), id.begin(), ::toupper);
}
lastL = l;
break;
}
}
}
unzipper.close();
writeTitlesToFile(extractedTitles);
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
}
void writeTitlesToFile(std::set<std::string> titles){
std::ofstream updatedTitlesFile;
std::set<std::string>::iterator it = titles.begin();
updatedTitlesFile.open(UPDATED_TITLES_PATH, std::ofstream::out | std::ofstream::trunc);
if(updatedTitlesFile.is_open()) {
while (it != titles.end()){
updatedTitlesFile << (*it) << std::endl;
it++;
}
updatedTitlesFile.close();
}
}
void removeCheats(CFW cfw){
ProgressEvent::instance().reset();
ProgressEvent::instance().setStep(1);
std::string path;
switch(cfw){
case ams:
path = std::string(AMS_PATH) + std::string(CONTENTS_PATH);
break;
case rnx:
path = std::string(REINX_PATH) + std::string(CONTENTS_PATH);
break;
case sxos:
path = std::string(SXOS_PATH) + std::string(TITLES_PATH);
break;
}
for (const auto & entry : std::filesystem::directory_iterator(path)){
std::string cheatsPath = entry.path().string() + "/cheats";
if(std::filesystem::exists(cheatsPath)){
for (const auto & cheat : std::filesystem::directory_iterator(cheatsPath)){
std::filesystem::remove(cheat);
}
rmdir(cheatsPath.c_str());
if(std::filesystem::is_empty(entry)){
rmdir(entry.path().string().c_str());
}
}
}
std::filesystem::remove(UPDATED_TITLES_PATH);
ProgressEvent::instance().setStep(ProgressEvent::instance().getMax());
}

View file

@ -0,0 +1,105 @@
#include "list_download_tab.hpp"
ListDownloadTab::ListDownloadTab(archiveType type) :
brls::List()
{
std::string operation = "Getting ";
this->description = new brls::Label(brls::LabelStyle::DESCRIPTION, "", true);
switch(type){
case sigpatches:
links = fetchLinks(SIGPATCHES_URL);
operation += "sigpatches";
this->description->setText(
"\uE016 Sigpatches allow your Switch to install and run unofficial NSP file. " \
"Make sure you pick the correct sigpatches for your setup (pure Atmosphere or Hekate+Atmosphere)."
);
break;
case fw:
links = fetchLinks(FIRMWARE_URL);
operation += "firmware";
this->description->setText(
"\uE016 Here are firmware dumps from \"https://darthsternie.net/switch-firmwares/\". "\
"Once downloaded, it will be extracted in \"/firmware\". You can then install the update though Daybreak or ChoiDuJour."
);
break;
case app:
std::get<0>(links).push_back("Latest version");
std::get<1>(links).push_back(APP_URL);
operation += "app";
break;
case cfw:
links = fetchLinks(CFW_URL);
operation += "CFW";
this->description->setText(
"\uE016 Here is a list of the main Switch CFWs. If you want to use Atmosphere + Hekate, download Atmosphere and then Hekate."
);
break;
case cheats:
std::string cheatsVer = fetchTitle(CHEATS_RELEASE_URL);
if(cheatsVer != "-1"){
std::get<0>(links).push_back("Latest (ver " + cheatsVer + ")");
switch(getCFW()){
case sxos:
std::get<1>(links).push_back(CHEATS_URL_TITLES);
break;
case ams:
std::get<1>(links).push_back(CHEATS_URL_CONTENTS);
break;
case rnx:
std::get<1>(links).push_back(CHEATS_URL_CONTENTS);
break;
}
}
operation += "cheats";
this->description->setText(
"\uE016 This will downloaded a daily updated archive of cheat code from gbatemp.net. "\
"Cheat codes for games you don't have installed won't be extracted to your SD card. "\
"Title IDs listed in '" + std::string(CHEATS_EXCLUDE) + "' won't recieve cheat updates."
);
break;
}
this->addView(description);
int nbLinks = std::get<0>(links).size();
if(nbLinks){
linkItems.reserve(nbLinks);
for (int i = 0; i<nbLinks; i++){
std::string url = std::get<1>(links)[i];
std::string text("Downloading:\n" + std::get<0>(links)[i] + "\n\nFrom:\n" + url);
linkItems[i] = new brls::ListItem(std::get<0>(links)[i]);
linkItems[i]->getClickEvent()->subscribe([&, text, url, type, operation](brls::View* view) {
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
stagedFrame->setTitle(operation);
stagedFrame->addStage(
new ConfirmPage(stagedFrame, text)
);
stagedFrame->addStage(
new WorkerPage(stagedFrame, "Downloading...", [url, type](){downloadArchive(url, type);})
);
stagedFrame->addStage(
new WorkerPage(stagedFrame, "Extracting...", [type](){extractArchive(type);})
);
stagedFrame->addStage(
new ConfirmPage(stagedFrame, "All done!", true)
);
brls::Application::pushView(stagedFrame);
});
this->addView(linkItems[i]);
}
}
else{
notFound = new brls::Label(
brls::LabelStyle::DESCRIPTION,
"Could not find a download link, make sure the Switch has access to the internet.\n"\
"If this problem persists, please open an issue on Github",
true
);
notFound->setHorizontalAlign(NVG_ALIGN_CENTER);
this->addView(notFound);
}
}
ListDownloadTab::~ListDownloadTab(){
}

58
source/main.cpp Normal file
View file

@ -0,0 +1,58 @@
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <switch.h>
#include <borealis.hpp>
#include "main_frame.hpp"
#include "constants.hpp"
#include "utils.hpp"
#include <string.h>
#include <stdio.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
// Init the app
if (!brls::Application::init(APP_TITLE))
{
brls::Logger::error("Unable to init Borealis application");
return EXIT_FAILURE;
}
// Setup verbose logging on PC
#ifndef __SWITCH__
brls::Logger::setLogLevel(brls::LogLevel::DEBUG);
#endif
// Initialize services with a PC shim
nsInitialize();
socketInitializeDefault();
nxlinkStdio();
std::cout << R_SUCCEEDED(splInitialize()) << std::endl;
createTree(CONFIG_PATH);
brls::Logger::setLogLevel(brls::LogLevel::DEBUG);
brls::Logger::debug("Start");
// Create root view
MainFrame *mainFrame = new MainFrame();
// Add the root view to the stack
brls::Application::pushView(mainFrame);
// Run the app
while (brls::Application::mainLoop());
// Exit
splExit();
socketExit();
nsExit();
return EXIT_SUCCESS;
}

22
source/main_frame.cpp Normal file
View file

@ -0,0 +1,22 @@
#include "main_frame.hpp"
MainFrame::MainFrame() : TabFrame()
{
//this->setIcon(new Logo(LogoStyle::HEADER));
brls::Logger::debug("MainFrame");
setTitle(std::string(APP_TITLE) + " v" + std::string(APP_VERSION));
this->addTab("About", new AboutTab());
this->addSeparator();
this->addTab("Update CFW", new ListDownloadTab(cfw));
this->addTab("Update sigpatches", new ListDownloadTab(sigpatches));
this->addTab("Download firmwares", new ListDownloadTab(fw));
this->addTab("Download cheats", new ListDownloadTab(cheats));
this->addSeparator();
this->addTab("Tools", new ToolsTab());
}

43
source/payload_page.cpp Normal file
View file

@ -0,0 +1,43 @@
#include "payload_page.hpp"
PayloadPage::PayloadPage() : AppletFrame(true, true)
{
this->setTitle("Reboot menu");
list = new brls::List();
label = new brls::Label(
brls::LabelStyle::DESCRIPTION,
"Select a payload to reboot to it.",
true
);
list->addView(label);
std::vector<std::string> payloads = fetchPayloads();
int nbPayloads = payloads.size();
items.reserve(nbPayloads);
for (int i = 0; i < nbPayloads; i++){
std::string payload = payloads[i];
items[i] = new brls::ListItem(payload);
items[i]->getClickEvent()->subscribe([&, payload](brls::View* view) {
reboot_to_payload(payload.c_str());
brls::Application::popView();
});
list->addView(items[i]);
}
list->addView(new brls::ListItemGroupSpacing(true));
shutDown = new brls::ListItem("Shut Down");
shutDown->getClickEvent()->subscribe([](brls::View* view) {
shut_down(false);
brls::Application::popView();
});
list->addView(shutDown);
reboot = new brls::ListItem("Reboot");
reboot->getClickEvent()->subscribe([](brls::View* view) {
shut_down(true);
brls::Application::popView();
});
list->addView(reboot);
this->setContentView(list);
}

77
source/reboot_payload.c Normal file
View file

@ -0,0 +1,77 @@
#include "reboot_payload.h"
#include "constants.hpp"
#define IRAM_PAYLOAD_MAX_SIZE 0x2F000
#define IRAM_PAYLOAD_BASE 0x40010000
static alignas(0x1000) u8 g_reboot_payload[IRAM_PAYLOAD_MAX_SIZE];
static alignas(0x1000) u8 g_ff_page[0x1000];
static alignas(0x1000) u8 g_work_page[0x1000];
void do_iram_dram_copy(void *buf, uintptr_t iram_addr, size_t size, int option) {
memcpy(g_work_page, buf, size);
SecmonArgs args = {0};
args.X[0] = 0xF0000201; /* smcAmsIramCopy */
args.X[1] = (uintptr_t)g_work_page; /* DRAM Address */
args.X[2] = iram_addr; /* IRAM Address */
args.X[3] = size; /* Copy size */
args.X[4] = option; /* 0 = Read, 1 = Write */
svcCallSecureMonitor(&args);
memcpy(buf, g_work_page, size);
}
void copy_to_iram(uintptr_t iram_addr, void *buf, size_t size) {
do_iram_dram_copy(buf, iram_addr, size, 1);
}
void copy_from_iram(void *buf, uintptr_t iram_addr, size_t size) {
do_iram_dram_copy(buf, iram_addr, size, 0);
}
static void clear_iram(void) {
memset(g_ff_page, 0xFF, sizeof(g_ff_page));
for (size_t i = 0; i < IRAM_PAYLOAD_MAX_SIZE; i += sizeof(g_ff_page)) {
copy_to_iram(IRAM_PAYLOAD_BASE + i, g_ff_page, sizeof(g_ff_page));
}
}
static void inject_payload(void) {
clear_iram();
for (size_t i = 0; i < IRAM_PAYLOAD_MAX_SIZE; i += 0x1000) {
copy_to_iram(IRAM_PAYLOAD_BASE + i, &g_reboot_payload[i], 0x1000);
}
splSetConfig((SplConfigItem)65001, 2);
}
int reboot_to_payload(const char* path){
bool can_reboot = true;
//Result rc = splInitialize();
/* if(0){
//if (R_FAILED(rc)) {
can_reboot = false;
printf("Can't reboot");
} */
const char* lol = "lol";
printf(lol);
printf(path);
FILE *f;
f = fopen(path, "rb");
if (f == NULL) can_reboot = false;
else {
printf("Can't open payload");
fread(g_reboot_payload, 1, sizeof(g_reboot_payload), f);
fclose(f);
}
if (can_reboot) {
printf("injecting payload");
inject_payload();
}
if (can_reboot) splExit();
return 0;
}

61
source/tools_tab.cpp Normal file
View file

@ -0,0 +1,61 @@
#include "tools_tab.hpp"
ToolsTab::ToolsTab() : brls::List()
{
this->label = new brls::Label(brls::LabelStyle::DESCRIPTION, "\uE016 Miscellaneous tools", true);
this->addView(label);
viewCheats = new brls::ListItem("View installed cheats");
viewCheats->getClickEvent()->subscribe([&](brls::View* view){
brls::Application::pushView(new AppPage());
});
this->addView(viewCheats);
deleteCheats = new brls::ListItem("Delete all existing cheat codes");
deleteCheats->getClickEvent()->subscribe([&](brls::View* view){
stagedFrame = new brls::StagedAppletFrame();
stagedFrame->setTitle("Delete all cheats");
stagedFrame->addStage(
new WorkerPage(stagedFrame, "Deleting", [](){removeCheats(getCFW());})
);
stagedFrame->addStage(
new ConfirmPage(stagedFrame, "All done!", true)
);
brls::Application::pushView(stagedFrame);
});
this->addView(deleteCheats);
JCcolor = new brls::ListItem("Change the Joy-Cons color");
JCcolor->getClickEvent()->subscribe([&](brls::View* view){
brls::Application::pushView(new JCPage());
});
this->addView(JCcolor);
updateApp = new brls::ListItem("Update the app");
std::string text("Downloading:\nAIO-switch-updater\n\nFrom:\n" + std::string(APP_URL));
updateApp->getClickEvent()->subscribe([&, text](brls::View* view) {
brls::StagedAppletFrame* stagedFrame = new brls::StagedAppletFrame();
stagedFrame->setTitle("Updating app");
stagedFrame->addStage(
new ConfirmPage(stagedFrame, text)
);
stagedFrame->addStage(
new WorkerPage(stagedFrame, "Downloading...", [](){downloadArchive(APP_URL, app);})
);
stagedFrame->addStage(
new WorkerPage(stagedFrame, "Extracting....", [](){extractArchive(app);})
);
stagedFrame->addStage(
new ConfirmPage(stagedFrame, "All done!", true)
);
brls::Application::pushView(stagedFrame);
});
this->addView(updateApp);
rebootPayload = new brls::ListItem("Shut down / Inject payload");
rebootPayload->getClickEvent()->subscribe([&](brls::View* view){
brls::Application::pushView(new PayloadPage());
});
this->addView(rebootPayload);
}

167
source/utils.cpp Normal file
View file

@ -0,0 +1,167 @@
#include "utils.hpp"
bool isServiceRunning(const char *serviceName) {
Handle handle;
SmServiceName service_name = smEncodeName(serviceName);
bool running = R_FAILED(smRegisterService(&handle, service_name, false, 1));
svcCloseHandle(handle);
if (!running)
smUnregisterService(service_name);
return running;
}
CFW getCFW(){
if(isServiceRunning("rnx")) return rnx;
else if(isServiceRunning("tx")) return sxos;
else return ams;
}
std::vector<std::string> htmlProcess(std::string s, std::regex rgx){
//std::vector<std::regex> rgx = {std::regex(">(?:(?!>).)*?</a>"), std::regex(">(?:(?!>).)*?</a>")};
std::vector<std::string> res;
std::smatch match;
while (std::regex_search(s, match, rgx)){
res.push_back(match[0].str());
s = match.suffix();
}
return res;
}
void createTree(std::string path){
std::string delimiter = "/";
size_t pos = 0;
std::string token;
std::string directories("");
while ((pos = path.find(delimiter)) != std::string::npos) {
token = path.substr(0, pos);
directories += token + "/";
std::filesystem::create_directory(directories);
path.erase(0, pos + delimiter.length());
}
}
bool isArchive(const char * path){
std::fstream file;
std::string fileContent;
if(std::filesystem::exists(path)){
file.open(path, std::fstream::in);
file >> fileContent;
file.close();
}
return fileContent.find("DOCTYPE") == std::string::npos;
}
void downloadArchive(std::string url, archiveType type){
createTree(DOWNLOAD_PATH);
switch(type){
case sigpatches:
downloadFile(url.c_str(), SIGPATCHES_FILENAME, OFF);
break;
case cheats:
downloadFile(url.c_str(), CHEATS_FILENAME, OFF);
break;
case fw:
downloadFile(url.c_str(), FIRMWARE_FILENAME, OFF);
break;
case app:
downloadFile(url.c_str(), APP_FILENAME, OFF);
break;
case cfw:
downloadFile(url.c_str(), CFW_FILENAME, OFF);
break;
}
}
void extractArchive(archiveType type){
std::vector<Title> titles;
switch(type){
case sigpatches:
if(isArchive(SIGPATCHES_FILENAME)) extract(SIGPATCHES_FILENAME);
break;
case cheats:
titles = getInstalledTitlesNs();
titles = excludeTitles((std::string(DOWNLOAD_PATH) + "exclude.txt").c_str(), titles);
extractCheats(CHEATS_FILENAME, titles, getCFW());
break;
case fw:
createTree(FIRMWARE_PATH);
extract(FIRMWARE_FILENAME, FIRMWARE_PATH);
break;
case app:
extract(APP_FILENAME);
break;
case cfw:
extract(CFW_FILENAME);
break;
}
}
void progressTest(std::string url, archiveType type){
ProgressEvent::instance().reset();
ProgressEvent::instance().setTotalSteps(2);
ProgressEvent::instance().setStep(1);
ProgressEvent::instance().setStep(2);
}
std::string formatListItemTitle(const std::string str, size_t maxScore) {
size_t score = 0;
for (size_t i = 0; i < str.length(); i++)
{
score += std::isupper(str[i]) ? 4 : 3;
if(score > maxScore)
{
return str.substr(0, i-1) + "\u2026";
}
}
return str;
}
std::string formatApplicationId(u64 ApplicationId){
std::stringstream strm;
strm << std::uppercase << std::setfill('0') << std::setw(16) << std::hex << ApplicationId;
return strm.str();
}
std::set<std::string> readLineByLine(const char * path){
std::set<std::string> titles;
std::string str;
std::ifstream in(path);
if(in){
while (std::getline(in, str))
{
// Line contains string of length > 0 then save it in vector
if(str.size() > 0)
titles.insert(str);
}
in.close();
}
return titles;
}
std::vector<std::string> fetchPayloads(){
std::vector<std::string> payloadPaths;
payloadPaths.push_back(ROOT_PATH);
if(std::filesystem::exists(PAYLOAD_PATH)) payloadPaths.push_back(PAYLOAD_PATH);
if(std::filesystem::exists(AMS_PATH)) payloadPaths.push_back(AMS_PATH);
if(std::filesystem::exists(REINX_PATH)) payloadPaths.push_back(REINX_PATH);
if(std::filesystem::exists(BOOTLOADER_PL_PATH)) payloadPaths.push_back(BOOTLOADER_PL_PATH);
if(std::filesystem::exists(SXOS_PATH)) payloadPaths.push_back(SXOS_PATH);
std::vector<std::string> res;
for (auto& path : payloadPaths){
for (const auto & entry : std::filesystem::directory_iterator(path)){
if(entry.path().extension().string() == ".bin")
res.push_back(entry.path().string().c_str());
}
}
return res;
}
void shut_down(bool reboot){
bpcInitialize();
if(reboot) bpcRebootSystem();
else bpcShutdownSystem();
bpcExit();
}

78
source/worker_page.cpp Normal file
View file

@ -0,0 +1,78 @@
#include "worker_page.hpp"
WorkerPage::WorkerPage(brls::StagedAppletFrame* frame, const std::string& text, worker_func_t worker_func): frame(frame), workerFunc(worker_func)
{
this->progressDisp = new brls::ProgressDisplay();
this->progressDisp->setParent(this);
this->label = new brls::Label(brls::LabelStyle::DIALOG, text, true);
this->label->setHorizontalAlign(NVG_ALIGN_CENTER);
this->label->setParent(this);
this->button = new brls::Button(brls::ButtonStyle::BORDERLESS); // avoid back button bug
this->button->setParent(this);
appletSetMediaPlaybackState(true);
}
void WorkerPage::draw(NVGcontext* vg, int x, int y, unsigned width, unsigned height, brls::Style* style, brls::FrameContext* ctx)
{
if (!this->workStarted)
{
this->workStarted = true;
ProgressEvent::instance().reset();
workerThread = new std::thread(&WorkerPage::doWork, this);
}
else if (ProgressEvent::instance().finished())
{
brls::Logger::debug("Worker done");
appletSetMediaPlaybackState(false);
frame->nextStage();
}
else
{
this->progressDisp->setProgress(ProgressEvent::instance().getStep(), ProgressEvent::instance().getMax());
this->progressDisp->frame(ctx);
this->label->frame(ctx);
}
}
void WorkerPage::layout(NVGcontext* vg, brls::Style* style, brls::FontStash* stash)
{
this->label->setWidth(roundf((float)this->width * style->CrashFrame.labelWidth));
this->label->setBoundaries(
this->x + this->width / 2 - this->label->getWidth() / 2,
this->y + (this->height - style->AppletFrame.footerHeight) / 2,
this->label->getWidth(),
this->label->getHeight());
this->progressDisp->setBoundaries(
this->x + this->width / 2 - style->CrashFrame.buttonWidth,
this->y + this->height / 2,
style->CrashFrame.buttonWidth * 2,
style->CrashFrame.buttonHeight);
}
void WorkerPage::doWork()
{
if (this->workerFunc)
this->workerFunc();
}
brls::View* WorkerPage::getDefaultFocus()
{
return this->button;
}
WorkerPage::~WorkerPage()
{
if (this->workStarted && workerThread->joinable())
{
this->workerThread->join();
if (this->workerThread)
delete this->workerThread;
}
delete this->progressDisp;
delete this->label;
delete this->button;
}