mirror of
https://github.com/ITotalJustice/sys-patch.git
synced 2024-11-01 06:35:06 +01:00
initial commit
This commit is contained in:
commit
0a6db1cdfa
19
.github/workflows/build_depoly.yml
vendored
Normal file
19
.github/workflows/build_depoly.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: build
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: devkitpro/devkita64:latest
|
||||
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Build
|
||||
run: make dist -j2
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: sys-patch
|
||||
path: sys-patch.zip
|
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
.vscode
|
||||
build
|
||||
Firmware
|
||||
420000000000000B
|
||||
*.txt
|
||||
*.elf
|
||||
*.npdm
|
||||
*.nso
|
||||
*.nsp
|
||||
*.zip
|
||||
out
|
||||
ignoreme
|
233
Makefile
Normal file
233
Makefile
Normal file
@ -0,0 +1,233 @@
|
||||
#---------------------------------------------------------------------------------
|
||||
.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
|
||||
DATA := data
|
||||
INCLUDES := include
|
||||
#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
|
||||
#---------------------------------------------------------------------------------
|
||||
dist: all
|
||||
@echo making dist ...
|
||||
|
||||
@rm 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
|
||||
#---------------------------------------------------------------------------------------
|
85
README.md
Normal file
85
README.md
Normal file
@ -0,0 +1,85 @@
|
||||
# sys-patch
|
||||
|
||||
A script-like system module that patches fs, es and ldr on boot.
|
||||
|
||||
---
|
||||
|
||||
## Building
|
||||
|
||||
### prerequisites
|
||||
- install devkitpro
|
||||
|
||||
```sh
|
||||
git clone https://github.com/ITotalJustice/sys-patch.git
|
||||
cd sys-patch
|
||||
make
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What is being patched?
|
||||
|
||||
Here's a quick run down of what's being patched
|
||||
|
||||
- fs
|
||||
- es
|
||||
- ldr
|
||||
|
||||
fs and es need new patches after every new fw version.
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## How does it work?
|
||||
|
||||
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.
|
||||
|
||||
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 14kib! 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.
|
||||
|
||||
---
|
||||
|
||||
## If i am using sigpatches already, is there any point in using this as well?
|
||||
|
||||
Yes, in 2 niche cases.
|
||||
|
||||
1. A new ldr patch needs to be created after every atosphere 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.
|
||||
|
||||
---
|
||||
|
||||
## My request
|
||||
|
||||
This repo is mainly a proof of concept. I would love for someone to build upon this, make it into something bigger / better.
|
||||
|
||||
here are a few ideas that i have:
|
||||
- option to only apply patches when in emunand
|
||||
- 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
|
||||
|
||||
---
|
||||
|
||||
## Credits / Thanks
|
||||
|
||||
software is built on the shoulders of giants. this tool wouldn't be possible wthout these people:
|
||||
|
||||
- DarkMatterCore
|
||||
- MrDude
|
||||
- BornToHonk (farni)
|
||||
- TeJay
|
||||
- ArchBox
|
||||
- Shchmue (lockpick)
|
||||
- Jakibaki (sys-netcheat)
|
||||
- SciresM (Atmosphere, hactool, etc)
|
||||
- Switchbrew (libnx, switch-examples)
|
||||
- DevkitPro (toolchain)
|
390
src/main.cpp
Normal file
390
src/main.cpp
Normal file
@ -0,0 +1,390 @@
|
||||
#include <cstring>
|
||||
#include <span>
|
||||
#include <algorithm> // for min
|
||||
#include <bit> // for byteswap
|
||||
#include <switch.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr u64 INNER_HEAP_SIZE = 0x4000; // Size of the inner heap (adjust as necessary).
|
||||
constexpr u64 READ_BUFFER_SIZE = 0x1000; // size of buffer which memory is read into
|
||||
constexpr u32 FW_VER_ANY = 0x0;
|
||||
constexpr u16 REGEX_SKIP = 0x100;
|
||||
|
||||
u32 FW_VERSION{}; // set on startup
|
||||
u32 AMS_VERSION{}; // set on startup
|
||||
|
||||
struct DebugEventInfo {
|
||||
u32 event_type;
|
||||
u32 flags;
|
||||
u64 thread_id;
|
||||
u64 title_id;
|
||||
u64 process_id;
|
||||
char process_name[12];
|
||||
u32 mmu_flags;
|
||||
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'; }
|
||||
};
|
||||
|
||||
// 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++;
|
||||
}
|
||||
}
|
||||
|
||||
// 32 is a reasonable max length for a byte pattern
|
||||
// will compile-time error is size is too small
|
||||
u16 data[32]{};
|
||||
u8 size{};
|
||||
};
|
||||
|
||||
struct PatchData {
|
||||
template<typename T>
|
||||
constexpr PatchData(T _data) {
|
||||
data = _data;
|
||||
size = sizeof(T);
|
||||
}
|
||||
u64 data;
|
||||
u8 size;
|
||||
};
|
||||
|
||||
struct Patterns {
|
||||
const char* patch_name; // name of patch
|
||||
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
|
||||
|
||||
bool (*cond)(u32 inst); // check condtion of the instruction
|
||||
PatchData (*patch)(u32 inst); // the patch data to be 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
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 bl_cond(u32 inst) -> bool {
|
||||
return ((inst >> 26) & 0x3F) == 0x25;
|
||||
}
|
||||
|
||||
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 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;
|
||||
};
|
||||
|
||||
constexpr auto bne_cond(u32 inst) -> bool {
|
||||
const auto type = inst >> 24;
|
||||
const auto cond = inst & 0x10;
|
||||
return type == 0x54 || cond == 0x0;
|
||||
}
|
||||
|
||||
// mov w0, wzr (w0 = 0)
|
||||
constexpr auto ret0_patch(u32 inst) -> PatchData {
|
||||
return std::byteswap(0xE0031F2A);
|
||||
}
|
||||
|
||||
// nop
|
||||
constexpr auto nop_patch(u32 inst) -> PatchData {
|
||||
return std::byteswap(0x1F2003D5);
|
||||
}
|
||||
|
||||
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 offset = (inst >> 5) & 0x7FFFF;
|
||||
return opcode | offset;
|
||||
}
|
||||
|
||||
// mov x0, xzr (x0 = 0)
|
||||
constexpr auto mov0_patch(u32 inst) -> PatchData {
|
||||
return std::byteswap(0xE0031FAA);
|
||||
}
|
||||
|
||||
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 Patterns ldr_patterns[] = {
|
||||
{ "noacidsigchk", "0xFD7BC6A8C0035FD6", 16, 2, subs_cond, subs_patch },
|
||||
};
|
||||
|
||||
// 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) },
|
||||
};
|
||||
|
||||
// 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[] = {
|
||||
{ "fs", 0x0100000000000000, fs_patterns },
|
||||
// ldr needs to be patched in fw 10+
|
||||
{ "ldr", 0x0100000000000001, ldr_patterns, MAKEHOSVERSION(10,0,0) },
|
||||
// es was added in fw 2
|
||||
{ "es", 0x0100000000000033, es_patterns, MAKEHOSVERSION(2,0,0) },
|
||||
};
|
||||
|
||||
auto patcher(Handle handle, std::span<const u8> data, u64 addr, std::span<const 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)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (p.byte_pattern.size >= data.size()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < data.size(); i++) {
|
||||
if (i + p.byte_pattern.size >= data.size()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// loop through every byte of the pattern data to find a match
|
||||
// skipping over any bytes if the value is REGEX_SKIP
|
||||
u32 count{};
|
||||
while (count < p.byte_pattern.size) {
|
||||
if (p.byte_pattern.data[count] != data[i + count] && p.byte_pattern.data[count] != REGEX_SKIP) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
|
||||
// if we have found a matching pattern
|
||||
if (count == p.byte_pattern.size) {
|
||||
// fetch the instruction
|
||||
u32 inst{};
|
||||
const auto inst_offset = i + p.inst_offset;
|
||||
std::memcpy(&inst, data.data() + inst_offset, sizeof(inst));
|
||||
|
||||
// check if the instruction is the one that we want
|
||||
if (p.cond(inst)) {
|
||||
const auto [patch_data, patch_size] = p.patch(inst);
|
||||
const auto patch_offset = addr + inst_offset + p.patch_offset;
|
||||
|
||||
// todo: log failed writes, although this should in theory never fail
|
||||
if (R_FAILED(svcWriteDebugProcessMemory(handle, &patch_data, patch_offset, patch_size))) {
|
||||
} else {
|
||||
// todo: log that this was successful
|
||||
}
|
||||
|
||||
break; // move onto next pattern
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto apply_patch(const PatchEntry& patch) -> bool {
|
||||
Handle handle{};
|
||||
DebugEventInfo event_info{};
|
||||
|
||||
u64 pids[0x50]{};
|
||||
s32 process_count{};
|
||||
static u8 buffer[READ_BUFFER_SIZE];
|
||||
|
||||
// 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)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (R_FAILED(svcGetProcessList(&process_count, pids, 0x50))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (s32 i = 0; i < (process_count - 1); i++) {
|
||||
if (R_SUCCEEDED(svcDebugActiveProcess(&handle, pids[i])) &&
|
||||
R_SUCCEEDED(svcGetDebugEvent(&event_info, handle)) &&
|
||||
patch.title_id == event_info.title_id) {
|
||||
MemoryInfo mem_info{};
|
||||
u64 addr{};
|
||||
u32 page_info{};
|
||||
|
||||
for (;;) {
|
||||
if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &page_info, handle, addr))) {
|
||||
break;
|
||||
}
|
||||
addr = mem_info.addr + mem_info.size;
|
||||
|
||||
// if addr=0 then we hit the reserved memory section
|
||||
if (!addr) {
|
||||
break;
|
||||
}
|
||||
// skip memory that we don't want
|
||||
if (!mem_info.size || (mem_info.perm & Perm_Rx) != Perm_Rx || ((mem_info.type & 0xFF) != MemType_CodeStatic)) {
|
||||
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!
|
||||
continue;
|
||||
} else {
|
||||
patcher(handle, std::span{buffer, actual_size}, mem_info.addr + sz, patch.patterns);
|
||||
}
|
||||
}
|
||||
}
|
||||
svcCloseHandle(handle);
|
||||
return true;
|
||||
} else if (handle) {
|
||||
svcCloseHandle(handle);
|
||||
handle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
for (auto& patch : patches) {
|
||||
apply_patch(patch);
|
||||
}
|
||||
|
||||
// note: sysmod exits here.
|
||||
// to keep it running, add a for (;;) loop (remember to sleep!)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// libnx stuff goes below
|
||||
extern "C" {
|
||||
|
||||
// Sysmodules should not use applet*.
|
||||
u32 __nx_applet_type = AppletType_None;
|
||||
|
||||
// Sysmodules will normally only want to use one FS session.
|
||||
u32 __nx_fs_num_sessions = 1;
|
||||
|
||||
// Newlib heap configuration function (makes malloc/free work).
|
||||
void __libnx_initheap(void) {
|
||||
static char inner_heap[INNER_HEAP_SIZE];
|
||||
extern char* fake_heap_start;
|
||||
extern char* fake_heap_end;
|
||||
|
||||
// Configure the newlib heap.
|
||||
fake_heap_start = inner_heap;
|
||||
fake_heap_end = inner_heap + sizeof(inner_heap);
|
||||
}
|
||||
|
||||
// Service initialization.
|
||||
void __appInit(void) {
|
||||
Result rc{};
|
||||
|
||||
// Open a service manager session.
|
||||
if (R_FAILED(rc = smInitialize()))
|
||||
fatalThrow(rc);
|
||||
|
||||
// Retrieve the current version of Horizon OS.
|
||||
if (R_SUCCEEDED(rc = setsysInitialize())) {
|
||||
SetSysFirmwareVersion fw{};
|
||||
if (R_SUCCEEDED(rc = setsysGetFirmwareVersion(&fw))) {
|
||||
FW_VERSION = MAKEHOSVERSION(fw.major, fw.minor, fw.micro);
|
||||
hosversionSet(FW_VERSION);
|
||||
}
|
||||
setsysExit();
|
||||
}
|
||||
|
||||
// get ams version
|
||||
if (R_SUCCEEDED(rc = splInitialize())) {
|
||||
u64 v{};
|
||||
if (R_SUCCEEDED(rc = splGetConfig((SplConfigItem)65000, &v))) {
|
||||
AMS_VERSION = (v >> 16) & 0xFFFFFF;
|
||||
}
|
||||
splExit();
|
||||
}
|
||||
|
||||
// Add other services you want to use here.
|
||||
if (R_FAILED(rc = pmdmntInitialize()))
|
||||
fatalThrow(rc);
|
||||
|
||||
// Close the service manager session.
|
||||
smExit();
|
||||
}
|
||||
|
||||
// Service deinitialization.
|
||||
void __appExit(void) {
|
||||
pmdmntExit();
|
||||
}
|
||||
|
||||
} // extern "C"
|
172
sys-patch.json
Normal file
172
sys-patch.json
Normal file
@ -0,0 +1,172 @@
|
||||
{
|
||||
"name": "sys-patch",
|
||||
"title_id": "0x420000000000000B",
|
||||
"title_id_range_min": "0x420000000000000B",
|
||||
"title_id_range_max": "0x420000000000000B",
|
||||
"main_thread_stack_size": "0x1000",
|
||||
"main_thread_priority": 49,
|
||||
"default_cpu_id": 3,
|
||||
"process_category": 1,
|
||||
"is_retail": true,
|
||||
"pool_partition": 2,
|
||||
"is_64_bit": true,
|
||||
"address_space_type": 1,
|
||||
"filesystem_access": {
|
||||
"permissions": "0xffffffffffffffff"
|
||||
},
|
||||
"service_access": ["*"],
|
||||
"service_host": ["patch"],
|
||||
"kernel_capabilities": [{
|
||||
"type": "kernel_flags",
|
||||
"value": {
|
||||
"highest_thread_priority": 63,
|
||||
"lowest_thread_priority": 24,
|
||||
"lowest_cpu_id": 3,
|
||||
"highest_cpu_id": 3
|
||||
}
|
||||
}, {
|
||||
"type": "syscalls",
|
||||
"value": {
|
||||
"svcUnknown": "0x00",
|
||||
"svcSetHeapSize": "0x01",
|
||||
"svcSetMemoryPermission": "0x02",
|
||||
"svcSetMemoryAttribute": "0x03",
|
||||
"svcMapMemory": "0x04",
|
||||
"svcUnmapMemory": "0x05",
|
||||
"svcQueryMemory": "0x06",
|
||||
"svcExitProcess": "0x07",
|
||||
"svcCreateThread": "0x08",
|
||||
"svcStartThread": "0x09",
|
||||
"svcExitThread": "0x0a",
|
||||
"svcSleepThread": "0x0b",
|
||||
"svcGetThreadPriority": "0x0c",
|
||||
"svcSetThreadPriority": "0x0d",
|
||||
"svcGetThreadCoreMask": "0x0e",
|
||||
"svcSetThreadCoreMask": "0x0f",
|
||||
"svcGetCurrentProcessorNumber": "0x10",
|
||||
"svcSignalEvent": "0x11",
|
||||
"svcClearEvent": "0x12",
|
||||
"svcMapSharedMemory": "0x13",
|
||||
"svcUnmapSharedMemory": "0x14",
|
||||
"svcCreateTransferMemory": "0x15",
|
||||
"svcCloseHandle": "0x16",
|
||||
"svcResetSignal": "0x17",
|
||||
"svcWaitSynchronization": "0x18",
|
||||
"svcCancelSynchronization": "0x19",
|
||||
"svcArbitrateLock": "0x1a",
|
||||
"svcArbitrateUnlock": "0x1b",
|
||||
"svcWaitProcessWideKeyAtomic": "0x1c",
|
||||
"svcSignalProcessWideKey": "0x1d",
|
||||
"svcGetSystemTick": "0x1e",
|
||||
"svcConnectToNamedPort": "0x1f",
|
||||
"svcSendSyncRequestLight": "0x20",
|
||||
"svcSendSyncRequest": "0x21",
|
||||
"svcSendSyncRequestWithUserBuffer": "0x22",
|
||||
"svcSendAsyncRequestWithUserBuffer": "0x23",
|
||||
"svcGetProcessId": "0x24",
|
||||
"svcGetThreadId": "0x25",
|
||||
"svcBreak": "0x26",
|
||||
"svcOutputDebugString": "0x27",
|
||||
"svcReturnFromException": "0x28",
|
||||
"svcGetInfo": "0x29",
|
||||
"svcFlushEntireDataCache": "0x2a",
|
||||
"svcFlushDataCache": "0x2b",
|
||||
"svcMapPhysicalMemory": "0x2c",
|
||||
"svcUnmapPhysicalMemory": "0x2d",
|
||||
"svcGetFutureThreadInfo": "0x2e",
|
||||
"svcGetLastThreadInfo": "0x2f",
|
||||
"svcGetResourceLimitLimitValue": "0x30",
|
||||
"svcGetResourceLimitCurrentValue": "0x31",
|
||||
"svcSetThreadActivity": "0x32",
|
||||
"svcGetThreadContext3": "0x33",
|
||||
"svcWaitForAddress": "0x34",
|
||||
"svcSignalToAddress": "0x35",
|
||||
"svcUnknown": "0x36",
|
||||
"svcUnknown": "0x37",
|
||||
"svcUnknown": "0x38",
|
||||
"svcUnknown": "0x39",
|
||||
"svcUnknown": "0x3a",
|
||||
"svcUnknown": "0x3b",
|
||||
"svcDumpInfo": "0x3c",
|
||||
"svcDumpInfoNew": "0x3d",
|
||||
"svcUnknown": "0x3e",
|
||||
"svcUnknown": "0x3f",
|
||||
"svcCreateSession": "0x40",
|
||||
"svcAcceptSession": "0x41",
|
||||
"svcReplyAndReceiveLight": "0x42",
|
||||
"svcReplyAndReceive": "0x43",
|
||||
"svcReplyAndReceiveWithUserBuffer": "0x44",
|
||||
"svcCreateEvent": "0x45",
|
||||
"svcUnknown": "0x46",
|
||||
"svcUnknown": "0x47",
|
||||
"svcMapPhysicalMemoryUnsafe": "0x48",
|
||||
"svcUnmapPhysicalMemoryUnsafe": "0x49",
|
||||
"svcSetUnsafeLimit": "0x4a",
|
||||
"svcCreateCodeMemory": "0x4b",
|
||||
"svcControlCodeMemory": "0x4c",
|
||||
"svcSleepSystem": "0x4d",
|
||||
"svcReadWriteRegister": "0x4e",
|
||||
"svcSetProcessActivity": "0x4f",
|
||||
"svcCreateSharedMemory": "0x50",
|
||||
"svcMapTransferMemory": "0x51",
|
||||
"svcUnmapTransferMemory": "0x52",
|
||||
"svcCreateInterruptEvent": "0x53",
|
||||
"svcQueryPhysicalAddress": "0x54",
|
||||
"svcQueryIoMapping": "0x55",
|
||||
"svcCreateDeviceAddressSpace": "0x56",
|
||||
"svcAttachDeviceAddressSpace": "0x57",
|
||||
"svcDetachDeviceAddressSpace": "0x58",
|
||||
"svcMapDeviceAddressSpaceByForce": "0x59",
|
||||
"svcMapDeviceAddressSpaceAligned": "0x5a",
|
||||
"svcMapDeviceAddressSpace": "0x5b",
|
||||
"svcUnmapDeviceAddressSpace": "0x5c",
|
||||
"svcInvalidateProcessDataCache": "0x5d",
|
||||
"svcStoreProcessDataCache": "0x5e",
|
||||
"svcFlushProcessDataCache": "0x5f",
|
||||
"svcDebugActiveProcess": "0x60",
|
||||
"svcBreakDebugProcess": "0x61",
|
||||
"svcTerminateDebugProcess": "0x62",
|
||||
"svcGetDebugEvent": "0x63",
|
||||
"svcContinueDebugEvent": "0x64",
|
||||
"svcGetProcessList": "0x65",
|
||||
"svcGetThreadList": "0x66",
|
||||
"svcGetDebugThreadContext": "0x67",
|
||||
"svcSetDebugThreadContext": "0x68",
|
||||
"svcQueryDebugProcessMemory": "0x69",
|
||||
"svcReadDebugProcessMemory": "0x6a",
|
||||
"svcWriteDebugProcessMemory": "0x6b",
|
||||
"svcSetHardwareBreakPoint": "0x6c",
|
||||
"svcGetDebugThreadParam": "0x6d",
|
||||
"svcUnknown": "0x6e",
|
||||
"svcGetSystemInfo": "0x6f",
|
||||
"svcCreatePort": "0x70",
|
||||
"svcManageNamedPort": "0x71",
|
||||
"svcConnectToPort": "0x72",
|
||||
"svcSetProcessMemoryPermission": "0x73",
|
||||
"svcMapProcessMemory": "0x74",
|
||||
"svcUnmapProcessMemory": "0x75",
|
||||
"svcQueryProcessMemory": "0x76",
|
||||
"svcMapProcessCodeMemory": "0x77",
|
||||
"svcUnmapProcessCodeMemory": "0x78",
|
||||
"svcCreateProcess": "0x79",
|
||||
"svcStartProcess": "0x7a",
|
||||
"svcTerminateProcess": "0x7b",
|
||||
"svcGetProcessInfo": "0x7c",
|
||||
"svcCreateResourceLimit": "0x7d",
|
||||
"svcSetResourceLimitLimitValue": "0x7e",
|
||||
"svcCallSecureMonitor": "0x7f"
|
||||
}
|
||||
}, {
|
||||
"type": "min_kernel_version",
|
||||
"value": "0x0030"
|
||||
}, {
|
||||
"type": "handle_table_size",
|
||||
"value": 64
|
||||
}, {
|
||||
"type": "debug_flags",
|
||||
"value": {
|
||||
"allow_debug": false,
|
||||
"force_debug": true
|
||||
}
|
||||
}]
|
||||
}
|
5
toolbox.json
Normal file
5
toolbox.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name" : "sys-patch",
|
||||
"tid" : "420000000000000B",
|
||||
"requires_reboot": false
|
||||
}
|
Loading…
Reference in New Issue
Block a user