add ctest patch, add per patch toggle, made overlay more organised

This commit is contained in:
TotalJustice 2023-05-26 21:51:45 +01:00
parent 28e52b7694
commit 3da8f21d20
4 changed files with 291 additions and 148 deletions

View File

@ -2,7 +2,7 @@ MAKEFILES := sysmod overlay
TARGETS := $(foreach dir,$(MAKEFILES),$(CURDIR)/$(dir)) TARGETS := $(foreach dir,$(MAKEFILES),$(CURDIR)/$(dir))
# the below was taken from atmosphere + switch-examples makefile # the below was taken from atmosphere + switch-examples makefile
export VERSION := 1.3.0 export VERSION := 1.4.0
export GIT_BRANCH := $(shell git symbolic-ref --short HEAD) export GIT_BRANCH := $(shell git symbolic-ref --short HEAD)
ifeq ($(strip $(shell git status --porcelain 2>/dev/null)),) ifeq ($(strip $(shell git status --porcelain 2>/dev/null)),)

View File

@ -1,14 +1,14 @@
# sys-patch # 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** and **nifm** on boot.
--- ---
## Config ## 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 ```ini
[options] [options]
@ -22,11 +22,11 @@ version_skip=1 ; 1=(default) skips out of date patterns, 0=search all patterns
## Overlay ## 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). - Unpatched means the patch wasn't applied (likely not found).
- Patched (green) means it was patched by sys-patch. - 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.
<p float="left"> <p float="left">
<img src="https://i.imgur.com/yDhTdI6.jpg" width="400" /> <img src="https://i.imgur.com/yDhTdI6.jpg" width="400" />
@ -40,63 +40,59 @@ the overlay can be used to change the config options and to see what patches are
## Building ## Building
### prerequisites ### 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 The output of `out/` can be copied to your SD card.
git clone --recurse-submodules https://github.com/ITotalJustice/sys-patch.git 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.
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.
--- ---
## What is being patched? ## What is being patched?
Here's a quick run down of what's being patched Here's a quick run down of what's being patched:
- fs - **fs**
- es - **es**
- ldr - **ldr**
- **nifm**
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.
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 :) Yes, in 3 situations.
--- 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.
## Does this mean i should stop downloading / using sigpatches? 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**.
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. 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.
--- ### Does this mean that I should stop downloading / using sigpatches?
## If i am using sigpatches already, is there any point in using this as well? 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 2 niche cases.
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.
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
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.
--- ---
## Credits / Thanks ## 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 - MrDude
- BornToHonk (farni) - BornToHonk (farni)
- TeJay - TeJay

View File

@ -43,12 +43,10 @@ auto create_dir(const char* path) -> bool {
} }
struct ConfigEntry { struct ConfigEntry {
const char* const section;
const char* const key;
bool value;
ConfigEntry(const char* _section, const char* _key, bool default_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() { void load_value_from_ini() {
this->value = ini_getbool(this->section, this->key, this->value, CONFIG_PATH); this->value = ini_getbool(this->section, this->key, this->value, CONFIG_PATH);
@ -62,23 +60,17 @@ struct ConfigEntry {
}); });
return item; 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: 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 { 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 frame = new tsl::elm::OverlayFrame("sys-patch", VERSION_WITH_HASH);
auto list = new tsl::elm::List(); auto list = new tsl::elm::List();
@ -88,6 +80,74 @@ public:
list->addItem(config_logging.create_list_item("Logging")); list->addItem(config_logging.create_list_item("Logging"));
list->addItem(config_version_skip.create_list_item("Version skip")); 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", "patch_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("noacidsigchk1"));
list->addItem(config_noacidsigchk2.create_list_item("noacidsigchk2"));
list->addItem(config_noncasigchk_old.create_list_item("noncasigchk_old"));
list->addItem(config_noncasigchk_new.create_list_item("noncasigchk_new"));
list->addItem(config_nocntchk_old.create_list_item("nocntchk_old"));
list->addItem(config_nocntchk_new.create_list_item("nocntchk_new"));
list->addItem(new tsl::elm::CategoryHeader("LDR - 0100000000000001"));
list->addItem(config_noacidsigchk.create_list_item("noacidsigchk"));
list->addItem(new tsl::elm::CategoryHeader("ES - 0100000000000033"));
list->addItem(config_es1.create_list_item("es1"));
list->addItem(config_es2.create_list_item("es2"));
list->addItem(config_es3.create_list_item("es3"));
list->addItem(config_es4.create_list_item("es4"));
list->addItem(config_es5.create_list_item("es5"));
list->addItem(config_es6.create_list_item("es6"));
list->addItem(new tsl::elm::CategoryHeader("NIFM - 010000000000000F"));
list->addItem(config_ctest.create_list_item("ctest"));
frame->setContent(list);
return frame;
}
ConfigEntry config_noacidsigchk1{"fs", "noacidsigchk1", true};
ConfigEntry config_noacidsigchk2{"fs", "noacidsigchk2", true};
ConfigEntry config_noncasigchk_old{"fs", "noncasigchk_old", true};
ConfigEntry config_noncasigchk_new{"fs", "noncasigchk_new", true};
ConfigEntry config_nocntchk_old{"fs", "nocntchk_old", true};
ConfigEntry config_nocntchk_new{"fs", "nocntchk_new", true};
ConfigEntry config_noacidsigchk{"ldr", "noacidsigchk", true};
ConfigEntry config_es1{"es", "es1", true};
ConfigEntry config_es2{"es", "es2", true};
ConfigEntry config_es3{"es", "es3", true};
ConfigEntry config_es4{"es", "es4", true};
ConfigEntry config_es5{"es", "es5", true};
ConfigEntry config_es6{"es", "es6", true};
ConfigEntry config_ctest{"nifm", "ctest", false};
};
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)) { if (does_file_exist(LOG_PATH)) {
struct CallbackUser { struct CallbackUser {
tsl::elm::List* list; tsl::elm::List* list;
@ -119,7 +179,7 @@ public:
} else { } else {
user->list->addItem(new tsl::elm::ListItem(Key, "Patched", colour_file)); 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)); user->list->addItem(new tsl::elm::ListItem(Key, Value, colour_unpatched));
} else if (user->last_section == "stats") { } else if (user->last_section == "stats") {
user->list->addItem(new tsl::elm::ListItem(Key, Value, tsl::style::color::ColorDescription)); user->list->addItem(new tsl::elm::ListItem(Key, Value, tsl::style::color::ColorDescription));
@ -130,17 +190,58 @@ public:
return 1; return 1;
}, &callback_userdata, LOG_PATH); }, &callback_userdata, LOG_PATH);
} else { } else {
list->addItem(new tsl::elm::ListItem("No log found!"));
} }
frame->setContent(list); frame->setContent(list);
return frame; return frame;
} }
};
ConfigEntry config_patch_sysmmc{"options", "patch_sysmmc", true}; class GuiMain final : public tsl::Gui {
ConfigEntry config_patch_emummc{"options", "patch_emummc", true}; public:
ConfigEntry config_logging{"options", "patch_logging", true}; GuiMain() { }
ConfigEntry config_version_skip{"options", "version_skip", true};
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<GuiOptions>();
return true;
}
return false;
});
toggle->setClickListener([](u64 keys) -> bool {
if (keys & HidNpadButton_A) {
tsl::changeTo<GuiToggle>();
return true;
}
return false;
});
log->setClickListener([](u64 keys) -> bool {
if (keys & HidNpadButton_A) {
tsl::changeTo<GuiLog>();
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 // libtesla already initialized fs, hid, pl, pmdmnt, hid:sys and set:sys
@ -154,5 +255,7 @@ public:
} // namespace } // namespace
int main(int argc, char **argv) { int main(int argc, char **argv) {
create_dir("/config/");
create_dir("/config/sys-patch/");
return tsl::loop<SysPatchOverlay>(argc, argv); return tsl::loop<SysPatchOverlay>(argc, argv);
} }

View File

@ -31,8 +31,8 @@ struct DebugEventInfo {
u8 _0x30[0x10]; u8 _0x30[0x10];
}; };
struct PatternData { template<typename T>
constexpr PatternData(const char* s) { constexpr void str2hex(const char* s, T* data, u8& size) {
// skip leading 0x (if any) // skip leading 0x (if any)
if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
s += 2; s += 2;
@ -47,7 +47,7 @@ struct PatternData {
// parse and convert string // parse and convert string
while (*s != '\0') { while (*s != '\0') {
if (*s == '.') { if (sizeof(T) == sizeof(u16) && *s == '.') {
data[size] = REGEX_SKIP; data[size] = REGEX_SKIP;
s++; s++;
} else { } else {
@ -56,27 +56,42 @@ struct PatternData {
} }
size++; size++;
} }
}
struct PatternData {
constexpr PatternData(const char* s) {
str2hex(s, data, size);
} }
// 32 is a reasonable max length for a byte pattern u16 data[44]{}; // reasonable max pattern length, adjust as needed
// will compile-time error is size is too small
u16 data[32]{};
u8 size{}; u8 size{};
}; };
struct PatchData { struct PatchData {
template<typename T> constexpr PatchData(const char* s) {
constexpr PatchData(T _data) { str2hex(s, data, size);
data = _data;
size = sizeof(T);
} }
u64 data;
u8 size; template<typename T>
constexpr PatchData(T v) {
for (u32 i = 0; i < sizeof(T); i++) {
data[size++] = v & 0xFF;
v >>= 8;
}
}
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, NOT_FOUND,
SKIPPED, SKIPPED,
DISABLED,
PATCHED_FILE, PATCHED_FILE,
PATCHED_SYSPATCH, PATCHED_SYSPATCH,
FAILED_WRITE, FAILED_WRITE,
@ -91,14 +106,16 @@ struct Patterns {
bool (*const cond)(u32 inst); // check condition of the instruction bool (*const cond)(u32 inst); // check condition of the instruction
PatchData (*const patch)(u32 inst); // the patch data to be applied 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 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 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 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 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 { struct PatchEntry {
@ -148,7 +165,7 @@ constexpr auto mov2_cond(u32 inst) -> bool {
if (hosversionBefore(15,0,0)) { if (hosversionBefore(15,0,0)) {
return (inst >> 24) == 0x92; // and x0, x19, #0xffffffff return (inst >> 24) == 0x92; // and x0, x19, #0xffffffff
} else { } else {
return (inst >> 24) == 0x2A; return (inst >> 24) == 0x2A; // mov x0, x20
} }
} }
@ -158,37 +175,37 @@ constexpr auto bne_cond(u32 inst) -> bool {
return type == 0x54 || cond == 0x0; return type == 0x54 || cond == 0x0;
} }
constexpr auto ret0_patch(u32 inst) -> PatchData { constexpr auto ctest_cond(u32 inst) -> bool {
return std::byteswap(0xE0031F2AU); return true; // we overwrite the function so we don't care to check
} }
constexpr auto nop_patch(u32 inst) -> PatchData { // to view patches, use https://armconverter.com/?lock=arm64
return std::byteswap(0x1F2003D5U); constexpr PatchData ret0_patch_data{ "0xE0031F2A" };
} constexpr PatchData nop_patch_data{ "0x1F2003D5" };
constexpr PatchData mov0_patch_data{ "0xE0031FAA" };
constexpr PatchData ctest_patch_data{ "00309AD2001EA1F2610100D4E0031FAAC0035FD6" };
constexpr auto subs_patch(u32 inst) -> PatchData { constexpr auto ret0_patch(u32 inst) -> PatchData { return ret0_patch_data; }
return subi_cond(inst) ? (u8)0x1 : (u8)0x0; constexpr auto nop_patch(u32 inst) -> PatchData { return nop_patch_data; }
} constexpr auto subs_patch(u32 inst) -> PatchData { return subi_cond(inst) ? (u8)0x1 : (u8)0x0; }
constexpr auto mov0_patch(u32 inst) -> PatchData { return mov0_patch_data; }
constexpr auto ctest_patch(u32 inst) -> PatchData { return ctest_patch_data; }
constexpr auto b_patch(u32 inst) -> PatchData { constexpr auto b_patch(u32 inst) -> PatchData {
const auto opcode = 0x14 << 24; const u32 opcode = 0x14 << 24;
const auto offset = (inst >> 5) & 0x7FFFF; const u32 offset = (inst >> 5) & 0x7FFFF;
return opcode | offset; return opcode | offset;
} }
constexpr auto mov0_patch(u32 inst) -> PatchData { constexpr auto ret0_applied(const u8* data, u32 inst) -> bool {
return std::byteswap(0xE0031FAAU); return ret0_patch(inst).cmp(data);
} }
constexpr auto ret0_applied(u32 inst) -> bool { constexpr auto nop_applied(const u8* data, u32 inst) -> bool {
return ret0_patch(inst).data == inst; return nop_patch(inst).cmp(data);
} }
constexpr auto nop_applied(u32 inst) -> bool { constexpr auto subs_applied(const u8* data, 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 type_i = (inst >> 24) & 0xFF;
const auto imm = (inst >> 10) & 0xFFF; const auto imm = (inst >> 10) & 0xFFF;
const auto type_r = (inst >> 21) & 0x7F9; const auto type_r = (inst >> 21) & 0x7F9;
@ -196,34 +213,42 @@ constexpr auto subs_applied(u32 inst) -> bool {
return ((type_i == 0x71) && (imm == 0x1)) || ((type_r == 0x358) && (reg == 0x0)); return ((type_i == 0x71) && (imm == 0x1)) || ((type_r == 0x358) && (reg == 0x0));
} }
constexpr auto b_applied(u32 inst) -> bool { constexpr auto b_applied(const u8* data, u32 inst) -> bool {
return 0x14 == (inst >> 24); return 0x14 == (inst >> 24);
} }
constexpr auto mov0_applied(u32 inst) -> bool { constexpr auto mov0_applied(const u8* data, u32 inst) -> bool {
return mov0_patch(inst).data == inst; return mov0_patch(inst).cmp(data);
}
constexpr auto ctest_applied(const u8* data, u32 inst) -> bool {
return ctest_patch(inst).cmp(data);
} }
constinit Patterns fs_patterns[] = { constinit Patterns fs_patterns[] = {
{ "noacidsigchk1", "0xC8FE4739", -24, 0, bl_cond, ret0_patch, ret0_applied, FW_VER_ANY, MAKEHOSVERSION(9,2,0) }, { "noacidsigchk1", "0xC8FE4739", -24, 0, bl_cond, ret0_patch, ret0_applied, true, FW_VER_ANY, MAKEHOSVERSION(9,2,0) },
{ "noacidsigchk2", "0x0210911F000072", -5, 0, bl_cond, ret0_patch, ret0_applied, FW_VER_ANY, MAKEHOSVERSION(9,2,0) }, { "noacidsigchk2", "0x0210911F000072", -5, 0, bl_cond, ret0_patch, ret0_applied, true, 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_old", "0x1E42B9", -5, 0, tbz_cond, nop_patch, nop_applied, true, MAKEHOSVERSION(10,0,0), MAKEHOSVERSION(14,2,1) },
{ "noncasigchk_new", "0x3E4479", -5, 0, tbz_cond, nop_patch, nop_applied, MAKEHOSVERSION(15,0,0) }, { "noncasigchk_new", "0x3E4479", -5, 0, tbz_cond, nop_patch, nop_applied, true, MAKEHOSVERSION(15,0,0) },
{ "nocntchk_old", "0x081C00121F05007181000054", -4, 0, bl_cond, ret0_patch, ret0_applied, MAKEHOSVERSION(10,0,0), MAKEHOSVERSION(14,2,1) }, { "nocntchk_old", "0x081C00121F05007181000054", -4, 0, bl_cond, ret0_patch, ret0_applied, true, MAKEHOSVERSION(10,0,0), MAKEHOSVERSION(14,2,1) },
{ "nocntchk_new", "0x081C00121F05007141010054", -4, 0, bl_cond, ret0_patch, ret0_applied, MAKEHOSVERSION(15,0,0) }, { "nocntchk_new", "0x081C00121F05007141010054", -4, 0, bl_cond, ret0_patch, ret0_applied, true, MAKEHOSVERSION(15,0,0) },
}; };
constinit Patterns ldr_patterns[] = { constinit Patterns ldr_patterns[] = {
{ "noacidsigchk", "0xFD7BC6A8C0035FD6", 16, 2, subs_cond, subs_patch, subs_applied }, { "noacidsigchk", "0xFD7BC6A8C0035FD6", 16, 2, subs_cond, subs_patch, subs_applied, true },
}; };
constinit Patterns es_patterns[] = { constinit Patterns es_patterns[] = {
{ "es1", "0x1F90013128928052", -4, 0, cbz_cond, b_patch, b_applied, FW_VER_ANY, MAKEHOSVERSION(13,2,1) }, { "es1", "0x1F90013128928052", -4, 0, cbz_cond, b_patch, b_applied, true, FW_VER_ANY, MAKEHOSVERSION(13,2,1) },
{ "es2", "0xC07240F9E1930091", -4, 0, tbz_cond, nop_patch, nop_applied, FW_VER_ANY, MAKEHOSVERSION(10,2,0) }, { "es2", "0xC07240F9E1930091", -4, 0, tbz_cond, nop_patch, nop_applied, true, FW_VER_ANY, MAKEHOSVERSION(10,2,0) },
{ "es3", "0xF3031FAA02000014", -4, 0, bne_cond, nop_patch, nop_applied, FW_VER_ANY, MAKEHOSVERSION(10,2,0) }, { "es3", "0xF3031FAA02000014", -4, 0, bne_cond, nop_patch, nop_applied, true, 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) }, { "es4", "0xC0FDFF35A8C35838", -4, 0, mov_cond, nop_patch, nop_applied, true, 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) }, { "es5", "0xE023009145EEFF97", -4, 0, cbz_cond, b_patch, b_applied, true, 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) }, { "es6", "0x.6300...0094A0..D1..FF97", 16, 0, mov2_cond, mov0_patch, mov0_applied, true, MAKEHOSVERSION(14,0,0) },
};
constinit Patterns nifm_patterns[] = {
{ "ctest", "................F50301AAF40300AA....F30314AAE00314AA9F0201397F8E04F8", 0, 0, ctest_cond, ctest_patch, ctest_applied, true },
}; };
// NOTE: add system titles that you want to be patched to this table. // NOTE: add system titles that you want to be patched to this table.
@ -234,6 +259,7 @@ constinit PatchEntry patches[] = {
{ "ldr", 0x0100000000000001, ldr_patterns, MAKEHOSVERSION(10,0,0) }, { "ldr", 0x0100000000000001, ldr_patterns, MAKEHOSVERSION(10,0,0) },
// es was added in fw 2 // es was added in fw 2
{ "es", 0x0100000000000033, es_patterns, MAKEHOSVERSION(2,0,0) }, { "es", 0x0100000000000033, es_patterns, MAKEHOSVERSION(2,0,0) },
{ "nifm", 0x010000000000000F, nifm_patterns },
}; };
struct EmummcPaths { struct EmummcPaths {
@ -257,18 +283,23 @@ auto is_emummc() -> bool {
void patcher(Handle handle, std::span<const u8> data, u64 addr, std::span<Patterns> patterns) { void patcher(Handle handle, std::span<const u8> data, u64 addr, std::span<Patterns> patterns) {
for (auto& p : patterns) { for (auto& p : patterns) {
// skip if disabled (controller by config.ini)
if (p.result == PatchResult::DISABLED) {
continue;
}
// skip if version isn't valid // skip if version isn't valid
if (VERSION_SKIP && if (VERSION_SKIP &&
((p.min_fw_ver && p.min_fw_ver > FW_VERSION) || ((p.min_fw_ver && p.min_fw_ver > FW_VERSION) ||
(p.max_fw_ver && p.max_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.min_ams_ver && p.min_ams_ver > AMS_VERSION) ||
(p.max_ams_ver && p.max_ams_ver < AMS_VERSION))) { (p.max_ams_ver && p.max_ams_ver < AMS_VERSION))) {
p.result = PatchedResult::SKIPPED; p.result = PatchResult::SKIPPED;
continue; continue;
} }
// skip if already patched // 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; continue;
} }
@ -301,15 +332,15 @@ void patcher(Handle handle, std::span<const u8> data, u64 addr, std::span<Patter
// todo: log failed writes, although this should in theory never fail // todo: log failed writes, although this should in theory never fail
if (R_FAILED(svcWriteDebugProcessMemory(handle, &patch_data, patch_offset, patch_size))) { if (R_FAILED(svcWriteDebugProcessMemory(handle, &patch_data, patch_offset, patch_size))) {
p.result = PatchedResult::FAILED_WRITE; p.result = PatchResult::FAILED_WRITE;
} else { } else {
p.result = PatchedResult::PATCHED_SYSPATCH; p.result = PatchResult::PATCHED_SYSPATCH;
} }
// move onto next pattern // move onto next pattern
break; break;
} else if (p.applied(inst)) { } else if (p.applied(data.data() + inst_offset, inst)) {
// patch already applied by sigpatches // patch already applied by sigpatches
p.result = PatchedResult::PATCHED_FILE; p.result = PatchResult::PATCHED_FILE;
break; break;
} }
} }
@ -330,7 +361,7 @@ auto apply_patch(PatchEntry& patch) -> bool {
((patch.min_fw_ver && patch.min_fw_ver > FW_VERSION) || ((patch.min_fw_ver && patch.min_fw_ver > FW_VERSION) ||
(patch.max_fw_ver && patch.max_fw_ver < FW_VERSION))) { (patch.max_fw_ver && patch.max_fw_ver < FW_VERSION))) {
for (auto& p : patch.patterns) { for (auto& p : patch.patterns) {
p.result = PatchedResult::SKIPPED; p.result = PatchResult::SKIPPED;
} }
return true; return true;
} }
@ -410,13 +441,14 @@ auto ini_load_or_write_default(const char* section, const char* key, long _defau
} }
} }
auto patch_result_to_str(PatchedResult result) -> const char* { auto patch_result_to_str(PatchResult result) -> const char* {
switch (result) { switch (result) {
case PatchedResult::NOT_FOUND: return "Unpatched"; case PatchResult::NOT_FOUND: return "Unpatched";
case PatchedResult::SKIPPED: return "Skipped"; case PatchResult::SKIPPED: return "Skipped";
case PatchedResult::PATCHED_FILE: return "Patched (file)"; case PatchResult::DISABLED: return "Disabled";
case PatchedResult::PATCHED_SYSPATCH: return "Patched (sys-patch)"; case PatchResult::PATCHED_FILE: return "Patched (file)";
case PatchedResult::FAILED_WRITE: return "Failed (svcWriteDebugProcessMemory)"; case PatchResult::PATCHED_SYSPATCH: return "Patched (sys-patch)";
case PatchResult::FAILED_WRITE: return "Failed (svcWriteDebugProcessMemory)";
} }
std::unreachable(); std::unreachable();
@ -507,10 +539,22 @@ int main(int argc, char* argv[]) {
create_dir("/config/sys-patch/"); create_dir("/config/sys-patch/");
ini_remove(log_path); ini_remove(log_path);
// load options
const auto patch_sysmmc = ini_load_or_write_default("options", "patch_sysmmc", 1, ini_path); 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 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 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); 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(); const auto emummc = is_emummc();
bool enable_patching = true; bool enable_patching = true;
@ -540,7 +584,7 @@ int main(int argc, char* argv[]) {
for (auto& patch : patches) { for (auto& patch : patches) {
for (auto& p : patch.patterns) { for (auto& p : patch.patterns) {
if (!enable_patching) { 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); ini_puts(patch.name, p.patch_name, patch_result_to_str(p.result), log_path);
} }