add overlay, fix es 14.0.0-14.1.2 patches, fix b_patch, add logging

This commit is contained in:
TotalJustice 2023-05-25 04:39:51 +01:00
parent 5d62cf2cdb
commit 7d2a332bdd
17 changed files with 808 additions and 284 deletions

View File

@ -9,6 +9,8 @@ jobs:
steps:
- name: Checkout 🛎️
uses: actions/checkout@master
with:
submodules: recursive
- name: Build
run: make dist -j2

6
.gitignore vendored
View File

@ -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

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "overlay/libtesla"]
path = overlay/libtesla
url = https://github.com/ITotalJustice/libtesla.git
branch = tj

20
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -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
}

236
Makefile
View File

@ -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=<path to>/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):
# - <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 := 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
#---------------------------------------------------------------------------------------

View File

@ -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.
<p float="left">
<img src="https://i.imgur.com/IlTkkYM.jpg" width="400" />
<img src="https://i.imgur.com/T4K5u5f.jpg" width="400" />
</p>
---
## 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)

View File

@ -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;
}

View File

@ -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);

225
overlay/Makefile Normal file
View File

@ -0,0 +1,225 @@
#---------------------------------------------------------------------------------
.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.
#---------------------------------------------------------------------------------
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
#---------------------------------------------------------------------------------------

1
overlay/libtesla Submodule

@ -0,0 +1 @@
Subproject commit 3d74aeb31f0f26cf222d406c7ea118a82cc8ee69

149
overlay/src/main.cpp Normal file
View File

@ -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 <tesla.hpp> // The Tesla Header
#include <string_view>
#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<tsl::Gui> loadInitialGui() override {
return initially<GuiMain>();
}
};
} // namespace
int main(int argc, char **argv) {
return tsl::loop<SysPatchOverlay>(argc, argv);
}

236
sysmod/Makefile Normal file
View File

@ -0,0 +1,236 @@
#---------------------------------------------------------------------------------
.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 := 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
#---------------------------------------------------------------------------------------

View File

@ -2,6 +2,7 @@
#include <span>
#include <algorithm> // for min
#include <bit> // for byteswap
#include <utility> // std::unreachable
#include <switch.h>
#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<const Patterns> 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> 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<const u8> data, u64 addr, std::span<const Patterns> patterns) -> bool {
auto patcher(Handle handle, std::span<const u8> data, u64 addr, std::span<Patterns> 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<const u8> data, u64 addr, std::span<const
// todo: log failed writes, although this should in theory never fail
if (R_FAILED(svcWriteDebugProcessMemory(handle, &patch_data, patch_offset, patch_size))) {
p.result = PatchedResult::FAILED_WRITE;
} else {
// todo: log that this was successful
p.result = PatchedResult::PATCHED_SYSPATCH;
}
break; // move onto next pattern
// move onto next pattern
break;
} else if (p.applied(inst)) {
// patch already applied by sigpatches
p.result = PatchedResult::PATCHED_FILE;
break;
}
}
}
@ -272,7 +318,7 @@ auto patcher(Handle handle, std::span<const u8> data, u64 addr, std::span<const
return false;
}
auto apply_patch(const PatchEntry& patch) -> 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.