Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ MAKEFILES := sysmod overlay
TARGETS := $(foreach dir,$(MAKEFILES),$(CURDIR)/$(dir))

# the below was taken from atmosphere + switch-examples makefile
export VERSION := 1.6.0
export VERSION := 1.6.1

ifneq ($(strip $(shell git symbolic-ref --short HEAD 2>/dev/null)),)
export GIT_BRANCH := $(shell git symbolic-ref --short HEAD)
Expand Down
115 changes: 100 additions & 15 deletions overlay/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#define TESLA_INIT_IMPL // If you have more than one file using the tesla header, only define this in the main one
#define STBTT_STATIC
#include <tesla.hpp> // The Tesla Header
#include <string>
#include <string_view>
#include "minIni/minIni.h"

Expand All @@ -9,6 +10,87 @@ namespace {
constexpr auto CONFIG_PATH = "/config/sys-patch/config.ini";
constexpr auto LOG_PATH = "/config/sys-patch/log.ini";

auto split_log_value(std::string_view value) -> std::pair<std::string, std::string> {
if (value.empty()) {
return {{}, {}};
}

const auto offset_start = value.rfind(" (0x");
if (offset_start == std::string_view::npos || value.back() != ')') {
return {std::string{value}, {}};
}

return {
std::string{value.substr(0, offset_start)},
std::string{value.substr(offset_start + 1, value.size() - offset_start - 2)}
};
}

class ColouredListItem : public tsl::elm::ListItem {
public:
ColouredListItem(const std::string& text, const std::string& value, tsl::Color value_colour)
: tsl::elm::ListItem(text, value), m_value_colour(value_colour) {
}

void draw(tsl::gfx::Renderer *renderer) override {
if (this->m_touched && Element::getInputMode() == tsl::InputMode::Touch) {
renderer->drawRect(ELEMENT_BOUNDS(this), a(tsl::style::color::ColorClickAnimation));
}

if (this->m_maxWidth == 0) {
if (!this->m_value.empty()) {
auto [valueWidth, valueHeight] = renderer->drawString(this->m_value.c_str(), false, 0, 0, 20, tsl::style::color::ColorTransparent);
this->m_maxWidth = this->getWidth() - valueWidth - 70;
} else {
this->m_maxWidth = this->getWidth() - 40;
}

auto [width, height] = renderer->drawString(this->m_text.c_str(), false, 0, 0, 23, tsl::style::color::ColorTransparent);
this->m_trunctuated = width > this->m_maxWidth;

if (this->m_trunctuated) {
this->m_scrollText = this->m_text + " ";
auto [scrollWidth, scrollHeight] = renderer->drawString(this->m_scrollText.c_str(), false, 0, 0, 23, tsl::style::color::ColorTransparent);
this->m_scrollText += this->m_text;
this->m_textWidth = scrollWidth;
this->m_ellipsisText = renderer->limitStringLength(this->m_text, false, 22, this->m_maxWidth);
} else {
this->m_textWidth = width;
}
}

renderer->drawRect(this->getX(), this->getY(), this->getWidth(), 1, a(tsl::style::color::ColorFrame));
renderer->drawRect(this->getX(), this->getTopBound(), this->getWidth(), 1, a(tsl::style::color::ColorFrame));

if (this->m_trunctuated) {
if (this->m_focused) {
renderer->enableScissoring(this->getX(), this->getY(), this->m_maxWidth + 40, this->getHeight());
renderer->drawString(this->m_scrollText.c_str(), false, this->getX() + 20 - this->m_scrollOffset, this->getY() + 45, 23, tsl::style::color::ColorText);
renderer->disableScissoring();
if (this->m_scrollAnimationCounter == 90) {
if (this->m_scrollOffset == this->m_textWidth) {
this->m_scrollOffset = 0;
this->m_scrollAnimationCounter = 0;
} else {
this->m_scrollOffset++;
}
} else {
this->m_scrollAnimationCounter++;
}
} else {
renderer->drawString(this->m_ellipsisText.c_str(), false, this->getX() + 20, this->getY() + 45, 23, a(tsl::style::color::ColorText));
}
} else {
renderer->drawString(this->m_text.c_str(), false, this->getX() + 20, this->getY() + 45, 23, a(tsl::style::color::ColorText));
}

renderer->drawString(this->m_value.c_str(), false, this->getX() + this->m_maxWidth + 45, this->getY() + 45, 20, a(m_value_colour));
}

private:
tsl::Color m_value_colour;
};

auto does_file_exist(const char* path) -> bool {
Result rc{};
FsFileSystem fs{};
Expand Down Expand Up @@ -120,6 +202,9 @@ class GuiToggle final : public tsl::Gui {
list->addItem(config_es4.create_list_item("es_19.0.0-21.2.0"));
list->addItem(config_es5.create_list_item("es_22.0.0+"));

list->addItem(new tsl::elm::CategoryHeader("AM - 0100000000000023"));
list->addItem(config_am1.create_list_item("am_homebrew_fix_22.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"));
Expand Down Expand Up @@ -155,6 +240,7 @@ class GuiToggle final : public tsl::Gui {
ConfigEntry config_es3{"es", "es_12.0.0-18.1.0", true};
ConfigEntry config_es4{"es", "es_19.0.0-21.2.0", true};
ConfigEntry config_es5{"es", "es_22.0.0+", true};
ConfigEntry config_am1{"am", "am_homebrew_fix_22.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};
Expand Down Expand Up @@ -185,8 +271,9 @@ class GuiLog final : public tsl::Gui {
ini_browse([](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData){
auto user = (CallbackUser*)UserData;
std::string_view value{Value};
const auto [status, detail] = split_log_value(value);

if (value == "Skipped") {
if (status == "Skipped") {
return 1;
}

Expand All @@ -201,22 +288,20 @@ class GuiLog final : public tsl::Gui {
constexpr tsl::Color colour_unpatched{F(250), F(90), F(58), F(255)};
#undef F

if (value.starts_with("Patched")) {
auto *item = new tsl::elm::ListItem(Key);
item->setValue("Patched", true);
user->list->addItem(item);
} else if (value.starts_with("Unpatched") || value.starts_with("Disabled")) {
auto *item = new tsl::elm::ListItem(Key);
item->setValue(Value, true);
user->list->addItem(item);
if (status.starts_with("Patched")) {
const auto is_sys_patch = status.ends_with("(sys-patch)");
const auto display_value = detail.empty() ? std::string{"Patched"} : "Patched @ " + detail;
user->list->addItem(new ColouredListItem(
Key,
display_value,
is_sys_patch ? colour_syspatch : colour_file
));
} else if (status.starts_with("Unpatched") || status.starts_with("Disabled")) {
user->list->addItem(new ColouredListItem(Key, Value, colour_unpatched));
} else if (user->last_section == "stats") {
auto *item = new tsl::elm::ListItem(Key);
item->setValue(Value, true);
user->list->addItem(item);
user->list->addItem(new ColouredListItem(Key, Value, tsl::style::color::ColorDescription));
} else {
auto *item = new tsl::elm::ListItem(Key);
item->setValue(Value, true);
user->list->addItem(item);
user->list->addItem(new ColouredListItem(Key, Value, tsl::style::color::ColorText));
}

return 1;
Expand Down
93 changes: 82 additions & 11 deletions sysmod/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ struct Patterns {
const u32 max_ams_ver{FW_VER_ANY}; // set to FW_VER_ANY to ignore

PatchResult result{PatchResult::NOT_FOUND};
u64 logged_offset{};
};

struct PatchEntry {
Expand Down Expand Up @@ -269,6 +270,10 @@ constinit Patterns es_patterns[] = {
{ "es_22.0.0+", "0xA0630091....FE97A08300D1....FE97", 16, 0, es_cond, mov0_patch, mov0_applied, true, MAKEHOSVERSION(22,0,0), FW_VER_ANY },
};

constinit Patterns am_patterns[] = {
{ "am_homebrew_fix_22.0.0+", "0x94......F9......F9........00410491", 17, 0, bl_cond, nop_patch, nop_applied, true, MAKEHOSVERSION(22,0,0), FW_VER_ANY },
};

constinit Patterns olsc_patterns[] = {
{ "olsc_6.0.0-14.1.2", "0x00..73....F9....4039", 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....F9....4039", 38, 0, bl_cond, ret1_patch, ret1_applied, true, MAKEHOSVERSION(15,0,0), MAKEHOSVERSION(18,1,0) },
Expand Down Expand Up @@ -303,6 +308,7 @@ constinit PatchEntry patches[] = {
{ "olsc", 0x010000000000003E, olsc_patterns, MAKEHOSVERSION(6,0,0) },
{ "nifm", 0x010000000000000F, nifm_patterns },
{ "nim", 0x0100000000000025, nim_patterns },
{ "am", 0x0100000000000023, am_patterns, MAKEHOSVERSION(22,0,0) },
};

struct EmummcPaths {
Expand All @@ -324,7 +330,7 @@ auto is_emummc() -> bool {
return (paths.unk[0] != '\0') || (paths.nintendo[0] != '\0');
}

void patcher(Handle handle, const u8* data, size_t data_size, u64 addr, std::span<Patterns> patterns) {
void patcher(Handle handle, const u8* data, size_t data_size, u64 addr, u64 base_addr, std::span<Patterns> patterns) {
for (auto& p : patterns) {
// skip if disabled (controller by config.ini)
if (p.result == PatchResult::DISABLED) {
Expand Down Expand Up @@ -368,22 +374,25 @@ void patcher(Handle handle, const u8* data, size_t data_size, u64 addr, std::spa
const auto inst_offset = i + p.inst_offset;
std::memcpy(&inst, data + inst_offset, sizeof(inst));

// check if the instruction is the one that we want
if (p.cond(inst)) {
const auto patch_offset = addr + inst_offset + p.patch_offset;
const auto logged_offset = base_addr && patch_offset >= base_addr ? patch_offset - base_addr : patch_offset;

// prefer detecting an already-present patch before deciding to write one
if (p.applied(data + inst_offset + p.patch_offset, inst)) {
// patch already applied by sigpatches / IPS
p.result = PatchResult::PATCHED_FILE;
p.logged_offset = logged_offset;
break;
} else if (p.cond(inst)) {
const auto patch_data = 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_data.size))) {
p.result = PatchResult::FAILED_WRITE;
} else {
p.result = PatchResult::PATCHED_SYSPATCH;
}
// move onto next pattern
break;
} else if (p.applied(data + inst_offset + p.patch_offset, inst)) {
// patch already applied by sigpatches
p.result = PatchResult::PATCHED_FILE;
p.logged_offset = logged_offset;
break;
}
}
Expand Down Expand Up @@ -422,8 +431,35 @@ auto apply_patch(PatchEntry& patch) -> bool {
patch.title_id == event_info.info.create_process.program_id) {
MemoryInfo mem_info{};
u64 addr{};
u64 base_addr{};
u64 base_size{};
u32 page_info{};

// Log offsets relative to the main module rather than the first executable region.
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;
}

if (mem_info.size > base_size) {
base_addr = mem_info.addr;
base_size = mem_info.size;
}
}

addr = 0;
std::memset(buffer, 0, sizeof(buffer));

for (;;) {
if (R_FAILED(svcQueryDebugProcessMemory(&mem_info, &page_info, handle, addr))) {
break;
Expand All @@ -444,7 +480,7 @@ auto apply_patch(PatchEntry& patch) -> bool {
if (R_FAILED(svcReadDebugProcessMemory(buffer + overlap_size, handle, mem_info.addr + sz, actual_size))) {
break;
} else {
patcher(handle, buffer, actual_size + overlap_size, mem_info.addr + sz - overlap_size, patch.patterns);
patcher(handle, buffer, actual_size + overlap_size, mem_info.addr + sz - overlap_size, base_addr, patch.patterns);
if (actual_size >= overlap_size) {
memcpy(buffer, buffer + READ_BUFFER_SIZE, overlap_size);
std::memset(buffer + overlap_size, 0, READ_BUFFER_SIZE);
Expand Down Expand Up @@ -506,6 +542,39 @@ auto patch_result_to_str(PatchResult result) -> const char* {
std::unreachable();
}

void offset_to_str(char* s, u64 offset) {
*s++ = '0';
*s++ = 'x';

bool wrote_digit = false;
for (int shift = 60; shift >= 0; shift -= 4) {
const auto nibble = static_cast<u8>((offset >> shift) & 0xF);
if (!wrote_digit && nibble == 0 && shift != 0) {
continue;
}

wrote_digit = true;
*s++ = nibble < 10 ? static_cast<char>('0' + nibble) : static_cast<char>('A' + nibble - 10);
}
}

void patch_result_to_log_str(char* s, PatchResult result, u64 offset) {
const auto* result_str = patch_result_to_str(result);
while (*result_str != '\0') {
*s++ = *result_str++;
}

if (offset != 0) {
*s++ = ' ';
*s++ = '(';
offset_to_str(s, offset);
while (*s != '\0') {
s++;
}
*s++ = ')';
}
}

void num_2_str(char*& s, u16 num) {
u16 max_v = 1000;
if (num > 9) {
Expand Down Expand Up @@ -638,7 +707,9 @@ int main(int argc, char* argv[]) {
if (!enable_patching) {
p.result = PatchResult::SKIPPED;
}
ini_puts(patch.name, p.patch_name, patch_result_to_str(p.result), log_path);
char log_value[96]{};
patch_result_to_log_str(log_value, p.result, p.logged_offset);
ini_puts(patch.name, p.patch_name, log_value, log_path);
}
}

Expand Down