diff --git a/.github/workflows/build_depoly.yml b/.github/workflows/build_depoly.yml index c45e9f9..9516f19 100644 --- a/.github/workflows/build_depoly.yml +++ b/.github/workflows/build_depoly.yml @@ -9,6 +9,8 @@ jobs: steps: - name: Checkout 🛎️ uses: actions/checkout@master + with: + submodules: recursive - name: Build run: make dist -j2 diff --git a/.gitignore b/.gitignore index 5ca16b6..f50443d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ -.vscode build Firmware +firmware 420000000000000B *.txt *.elf @@ -8,5 +8,9 @@ Firmware *.nso *.nsp *.zip +*.nacp +*.ovl +*.nca out ignoreme +.vscode/settings.json diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..9b1605c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "overlay/libtesla"] + path = overlay/libtesla + url = https://github.com/ITotalJustice/libtesla.git + branch = tj diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..64286d4 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "name": "switch", + "includePath": [ + "${default}", + "${workspaceFolder}/**", + "${DEVKITPRO}/libnx/include/", + "${DEVKITPRO}/portlibs/switch/include/", + "${workspaceFolder}/overlay/libtesla/include", + "${workspaceFolder}/common" + ], + "defines": [], + "cStandard": "c17", + "cppStandard": "c++23", + "compilerPath": "${DEVKITPRO}/devkitA64/bin/aarch64-none-elf-gcc" + } + ], + "version": 4 +} diff --git a/Makefile b/Makefile index 16cb51b..8ea46f1 100644 --- a/Makefile +++ b/Makefile @@ -1,233 +1,23 @@ -#--------------------------------------------------------------------------------- -.SUFFIXES: -#--------------------------------------------------------------------------------- +MAKEFILES := sysmod overlay +TARGETS := $(foreach dir,$(MAKEFILES),$(CURDIR)/$(dir)) -ifeq ($(strip $(DEVKITPRO)),) -$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") -endif +all: $(TARGETS) + @mkdir -p out/ + @cp -R sysmod/out/* out/ + @cp -R overlay/out/* out/ -TOPDIR ?= $(CURDIR) -include $(DEVKITPRO)/libnx/switch_rules +.PHONY: $(TARGETS) -#--------------------------------------------------------------------------------- -# 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. -#--------------------------------------------------------------------------------- -TARGET := sys-patch -BUILD := build -SOURCES := src src/minIni -DATA := data -INCLUDES := include -#ROMFS := romfs +$(TARGETS): + @$(MAKE) -C $@ -# sys-patch -#--------------------------------------------------------------------------------- -# 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__ - -CXXFLAGS := $(CFLAGS) -std=c++23 -fno-rtti -fno-exceptions - -ASFLAGS := -g $(ARCH) -LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) - -LIBS := -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/atmosphere/contents/420000000000000B/flags - @touch out/atmosphere/contents/420000000000000B/flags/boot2.flag - @cp $(CURDIR)/toolbox.json out/atmosphere/contents/420000000000000B/toolbox.json - @cp $(CURDIR)/$(TARGET).nsp out/atmosphere/contents/420000000000000B/exefs.nsp - -#--------------------------------------------------------------------------------- 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 -#--------------------------------------------------------------------------------- + @rm -rf out + @for i in $(TARGETS); do $(MAKE) -C $$i clean || exit 1; done; + dist: all + @for i in $(TARGETS); do $(MAKE) -C $$i dist || exit 1; done; @echo making dist ... @rm -f sys-patch.zip @cd out; zip -r ../sys-patch.zip ./*; cd ../ -#--------------------------------------------------------------------------------- -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 index 2655228..8e9f30d 100644 --- a/README.md +++ b/README.md @@ -6,25 +6,41 @@ A script-like system module that patches fs, es and ldr on boot. ## Config -sys-patch features a *very* simple config, only 2 options so far and they both do the same thing :p +sys-patch features a simple config. This can be manually editied or updated using the overlay. -this config file can be found in `/config/sys-patch/config.ini`, if the file does not exist, the file will be created when sys-patch is run. +the config file can be found in `/config/sys-patch/config.ini`, if the file does not exist, the file will be created when sys-patch is run. ```ini [options] patch_sysmmc=1 ; 1=(default) patch sysmmc, 0=don't patch sysmmc patch_emummc=1 ; 1=(default) patch emummc, 0=don't patch emummc +logging=1 ; 1=(default) output /config/sys-patch/log.inim 0=no log ``` --- +## Overlay + +the overlay can be used to change the config options and to see what patches are applied (if any). + +- Unpatched means the patch wasn't applied (likely not found). +- Patched (green) means it was patched by sys-patch. +- Patched (yellow) means it was already patched, likely by sigpatches or a custom atmosphere build. + +

+ + +

+ +--- + ## Building ### prerequisites - install devkitpro ```sh -git clone https://github.com/ITotalJustice/sys-patch.git +git clone --recurse-submodules https://github.com/ITotalJustice/sys-patch.git cd sys-patch make ``` @@ -77,7 +93,6 @@ This repo is mainly a proof of concept. I would love for someone to build upon t here are a few ideas that i have: - option to load new patterns from file -- make this into a service / overlay - make homebrew frontend that can update this sysmod, apply patches, all without having to reboot --- @@ -97,3 +112,4 @@ software is built on the shoulders of giants. this tool wouldn't be possible wth - Switchbrew (libnx, switch-examples) - DevkitPro (toolchain) - [minIni](https://github.com/compuphase/minIni) +- [libtesla](https://github.com/WerWolv/libtesla) diff --git a/src/minIni/minGlue.c b/common/minIni/minGlue.c similarity index 99% rename from src/minIni/minGlue.c rename to common/minIni/minGlue.c index 5e3766a..631ca1e 100644 --- a/src/minIni/minGlue.c +++ b/common/minIni/minGlue.c @@ -46,7 +46,6 @@ bool ini_openrewrite(const char* filename, struct NxFile* nxfile) { bool ini_close(struct NxFile* nxfile) { fsFileClose(&nxfile->file); - fsFsCommit(&nxfile->system); fsFsClose(&nxfile->system); return true; } diff --git a/src/minIni/minGlue.h b/common/minIni/minGlue.h similarity index 93% rename from src/minIni/minGlue.h rename to common/minIni/minGlue.h index 68327ca..261f13d 100644 --- a/src/minIni/minGlue.h +++ b/common/minIni/minGlue.h @@ -16,7 +16,6 @@ struct NxFile { #define INI_FILEPOS s64 #define INI_OPENREWRITE #define INI_REMOVE -#define INI_NOBROWSE bool ini_openread(const char* filename, struct NxFile* nxfile); bool ini_openwrite(const char* filename, struct NxFile* nxfile); diff --git a/src/minIni/minIni.c b/common/minIni/minIni.c similarity index 100% rename from src/minIni/minIni.c rename to common/minIni/minIni.c diff --git a/src/minIni/minIni.h b/common/minIni/minIni.h similarity index 100% rename from src/minIni/minIni.h rename to common/minIni/minIni.h diff --git a/overlay/Makefile b/overlay/Makefile new file mode 100644 index 0000000..1f8f277 --- /dev/null +++ b/overlay/Makefile @@ -0,0 +1,225 @@ +#--------------------------------------------------------------------------------- +.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 := sys-patch +APP_AUTHOR := TotalJustice +APP_VERSION := 1.2.0 + +TARGET := sys-patch-overlay +BUILD := build +SOURCES := src ../common/minIni +DATA := data +INCLUDES := include ../common libtesla/include + +NO_ICON := 1 + +#--------------------------------------------------------------------------------- +# 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__ + +CXXFLAGS := $(CFLAGS) -std=c++23 -fno-exceptions + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -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 + + @mkdir -p out/switch/.overlays + @cp $(CURDIR)/$(TARGET).ovl out/switch/.overlays/ + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).ovl $(TARGET).nro $(TARGET).nacp $(TARGET).elf + @rm -rf out/ + @rm -f sys-patch-overlay.zip + +#--------------------------------------------------------------------------------- +ftp: all + @echo making dist ... + curl -T sys-patch-overlay.ovl ftp://192.168.200.71:5000/switch/.overlays/ --user tj:12345678 + +#--------------------------------------------------------------------------------- +dist: all + @echo making dist ... + + @rm -f sys-patch-overlay.zip + @cd out; zip -r ../sys-patch-overlay.zip ./*; cd ../ +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).ovl + +$(OUTPUT).ovl : $(OUTPUT).elf $(OUTPUT).nacp + @elf2nro $< $@ $(NROFLAGS) + @echo "built ... $(notdir $(OUTPUT).ovl)" + +$(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/overlay/libtesla b/overlay/libtesla new file mode 160000 index 0000000..3d74aeb --- /dev/null +++ b/overlay/libtesla @@ -0,0 +1 @@ +Subproject commit 3d74aeb31f0f26cf222d406c7ea118a82cc8ee69 diff --git a/overlay/src/main.cpp b/overlay/src/main.cpp new file mode 100644 index 0000000..23a4439 --- /dev/null +++ b/overlay/src/main.cpp @@ -0,0 +1,149 @@ +#define TESLA_INIT_IMPL // If you have more than one file using the tesla header, only define this in the main one +#include // The Tesla Header +#include +#include "minIni/minIni.h" + +namespace { + +constexpr auto CONFIG_PATH = "/config/sys-patch/config.ini"; +constexpr auto LOG_PATH = "/config/sys-patch/log.ini"; + +auto does_file_exist(const char* path) -> bool { + Result rc{}; + FsFileSystem fs{}; + FsFile file{}; + char path_buf[FS_MAX_PATH]{}; + + if (R_FAILED(fsOpenSdCardFileSystem(&fs))) { + return false; + } + + strcpy(path_buf, path); + rc = fsFsOpenFile(&fs, path_buf, FsOpenMode_Read, &file); + fsFileClose(&file); + fsFsClose(&fs); + return R_SUCCEEDED(rc); +} + +// creates a directory, non-recursive! +auto create_dir(const char* path) -> bool { + Result rc{}; + FsFileSystem fs{}; + char path_buf[FS_MAX_PATH]{}; + + if (R_FAILED(fsOpenSdCardFileSystem(&fs))) { + return false; + } + + strcpy(path_buf, path); + rc = fsFsCreateDirectory(&fs, path_buf); + fsFsClose(&fs); + return R_SUCCEEDED(rc); +} + +struct ConfigEntry { + const char* const section; + const char* const key; + bool value; + + ConfigEntry(const char* _section, const char* _key, bool default_value) : + section{_section}, key{_key}, value{default_value} {} + + void load_value_from_ini() { + this->value = ini_getbool(this->section, this->key, this->value, CONFIG_PATH); + } + + auto create_list_item(const char* text) { + auto item = new tsl::elm::ToggleListItem(text, value); + item->setStateChangedListener([this](bool new_value){ + this->value = new_value; + ini_putl(this->section, this->key, this->value, CONFIG_PATH); + }); + return item; + } +}; + +class GuiMain final : public tsl::Gui { +public: + GuiMain() { } + + // Called when this Gui gets loaded to create the UI + // Allocate all elements on the heap. libtesla will make sure to clean them up when not needed anymore + tsl::elm::Element* createUI() override { + create_dir("/config/"); + create_dir("/config/sys-patch/"); + + config_patch_sysmmc.load_value_from_ini(); + config_patch_emummc.load_value_from_ini(); + config_logging.load_value_from_ini(); + + auto frame = new tsl::elm::OverlayFrame("sys-patch", "v1.2.0"); + auto list = new tsl::elm::List(); + + list->addItem(new tsl::elm::CategoryHeader("Options")); + list->addItem(config_patch_sysmmc.create_list_item("Patch SysMMC")); + list->addItem(config_patch_emummc.create_list_item("Patch EmuMMC")); + list->addItem(config_logging.create_list_item("Logging")); + + if (does_file_exist(LOG_PATH)) { + struct CallbackUser { + tsl::elm::List* list; + std::string last_section; + } callback_userdata{list}; + + ini_browse([](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData){ + auto user = (CallbackUser*)UserData; + if (!std::strcmp("Skipped", Value)) { + return 1; + } + + if (user->last_section != Section) { + user->last_section = Section; + user->list->addItem(new tsl::elm::CategoryHeader("Log: " + user->last_section)); + } + + #define F(x) ((x) >> 4) // 8bit -> 4bit + constexpr tsl::Color colour_syspatch{F(0), F(255), F(200), F(255)}; + constexpr tsl::Color colour_file{F(255), F(177), F(66), F(255)}; + constexpr tsl::Color colour_unpatched{F(250), F(90), F(58), F(255)}; + #undef F + + std::string_view value{Value}; + if (value.starts_with("Patched")) { + if (value.ends_with("(sys-patch)")) { + user->list->addItem(new tsl::elm::ListItem(Key, "Patched", colour_syspatch)); + } else { + user->list->addItem(new tsl::elm::ListItem(Key, "Patched", colour_file)); + } + } else if (value.starts_with("Unpatched")) { + user->list->addItem(new tsl::elm::ListItem(Key, Value, colour_unpatched)); + } + + return 1; + }, &callback_userdata, LOG_PATH); + } else { + + } + + frame->setContent(list); + return frame; + } + + ConfigEntry config_patch_sysmmc{"options", "patch_sysmmc", true}; + ConfigEntry config_patch_emummc{"options", "patch_emummc", true}; + ConfigEntry config_logging{"options", "patch_logging", true}; +}; + +// libtesla already initialized fs, hid, pl, pmdmnt, hid:sys and set:sys +class SysPatchOverlay final : public tsl::Overlay { +public: + std::unique_ptr loadInitialGui() override { + return initially(); + } +}; + +} // namespace + +int main(int argc, char **argv) { + return tsl::loop(argc, argv); +} diff --git a/sysmod/Makefile b/sysmod/Makefile new file mode 100644 index 0000000..bcd7eb4 --- /dev/null +++ b/sysmod/Makefile @@ -0,0 +1,236 @@ +#--------------------------------------------------------------------------------- +.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. +#--------------------------------------------------------------------------------- +TARGET := sys-patch +BUILD := build +SOURCES := src ../common/minIni +DATA := data +INCLUDES := include ../common +#ROMFS := romfs + +# sys-patch +#--------------------------------------------------------------------------------- +# 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__ + +CXXFLAGS := $(CFLAGS) -std=c++23 -fno-rtti -fno-exceptions + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -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/atmosphere/contents/420000000000000B/flags + @touch out/atmosphere/contents/420000000000000B/flags/boot2.flag + @cp $(CURDIR)/toolbox.json out/atmosphere/contents/420000000000000B/toolbox.json + @cp $(CURDIR)/$(TARGET).nsp out/atmosphere/contents/420000000000000B/exefs.nsp + +#--------------------------------------------------------------------------------- +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 + @rm -rf out/ + @rm -f sys-patch.zip + +#--------------------------------------------------------------------------------- +dist: all + @echo making dist ... + + @rm -f sys-patch-no-overlay.zip + @cd out; zip -r ../sys-patch-no-overlay.zip ./*; cd ../ +#--------------------------------------------------------------------------------- +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/src/main.cpp b/sysmod/src/main.cpp similarity index 68% rename from src/main.cpp rename to sysmod/src/main.cpp index c02d379..982bfab 100644 --- a/src/main.cpp +++ b/sysmod/src/main.cpp @@ -2,6 +2,7 @@ #include #include // for min #include // for byteswap +#include // std::unreachable #include #include "minIni/minIni.h" @@ -69,28 +70,39 @@ struct PatchData { u8 size; }; +enum class PatchedResult { + NOT_FOUND, + SKIPPED, + PATCHED_FILE, + PATCHED_SYSPATCH, + FAILED_WRITE, +}; + struct Patterns { const char* patch_name; // name of patch - PatternData byte_pattern; // the pattern to search + const PatternData byte_pattern; // the pattern to search - s32 inst_offset; // instruction offset relative to byte pattern - s32 patch_offset; // patch offset relative to inst_offset + const s32 inst_offset; // instruction offset relative to byte pattern + const s32 patch_offset; // patch offset relative to inst_offset - bool (*cond)(u32 inst); // check condtion of the instruction - PatchData (*patch)(u32 inst); // the patch data to be applied + bool (*const cond)(u32 inst); // check condition of the instruction + PatchData (*const patch)(u32 inst); // the patch data to be applied + bool (*const applied)(u32 inst); // check to see if patch already applied - u32 min_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore - u32 max_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore - u32 min_ams_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore - u32 max_ams_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore + const u32 min_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore + const u32 max_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore + const u32 min_ams_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore + const u32 max_ams_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore + + PatchedResult result{PatchedResult::NOT_FOUND}; }; struct PatchEntry { const char* name; // name of the system title - u64 title_id; // title id of the system title - std::span patterns; // list of patterns to find - u32 min_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore - u32 max_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore + const u64 title_id; // title id of the system title + const std::span patterns; // list of patterns to find + const u32 min_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore + const u32 max_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore }; constexpr auto subi_cond(u32 inst) -> bool { @@ -122,15 +134,19 @@ constexpr auto subs_cond(u32 inst) -> bool { constexpr auto cbz_cond(u32 inst) -> bool { const auto type = inst >> 24; return type == 0x34 || type == 0xB4; -}; +} constexpr auto mov_cond(u32 inst) -> bool { return ((inst >> 24) & 0x7F) == 0x52; -}; +} constexpr auto mov2_cond(u32 inst) -> bool { - return (inst >> 24) == 0x2A; -}; + if (hosversionBefore(15,0,0)) { + return (inst >> 24) == 0x92; // and x0, x19, #0xffffffff + } else { + return (inst >> 24) == 0x2A; + } +} constexpr auto bne_cond(u32 inst) -> bool { const auto type = inst >> 24; @@ -138,58 +154,77 @@ constexpr auto bne_cond(u32 inst) -> bool { return type == 0x54 || cond == 0x0; } -// mov w0, wzr (w0 = 0) constexpr auto ret0_patch(u32 inst) -> PatchData { - return std::byteswap(0xE0031F2A); + return std::byteswap(0xE0031F2AU); } -// nop constexpr auto nop_patch(u32 inst) -> PatchData { - return std::byteswap(0x1F2003D5); + return std::byteswap(0x1F2003D5U); } constexpr auto subs_patch(u32 inst) -> PatchData { return subi_cond(inst) ? (u8)0x1 : (u8)0x0; } -// b offset constexpr auto b_patch(u32 inst) -> PatchData { - const auto opcode = 0x14; + const auto opcode = 0x14 << 24; const auto offset = (inst >> 5) & 0x7FFFF; return opcode | offset; } -// mov x0, xzr (x0 = 0) constexpr auto mov0_patch(u32 inst) -> PatchData { - return std::byteswap(0xE0031FAA); + return std::byteswap(0xE0031FAAU); } -constexpr Patterns fs_patterns[] = { - { "noacidsigchk1", "0xC8FE4739", -24, 0, bl_cond, ret0_patch }, - { "noacidsigchk2", "0x0210911F000072", -5, 0, bl_cond, ret0_patch }, - { "noncasigchk_old", "0x1E42B9", -5, 0, tbz_cond, nop_patch }, - { "noncasigchk_new", "0x3E4479", -5, 0, tbz_cond, nop_patch }, - { "nocntchk_old", "0x081C00121F05007181000054", -4, 0, bl_cond, ret0_patch }, - { "nocntchk_new", "0x081C00121F05007141010054", -4, 0, bl_cond, ret0_patch }, +constexpr auto ret0_applied(u32 inst) -> bool { + return ret0_patch(inst).data == inst; +} + +constexpr auto nop_applied(u32 inst) -> bool { + return nop_patch(inst).data == inst; +} + +constexpr auto subs_applied(u32 inst) -> bool { + const auto type_i = (inst >> 24) & 0xFF; + const auto imm = (inst >> 10) & 0xFFF; + const auto type_r = (inst >> 21) & 0x7F9; + const auto reg = (inst >> 16) & 0x1F; + return ((type_i == 0x71) && (imm == 0x1)) || ((type_r == 0x358) && (reg == 0x0)); +} + +constexpr auto b_applied(u32 inst) -> bool { + return 0x14 == (inst >> 24); +} + +constexpr auto mov0_applied(u32 inst) -> bool { + return mov0_patch(inst).data == inst; +} + +constinit Patterns fs_patterns[] = { + { "noacidsigchk1", "0xC8FE4739", -24, 0, bl_cond, ret0_patch, ret0_applied }, + { "noacidsigchk2", "0x0210911F000072", -5, 0, bl_cond, ret0_patch, ret0_applied }, + { "noncasigchk_old", "0x1E42B9", -5, 0, tbz_cond, nop_patch, nop_applied }, + { "noncasigchk_new", "0x3E4479", -5, 0, tbz_cond, nop_patch, nop_applied }, + { "nocntchk_old", "0x081C00121F05007181000054", -4, 0, bl_cond, ret0_patch, ret0_applied }, + { "nocntchk_new", "0x081C00121F05007141010054", -4, 0, bl_cond, ret0_patch, ret0_applied }, }; -constexpr Patterns ldr_patterns[] = { - { "noacidsigchk", "0xFD7BC6A8C0035FD6", 16, 2, subs_cond, subs_patch }, +constinit Patterns ldr_patterns[] = { + { "noacidsigchk", "0xFD7BC6A8C0035FD6", 16, 2, subs_cond, subs_patch, subs_applied }, }; -// todo: make patch for fw 14.0.0 - 14.1.2 -constexpr Patterns es_patterns[] = { - { "es", "0x1F90013128928052", -4, 0, cbz_cond, b_patch, FW_VER_ANY, MAKEHOSVERSION(13,2,1) }, - { "es", "0xC07240F9E1930091", -4, 0, tbz_cond, nop_patch, FW_VER_ANY, MAKEHOSVERSION(10,2,0) }, - { "es", "0xF3031FAA02000014", -4, 0, bne_cond, nop_patch, FW_VER_ANY, MAKEHOSVERSION(10,2,0) }, - { "es", "0xC0FDFF35A8C35838", -4, 0, mov_cond, nop_patch, MAKEHOSVERSION(11,0,0), MAKEHOSVERSION(13,2,1) }, - { "es", "0xE023009145EEFF97", -4, 0, cbz_cond, b_patch, MAKEHOSVERSION(11,0,0), MAKEHOSVERSION(13,2,1) }, - { "es", "0x.6300...0094A0..D1..FF97", 16, 0, mov2_cond, mov0_patch, MAKEHOSVERSION(15,0,0) }, +constinit Patterns es_patterns[] = { + { "es1", "0x1F90013128928052", -4, 0, cbz_cond, b_patch, b_applied, FW_VER_ANY, MAKEHOSVERSION(13,2,1) }, + { "es2", "0xC07240F9E1930091", -4, 0, tbz_cond, nop_patch, nop_applied, FW_VER_ANY, MAKEHOSVERSION(10,2,0) }, + { "es3", "0xF3031FAA02000014", -4, 0, bne_cond, nop_patch, nop_applied, FW_VER_ANY, MAKEHOSVERSION(10,2,0) }, + { "es4", "0xC0FDFF35A8C35838", -4, 0, mov_cond, nop_patch, nop_applied, MAKEHOSVERSION(11,0,0), MAKEHOSVERSION(13,2,1) }, + { "es5", "0xE023009145EEFF97", -4, 0, cbz_cond, b_patch, b_applied, MAKEHOSVERSION(11,0,0), MAKEHOSVERSION(13,2,1) }, + { "es6", "0x.6300...0094A0..D1..FF97", 16, 0, mov2_cond, mov0_patch, mov0_applied, MAKEHOSVERSION(14,0,0) }, }; // NOTE: add system titles that you want to be patched to this table. // a list of system titles can be found here https://switchbrew.org/wiki/Title_list -constexpr PatchEntry patches[] = { +constinit PatchEntry patches[] = { { "fs", 0x0100000000000000, fs_patterns }, // ldr needs to be patched in fw 10+ { "ldr", 0x0100000000000001, ldr_patterns, MAKEHOSVERSION(10,0,0) }, @@ -216,13 +251,19 @@ auto is_emummc() -> bool { return (paths.unk[0] != '\0') || (paths.nintendo[0] != '\0'); } -auto patcher(Handle handle, std::span data, u64 addr, std::span patterns) -> bool { +auto patcher(Handle handle, std::span data, u64 addr, std::span patterns) -> bool { for (auto& p : patterns) { // skip if version isn't valid if ((p.min_fw_ver && p.min_fw_ver > FW_VERSION) || (p.max_fw_ver && p.max_fw_ver < FW_VERSION) || (p.min_ams_ver && p.min_ams_ver > AMS_VERSION) || (p.max_ams_ver && p.max_ams_ver < AMS_VERSION)) { + p.result = PatchedResult::SKIPPED; + continue; + } + + // skip if already patched + if (p.result == PatchedResult::PATCHED_FILE || p.result == PatchedResult::PATCHED_SYSPATCH) { continue; } @@ -259,11 +300,16 @@ auto patcher(Handle handle, std::span data, u64 addr, std::span data, u64 addr, std::span bool { +auto apply_patch(PatchEntry& patch) -> bool { Handle handle{}; DebugEventInfo event_info{}; @@ -283,6 +329,9 @@ auto apply_patch(const PatchEntry& patch) -> bool { // skip if version isn't valid if ((patch.min_fw_ver && patch.min_fw_ver > FW_VERSION) || (patch.max_fw_ver && patch.max_fw_ver < FW_VERSION)) { + for (auto& p : patch.patterns) { + p.result = PatchedResult::SKIPPED; + } return true; } @@ -318,7 +367,7 @@ auto apply_patch(const PatchEntry& patch) -> bool { const auto actual_size = std::min(READ_BUFFER_SIZE, mem_info.size); if (R_FAILED(svcReadDebugProcessMemory(buffer, handle, mem_info.addr + sz, actual_size))) { // todo: log failed reads! - continue; + break; } else { patcher(handle, std::span{buffer, actual_size}, mem_info.addr + sz, patch.patterns); } @@ -361,29 +410,59 @@ auto ini_load_or_write_default(const char* section, const char* key, long _defau } } +auto patch_result_to_str(PatchedResult result) -> const char* { + switch (result) { + case PatchedResult::NOT_FOUND: return "Unpatched"; + case PatchedResult::SKIPPED: return "Skipped"; + case PatchedResult::PATCHED_FILE: return "Patched (file)"; + case PatchedResult::PATCHED_SYSPATCH: return "Patched (sys-patch)"; + case PatchedResult::FAILED_WRITE: return "Failed (svcWriteDebugProcessMemory)"; + } + + std::unreachable(); +} + } // namespace int main(int argc, char* argv[]) { + constexpr auto ini_path = "/config/sys-patch/config.ini"; + constexpr auto log_path = "/config/sys-patch/log.ini"; + create_dir("/config/"); create_dir("/config/sys-patch/"); + ini_remove(log_path); - const auto ini_path = "/config/sys-patch/config.ini"; const auto patch_sysmmc = ini_load_or_write_default("options", "patch_sysmmc", 1, ini_path); const auto patch_emummc = ini_load_or_write_default("options", "patch_emummc", 1, ini_path); + const auto enable_logging = ini_load_or_write_default("options", "enable_logging", 1, ini_path); const auto emummc = is_emummc(); + bool enable_patching = true; // check if we should patch sysmmc if (!patch_sysmmc && !emummc) { - return 0; + enable_patching = false; } // check if we should patch emummc if (!patch_emummc && emummc) { - return 0; + enable_patching = false; } - for (auto& patch : patches) { - apply_patch(patch); + if (enable_patching) { + for (auto& patch : patches) { + apply_patch(patch); + } + } + + if (enable_logging) { + for (auto& patch : patches) { + for (auto& p : patch.patterns) { + if (!enable_patching) { + p.result = PatchedResult::SKIPPED; + } + ini_puts(patch.name, p.patch_name, patch_result_to_str(p.result), log_path); + } + } } // note: sysmod exits here. diff --git a/sys-patch.json b/sysmod/sys-patch.json similarity index 100% rename from sys-patch.json rename to sysmod/sys-patch.json diff --git a/toolbox.json b/sysmod/toolbox.json similarity index 100% rename from toolbox.json rename to sysmod/toolbox.json