commit 9ef4252e8a2951183e59bcfe9241bd44be96eba4 Author: e1ite007 Date: Sat Apr 27 04:27:28 2024 -0600 Initial commit diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.github/workflows/build_depoly.yml b/.github/workflows/build_depoly.yml new file mode 100644 index 0000000..1b7a6c7 --- /dev/null +++ b/.github/workflows/build_depoly.yml @@ -0,0 +1,19 @@ +name: build +on: [push, pull_request] + +jobs: + build: + runs-on: ubuntu-latest + container: devkitpro/devkita64:latest + + steps: + - name: Checkout 🛎️ + uses: actions/checkout@master + + - name: Build + run: make dist -j2 + + - uses: actions/upload-artifact@master + with: + name: sigpatch-updater + path: sigpatch-updater.zip diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f923385 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +build +*.elf +*.nacp +*.nro +.vscode/* +!.vscode/c_cpp_properties.json +*.zip +out diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..905d36e --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "switch", + "includePath": [ + "${default}", + "${workspaceFolder}/**", + "${DEVKITPRO}/libnx/include/", + "${DEVKITPRO}/portlibs/switch/include/" + ], + "defines": [], + "cStandard": "c17", + "cppStandard": "c++23", + "compilerPath": "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc" + } + ], + "version": 4 +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..12e8f42 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 ITotalJustice + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c511079 --- /dev/null +++ b/Makefile @@ -0,0 +1,240 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/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): +# - .jpg +# - icon.jpg +# - /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): +# - .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. +#--------------------------------------------------------------------------------- +APP_TITLE := sigpatch-updater +APP_AUTHOR := TotalJustice +APP_VERSION := 0.3.0 + +TARGET := sigpatch-updater +BUILD := build +SOURCES := source +DATA := data +INCLUDES := includes +#ROMFS := romfs + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + +CFLAGS := -Wall -O2 -ffunction-sections \ + $(ARCH) $(DEFINES) + +CFLAGS += $(INCLUDE) -D__SWITCH__ + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions + +ASFLAGS := $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lminizip -lz -lnx + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + + +#--------------------------------------------------------------------------------- +# 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) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + + @rm -rf out/ + @mkdir -p out/switch/sigpatch-updater/ + @cp $(CURDIR)/$(TARGET).nro out/switch/sigpatch-updater/ + +#--------------------------------------------------------------------------------- +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 +#--------------------------------------------------------------------------------- +dist: all + @echo making dist ... + + @rm -f sigpatch-updater.zip + @cd out; zip -r ../sigpatch-updater.zip ./*; cd ../ +#--------------------------------------------------------------------------------- +nxlink: all + @echo making and nxlinking ... + + nxlink $(CURDIR)/$(TARGET).nro + +#--------------------------------------------------------------------------------- +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 +#--------------------------------------------------------------------------------------- diff --git a/README.md b/README.md new file mode 100644 index 0000000..1372b33 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Sigpatch-Updater + +This is a updater for you sigpatches! + +Currently it has the option to: + +- Update patches needed for Atmosphere +- Update sys-patch and auto run the sysmod +- Update the app + +--- + +![Img](images/example.jpg) + +---- + +## Where does it download the patches from? + +The patches are download from [a new host](https://sigmapatches.coomer.party). Huge thanks to them! + +--- + +## What is sys-patch? + +you can read more about it [here](https://github.com/ITotalJustice/sys-patch). + +---- + +## Special Thanks! + +- [toph](https://github.com/sudot0ph) for the design of the app icon +- The-4n for previously updating the sigpatch thread on gbatemp +- Joonie for their help with hekate patches in the past +- TeJay for previously maintaining the patches repo +- Archbox for being very helpful as always diff --git a/icon.jpg b/icon.jpg new file mode 100644 index 0000000..dafd7e8 Binary files /dev/null and b/icon.jpg differ diff --git a/images/example.jpg b/images/example.jpg new file mode 100644 index 0000000..d2ffd88 Binary files /dev/null and b/images/example.jpg differ diff --git a/includes/download.h b/includes/download.h new file mode 100644 index 0000000..6330d99 --- /dev/null +++ b/includes/download.h @@ -0,0 +1,17 @@ +#ifndef _DOWNLOAD_H_ +#define _DOWNLOAD_H_ + +#define AMS_SIG_URL "https://sigmapatches.su/sigpatches.zip" +#define APP_URL "https://gitflic.ru/project/e1ite007/sigpatch-updater/releases/latest/download/sigpatch-updater.nro" +#define SYS_PATCH_URL "https://sigmapatches.su/sys-patch.zip" +#define TEMP_FILE "/switch/sigpatch-updater/temp" + +#include +#include + +typedef bool(*DlProgressCallback)(u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow); + +// +bool downloadFile(const char *url, const char *output, DlProgressCallback pcall); + +#endif diff --git a/includes/unzip.h b/includes/unzip.h new file mode 100644 index 0000000..2154d35 --- /dev/null +++ b/includes/unzip.h @@ -0,0 +1,6 @@ +#ifndef _UNZIP_H_ +#define _UNZIP_H_ + +bool unzip(const char *output); + +#endif diff --git a/includes/util.h b/includes/util.h new file mode 100644 index 0000000..0b45040 --- /dev/null +++ b/includes/util.h @@ -0,0 +1,10 @@ +#ifndef _UTIL_H_ +#define _UTIL_H_ + +#include + +void consolePrint(const char* s, ...) __attribute__ ((format (printf, 1, 2))); + +bool stillRunning(void); + +#endif diff --git a/source/download.c b/source/download.c new file mode 100644 index 0000000..fab6e16 --- /dev/null +++ b/source/download.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "download.h" +#include "util.h" + +#define API_AGENT "ITotalJustice" +#define _1MiB 0x100000 + +typedef struct { + char *memory; + size_t size; +} MemoryStruct_t; + +typedef struct { + uint8_t *data; + size_t data_size; + uint64_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; + const 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; +} + +static int download_progress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { + const DlProgressCallback callback = (DlProgressCallback)clientp; + if (!callback(dltotal, dlnow, ultotal, ulnow)) { + return 1; + } + return 0; +} + +bool downloadFile(const char *url, const char *output, DlProgressCallback pcall) { + CURL *curl = curl_easy_init(); + if (curl) { + FILE *fp = fopen(output, "wb"); + if (fp) { + consolePrint("\nDownload in progress, Press (B) to cancel...\n\n"); + + ntwrk_struct_t chunk = {0}; + chunk.data = 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); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + + // write calls + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk); + + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); + curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, download_progress); + curl_easy_setopt(curl, CURLOPT_XFERINFODATA, pcall); + + CURLM *multi_handle = curl_multi_init(); + curl_multi_add_handle(multi_handle, curl); + int still_running = 1; + CURLMcode mc; + + while (still_running) { + mc = curl_multi_perform(multi_handle, &still_running); + + if (!mc) + mc = curl_multi_poll(multi_handle, NULL, 0, 1000, NULL); + + if (mc) { + break; + } + } + + int msgs_left; + CURLMsg *msg; + CURLcode res = 1; + + while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) { + if (msg->msg == CURLMSG_DONE) { + res = msg->data.result; + } + } + + // write from mem to file + if (chunk.offset) { + fwrite(chunk.data, 1, chunk.offset, fp); + } + + // clean + curl_multi_remove_handle(multi_handle, curl); + curl_easy_cleanup(curl); + curl_multi_cleanup(multi_handle); + free(chunk.data); + fclose(chunk.out); + + if (res == CURLE_OK) { + consolePrint("\n\ndownload complete!\n\n"); + return true; + } else { + consolePrint("\n\ncurl error: %s", curl_easy_strerror(res)); + } + } + } + + consolePrint("\n\ndownload failed...\n\n"); + return false; +} diff --git a/source/main.c b/source/main.c new file mode 100644 index 0000000..58ce2ff --- /dev/null +++ b/source/main.c @@ -0,0 +1,272 @@ +#include +#include +#include +#include +#include // chdir +#include // mkdir +#include +#include +#include + +#include "download.h" +#include "unzip.h" +#include "util.h" + + +#define ROOT "/" +#define APP_PATH "/switch/sigpatch-updater/" +#define APP_OUTPUT "/switch/sigpatch-updater/sigpatch-updater.nro" +#define OLD_APP_PATH "/switch/sigpatch-updater.nro" + +#define APP_VERSION "0.3.0" + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) + +typedef bool (*func_handler)(void); +struct Entry { + const char* display_text; + func_handler func; + const char* description; +}; + +static PadState g_pad; + +static bool progressCallback(u32 dltotal, u32 dlnow, u32 ultotal, u32 ulnow) { + if (!stillRunning()) { + return false; + } + + padUpdate(&g_pad); + const u64 kDown = padGetButtonsDown(&g_pad); + if (kDown & HidNpadButton_B) { + return false;; + } + + if (dltotal > 0) { + struct timeval tv = {0}; + gettimeofday(&tv, NULL); + const int counter = round(tv.tv_usec / 100000); + + if (counter == 0 || counter == 2 || counter == 4 || counter == 6 || counter == 8) { + const double mb = 1024*1024; + consolePrint("* DOWNLOADING: %.2fMB of %.2fMB *\r", (double)dlnow / mb, (double)dltotal / mb); + } + } + + return true; +} + +static bool update_sigpatches_handler(void) { + if (downloadFile(AMS_SIG_URL, TEMP_FILE, progressCallback)) { + if (!unzip(TEMP_FILE)) { + return false; + } + consolePrint("\nfinished!\n\nRemember to reboot for the patches to be loaded!\n"); + return true; + } else { + return false; + } +} + +static bool update_syspatch_handler(void) { + if (downloadFile(SYS_PATCH_URL, TEMP_FILE, progressCallback)) { + if (!unzip(TEMP_FILE)) { + return false; + } + + u64 pid = 0; + const NcmProgramLocation location = { + .program_id = 0x420000000000000BULL, + .storageID = NcmStorageId_None, + }; + + if (R_FAILED(pmshellLaunchProgram(0, &location, &pid))) { + consolePrint( + "\nFailed to start sys-patch!\n" + "A reboot is needed...\n" + "Report this issue on GitHub please!\n"); + return false; + } else { + consolePrint("\nsys-patch ran successfully, patches should be applied!\n"); + return true; + } + } else { + return false; + } +} + +static bool update_app_handler(void) { + if (downloadFile(APP_URL, TEMP_FILE, progressCallback)) { + remove(APP_OUTPUT); + rename(TEMP_FILE, APP_OUTPUT); + remove(OLD_APP_PATH); + consolePrint("\nApp updated!\nRestart app to take effect"); + return true; + } else { + return false; + } +} + +static const struct Entry ENTRIES[] = { + { "Update sigpatches", update_sigpatches_handler, "sigpatches require a reboot" }, + { "Update sys-patch", update_syspatch_handler, "sys-patch applies patches without reboot" }, + { "Update app ", update_app_handler, "requires restarting the app" }, +}; + +enum WifiState { + WifiState_AIRPLANE, + WifiState_0, // no signal + WifiState_1, // low strength + WifiState_2, // medium strength + WifiState_3, // max strength +}; + +static enum WifiState get_wifi_state() { + Result rc = 0; + NifmInternetConnectionType type = {0}; + NifmInternetConnectionStatus status = {0}; + u32 strength = 0; + + if (R_FAILED(rc = nifmGetInternetConnectionStatus(&type, &strength, &status))) { + return WifiState_AIRPLANE; + } + + if (type == NifmInternetConnectionType_Ethernet) { + if (status != NifmInternetConnectionStatus_Connected) { + return WifiState_0; + } else { + return WifiState_3; + } + } + + switch (strength) { + case 0: return WifiState_0; + case 2: return WifiState_2; + case 3: return WifiState_3; + default: return WifiState_1; + } +} + +static void refreshScreen(int cursor) { + consoleClear(); + + printf("\x1B[36mSigpatch-Updater: v%s\x1B[37m\n\n\n", APP_VERSION); + // printf("This app is unmaintained, please consider using a different tool to update your patches!\n\n\n"); + printf("Press (A) to select option\n\n"); + printf("Press (+) to exit\n\n\n"); + + for (int i = 0; i < ARRAY_SIZE(ENTRIES); i++) { + printf("[%c] = %s\t\t(%s)\n\n", cursor == i ? 'X' : ' ', ENTRIES[i].display_text, ENTRIES[i].description); + } + + consoleUpdate(NULL); +} + +// update the cursor so that it wraps around +static void update_cursor(int* cur, int new_value, int max) { + if (new_value >= max) { + new_value = 0; + } else if (new_value < 0) { + new_value = max - 1; + } + + *cur = new_value; + refreshScreen(new_value); +} + +// where the app starts +int main(int argc, char **argv) { + curl_global_init(CURL_GLOBAL_ALL); + + // init stuff + mkdir(APP_PATH, 0777); + + // move nro to app folder + if (!strstr(argv[0], APP_OUTPUT)) { + remove(APP_OUTPUT); + rename(argv[0], APP_OUTPUT); + } + + // change directory to root (defaults to /switch/) + chdir(ROOT); + + // set the cursor position to 0 + int cursor = 0; + + padConfigureInput(1, HidNpadStyleSet_NpadStandard); + padInitializeDefault(&g_pad); + + // main menu + refreshScreen(cursor); + + // muh loooooop + while (stillRunning()) { + padUpdate(&g_pad); + const u64 kDown = padGetButtonsDown(&g_pad); + + // move cursor down... + if (kDown & HidNpadButton_AnyDown) { + update_cursor(&cursor, cursor + 1, ARRAY_SIZE(ENTRIES)); + } + + // move cursor up... + else if (kDown & HidNpadButton_AnyUp) { + update_cursor(&cursor, cursor - 1, ARRAY_SIZE(ENTRIES)); + } + + else if (kDown & HidNpadButton_A) { + if (get_wifi_state() < WifiState_1) { + consolePrint( + "\n\n[Error] not connected to the internet!\n\n" + "An internet connection is required to download updates!\n"); + } + else { + if (!ENTRIES[cursor].func()) { + consolePrint("Failed to %s\n", ENTRIES[cursor].display_text); + } + } + + consolePrint("\nPress (A) to continue...\n"); + + while (stillRunning()) { + padUpdate(&g_pad); + const u64 kDown = padGetButtonsDown(&g_pad); + if (kDown & HidNpadButton_A) { + break; + } + svcSleepThread(1e+9 / 60); + } + + refreshScreen(cursor); + } + + // exit... + else if (kDown & HidNpadButton_Plus) { + break; + } + + // 1e+9 = 1 second + svcSleepThread(1e+9 / 60); + } + + curl_global_cleanup(); + return 0; +} + +// this is called before main +void userAppInit(void) { + appletLockExit(); + pmshellInitialize(); + consoleInit(NULL); + socketInitializeDefault(); + nifmInitialize(NifmServiceType_User); +} + +// this is called after main exits +void userAppExit(void) { + pmshellExit(); + nifmExit(); + socketExit(); + consoleExit(NULL); + appletUnlockExit(); +} diff --git a/source/unzip.c b/source/unzip.c new file mode 100644 index 0000000..e58959f --- /dev/null +++ b/source/unzip.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include + +#include "unzip.h" + +#define WRITEBUFFERSIZE 0x1000 // 4KiB +#define MAXFILENAME 0x301 + +bool unzip(const char *output) { + unzFile zfile = unzOpen(output); + if (!zfile) { + return false; + } + + unz_global_info gi = {0}; + if (UNZ_OK != unzGetGlobalInfo(zfile, &gi)) { + return false; + } + + for (int i = 0; i < gi.number_entry; i++) { + char filename_inzip[MAXFILENAME] = {0}; + unz_file_info file_info = {0}; + + unzOpenCurrentFile(zfile); + unzGetCurrentFileInfo(zfile, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0); + + // check if the string ends with a /, if so, then its a directory. + if ((filename_inzip[strlen(filename_inzip) - 1]) == '/') { + // check if directory exists + DIR *dir = opendir(filename_inzip); + if (dir) { + closedir(dir); + } else { + printf("creating directory: %s\n", filename_inzip); + mkdir(filename_inzip, 0777); + } + } else { + const char *write_filename = filename_inzip; + FILE *outfile = fopen(write_filename, "wb"); + void *buf = malloc(WRITEBUFFERSIZE); + + printf("writing file: %s\n", filename_inzip); + consoleUpdate(NULL); + + for (int j = unzReadCurrentFile(zfile, buf, WRITEBUFFERSIZE); j > 0; j = unzReadCurrentFile(zfile, buf, WRITEBUFFERSIZE)) { + fwrite(buf, 1, j, outfile); + } + + fclose(outfile); + free(buf); + } + + unzCloseCurrentFile(zfile); + unzGoToNextFile(zfile); + consoleUpdate(NULL); + } + + unzClose(zfile); + remove(output); + + // todo: i assume everything above works fine + // this might not always be the case, add error handling! + return true; +} diff --git a/source/util.c b/source/util.c new file mode 100644 index 0000000..1ada9f0 --- /dev/null +++ b/source/util.c @@ -0,0 +1,25 @@ +#include +#include +#include + +#include "util.h" + +void consolePrint(const char* s, ...) { + va_list v; + va_start(v, s); + vprintf(s, v); + consoleUpdate(NULL); + va_end(v); +} + +bool stillRunning(void) { + static Mutex mutex = {0}; + static bool running = true; + mutexLock(&mutex); + if (!appletMainLoop()) { + running = false; + } + const bool result = running; + mutexUnlock(&mutex); + return result; +}