diff --git a/.github/workflows/build-jobs.yaml b/.github/workflows/build-jobs.yaml new file mode 100644 index 0000000..dfd713e --- /dev/null +++ b/.github/workflows/build-jobs.yaml @@ -0,0 +1,52 @@ +name: Build jobs + +on: + workflow_call: + workflow_dispatch: + push: + +jobs: + build: + runs-on: ubuntu-latest + container: devkitpro/devkita64 + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-tags: true + path: sys-patch + submodules: recursive + + - name: Build sys-patch + run: | + make -C sys-patch -j$(nproc) dist && \ + VERSION=$(grep 'export VERSION := ' sys-patch/Makefile | cut -c 19-) + TAGVERSION=$(curl -s https://api.github.com/repos/$GITHUB_REPOSITORY/releases/latest | grep "tag_name" | head -1 | cut -d '"' -f 4) + echo "VERSION=${VERSION}" >> $GITHUB_ENV + echo "TAGVERSION=${TAGVERSION}" >> $GITHUB_ENV + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + include-hidden-files: true + overwrite: true + name: sys-patch-${{ env.VERSION }} + path: sys-patch/out/ + + - name: Fetch git cli and upload release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [ ${{ env.TAGVERSION }} = v${{ env.VERSION }} ]; + then echo "Tag version and makefile version are same, don't publish release, only artifact uploaded." + else + wget -q $(curl -s https://api.github.com/repos/cli/cli/releases/latest | grep "browser_download_url" | grep "linux_amd64.tar.gz" | head -1 | cut -d '"' -f 4) && \ + tar -xzf gh*.tar.gz && \ + chmod +x gh*/bin/gh && \ + chmod +x gh*/bin/gh && \ + cp gh*/bin/gh /bin/gh && \ + rm gh*.tar.gz && \ + rm -rf gh* + gh release create v${{ env.VERSION }} sys-patch/sys-patch.zip --title "Sys-patch version ${{ env.VERSION }}" --repo github.com/$GITHUB_REPOSITORY + fi diff --git a/.github/workflows/build_depoly.yml b/.github/workflows/build_depoly.yml deleted file mode 100644 index 9516f19..0000000 --- a/.github/workflows/build_depoly.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: build -on: [push, pull_request] - -jobs: - build: - runs-on: ubuntu-latest - container: devkitpro/devkita64:latest - - steps: - - name: Checkout 🛎️ - uses: actions/checkout@master - with: - submodules: recursive - - - name: Build - run: make dist -j2 - - - uses: actions/upload-artifact@master - with: - name: sys-patch - path: sys-patch.zip diff --git a/.gitmodules b/.gitmodules index 9b1605c..2088864 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "overlay/libtesla"] path = overlay/libtesla - url = https://github.com/ITotalJustice/libtesla.git - branch = tj + url = https://github.com/WerWolv/libtesla + branch = master diff --git a/Makefile b/Makefile index 3536911..f760ec7 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,13 @@ MAKEFILES := sysmod overlay TARGETS := $(foreach dir,$(MAKEFILES),$(CURDIR)/$(dir)) # the below was taken from atmosphere + switch-examples makefile -export VERSION := 1.3.0 +export VERSION := 1.5.9 + +ifneq ($(strip $(shell git symbolic-ref --short HEAD 2>/dev/null)),) export GIT_BRANCH := $(shell git symbolic-ref --short HEAD) +else +export GIT_BRANCH := notbranch +endif ifeq ($(strip $(shell git status --porcelain 2>/dev/null)),) export GIT_REVISION := $(GIT_BRANCH)-$(shell git rev-parse --short HEAD) @@ -41,6 +46,7 @@ $(TARGETS): clean: @rm -rf out + @rm -f sys-patch.zip @for i in $(TARGETS); do $(MAKE) -C $$i clean || exit 1; done; dist: all diff --git a/README.md b/README.md index 6d293de..f1940d8 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,32 @@ # sys-patch -A script-like system module that patches fs, es and ldr on boot. +A script-like system module that patches **fs**, **es**, **ldr**, **nifm** and **nim** on boot. --- ## Config -sys-patch features a simple config. This can be manually editied or updated using the overlay. +**sys-patch** features a simple config. This can be manually edited or updated using the overlay. -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. +The configuration file can be found in `/config/sys-patch/config.ini`. The file is generated once the module is ran for the first time. ```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.ini 0=no log -version_skip=1 ; 1=(default) skips out of date patterns, 0=search all patterns +patch_sysmmc=1 ; 1=(default) patch sysmmc, 0=don't patch sysmmc +patch_emummc=1 ; 1=(default) patch emummc, 0=don't patch emummc +enable_logging=1 ; 1=(default) output /config/sys-patch/log.ini 0=no log +version_skip=1 ; 1=(default) skips out of date patterns, 0=search all patterns ``` --- ## Overlay -the overlay can be used to change the config options and to see what patches are applied (if any). +The overlay can be used to change the config options and to see what patches are applied. - 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. +- Patched (yellow) means it was already patched, likely by sigpatches or a custom Atmosphere build.

@@ -40,63 +40,55 @@ the overlay can be used to change the config options and to see what patches are ## Building ### prerequisites -- install devkitpro +- Install [devkitpro](https://devkitpro.org/wiki/Getting_Started) +- Run the following: + ```sh + git clone --recurse-submodules https://github.com/ITotalJustice/sys-patch.git + cd ./sys-patch + make + ``` -```sh -git clone --recurse-submodules https://github.com/ITotalJustice/sys-patch.git -cd sys-patch -make -``` - -the output of `out/` can be copied to your sd card. for the sysmodule to take effect, rebot your switch, or, use [sysmodules overlay](https://github.com/WerWolv/ovl-sysmodules/tree/master/source) to start it. +The output of `out/` can be copied to your SD card. +To activate the sys-module, reboot your switch, or, use [sysmodules overlay](https://github.com/WerWolv/ovl-sysmodules/releases/latest) with the accompanying overlay to activate it. --- ## What is being patched? -Here's a quick run down of what's being patched - -- fs -- es -- ldr +Here's a quick run down of what's being patched: -fs and es need new patches after every new fw version. +- **fs** and **es** need new patches after every new firmware version. +- **ldr** needs new patches after every new [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere/) release. +- **nifm** ctest patch allows the device to connect to a network without needing to make a connection to a server +- **nim** patches to the ssl function call within nim that queries "https://api.hac.%.ctest.srv.nintendo.net/v1/time", and crashes the console if console ssl certificate is not intact. This patch instead makes the console not crash. -ldr on the other hand needs new patches after every new atmosphere release. this is due to ldr service being reimplemented by atmosphere. in fw 10.0.0, a new check was added to ofw which we needed to patch out. As atmosphere closely follows what ofw does, it also added this check. This is why a new patch is needed per atmosphere update. +The patches are applied on boot. Once done, the sys-module stops running. +The memory footprint *(16kib)* and the binary size *(~50kib)* are both very small. --- -## How does it work? +## FAQ: -it uses a collection of patterns to find the piece of code to patch. alternatively, it could just use offsets, however this would mean this tool would have to be updated after every new fw update, that's not ideal. +### If I am using sigpatches already, is there any point in using this? -the patches are applied at boot, then, the sysmod stops running. the memory footpint of the sysmod is very very small, only using 16kib in total. the size of the binary itself is only ~50kib! this doesnt really mean much, but im pretty proud of it :) - ---- - -## Does this mean i should stop downloading / using sigpatches? - -No, i would personally recommend continuing to use sigpatches. Reason being is that should this tool ever break, i likely wont be quick to fix it. - ---- +Yes, in 3 situations. -## If i am using sigpatches already, is there any point in using this as well? +1. A new **ldr** patch needs to be created after every Atmosphere update. Sometimes, a new silent Atmosphere update is released. This tool will always patch **ldr** without having to update patches. -Yes, in 2 niche cases. +2. Building Atmosphere from src will require you to generate a new **ldr** patch for that custom built Atmosphere. This is easy enough due to the public scripts / tools that exist out there, however this will always be able to patch **ldr**. -1. A new ldr patch needs to be created after every atmosphere update. Sometimes, a new silent atmosphere update is released. This tool will always patch ldr without having to update patches. +3. If you forget to update your patches when you update your firmware / Atmosphere, this sys-module should be able to patch everything. So it can be used as a fall back. -2. Building atmosphere from src will require you to generate a new ldr patch for that custom built atmosphere. This is easy enough due to the public scripts / tools that exist out there, however this will always be able to +### Does this mean that I should stop downloading / using sigpatches? -Also, if you forget to update your patches when you update fw / atmosphere, this sysmod should be able to patch everything just fine! so it's nice to have as a fallback. +No, I would personally recommend continuing to use sigpatches. Reason being is that should this tool ever break, i likely wont be quick to fix it. --- ## Credits / Thanks -software is built on the shoulders of giants. this tool wouldn't be possible wthout these people: +Software is built on the shoulders of giants. This tool wouldn't be possible without these people: -- DarkMatterCore - MrDude - BornToHonk (farni) - TeJay @@ -105,3 +97,5 @@ software is built on the shoulders of giants. this tool wouldn't be possible wth - DevkitPro (toolchain) - [minIni](https://github.com/compuphase/minIni) - [libtesla](https://github.com/WerWolv/libtesla) +- [Shoutout to the best switch cfw setup guide](https://rentry.org/SwitchHackingIsEasy) +- N diff --git a/configs/cliff.toml b/configs/cliff.toml new file mode 100644 index 0000000..f976285 --- /dev/null +++ b/configs/cliff.toml @@ -0,0 +1,69 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[changelog] +# changelog header +header = """ +# Changelog\n +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{% if version -%} + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else -%} + ## [Unreleased] +{% endif -%} +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# template for the changelog footer +footer = """ + +""" +# remove the leading and trailing whitespace from the templates +trim = true + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = false +# process each line of a commit as an individual commit +split_commits = false +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^feat", group = "Features" }, + { message = "^fix", group = "Bug Fixes" }, + { message = "^doc", group = "Documentation" }, + { message = "^perf", group = "Performance" }, + { message = "^refactor", group = "Refactor" }, + { message = "^style", group = "Styling" }, + { message = "^test", group = "Testing" }, + { message = "^chore\\(release\\): prepare for", skip = true }, + { message = "^chore", group = "Miscellaneous Tasks" }, + { body = ".*security", group = "Security" }, + { body = ".*", group = "Other (unconventional)" }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = false +# filter out the commits that are not matched by commit parsers +filter_commits = true +# regex for matching git tags +tag_pattern = "[0-9]+.[0-9]+.[0-9]+" +# regex for skipping tags +skip_tags = "v0.1.0-beta.1" +# regex for ignoring tags +ignore_tags = "" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" diff --git a/overlay/src/main.cpp b/overlay/src/main.cpp index 402742c..eb44735 100644 --- a/overlay/src/main.cpp +++ b/overlay/src/main.cpp @@ -43,12 +43,10 @@ auto create_dir(const char* path) -> bool { } 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} {} + section{_section}, key{_key}, value{default_value} { + this->load_value_from_ini(); + } void load_value_from_ini() { this->value = ini_getbool(this->section, this->key, this->value, CONFIG_PATH); @@ -62,23 +60,17 @@ struct ConfigEntry { }); return item; } + + const char* const section; + const char* const key; + bool value; }; -class GuiMain final : public tsl::Gui { +class GuiOptions final : public tsl::Gui { public: - GuiMain() { } + GuiOptions() { } - // 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(); - config_version_skip.load_value_from_ini(); - auto frame = new tsl::elm::OverlayFrame("sys-patch", VERSION_WITH_HASH); auto list = new tsl::elm::List(); @@ -88,6 +80,98 @@ class GuiMain final : public tsl::Gui { list->addItem(config_logging.create_list_item("Logging")); list->addItem(config_version_skip.create_list_item("Version skip")); + 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", "enable_logging", true}; + ConfigEntry config_version_skip{"options", "version_skip", true}; +}; + +class GuiToggle final : public tsl::Gui { +public: + GuiToggle() { } + + tsl::elm::Element* createUI() override { + auto frame = new tsl::elm::OverlayFrame("sys-patch", VERSION_WITH_HASH); + auto list = new tsl::elm::List(); + + list->addItem(new tsl::elm::CategoryHeader("FS - 0100000000000000")); + list->addItem(config_noacidsigchk1.create_list_item("noacidsigchk_1.0.0-9.2.0")); + list->addItem(config_noacidsigchk2.create_list_item("noacidsigchk_1.0.0-9.2.0")); + list->addItem(config_noncasigchk1.create_list_item("noncasigchk_10.0.0-16.1.0")); + list->addItem(config_noncasigchk2.create_list_item("noncasigchk_17.0.0+")); + list->addItem(config_nocntchk1.create_list_item("nocntchk_10.0.0-18.1.0")); + list->addItem(config_nocntchk2.create_list_item("nocntchk_19.0.0-20.5.0")); + list->addItem(config_nocntchk3.create_list_item("nocntchk_21.0.0+")); + + list->addItem(new tsl::elm::CategoryHeader("LDR - 0100000000000001")); + list->addItem(config_noacidsigchk3.create_list_item("noacidsigchk_10.0.0+")); + + list->addItem(new tsl::elm::CategoryHeader("ERPT - 010000000000002B")); + list->addItem(config_no_erpt.create_list_item("no_erpt")); + + list->addItem(new tsl::elm::CategoryHeader("ES - 0100000000000033")); + list->addItem(config_es1.create_list_item("es_1.0.0-8.1.1")); + list->addItem(config_es2.create_list_item("es_9.0.0-11.0.1")); + list->addItem(config_es3.create_list_item("es_12.0.0-18.1.0")); + list->addItem(config_es4.create_list_item("es_19.0.0+")); + + list->addItem(new tsl::elm::CategoryHeader("OLSC - 010000000000003E")); + list->addItem(config_olsc1.create_list_item("olsc_6.0.0-14.1.2")); + list->addItem(config_olsc2.create_list_item("olsc_15.0.0-18.1.0")); + list->addItem(config_olsc3.create_list_item("olsc_19.0.0+")); + + list->addItem(new tsl::elm::CategoryHeader("NIFM - 010000000000000F")); + list->addItem(config_ctest1.create_list_item("ctest_1.0.0-19.0.1")); + list->addItem(config_ctest2.create_list_item("ctest_20.0.0+")); + + list->addItem(new tsl::elm::CategoryHeader("NIM - 0100000000000025")); + list->addItem(config_nim1.create_list_item("blankcal0crashfix_17.0.0+")); + list->addItem(config_nim_fw1.create_list_item("blockfirmwareupdates_1.0.0-5.1.0")); + list->addItem(config_nim_fw2.create_list_item("blockfirmwareupdates_6.0.0-6.2.0")); + list->addItem(config_nim_fw3.create_list_item("blockfirmwareupdates_7.0.0-11.0.1")); + list->addItem(config_nim_fw4.create_list_item("blockfirmwareupdates_12.0.0+")); + + frame->setContent(list); + return frame; + } + + ConfigEntry config_noacidsigchk1{"fs", "noacidsigchk_1.0.0-9.2.0", true}; + ConfigEntry config_noacidsigchk2{"fs", "noacidsigchk_1.0.0-9.2.0", true}; + ConfigEntry config_noncasigchk1{"fs", "noncasigchk_10.0.0-16.1.0", true}; + ConfigEntry config_noncasigchk2{"fs", "noncasigchk_17.0.0+", true}; + ConfigEntry config_nocntchk1{"fs", "nocntchk_10.0.0-18.1.0", true}; + ConfigEntry config_nocntchk2{"fs", "nocntchk_19.0.0-20.5.0", true}; + ConfigEntry config_nocntchk3{"fs", "nocntchk_21.0.0+", true}; + ConfigEntry config_noacidsigchk3{"ldr", "noacidsigchk_10.0.0+", true}; + ConfigEntry config_no_erpt{"erpt", "no_erpt", true}; + ConfigEntry config_es1{"es", "es_1.0.0-8.1.1", true}; + ConfigEntry config_es2{"es", "es_9.0.0-11.0.1", true}; + ConfigEntry config_es3{"es", "es_12.0.0-18.1.0", true}; + ConfigEntry config_es4{"es", "es_19.0.0+", true}; + ConfigEntry config_olsc1{"olsc", "olsc_6.0.0-14.1.2", true}; + ConfigEntry config_olsc2{"olsc", "olsc_15.0.0-18.1.0", true}; + ConfigEntry config_olsc3{"olsc", "olsc_19.0.0+", true}; + ConfigEntry config_ctest1{"nifm", "ctest_1.0.0-19.0.1", true}; + ConfigEntry config_ctest2{"nifm", "ctest_20.0.0+", true}; + ConfigEntry config_nim1{"nim", "blankcal0crashfix_17.0.0+", true}; + ConfigEntry config_nim_fw1{"nim", "blockfirmwareupdates_1.0.0-5.1.0", true}; + ConfigEntry config_nim_fw2{"nim", "blockfirmwareupdates_6.0.0-6.2.0", true}; + ConfigEntry config_nim_fw3{"nim", "blockfirmwareupdates_7.0.0-11.0.1", true}; + ConfigEntry config_nim_fw4{"nim", "blockfirmwareupdates_12.0.0+", true}; +}; + +class GuiLog final : public tsl::Gui { +public: + GuiLog() { } + + tsl::elm::Element* createUI() override { + auto frame = new tsl::elm::OverlayFrame("sys-patch", VERSION_WITH_HASH); + auto list = new tsl::elm::List(); + if (does_file_exist(LOG_PATH)) { struct CallbackUser { tsl::elm::List* list; @@ -119,7 +203,7 @@ class GuiMain final : public tsl::Gui { } else { user->list->addItem(new tsl::elm::ListItem(Key, "Patched", colour_file)); } - } else if (value.starts_with("Unpatched")) { + } else if (value.starts_with("Unpatched") || value.starts_with("Disabled")) { user->list->addItem(new tsl::elm::ListItem(Key, Value, colour_unpatched)); } else if (user->last_section == "stats") { user->list->addItem(new tsl::elm::ListItem(Key, Value, tsl::style::color::ColorDescription)); @@ -130,17 +214,58 @@ class GuiMain final : public tsl::Gui { return 1; }, &callback_userdata, LOG_PATH); } else { - + list->addItem(new tsl::elm::ListItem("No log found!")); } 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}; - ConfigEntry config_version_skip{"options", "version_skip", true}; +class GuiMain final : public tsl::Gui { +public: + GuiMain() { } + + tsl::elm::Element* createUI() override { + auto frame = new tsl::elm::OverlayFrame("sys-patch", VERSION_WITH_HASH); + auto list = new tsl::elm::List(); + + auto options = new tsl::elm::ListItem("Options"); + auto toggle = new tsl::elm::ListItem("Toggle patches"); + auto log = new tsl::elm::ListItem("Log"); + + options->setClickListener([](u64 keys) -> bool { + if (keys & HidNpadButton_A) { + tsl::changeTo(); + return true; + } + return false; + }); + + toggle->setClickListener([](u64 keys) -> bool { + if (keys & HidNpadButton_A) { + tsl::changeTo(); + return true; + } + return false; + }); + + log->setClickListener([](u64 keys) -> bool { + if (keys & HidNpadButton_A) { + tsl::changeTo(); + return true; + } + return false; + }); + + list->addItem(new tsl::elm::CategoryHeader("Menu")); + list->addItem(options); + list->addItem(toggle); + list->addItem(log); + + frame->setContent(list); + return frame; + } }; // libtesla already initialized fs, hid, pl, pmdmnt, hid:sys and set:sys @@ -154,5 +279,7 @@ class SysPatchOverlay final : public tsl::Overlay { } // namespace int main(int argc, char **argv) { + create_dir("/config/"); + create_dir("/config/sys-patch/"); return tsl::loop(argc, argv); } diff --git a/sysmod/Makefile b/sysmod/Makefile index 41098a7..2663479 100644 --- a/sysmod/Makefile +++ b/sysmod/Makefile @@ -180,14 +180,14 @@ else @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).nso $(TARGET).npdm $(TARGET).elf endif @rm -rf out/ - @rm -f sys-patch.zip + @rm -f sys-patch-sysmodule.zip #--------------------------------------------------------------------------------- dist: all @echo making dist ... - @rm -f sys-patch-no-overlay.zip - @cd out; zip -r ../sys-patch-no-overlay.zip ./*; cd ../ + @rm -f sys-patch-sysmodule.zip + @cd out; zip -r ../sys-patch-sysmodule.zip ./*; cd ../ #--------------------------------------------------------------------------------- else .PHONY: all diff --git a/sysmod/src/main.cpp b/sysmod/src/main.cpp index f405f8d..a542164 100644 --- a/sysmod/src/main.cpp +++ b/sysmod/src/main.cpp @@ -31,52 +31,67 @@ struct DebugEventInfo { u8 _0x30[0x10]; }; -struct PatternData { - constexpr PatternData(const char* s) { - // skip leading 0x (if any) - if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { - s += 2; - } - - // invalid string will cause a compile-time error due to no return - constexpr auto hexstr_2_nibble = [](char c) -> u8 { - if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } - if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } - if (c >= '0' && c <= '9') { return c - '0'; } - }; +template +constexpr void str2hex(const char* s, T* data, u8& size) { + // skip leading 0x (if any) + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { + s += 2; + } - // parse and convert string - while (*s != '\0') { - if (*s == '.') { - data[size] = REGEX_SKIP; - s++; - } else { - data[size] |= hexstr_2_nibble(*s++) << 4; - data[size] |= hexstr_2_nibble(*s++) << 0; - } - size++; + // invalid string will cause a compile-time error due to no return + constexpr auto hexstr_2_nibble = [](char c) -> u8 { + if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } + if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } + if (c >= '0' && c <= '9') { return c - '0'; } + }; + + // parse and convert string + while (*s != '\0') { + if (sizeof(T) == sizeof(u16) && *s == '.') { + data[size] = REGEX_SKIP; + s++; + } else { + data[size] |= hexstr_2_nibble(*s++) << 4; + data[size] |= hexstr_2_nibble(*s++) << 0; } + size++; + } +} + +struct PatternData { + constexpr PatternData(const char* s) { + str2hex(s, data, size); } - // 32 is a reasonable max length for a byte pattern - // will compile-time error is size is too small - u16 data[32]{}; + u16 data[60]{}; // reasonable max pattern length, adjust as needed u8 size{}; }; struct PatchData { + constexpr PatchData(const char* s) { + str2hex(s, data, size); + } + template - constexpr PatchData(T _data) { - data = _data; - size = sizeof(T); + constexpr PatchData(T v) { + for (u32 i = 0; i < sizeof(T); i++) { + data[size++] = v & 0xFF; + v >>= 8; + } } - u64 data; - u8 size; + + constexpr auto cmp(const void* _data) -> bool { + return !std::memcmp(data, _data, size); + } + + u8 data[20]{}; // reasonable max patch length, adjust as needed + u8 size{}; }; -enum class PatchedResult { +enum class PatchResult { NOT_FOUND, SKIPPED, + DISABLED, PATCHED_FILE, PATCHED_SYSPATCH, FAILED_WRITE, @@ -91,14 +106,16 @@ struct Patterns { 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 + bool (*const applied)(const u8* data, u32 inst); // check to see if patch already applied + + bool enabled; // controlled by config.ini 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}; + PatchResult result{PatchResult::NOT_FOUND}; }; struct PatchEntry { @@ -109,121 +126,157 @@ struct PatchEntry { const u32 max_fw_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore }; -constexpr auto subi_cond(u32 inst) -> bool { - // # Used on Atmosphère-NX 0.11.0 - 0.12.0. - const auto type = (inst >> 24) & 0xFF; - const auto imm = (inst >> 10) & 0xFFF; - return (type == 0x71) && (imm == 0x0A); +// naming convention should if possible adhere to either an arm instruction + _cond, +// example: "bl_cond" +// or naming it specific to what is being patched, and including all possible bytes within the address being tested for the given patch. +// example: "ctest_cond" + +constexpr auto sub_cond(u32 inst) -> bool { + const auto type = inst >> 24; + return type == 0xD1; // sub sp, sp, #0x150 } -constexpr auto subr_cond(u32 inst) -> bool { - // # Used on Atmosphère-NX 0.13.0 and later. - const auto type = (inst >> 21) & 0x7F9; - const auto reg = (inst >> 16) & 0x1F; - return (type == 0x358) && (reg == 0x01); +constexpr auto cmp_cond(u32 inst) -> bool { + const auto type = inst >> 24; + return type == 0x6B || // cmp w0, w1 + type == 0xF1; // cmp x0, #0x1 } constexpr auto bl_cond(u32 inst) -> bool { - return ((inst >> 26) & 0x3F) == 0x25; + const auto type = inst >> 24; + return type == 0x25 || + type == 0x94 || + type == 0x97; } constexpr auto tbz_cond(u32 inst) -> bool { return ((inst >> 24) & 0x7F) == 0x36; } -constexpr auto subs_cond(u32 inst) -> bool { - return subi_cond(inst) || subr_cond(inst); +constexpr auto adr_cond(u32 inst) -> bool { + return (inst >> 24) == 0x10; // adr x2, LAB } -constexpr auto cbz_cond(u32 inst) -> bool { +constexpr auto block_fw_updates_cond(u32 inst) -> bool { const auto type = inst >> 24; - return type == 0x34 || type == 0xB4; + return type == 0xA8 || + type == 0xA9 || + type == 0xF8 || + type == 0xF9; } -constexpr auto mov_cond(u32 inst) -> bool { - return ((inst >> 24) & 0x7F) == 0x52; -} - -constexpr auto mov2_cond(u32 inst) -> bool { - if (hosversionBefore(15,0,0)) { - return (inst >> 24) == 0x92; // and x0, x19, #0xffffffff - } else { - return (inst >> 24) == 0x2A; - } +constexpr auto es_cond(u32 inst) -> bool { + const auto type = inst >> 24; + return type == 0xD1 || + type == 0xA9 || + type == 0xAA || + type == 0x2A || + type == 0x92; } -constexpr auto bne_cond(u32 inst) -> bool { +constexpr auto ctest_cond(u32 inst) -> bool { const auto type = inst >> 24; - const auto cond = inst & 0x10; - return type == 0x54 || cond == 0x0; + return type == 0xF9 || + type == 0xA9 || + type == 0xF8; } -constexpr auto ret0_patch(u32 inst) -> PatchData { - return std::byteswap(0xE0031F2AU); -} -constexpr auto nop_patch(u32 inst) -> PatchData { - return std::byteswap(0x1F2003D5U); -} +// to view patches, use https://armconverter.com/?lock=arm64 +constexpr PatchData ret0_patch_data{ "0xE0031F2A" }; +constexpr PatchData ret1_patch_data{ "0x200080D2" }; +constexpr PatchData mov0_ret_patch_data{ "0xE0031F2AC0035FD6" }; +constexpr PatchData nop_patch_data{ "0x1F2003D5" }; +//mov x0, xzr +constexpr PatchData mov0_patch_data{ "0xE0031FAA" }; +//mov x2, xzr +constexpr PatchData mov2_patch_data{ "0xE2031FAA" }; +constexpr PatchData cmp_patch_data{ "0x00" }; +constexpr PatchData ctest_patch_data{ "0x00309AD2001EA1F2610100D4E0031FAAC0035FD6" }; + +constexpr auto ret0_patch(u32 inst) -> PatchData { return ret0_patch_data; } +constexpr auto ret1_patch(u32 inst) -> PatchData { return ret1_patch_data; } +constexpr auto mov0_ret_patch(u32 inst) -> PatchData { return mov0_ret_patch_data; } +constexpr auto nop_patch(u32 inst) -> PatchData { return nop_patch_data; } +constexpr auto mov0_patch(u32 inst) -> PatchData { return mov0_patch_data; } +constexpr auto mov2_patch(u32 inst) -> PatchData { return mov2_patch_data; } +constexpr auto cmp_patch(u32 inst) -> PatchData { return cmp_patch_data; } +constexpr auto ctest_patch(u32 inst) -> PatchData { return ctest_patch_data; } -constexpr auto subs_patch(u32 inst) -> PatchData { - return subi_cond(inst) ? (u8)0x1 : (u8)0x0; +constexpr auto ret0_applied(const u8* data, u32 inst) -> bool { + return ret0_patch(inst).cmp(data); } -constexpr auto b_patch(u32 inst) -> PatchData { - const auto opcode = 0x14 << 24; - const auto offset = (inst >> 5) & 0x7FFFF; - return opcode | offset; +constexpr auto ret1_applied(const u8* data, u32 inst) -> bool { + return ret1_patch(inst).cmp(data); } -constexpr auto mov0_patch(u32 inst) -> PatchData { - return std::byteswap(0xE0031FAAU); +constexpr auto nop_applied(const u8* data, u32 inst) -> bool { + return nop_patch(inst).cmp(data); } -constexpr auto ret0_applied(u32 inst) -> bool { - return ret0_patch(inst).data == inst; +constexpr auto cmp_applied(const u8* data, u32 inst) -> bool { + return cmp_patch(inst).cmp(data); } -constexpr auto nop_applied(u32 inst) -> bool { - return nop_patch(inst).data == inst; +constexpr auto mov0_ret_applied(const u8* data, u32 inst) -> bool { + return mov0_ret_patch(inst).cmp(data); } -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 mov0_applied(const u8* data, u32 inst) -> bool { + return mov0_patch(inst).cmp(data); } -constexpr auto b_applied(u32 inst) -> bool { - return 0x14 == (inst >> 24); +constexpr auto mov2_applied(const u8* data, u32 inst) -> bool { + return mov2_patch(inst).cmp(data); } -constexpr auto mov0_applied(u32 inst) -> bool { - return mov0_patch(inst).data == inst; +constexpr auto ctest_applied(const u8* data, u32 inst) -> bool { + return ctest_patch(inst).cmp(data); } constinit Patterns fs_patterns[] = { - { "noacidsigchk1", "0xC8FE4739", -24, 0, bl_cond, ret0_patch, ret0_applied, FW_VER_ANY, MAKEHOSVERSION(9,2,0) }, - { "noacidsigchk2", "0x0210911F000072", -5, 0, bl_cond, ret0_patch, ret0_applied, FW_VER_ANY, MAKEHOSVERSION(9,2,0) }, - { "noncasigchk_old", "0x1E42B9", -5, 0, tbz_cond, nop_patch, nop_applied, MAKEHOSVERSION(10,0,0), MAKEHOSVERSION(14,2,1) }, - { "noncasigchk_new", "0x3E4479", -5, 0, tbz_cond, nop_patch, nop_applied, MAKEHOSVERSION(15,0,0) }, - { "nocntchk_old", "0x081C00121F05007181000054", -4, 0, bl_cond, ret0_patch, ret0_applied, MAKEHOSVERSION(10,0,0), MAKEHOSVERSION(14,2,1) }, - { "nocntchk_new", "0x081C00121F05007141010054", -4, 0, bl_cond, ret0_patch, ret0_applied, MAKEHOSVERSION(15,0,0) }, + { "noacidsigchk_1.0.0-9.2.0", "0xC8FE4739", -24, 0, bl_cond, ret0_patch, ret0_applied, true, FW_VER_ANY, MAKEHOSVERSION(9,2,0) }, // moved to loader 10.0.0 + { "noacidsigchk_1.0.0-9.2.0", "0x0210911F000072", -5, 0, bl_cond, ret0_patch, ret0_applied, true, FW_VER_ANY, MAKEHOSVERSION(9,2,0) }, // moved to loader 10.0.0 + { "noncasigchk_10.0.0-16.1.0", "0x1E48391F.0071..0054", -17, 0, tbz_cond, nop_patch, nop_applied, true, MAKEHOSVERSION(10,0,0), MAKEHOSVERSION(16,1,0) }, + { "noncasigchk_17.0.0+", "0x0694..00.42.0091", -18, 0, tbz_cond, nop_patch, nop_applied, true, MAKEHOSVERSION(17,0,0), FW_VER_ANY }, + { "nocntchk_10.0.0-18.1.0", "0x00..0240F9....08.....00...00...0037", 6, 0, bl_cond, ret0_patch, ret0_applied, true, MAKEHOSVERSION(10,0,0), MAKEHOSVERSION(18,1,0) }, + { "nocntchk_19.0.0-20.5.0", "0x00..0240F9....08.....00...00...0054", 6, 0, bl_cond, ret0_patch, ret0_applied, true, MAKEHOSVERSION(19,0,0), MAKEHOSVERSION(20,5,0) }, + { "nocntchk_21.0.0+", "0x00..0240F9....E8.....00...00...0054", 6, 0, bl_cond, ret0_patch, ret0_applied, true, MAKEHOSVERSION(21,0,0), FW_VER_ANY }, }; constinit Patterns ldr_patterns[] = { - { "noacidsigchk", "0xFD7BC6A8C0035FD6", 16, 2, subs_cond, subs_patch, subs_applied }, + { "noacidsigchk_10.0.0+", "0x009401C0BE121F00", 6, 2, cmp_cond, cmp_patch, cmp_applied, true, FW_VER_ANY }, // 1F00016B - cmp w0, w1 patched to 1F00006B - cmp w0, w0 +}; + +constinit Patterns erpt_patterns[] = { + { "no_erpt", "0x...D1FD7B02A9FD830091F76305A9", 0, 0, sub_cond, mov0_ret_patch, mov0_ret_applied, true, FW_VER_ANY }, // FF4305D1 - sub sp, sp, #0x150 patched to E0031F2AC0035FD6 - mov w0, wzr, ret }; 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) }, + { "es_1.0.0-8.1.1", "0x....E8.00...FF97.0300AA..00.....E0.0091..0094.7E4092.......A9", 36, 0, es_cond, mov0_patch, mov0_applied, true, MAKEHOSVERSION(1,0,0), MAKEHOSVERSION(8,1,1) }, + { "es_9.0.0-11.0.1", "0x00...............00.....A0..D1...97.......A9", 30, 0, es_cond, mov0_patch, mov0_applied, true, MAKEHOSVERSION(9,0,0), MAKEHOSVERSION(11,0,1) }, + { "es_12.0.0-18.1.0", "0x02.00...........00...00.....A0..D1...97.......A9", 32, 0, es_cond, mov0_patch, mov0_applied, true, MAKEHOSVERSION(12,0,0), MAKEHOSVERSION(18,1,0) }, + { "es_19.0.0+", "0xA1.00...........00...00.....A0..D1...97.......A9", 32, 0, es_cond, mov0_patch, mov0_applied, true, MAKEHOSVERSION(19,0,0), FW_VER_ANY }, +}; + +constinit Patterns olsc_patterns[] = { + { "olsc_6.0.0-14.1.2", "0x00.73..F968024039..00...00", 42, 0, bl_cond, ret1_patch, ret1_applied, true, MAKEHOSVERSION(6,0,0), MAKEHOSVERSION(14,1,2) }, + { "olsc_15.0.0-18.1.0", "0x00.73..F968024039..00...00", 38, 0, bl_cond, ret1_patch, ret1_applied, true, MAKEHOSVERSION(15,0,0), MAKEHOSVERSION(18,1,0) }, + { "olsc_19.0.0+", "0x00.73..F968024039..00...00", 42, 0, bl_cond, ret1_patch, ret1_applied, true, MAKEHOSVERSION(19,0,0), FW_VER_ANY }, +}; + +constinit Patterns nifm_patterns[] = { + { "ctest_1.0.0-19.0.1", "0x03.AAE003.AA...39..04F8....E0", -29, 0, ctest_cond, ctest_patch, ctest_applied, true, FW_VER_ANY, MAKEHOSVERSION(18,1,0) }, + { "ctest_20.0.0+", "0x03.AA...AA.........0314AA..14AA", -17, 0, ctest_cond, ctest_patch, ctest_applied, true, MAKEHOSVERSION(20,0,0), FW_VER_ANY }, +}; + +constinit Patterns nim_patterns[] = { + { "blankcal0crashfix_17.0.0+", "0x00351F2003D5...............97..0094..00.....61", 6, 0, adr_cond, mov2_patch, mov2_applied, true, MAKEHOSVERSION(17,0,0), FW_VER_ANY }, + { "blockfirmwareupdates_1.0.0-5.1.0", "0x1139F30301AA81.40F9E0.1191", -30, 0, block_fw_updates_cond, mov0_ret_patch, mov0_ret_applied, true, MAKEHOSVERSION(1,0,0), MAKEHOSVERSION(5,1,0) }, + { "blockfirmwareupdates_6.0.0-6.2.0", "0xF30301AA.4E40F9E0..91", -40, 0, block_fw_updates_cond, mov0_ret_patch, mov0_ret_applied, true, MAKEHOSVERSION(6,0,0), MAKEHOSVERSION(6,2,0) }, + { "blockfirmwareupdates_7.0.0-11.0.1", "0xF30301AA014C40F9F40300AAE0..91", -36, 0, block_fw_updates_cond, mov0_ret_patch, mov0_ret_applied, true, MAKEHOSVERSION(7,0,0), MAKEHOSVERSION(11,0,1) }, + { "blockfirmwareupdates_12.0.0+", "0x280841F9084C00F9E0031F.C0035FD6", 16, 0, block_fw_updates_cond, mov0_ret_patch, mov0_ret_applied, true, MAKEHOSVERSION(12,0,0), FW_VER_ANY }, }; // NOTE: add system titles that you want to be patched to this table. @@ -232,8 +285,14 @@ constinit PatchEntry patches[] = { { "fs", 0x0100000000000000, fs_patterns }, // ldr needs to be patched in fw 10+ { "ldr", 0x0100000000000001, ldr_patterns, MAKEHOSVERSION(10,0,0) }, + // erpt no write patch + { "erpt", 0x010000000000002B, erpt_patterns, MAKEHOSVERSION(10,0,0) }, // es was added in fw 2 { "es", 0x0100000000000033, es_patterns, MAKEHOSVERSION(2,0,0) }, + // olsc was added in fw 6 + { "olsc", 0x010000000000003E, olsc_patterns, MAKEHOSVERSION(6,0,0) }, + { "nifm", 0x010000000000000F, nifm_patterns }, + { "nim", 0x0100000000000025, nim_patterns }, }; struct EmummcPaths { @@ -255,25 +314,30 @@ auto is_emummc() -> bool { return (paths.unk[0] != '\0') || (paths.nintendo[0] != '\0'); } -void patcher(Handle handle, std::span data, u64 addr, std::span patterns) { +void patcher(Handle handle, const u8* data, size_t data_size, u64 addr, std::span patterns) { for (auto& p : patterns) { + // skip if disabled (controller by config.ini) + if (p.result == PatchResult::DISABLED) { + continue; + } + // skip if version isn't valid if (VERSION_SKIP && ((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; + p.result = PatchResult::SKIPPED; continue; } // skip if already patched - if (p.result == PatchedResult::PATCHED_FILE || p.result == PatchedResult::PATCHED_SYSPATCH) { + if (p.result == PatchResult::PATCHED_FILE || p.result == PatchResult::PATCHED_SYSPATCH) { continue; } - for (u32 i = 0; i < data.size(); i++) { - if (i + p.byte_pattern.size >= data.size()) { + for (u32 i = 0; i < data_size; i++) { + if (i + p.byte_pattern.size >= data_size) { break; } @@ -292,24 +356,24 @@ void patcher(Handle handle, std::span data, u64 addr, std::span bool { u64 pids[0x50]{}; s32 process_count{}; - static u8 buffer[READ_BUFFER_SIZE]; + constexpr u64 overlap_size = 0x4f; + static u8 buffer[READ_BUFFER_SIZE + overlap_size]; + + std::memset(buffer, 0, sizeof(buffer)); // skip if version isn't valid if (VERSION_SKIP && ((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; + p.result = PatchResult::SKIPPED; } return true; } @@ -362,14 +429,20 @@ auto apply_patch(PatchEntry& patch) -> bool { continue; } - // todo: the byte pattern can in between 2 READ_BUFFER_SIZE boundries! - for (u64 sz = 0; sz < mem_info.size; sz += READ_BUFFER_SIZE) { - 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! + for (u64 sz = 0; sz < mem_info.size; sz += READ_BUFFER_SIZE - overlap_size) { + const auto actual_size = std::min(READ_BUFFER_SIZE, mem_info.size - sz); + if (R_FAILED(svcReadDebugProcessMemory(buffer + overlap_size, handle, mem_info.addr + sz, actual_size))) { break; } else { - patcher(handle, std::span{buffer, actual_size}, mem_info.addr + sz, patch.patterns); + patcher(handle, buffer, actual_size + overlap_size, mem_info.addr + sz - overlap_size, patch.patterns); + if (actual_size >= overlap_size) { + memcpy(buffer, buffer + READ_BUFFER_SIZE, overlap_size); + std::memset(buffer + overlap_size, 0, READ_BUFFER_SIZE); + } else { + const auto bytes_to_overlap = std::min(overlap_size, actual_size); + memcpy(buffer, buffer + READ_BUFFER_SIZE + (actual_size - bytes_to_overlap), bytes_to_overlap); + std::memset(buffer + bytes_to_overlap, 0, sizeof(buffer) - bytes_to_overlap); + } } } } @@ -406,17 +479,18 @@ auto ini_load_or_write_default(const char* section, const char* key, long _defau ini_putl(section, key, _default, path); return _default; } else { - return ini_getl(section, key, _default, path); + return ini_getbool(section, key, _default, path); } } -auto patch_result_to_str(PatchedResult result) -> const char* { +auto patch_result_to_str(PatchResult 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)"; + case PatchResult::NOT_FOUND: return "Unpatched"; + case PatchResult::SKIPPED: return "Skipped"; + case PatchResult::DISABLED: return "Disabled"; + case PatchResult::PATCHED_FILE: return "Patched (file)"; + case PatchResult::PATCHED_SYSPATCH: return "Patched (sys-patch)"; + case PatchResult::FAILED_WRITE: return "Failed (svcWriteDebugProcessMemory)"; } std::unreachable(); @@ -507,10 +581,22 @@ int main(int argc, char* argv[]) { create_dir("/config/sys-patch/"); ini_remove(log_path); + // load options 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); VERSION_SKIP = ini_load_or_write_default("options", "version_skip", 1, ini_path); + + // load patch toggles + for (auto& patch : patches) { + for (auto& p : patch.patterns) { + p.enabled = ini_load_or_write_default(patch.name, p.patch_name, p.enabled, ini_path); + if (!p.enabled) { + p.result = PatchResult::DISABLED; + } + } + } + const auto emummc = is_emummc(); bool enable_patching = true; @@ -540,7 +626,7 @@ int main(int argc, char* argv[]) { for (auto& patch : patches) { for (auto& p : patch.patterns) { if (!enable_patching) { - p.result = PatchedResult::SKIPPED; + p.result = PatchResult::SKIPPED; } ini_puts(patch.name, p.patch_name, patch_result_to_str(p.result), log_path); } diff --git a/sysmod/sys-patch.json b/sysmod/sys-patch.json index 1b0dcc5..c1ca57a 100644 --- a/sysmod/sys-patch.json +++ b/sysmod/sys-patch.json @@ -166,6 +166,7 @@ "type": "debug_flags", "value": { "allow_debug": false, + "force_debug_prod": false, "force_debug": true } }]