From 79aef18d1602d614df108f2473c7a46cf1530d8b Mon Sep 17 00:00:00 2001 From: illusiony <37698908+illusion0001@users.noreply.github.com> Date: Mon, 15 Dec 2025 11:54:48 +0000 Subject: [PATCH 1/6] Plugin loader: Upload `app_info` to plugins --- plugin_loader/source/lib.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugin_loader/source/lib.cpp b/plugin_loader/source/lib.cpp index c97defa..b6f4f4c 100644 --- a/plugin_loader/source/lib.cpp +++ b/plugin_loader/source/lib.cpp @@ -29,21 +29,21 @@ static bool simple_get_bool(const char* val) startsWithCase(val, "true"); } -static void load_module(const char* path, SceEntry* args) +static void load_module(const char* path, SceEntry* args, disk_appinfo* info) { const int m = sceKernelLoadStartModule(path, 0, 0, 0, 0, 0); final_printf("load res 0x%08x for %s\n", m, path); if (m > 0) { - int32_t (*load)(struct SceEntry*) = NULL; + int32_t (*load)(struct SceEntry*, disk_appinfo* info) = NULL; sceKernelDlsym(m, "plugin_load", (void**)&load); - int32_t (*unload)(struct SceEntry*) = NULL; + int32_t (*unload)(struct SceEntry*, disk_appinfo* info) = NULL; sceKernelDlsym(m, "plugin_unload", (void**)&unload); if (load) { - if (load(args) && unload) + if (load(args, info) && unload) { - unload(args); + unload(args, info); const int unload = sceKernelStopUnloadModule(m, 0, 0, 0, 0, 0); final_printf("Unload result 0x%08x\n", unload); } @@ -98,7 +98,7 @@ static void loadPlugins(SceEntry* args) { if (simple_get_bool(key->value)) { - load_module(key->key, args); + load_module(key->key, args, &info); } key = key->next; } From 10e6f678c8f9511b48d7badbe585cdc352ab6741 Mon Sep 17 00:00:00 2001 From: illusiony <37698908+illusion0001@users.noreply.github.com> Date: Mon, 15 Dec 2025 12:15:51 +0000 Subject: [PATCH 2/6] Game Patch Plugin Port --- .github/workflows/CI.yml | 6 + .gitmodules | 3 + plugin_game_patch/Makefile | 103 +++++++++ plugin_game_patch/extern/mxml | 1 + plugin_game_patch/source/lib.cpp | 304 +++++++++++++++++++++++++ plugin_game_patch/source/memory.c | 2 + plugin_game_patch/source/patch.cpp | 346 +++++++++++++++++++++++++++++ plugin_game_patch/source/patch.h | 12 + plugin_game_patch/source/utils.cpp | 202 +++++++++++++++++ plugin_game_patch/source/utils.h | 19 ++ 10 files changed, 998 insertions(+) create mode 100644 plugin_game_patch/Makefile create mode 160000 plugin_game_patch/extern/mxml create mode 100644 plugin_game_patch/source/lib.cpp create mode 100644 plugin_game_patch/source/memory.c create mode 100644 plugin_game_patch/source/patch.cpp create mode 100644 plugin_game_patch/source/patch.h create mode 100644 plugin_game_patch/source/utils.cpp create mode 100644 plugin_game_patch/source/utils.h diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ff616eb..27eb5e7 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -57,6 +57,12 @@ jobs: ls . echo "OO_PS4_TOOLCHAIN=$PWD/OpenOrbis/PS4Toolchain" >> $GITHUB_ENV + - name: Build mxml + working-directory: plugin_game_patch/extern/mxml/ps4 + run: | + wget https://raw.githubusercontent.com/bucanero/oosdk_libraries/master/build_rules.mk -O $OO_PS4_TOOLCHAIN/build_rules.mk + make install PRX_BUILD=1 + - name: Build all modules run: bash build.sh diff --git a/.gitmodules b/.gitmodules index e80b7b7..d71c83e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "extern/libjbc"] path = extern/libjbc url = https://github.com/illusion0001/ps4-libjbc.git +[submodule "plugin_game_patch/extern/mxml"] + path = plugin_game_patch/extern/mxml + url = https://github.com/illusion0001/mxml.git diff --git a/plugin_game_patch/Makefile b/plugin_game_patch/Makefile new file mode 100644 index 0000000..cd6e5b7 --- /dev/null +++ b/plugin_game_patch/Makefile @@ -0,0 +1,103 @@ +# Library metadata. + +DEBUG_FLAGS = -D__FINAL__=1 +LOG_TYPE = -D__USE_PRINTF__ + +ifeq ($(DEBUG),1) + DEBUG_FLAGS = -D__FINAL__=0 +endif + +TYPE := $(BUILD_TYPE) +FINAL := $(DEBUG_FLAGS) +BUILD_FOLDER := $(shell pwd)/../bin/plugins +OUTPUT_PRX := $(shell basename $(CURDIR)) +TARGET := $(BUILD_FOLDER)/prx$(TYPE)/$(OUTPUT_PRX) +TARGET_ELF := $(BUILD_FOLDER)/elf$(TYPE)/$(OUTPUT_PRX) +TARGETSTUB := $(OUTPUT_PRX).so +COMMON_DIR := ../common + +# Libraries linked into the ELF. +LIBS := -lSceLibcInternal -lkernel -lc -lmxml + +EXTRAFLAGS := -fvisibility=hidden -D__PS4__ $(DEBUG_FLAGS) $(LOG_TYPE) -fcolor-diagnostics -Wall + +# You likely won't need to touch anything below this point. +# Root vars +TOOLCHAIN := $(OO_PS4_TOOLCHAIN) +PROJDIR := ../$(shell basename $(CURDIR))/source +INTDIR := ../$(shell basename $(CURDIR))/build +INCLUDEDIR := ../$(shell basename $(CURDIR))/include + +# Define objects to build +CFILES := $(wildcard $(PROJDIR)/*.c) +CPPFILES := $(wildcard $(PROJDIR)/*.cpp) +COMMONFILES := $(wildcard $(COMMONDIR)/*.cpp) +OBJS := $(patsubst $(PROJDIR)/%.c, $(INTDIR)/%.o, $(CFILES)) $(patsubst $(PROJDIR)/%.cpp, $(INTDIR)/%.o, $(CPPFILES)) $(patsubst $(COMMONDIR)/%.cpp, $(INTDIR)/%.o, $(COMMONFILES)) +STUBOBJS := $(patsubst $(PROJDIR)/%.c, $(INTDIR)/%.o, $(CFILES)) $(patsubst $(PROJDIR)/%.cpp, $(INTDIR)/%.o.stub, $(CPPFILES)) $(patsubst $(COMMONDIR)/%.cpp, $(INTDIR)/%.o.stub, $(COMMONFILES)) + +# Define final C/C++ flags +CFLAGS := --target=x86_64-pc-freebsd12-elf -fPIC -funwind-tables -c $(EXTRAFLAGS) -isysroot $(TOOLCHAIN) -isystem $(TOOLCHAIN)/include +CXXFLAGS := $(CFLAGS) -isystem $(TOOLCHAIN)/include/c++/v1 +LDFLAGS := -m elf_x86_64 -pie --script $(TOOLCHAIN)/link.x -e _init --eh-frame-hdr -L$(TOOLCHAIN)/lib $(LIBS) $(TOOLCHAIN)/lib/crtlib.o + +# Check for linux vs macOS and account for clang/ld path +UNAME_S := $(shell uname -s) + +ifeq ($(UNAME_S),Linux) + CC := clang + CCX := clang++ + LD := ld.lld + CDIR := linux +endif +ifeq ($(UNAME_S),Darwin) + CC := /usr/local/opt/llvm/bin/clang + CCX := /usr/local/opt/llvm/bin/clang++ + LD := /usr/local/opt/llvm/bin/ld.lld + CDIR := macos +endif + +$(INTDIR): + mkdir -p $(INTDIR) + +$(BUILD_FOLDER)/prx$(TYPE): + mkdir -p $(BUILD_FOLDER)/prx$(TYPE) + +$(BUILD_FOLDER)/elf$(TYPE): + mkdir -p $(BUILD_FOLDER)/elf$(TYPE) + +$(TARGET): $(INTDIR) $(BUILD_FOLDER)/prx$(TYPE) $(BUILD_FOLDER)/elf$(TYPE) $(OBJS) + $(LD) $(INTDIR)/*.o -o $(TARGET_ELF).elf $(LDFLAGS) + $(TOOLCHAIN)/bin/$(CDIR)/create-fself -in=$(TARGET_ELF).elf -out=$(TARGET_ELF).oelf --lib=$(TARGET).prx --paid 0x3800000000000011 + +$(TARGETSTUB): $(INTDIR) $(STUBOBJS) + $(CC) $(INTDIR)/*.o.stub -o $(TARGETSTUB) -target x86_64-pc-linux-gnu -shared -fuse-ld=lld -ffreestanding -nostdlib -fno-builtin -L$(TOOLCHAIN)/lib $(LIBS) + +$(INTDIR)/%.o: $(PROJDIR)/%.c + $(CC) $(CFLAGS) -o $@ $< + +$(INTDIR)/%.o: $(PROJDIR)/%.cpp + $(CCX) $(CXXFLAGS) -o $@ $< + +$(INTDIR)/%.o.stub: $(PROJDIR)/%.c + $(CC) -target x86_64-pc-linux-gnu -ffreestanding -nostdlib -fno-builtin -fPIC $(O_FLAG) -s -c -o $@ $< + +$(INTDIR)/%.o.stub: $(PROJDIR)/%.cpp + $(CCX) -target x86_64-pc-linux-gnu -ffreestanding -nostdlib -fno-builtin -fPIC $(O_FLAG) -s -c -o $@ $< + +send: $(TARGET) + curl -T $(TARGET).prx ftp://$(PS4_HOST):2121/data/hen/plugins/$(OUTPUT_PRX).prx + +build-info: + $(shell echo "#define GIT_COMMIT \"$(shell git rev-parse HEAD)\"" > $(COMMON_DIR)/git_ver.h) + $(shell echo "#define GIT_VER \"$(shell git branch --show-current)\"" >> $(COMMON_DIR)/git_ver.h) + $(shell echo "#define GIT_NUM $(shell git rev-list HEAD --count)" >> $(COMMON_DIR)/git_ver.h) + $(shell echo "#define GIT_NUM_STR \"$(shell git rev-list HEAD --count)\"" >> $(COMMON_DIR)/git_ver.h) + $(shell echo "#define BUILD_DATE \"$(shell date '+%b %d %Y @ %T')\"" >> $(COMMON_DIR)/git_ver.h) + +.PHONY: clean +.DEFAULT_GOAL := all + +all: build-info $(TARGET) + +clean: + rm -rf $(TARGET) $(TARGETSTUB) $(INTDIR) $(OBJS) diff --git a/plugin_game_patch/extern/mxml b/plugin_game_patch/extern/mxml new file mode 160000 index 0000000..b29e022 --- /dev/null +++ b/plugin_game_patch/extern/mxml @@ -0,0 +1 @@ +Subproject commit b29e022bbe2673b9c61bee7e383eed46abecce50 diff --git a/plugin_game_patch/source/lib.cpp b/plugin_game_patch/source/lib.cpp new file mode 100644 index 0000000..d516fa2 --- /dev/null +++ b/plugin_game_patch/source/lib.cpp @@ -0,0 +1,304 @@ +#include +#include +#include "patch.h" +#include "utils.h" +#include "../../common/path.h" +#include "../../common/plugin_common.h" +#include "../../common/notify.h" + +#include "../../common/entry.h" +#include "../../plugin_shellcore/source/local_appinfo.h" + +#define HEN_PATH BASE_PATH +// Legacy path testing +// #define HEN_PATH "/data/GoldHEN" +#define BASE_PATH_PATCH HEN_PATH "/patches" +#define BASE_PATH_PATCH_SETTINGS BASE_PATH_PATCH "/settings" +#define BASE_PATH_PATCH_XML BASE_PATH_PATCH "/xml" +#define PLUGIN_NAME "game_patch" +#define PLUGIN_DESC "Patches game at boot" +#define PLUGIN_AUTH "illusion" +#define PLUGIN_VER 0x110 // 1.10 + +#define NO_ASLR_ADDR 0x00400000 + +attr_public const char* g_pluginName = PLUGIN_NAME; +attr_public const char* g_pluginDesc = PLUGIN_DESC; +attr_public const char* g_pluginAuth = PLUGIN_AUTH; +attr_public uint32_t g_pluginVersion = PLUGIN_VER; + +char g_titleid[16] = {0}; +char g_game_elf[MAX_PATH_] = {0}; +char g_game_prx[MAX_PATH_] = {0}; +char g_game_ver[8] = {0}; + +uint64_t g_module_base = 0; +uint32_t g_module_size = 0; +// unused for now +bool g_PRX = false; +uint64_t g_PRX_module_base = 0; +uint32_t g_PRX_module_size = 0; + +static const char* GetXMLAttr(mxml_node_t* node, const char* name) +{ + const char* AttrData = mxmlElementGetAttr(node, name); + if (AttrData == NULL) + { + AttrData = "\0"; + } + return AttrData; +} + +static void get_key_init(void) +{ + uint32_t patch_lines = 0; + uint32_t patch_items = 0; + char* patch_buffer = nullptr; + uint64_t patch_size = 0; + char input_file[MAX_PATH_] = {0}; + snprintf(input_file, sizeof(input_file), BASE_PATH_PATCH_XML "/%s.xml", g_titleid); + int32_t res = Read_File(input_file, &patch_buffer, &patch_size, 0); + + if (res < 0) + { + final_printf("failed to open %s(0x%08x), trying legacy path\n", input_file, res); + // try old goldhen path + memset(input_file, 0, sizeof(input_file)); + snprintf(input_file, sizeof(input_file), "/data/GoldHEN/patches/xml/%s.xml", g_titleid); + res = Read_File(input_file, &patch_buffer, &patch_size, 0); + } + if (res < 0) + { + final_printf("file %s not found\n", input_file); + final_printf("error: 0x%08x\n", res); + return; + } + final_printf("open success %s\n", input_file); + + if (patch_buffer && patch_size) + { + mxml_node_t *node, *tree = NULL; + tree = mxmlLoadString(NULL, patch_buffer, MXML_NO_CALLBACK); + + if (!tree) + { + final_printf("XML: could not parse XML:\n%s\n", patch_buffer); + free(patch_buffer); + return; + } + + for (node = mxmlFindElement(tree, tree, "Metadata", NULL, NULL, MXML_DESCEND); node != NULL; + node = mxmlFindElement(node, tree, "Metadata", NULL, NULL, MXML_DESCEND)) + { + char* settings_buffer = nullptr; + uint64_t settings_size = 0; + bool PRX_patch = false; + const char* TitleData = GetXMLAttr(node, "Title"); + const char* NameData = GetXMLAttr(node, "Name"); + const char* AppVerData = GetXMLAttr(node, "AppVer"); + const char* AppElfData = GetXMLAttr(node, "AppElf"); + + debug_printf("Title: \"%s\"\n", TitleData); + debug_printf("Name: \"%s\"\n", NameData); + debug_printf("AppVer: \"%s\"\n", AppVerData); + debug_printf("AppElf: \"%s\"\n", AppElfData); + + uint64_t hashout = patch_hash_calc(TitleData, NameData, AppVerData, input_file, AppElfData); + char settings_path[MAX_PATH_] = {0}; + snprintf(settings_path, sizeof(settings_path), BASE_PATH_PATCH_SETTINGS "/0x%016lx.txt", hashout); + sceKernelChmod(settings_path, 0777); + int32_t res = Read_File(settings_path, &settings_buffer, &settings_size, 0); + final_printf("settings_path: %s, 0x%08x\n", settings_path, res); + if (res == ORBIS_KERNEL_ERROR_ENOENT) + { + debug_printf("file %s not found, initializing false. ret: 0x%08x\n", settings_path, res); + static const uint8_t false_data[] = {'0', '\n'}; + Write_File(settings_path, false_data, sizeof(false_data)); + continue; + } + if (!settings_buffer || !settings_size) + { + final_printf("Settings 0x%016lx has no data!\n", hashout); + final_printf("File size %li bytes\n", settings_size); + continue; + } + if (settings_buffer[0] == '1' && !strcmp(g_game_elf, AppElfData)) + { + int32_t ret_cmp = strcmp(g_game_ver, AppVerData); + if (!ret_cmp) + { + final_printf("App ver %s == %s\n", g_game_ver, AppVerData); + } + else if (startsWith(AppVerData, "mask") || startsWith(AppVerData, "all")) + { + final_printf("App ver masked: %s\n", AppVerData); + } + else if (ret_cmp) + { + final_printf("App ver %s != %s\n", g_game_ver, AppVerData); + final_printf("Skipping patch entry\n"); + continue; + } + patch_items++; + mxml_node_t* Patchlist_node = mxmlFindElement(node, node, "PatchList", NULL, NULL, MXML_DESCEND); + for (mxml_node_t* Line_node = mxmlFindElement(node, node, "Line", NULL, NULL, MXML_DESCEND); Line_node != NULL; + Line_node = mxmlFindElement(Line_node, Patchlist_node, "Line", NULL, NULL, MXML_DESCEND)) + { + uint64_t addr_real = 0; + uint64_t jump_addr = 0; + uint32_t jump_size = 0; + bool use_mask = false; + const char* gameType = GetXMLAttr(Line_node, "Type"); + const char* gameAddr = GetXMLAttr(Line_node, "Address"); + const char* gameValue = GetXMLAttr(Line_node, "Value"); + const char* gameOffset = nullptr; + // starts with `mask` + if (startsWith(gameType, "mask")) + { + use_mask = true; + } + if (use_mask) + { + if (startsWith(gameType, "mask_jump32")) + { + const char* gameJumpTarget = GetXMLAttr(Line_node, "Target"); + const char* gameJumpSize = GetXMLAttr(Line_node, "Size"); + jump_addr = addr_real = (uint64_t)PatternScan(g_module_base, g_module_size, gameJumpTarget); + jump_size = strtoul(gameJumpSize, NULL, 10); + debug_printf("Target: 0x%lx jump size %u\n", jump_addr, jump_size); + } + gameOffset = GetXMLAttr(Line_node, "Offset"); + addr_real = (uint64_t)PatternScan(g_module_base, g_module_size, gameAddr); + if (!addr_real) + { + final_printf("Masked Address: %s not found\n", gameAddr); + continue; + } + final_printf("Masked Address: 0x%lx\n", addr_real); + debug_printf("Offset: %s\n", gameOffset); + uint32_t real_offset = 0; + if (gameOffset[0] != '0') + { + if (gameOffset[0] == '-') + { + debug_printf("Offset mode: subtract\n"); + real_offset = strtoul(gameOffset + 1, NULL, 10); + debug_printf("before offset: 0x%lx\n", addr_real); + addr_real = addr_real - real_offset; + debug_printf("after offset: 0x%lx\n", addr_real); + } + else if (gameOffset[0] == '+') + { + debug_printf("Offset mode: addition\n"); + real_offset = strtoul(gameOffset + 1, NULL, 10); + debug_printf("before offset: 0x%lx\n", addr_real); + addr_real = addr_real + real_offset; + debug_printf("after offset: 0x%lx\n", addr_real); + } + } + else + { + debug_printf("Mask does not reqiure offsetting.\n"); + } + } + debug_printf("Type: \"%s\"\n", gameType); + if (gameAddr && !use_mask) + { + addr_real = strtoull(gameAddr, NULL, 16); + debug_printf("Address: 0x%lx\n", addr_real); + } + debug_printf("Value: \"%s\"\n", gameValue); + debug_printf("patch line: %u\n", patch_lines); + if (gameType && addr_real && *gameValue != '\0') // type, address and value must be present + { + if (!PRX_patch && !use_mask) + { + // previous self, eboot patches were made with no aslr addresses + addr_real = g_module_base + (addr_real - NO_ASLR_ADDR); + } + else if (PRX_patch && !use_mask) + { + addr_real = g_module_base + addr_real; + } + patch_data1(gameType, addr_real, gameValue, jump_size, jump_addr); + patch_lines++; + } + } + } + if (settings_buffer) + { + free(settings_buffer); + } + } + + mxmlDelete(node); + mxmlDelete(tree); + free(patch_buffer); + + if (patch_items > 0 && patch_lines > 0) + { + char msg[128] = {0}; + snprintf(msg, sizeof(msg), + "%u %s Applied\n" + "%u %s Applied", + patch_items, + (patch_items == 1) ? "Patch" : "Patches", + patch_lines, + (patch_lines == 1) ? "Patch Line" : "Patch Lines"); + Notify("%s", msg); + } + } + else // if (!patch_buffer && !patch_size) + { + char msg[128] = {0}; + snprintf(msg, sizeof(msg), "File %s\nis empty", input_file); + Notify("%s", msg); + } +} + +static void mkdir_chmod(const char* path, OrbisKernelMode mode) +{ + sceKernelMkdir(path, mode); + sceKernelChmod(path, mode); +} + +static void make_folders(void) +{ + mkdir_chmod(HEN_PATH, 0777); + mkdir_chmod(BASE_PATH_PATCH, 0777); + mkdir_chmod(BASE_PATH_PATCH_XML, 0777); + mkdir_chmod(BASE_PATH_PATCH_SETTINGS, 0777); +} + +extern "C" +{ +int32_t attr_public plugin_load(SceEntry* e, disk_appinfo* info) +{ + struct OrbisKernelModuleInfo info2 = {0}; + info2.size = sizeof(info2); + const int r = sceKernelGetModuleInfo(0, &info2); + printf("sceKernelGetModuleInfoEx 0x%08x\n", r); + if (r == 0) + { + make_folders(); + strncpy(g_titleid, info->m_titleid, sizeof(info->m_titleid)); + strncpy(g_game_elf, info2.name, sizeof(info2.name)); + strncpy(g_game_ver, info->m_appver, sizeof(info->m_appver)); + printf("g_titleid: %s\n", g_titleid); + printf("g_game_elf: %s\n", g_game_elf); + printf("g_game_ver: %s\n", g_game_ver); + g_module_base = (uint64_t)info2.segmentInfo[0].address; + printf("g_module_base 0x%lx\n", g_module_base); + get_key_init(); + return 0; + } + + return 1; +} + +int32_t attr_public plugin_unload(SceEntry* e) +{ + final_printf("<%s\\Ver.0x%08x> %s\n", g_pluginName, g_pluginVersion, __func__); + return 0; +} +} diff --git a/plugin_game_patch/source/memory.c b/plugin_game_patch/source/memory.c new file mode 100644 index 0000000..b9d930c --- /dev/null +++ b/plugin_game_patch/source/memory.c @@ -0,0 +1,2 @@ +#include "../../common/syscall.c" +#include "../../common/memory.c" diff --git a/plugin_game_patch/source/patch.cpp b/plugin_game_patch/source/patch.cpp new file mode 100644 index 0000000..439b12e --- /dev/null +++ b/plugin_game_patch/source/patch.cpp @@ -0,0 +1,346 @@ +#include +#include +#include +#include +#include + +extern "C" +{ +#include "../../common/memory.h" +} +#include "../../common/plugin_common.h" + +char* unescape(const char* s) +{ + size_t len = strlen(s); + char* unescaped_str = (char*)malloc(len + 1); + if (!unescaped_str) + { + return nullptr; + } + uint32_t i, j; + for (i = 0, j = 0; s[i] != '\0'; i++, j++) + { + if (s[i] == '\\') + { + i++; + switch (s[i]) + { + case 'n': + unescaped_str[j] = '\n'; + break; + case '0': + unescaped_str[j] = '\0'; + break; + case 't': + unescaped_str[j] = '\t'; + break; + case 'r': + unescaped_str[j] = '\r'; + break; + case '\\': + unescaped_str[j] = '\\'; + break; + case 'x': + { + char hex_string[3] = {0}; + uint32_t val = 0; + hex_string[0] = s[++i]; + hex_string[1] = s[++i]; + hex_string[2] = '\0'; + if (sscanf(hex_string, "%x", &val) != 1) + { + final_printf("Invalid hex escape sequence: %s\n", hex_string); + val = '?'; + } + unescaped_str[j] = (char)val; + } + break; + default: + unescaped_str[j] = s[i]; + break; + } + } + else + { + unescaped_str[j] = s[i]; + } + } + unescaped_str[j] = '\0'; + return unescaped_str; +} + +// valid hex look up table. +const uint8_t hex_lut[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +uint8_t* hexstrtochar2(const char* hexstr, size_t* size) +{ + uint32_t str_len = strlen(hexstr); + size_t data_len = ((str_len + 1) / 2) * sizeof(uint8_t); + *size = (str_len) * sizeof(uint8_t); + if (!*size) + { + return nullptr; + } + uint8_t* data = (uint8_t*)malloc(*size); + if (!data) + { + return nullptr; + } + uint32_t j = 0; // hexstr position + uint32_t i = 0; // data position + + if (str_len % 2 == 1) + { + data[i] = (uint8_t)(hex_lut[0] << 4) | hex_lut[(uint8_t)hexstr[j]]; + j = ++i; + } + + for (; j < str_len; j += 2, i++) + { + data[i] = (uint8_t)(hex_lut[(uint8_t)hexstr[j]] << 4) | + hex_lut[(uint8_t)hexstr[j + 1]]; + } + + *size = data_len; + return data; +} + +bool hex_prefix(const char* str) +{ + return (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')); +} + +// http://www.cse.yorku.ca/~oz/hash.html +constexpr uint64_t djb2_hash(const char* str) +{ + uint64_t hash = 5381; + uint32_t c = 0; + while ((c = *str++)) + { + hash = hash * 33 ^ c; + } + return hash; +} + +static void sys_proc_rw_l(const uint64_t addr, const void* data, const size_t len) +{ + static int pid = 0; + if (!pid) + { + pid = getpid(); + } + final_printf("addr 0x%lx\n", addr); + sys_proc_rw(pid, addr, data, len, 1); +} + +uint64_t patch_hash_calc(const char* title, const char* name, const char* app_ver, const char* title_id, const char* elf) +{ + uint64_t output_hash = 0; + char hash_str[256] = {0}; + snprintf(hash_str, sizeof(hash_str), "%s%s%s%s%s", title, name, app_ver, title_id, elf); + output_hash = djb2_hash(hash_str); + debug_printf("input: \"%s\"\n", hash_str); + debug_printf("output: 0x%016lx\n", output_hash); + return output_hash; +} + +void patch_data1(const char* patch_type_str, uint64_t addr, const char* value, uint32_t source_size, uint64_t jump_target) +{ + uint64_t patch_type = djb2_hash(patch_type_str); + switch (patch_type) + { + case djb2_hash("byte"): + case djb2_hash("mask_byte"): + { + uint8_t real_value = 0; + if (hex_prefix(value)) + { + real_value = strtol(value, NULL, 16); + } + else + { + real_value = strtol(value, NULL, 10); + } + sys_proc_rw_l(addr, &real_value, sizeof(real_value)); + break; + } + case djb2_hash("bytes16"): + case djb2_hash("mask_bytes16"): + { + uint16_t real_value = 0; + if (hex_prefix(value)) + { + real_value = strtol(value, NULL, 16); + } + else + { + real_value = strtol(value, NULL, 10); + } + sys_proc_rw_l(addr, &real_value, sizeof(real_value)); + break; + } + case djb2_hash("bytes32"): + case djb2_hash("mask_bytes32"): + { + uint32_t real_value = 0; + if (hex_prefix(value)) + { + real_value = strtol(value, NULL, 16); + } + else + { + real_value = strtol(value, NULL, 10); + } + sys_proc_rw_l(addr, &real_value, sizeof(real_value)); + break; + } + case djb2_hash("bytesize_t"): + case djb2_hash("mask_bytesize_t"): + { + size_t real_value = 0; + if (hex_prefix(value)) + { + real_value = strtoll(value, NULL, 16); + } + else + { + real_value = strtoll(value, NULL, 10); + } + sys_proc_rw_l(addr, &real_value, sizeof(real_value)); + break; + } + case djb2_hash("bytes"): + case djb2_hash("mask"): + case djb2_hash("mask_bytes"): + { + size_t bytearray_size = 0; + uint8_t* bytearray = hexstrtochar2(value, &bytearray_size); + if (!bytearray) + { + break; + } + sys_proc_rw_l(addr, bytearray, bytearray_size); + free(bytearray); + break; + } + case djb2_hash("float32"): + case djb2_hash("mask_float32"): + { + float real_value = 0; + real_value = strtod(value, NULL); + sys_proc_rw_l(addr, &real_value, sizeof(real_value)); + break; + } + case djb2_hash("float64"): + case djb2_hash("mask_float64"): + { + double real_value = 0; + real_value = strtod(value, NULL); + sys_proc_rw_l(addr, &real_value, sizeof(real_value)); + break; + } + case djb2_hash("utf8"): + case djb2_hash("mask_utf8"): + { + char* new_str = unescape(value); + if (!new_str) + { + break; + } + uint64_t char_len = strlen(new_str); + sys_proc_rw_l(addr, (void*)new_str, char_len + 1); // get null + free(new_str); + break; + } + case djb2_hash("utf16"): + case djb2_hash("mask_utf16"): + { + char* new_str = unescape(value); + if (!new_str) + { + break; + } + for (uint32_t i = 0; new_str[i] != '\x00'; i++) + { + uint8_t val_ = new_str[i]; + uint8_t value_[2] = {val_, 0x00}; + sys_proc_rw_l(addr, value_, sizeof(value_)); + addr = addr + 2; + } + uint8_t value_[2] = {0x00, 0x00}; + sys_proc_rw_l(addr, value_, sizeof(value_)); + free(new_str); + break; + } + case djb2_hash("mask_jump32"): + { + constexpr uint32_t MAX_PATTERN_LENGTH = 256; + if (source_size < 5) + { + final_printf("Can't create code cave with size less than 32 bit jump!\n"); + break; + } + if (source_size > MAX_PATTERN_LENGTH) + { + final_printf("Can't create code cave with size more than %u bytes!\n", MAX_PATTERN_LENGTH); + break; + } + uint8_t nop_bytes[MAX_PATTERN_LENGTH]; + memset(nop_bytes, 0x90, sizeof(nop_bytes)); + sys_proc_rw_l(addr, nop_bytes, source_size); + size_t bytearray_size = 0; + uint8_t* bytearray = hexstrtochar2(value, &bytearray_size); + if (!bytearray) + { + break; + } + uint64_t code_cave_end = jump_target + bytearray_size; + uint8_t jump_32[5] = {0xe9, 0x00, 0x00, 0x00, 0x00}; + int32_t target_jmp = (int32_t)(jump_target - addr - sizeof(jump_32)); + int32_t target_return = (int32_t)(addr) - (code_cave_end); + sys_proc_rw_l(jump_target, bytearray, bytearray_size); + sys_proc_rw_l(addr, jump_32, sizeof(jump_32)); + sys_proc_rw_l(addr + 1, &target_jmp, sizeof(target_jmp)); + sys_proc_rw_l(jump_target + bytearray_size, jump_32, sizeof(jump_32)); + sys_proc_rw_l(code_cave_end + 1, &target_return, sizeof(target_return)); + free(bytearray); + break; + } + case djb2_hash("patchCall"): + case djb2_hash("mask_patchCall"): + { + uint8_t call_bytes[5] = {0}; + memcpy(call_bytes, (void*)addr, sizeof(call_bytes)); + if (call_bytes[0] == 0xe8 || call_bytes[0] == 0xe9) + { + int32_t branch_target = *(int32_t*)(call_bytes + 1); + if (branch_target) + { + uintptr_t branched_call = addr + branch_target + sizeof(call_bytes); + final_printf("0x%016lx: 0x%08x -> 0x%016lx\n", addr, branch_target, branched_call); + size_t bytearray_size = 0; + uint8_t* bytearray = hexstrtochar2(value, &bytearray_size); + if (!bytearray) + { + break; + } + sys_proc_rw_l(branched_call, bytearray, bytearray_size); + free(bytearray); + } + } + break; + } + default: + { + final_printf("Patch type: '%s (#%.16lx) not found or unsupported\n", patch_type_str, patch_type); + final_printf("Patch data:\n"); + final_printf(" Address: 0x%lx\n", addr); + final_printf(" Value: %s\n", value); + final_printf(" Jump Size: %u\n", source_size); + final_printf(" Jump Target: 0x%lx\n", jump_target); + break; + } + } +} diff --git a/plugin_game_patch/source/patch.h b/plugin_game_patch/source/patch.h new file mode 100644 index 0000000..1fab7c9 --- /dev/null +++ b/plugin_game_patch/source/patch.h @@ -0,0 +1,12 @@ +#include +#include + +unsigned char* hexstrtochar2(const char* hexstr, size_t* size); +void sys_proc_rw(uint64_t address, void* data, uint64_t length); +bool hex_prefix(const char* str); + +// http://www.cse.yorku.ca/~oz/hash.html +constexpr inline uint64_t djb2_hash(const char* str); + +uint64_t patch_hash_calc(const char* title, const char* name, const char* app_ver, const char* title_id, const char* elf); +void patch_data1(const char* patch_type_str, uint64_t addr, const char* value, uint32_t source_size, uint64_t jump_target); diff --git a/plugin_game_patch/source/utils.cpp b/plugin_game_patch/source/utils.cpp new file mode 100644 index 0000000..9a31871 --- /dev/null +++ b/plugin_game_patch/source/utils.cpp @@ -0,0 +1,202 @@ +#include +#include +#include +#include "utils.h" +#include "../../common/plugin_common.h" + +int32_t Read_File(const char* input_file, char** file_data, uint64_t* filesize, uint32_t extra) +{ + int32_t res = 0; + int32_t fd = 0; + + debug_printf("Reading input_file \"%s\"\n", input_file); + + fd = sceKernelOpen(input_file, 0, 0777); + if (fd < 0) + { + debug_printf("sceKernelOpen() 0x%08x\n", fd); + res = fd; + goto term; + } + + *filesize = sceKernelLseek(fd, 0, SEEK_END); + if (*filesize == 0) + { + debug_printf("ERROR: input_file is empty %i\n", res); + res = -1; + goto term; + } + + res = sceKernelLseek(fd, 0, SEEK_SET); + if (res < 0) + { + debug_printf("sceKernelLseek() 0x%08x\n", res); + goto term; + } + + *file_data = (char*)malloc(*filesize + extra); + if (*file_data == NULL) + { + debug_printf("ERROR: malloc()\n"); + goto term; + } + + res = sceKernelRead(fd, *file_data, *filesize); + if (res < 0) + { + debug_printf("sceKernelRead() 0x%08x\n", res); + goto term; + } + + res = sceKernelClose(fd); + + if (res < 0) + { + debug_printf("ERROR: sceKernelClose() 0x%08x\n", res); + goto term; + } + + debug_printf("input_file %s has been read - Res: %d - filesize: %jd\n", input_file, res, *filesize); + + return res; + +term: + + if (fd != 0) + { + sceKernelClose(fd); + } + + return res; +} + +int32_t Write_File(const char* input_file, const void* file_data, uint64_t filesize) +{ + int32_t fd = 0; + size_t size_written = 0; + fd = sceKernelOpen(input_file, 0x200 | 0x002, 0777); + if (fd < 0) + { + debug_printf("Failed to make file \"%s\"\n", input_file); + return 0; + } + debug_printf("Writing input_file \"%s\" %li\n", input_file, filesize); + size_written = sceKernelWrite(fd, file_data, filesize); + final_printf("Written input_file \"%s\" %li\n", input_file, size_written); + sceKernelClose(fd); + return 1; +} + +// https://github.com/bucanero/apollo-ps4/blob/a530cae3c81639eedebac606c67322acd6fa8965/source/orbis_jbc.c#L62 +int32_t get_module_info(OrbisKernelModuleInfo moduleInfo, const char* name, uint64_t* base, uint32_t* size) +{ + OrbisKernelModule handles[256]; + size_t numModules; + int32_t ret = 0; + ret = sceKernelGetModuleList(handles, sizeof(handles), &numModules); + if (ret) + { + final_printf("sceKernelGetModuleList (0x%08x)\n", ret); + return ret; + } + final_printf("numModules: %li\n", numModules); + for (size_t i = 0; i < numModules; ++i) + { + ret = sceKernelGetModuleInfo(handles[i], &moduleInfo); + final_printf("ret 0x%x\n", ret); + final_printf("module %li\n", i); + final_printf("name: %s\n", moduleInfo.name); + final_printf("start: 0x%lx\n", (uint64_t)moduleInfo.segmentInfo[0].address); + final_printf("size: %u (0x%08x) bytes\n", moduleInfo.segmentInfo[0].size, moduleInfo.segmentInfo[0].size); + if (ret) + { + final_printf("sceKernelGetModuleInfo (%X)\n", ret); + return ret; + } + + if (strcmp(moduleInfo.name, name) == 0 || name[0] == '0') + { + if (base) + { + *base = (uint64_t)moduleInfo.segmentInfo[0].address; + } + + if (size) + { + *size = moduleInfo.segmentInfo[0].size; + } + return 1; + } + } + return 0; +} + +uint32_t pattern_to_byte(const char* pattern, uint8_t* bytes) +{ + uint32_t count = 0; + const char* start = pattern; + const char* end = pattern + strlen(pattern); + + for (const char* current = start; current < end; ++current) + { + if (*current == '?') + { + ++current; + if (*current == '?') + { + ++current; + } + bytes[count++] = -1; + } + else + { + bytes[count++] = strtoul(current, (char**)¤t, 16); + } + } + return count; +} + +/* + * @brief Scan for a given byte pattern on a module + * + * @param module_base Base of the module to search + * @param module_size Size of the module to search + * @param signature IDA-style byte array pattern + * @credit https://github.com/OneshotGH/CSGOSimple-master/blob/59c1f2ec655b2fcd20a45881f66bbbc9cd0e562e/CSGOSimple/helpers/utils.cpp#L182 + * @returns Address of the first occurrence + */ +uint8_t* PatternScan(uint64_t module_base, uint32_t module_size, const char* signature) +{ + if (!module_base || !module_size) + { + return nullptr; + } + constexpr uint32_t MAX_PATTERN_LENGTH = 256; + uint8_t patternBytes[MAX_PATTERN_LENGTH] = {0}; + int32_t patternLength = pattern_to_byte(signature, patternBytes); + if (!patternLength || patternLength >= MAX_PATTERN_LENGTH) + { + final_printf("Pattern length too large or invalid! %i (0x%08x)\n", patternLength, patternLength); + final_printf("Input Pattern %s\n", signature); + return nullptr; + } + uint8_t* scanBytes = (uint8_t*)module_base; + + for (uint64_t i = 0; i < module_size; ++i) + { + bool found = true; + for (int32_t j = 0; j < patternLength; ++j) + { + if (scanBytes[i + j] != patternBytes[j] && patternBytes[j] != 0xff) + { + found = false; + break; + } + } + if (found) + { + return &scanBytes[i]; + } + } + return nullptr; +} diff --git a/plugin_game_patch/source/utils.h b/plugin_game_patch/source/utils.h new file mode 100644 index 0000000..b3a67c4 --- /dev/null +++ b/plugin_game_patch/source/utils.h @@ -0,0 +1,19 @@ +#include +#include + +int32_t Read_File(const char* input_file, char** file_data, uint64_t* filesize, uint32_t extra); +int32_t Write_File(const char* input_file, const void* file_data, uint64_t filesize); + +int32_t get_module_info(OrbisKernelModuleInfo moduleInfo, const char* name, uint64_t* base, uint32_t* size); +uint32_t pattern_to_byte(const char* pattern, uint8_t* bytes); + +/* + * @brief Scan for a given byte pattern on a module + * + * @param module_base Base of the module to search + * @param module_size Size of the module to search + * @param signature IDA-style byte array pattern + * @credit https://github.com/OneshotGH/CSGOSimple-master/blob/59c1f2ec655b2fcd20a45881f66bbbc9cd0e562e/CSGOSimple/helpers/utils.cpp#L182 + * @returns Address of the first occurrence + */ +uint8_t* PatternScan(uint64_t module_base, uint32_t module_size, const char* signature); From df7b9c05085083736e769c9e2002be549c659965 Mon Sep 17 00:00:00 2001 From: illusiony <37698908+illusion0001@users.noreply.github.com> Date: Mon, 15 Dec 2025 16:37:47 +0000 Subject: [PATCH 3/6] Build: Fix script continuing on failure --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 05a887f..91fa608 100644 --- a/build.sh +++ b/build.sh @@ -6,7 +6,7 @@ bash int3.sh 4096 > common/cave.inc.c for dr in plugin_*/ ; do if [ -d "$dr" ]; then echo "::group::Build $dr" - make -C "$dr" clean all + make -C "$dr" clean all || exit 1 echo "::endgroup::" fi done From 6ff75c1ed8f8a62001e9df03025308cbf5b05238 Mon Sep 17 00:00:00 2001 From: illusiony <37698908+illusion0001@users.noreply.github.com> Date: Tue, 16 Dec 2025 01:02:45 +0000 Subject: [PATCH 4/6] Game Patch: Support legacy paths --- plugin_game_patch/source/lib.cpp | 34 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/plugin_game_patch/source/lib.cpp b/plugin_game_patch/source/lib.cpp index d516fa2..ae5ba63 100644 --- a/plugin_game_patch/source/lib.cpp +++ b/plugin_game_patch/source/lib.cpp @@ -10,11 +10,15 @@ #include "../../plugin_shellcore/source/local_appinfo.h" #define HEN_PATH BASE_PATH -// Legacy path testing -// #define HEN_PATH "/data/GoldHEN" #define BASE_PATH_PATCH HEN_PATH "/patches" #define BASE_PATH_PATCH_SETTINGS BASE_PATH_PATCH "/settings" #define BASE_PATH_PATCH_XML BASE_PATH_PATCH "/xml" +// Legacy path +#define GOLDHEN_PATH "/data/GoldHEN" +#define GOLDHEN_PATCHES_PATH GOLDHEN_PATH "/patches" +#define GOLDHEN_PATCHES_SETTINGS_PATH GOLDHEN_PATCHES_PATH "/settings" +#define GOLDHEN_PATCHES_XML_PATH GOLDHEN_PATCHES_PATH "/xml" + #define PLUGIN_NAME "game_patch" #define PLUGIN_DESC "Patches game at boot" #define PLUGIN_AUTH "illusion" @@ -55,6 +59,7 @@ static void get_key_init(void) uint32_t patch_items = 0; char* patch_buffer = nullptr; uint64_t patch_size = 0; + bool is_goldhen = false; char input_file[MAX_PATH_] = {0}; snprintf(input_file, sizeof(input_file), BASE_PATH_PATCH_XML "/%s.xml", g_titleid); int32_t res = Read_File(input_file, &patch_buffer, &patch_size, 0); @@ -64,8 +69,10 @@ static void get_key_init(void) final_printf("failed to open %s(0x%08x), trying legacy path\n", input_file, res); // try old goldhen path memset(input_file, 0, sizeof(input_file)); - snprintf(input_file, sizeof(input_file), "/data/GoldHEN/patches/xml/%s.xml", g_titleid); + snprintf(input_file, sizeof(input_file), GOLDHEN_PATCHES_XML_PATH "/%s.xml", g_titleid); res = Read_File(input_file, &patch_buffer, &patch_size, 0); + is_goldhen = res == 0; + printf("res 0x%08x is_goldhen %s\n", res, is_goldhen ? "true" : "false"); } if (res < 0) { @@ -105,7 +112,7 @@ static void get_key_init(void) uint64_t hashout = patch_hash_calc(TitleData, NameData, AppVerData, input_file, AppElfData); char settings_path[MAX_PATH_] = {0}; - snprintf(settings_path, sizeof(settings_path), BASE_PATH_PATCH_SETTINGS "/0x%016lx.txt", hashout); + snprintf(settings_path, sizeof(settings_path), "%s/0x%016lx.txt", !is_goldhen ? BASE_PATH_PATCH_SETTINGS : GOLDHEN_PATCHES_SETTINGS_PATH, hashout); sceKernelChmod(settings_path, 0777); int32_t res = Read_File(settings_path, &settings_buffer, &settings_size, 0); final_printf("settings_path: %s, 0x%08x\n", settings_path, res); @@ -264,10 +271,21 @@ static void mkdir_chmod(const char* path, OrbisKernelMode mode) static void make_folders(void) { - mkdir_chmod(HEN_PATH, 0777); - mkdir_chmod(BASE_PATH_PATCH, 0777); - mkdir_chmod(BASE_PATH_PATCH_XML, 0777); - mkdir_chmod(BASE_PATH_PATCH_SETTINGS, 0777); + static const char* path_list[] = { + HEN_PATH, + BASE_PATH_PATCH, + BASE_PATH_PATCH_XML, + BASE_PATH_PATCH_SETTINGS, + GOLDHEN_PATH, + GOLDHEN_PATCHES_PATH, + GOLDHEN_PATCHES_SETTINGS_PATH, + GOLDHEN_PATCHES_XML_PATH, + }; + for (size_t i = 0; i < _countof(path_list); i++) + { + printf("create %s idx %ld\n", path_list[i], i); + mkdir_chmod(path_list[i], 0777); + } } extern "C" From 10bf4608b8306e4df7385023226afeb7de62dd99 Mon Sep 17 00:00:00 2001 From: illusiony <37698908+illusion0001@users.noreply.github.com> Date: Tue, 16 Dec 2025 02:12:35 +0000 Subject: [PATCH 5/6] Game Patch: Register mxml error callback --- plugin_game_patch/source/lib.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugin_game_patch/source/lib.cpp b/plugin_game_patch/source/lib.cpp index ae5ba63..0cd742b 100644 --- a/plugin_game_patch/source/lib.cpp +++ b/plugin_game_patch/source/lib.cpp @@ -53,6 +53,13 @@ static const char* GetXMLAttr(mxml_node_t* node, const char* name) return AttrData; } +static char input_file[MAX_PATH_] = {0}; + +static void my_error_xml(const char* m) +{ + Notify(TEX_ICON_SYSTEM, "Error in file\n%s\n%s", input_file, m); +} + static void get_key_init(void) { uint32_t patch_lines = 0; @@ -60,7 +67,7 @@ static void get_key_init(void) char* patch_buffer = nullptr; uint64_t patch_size = 0; bool is_goldhen = false; - char input_file[MAX_PATH_] = {0}; + memset(input_file, 0, sizeof(input_file)); snprintf(input_file, sizeof(input_file), BASE_PATH_PATCH_XML "/%s.xml", g_titleid); int32_t res = Read_File(input_file, &patch_buffer, &patch_size, 0); @@ -81,6 +88,7 @@ static void get_key_init(void) return; } final_printf("open success %s\n", input_file); + mxmlSetErrorCallback(my_error_xml); if (patch_buffer && patch_size) { @@ -89,7 +97,7 @@ static void get_key_init(void) if (!tree) { - final_printf("XML: could not parse XML:\n%s\n", patch_buffer); + final_printf("XML: could not parse XML `%s`\n", input_file); free(patch_buffer); return; } From d3ba65873bbdb9a5c02bcb7a0e5d2d0de6ad9228 Mon Sep 17 00:00:00 2001 From: illusiony <37698908+illusion0001@users.noreply.github.com> Date: Tue, 16 Dec 2025 02:27:29 +0000 Subject: [PATCH 6/6] Plugin Loader+Game Patch: Supply moduleID for future work --- plugin_game_patch/source/lib.cpp | 7 ++++--- plugin_loader/source/lib.cpp | 12 ++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugin_game_patch/source/lib.cpp b/plugin_game_patch/source/lib.cpp index 0cd742b..a034473 100644 --- a/plugin_game_patch/source/lib.cpp +++ b/plugin_game_patch/source/lib.cpp @@ -298,11 +298,12 @@ static void make_folders(void) extern "C" { -int32_t attr_public plugin_load(SceEntry* e, disk_appinfo* info) +int32_t attr_public plugin_load(SceEntry* e, disk_appinfo* info, int32_t moduleId) { + printf("moduleId 0x%x\n", moduleId); struct OrbisKernelModuleInfo info2 = {0}; info2.size = sizeof(info2); - const int r = sceKernelGetModuleInfo(0, &info2); + const int r = sceKernelGetModuleInfo(moduleId, &info2); printf("sceKernelGetModuleInfoEx 0x%08x\n", r); if (r == 0) { @@ -322,7 +323,7 @@ int32_t attr_public plugin_load(SceEntry* e, disk_appinfo* info) return 1; } -int32_t attr_public plugin_unload(SceEntry* e) +int32_t attr_public plugin_unload(SceEntry* e, disk_appinfo* info, int32_t moduleId) { final_printf("<%s\\Ver.0x%08x> %s\n", g_pluginName, g_pluginVersion, __func__); return 0; diff --git a/plugin_loader/source/lib.cpp b/plugin_loader/source/lib.cpp index b6f4f4c..5c34e17 100644 --- a/plugin_loader/source/lib.cpp +++ b/plugin_loader/source/lib.cpp @@ -29,21 +29,21 @@ static bool simple_get_bool(const char* val) startsWithCase(val, "true"); } -static void load_module(const char* path, SceEntry* args, disk_appinfo* info) +static void load_module(const char* path, SceEntry* args, disk_appinfo* info, int32_t moduleId) { const int m = sceKernelLoadStartModule(path, 0, 0, 0, 0, 0); final_printf("load res 0x%08x for %s\n", m, path); if (m > 0) { - int32_t (*load)(struct SceEntry*, disk_appinfo* info) = NULL; + int32_t (*load)(struct SceEntry*, disk_appinfo* info, int32_t) = NULL; sceKernelDlsym(m, "plugin_load", (void**)&load); - int32_t (*unload)(struct SceEntry*, disk_appinfo* info) = NULL; + int32_t (*unload)(struct SceEntry*, disk_appinfo* info, int32_t) = NULL; sceKernelDlsym(m, "plugin_unload", (void**)&unload); if (load) { - if (load(args, info) && unload) + if (load(args, info, moduleId) && unload) { - unload(args, info); + unload(args, info, moduleId); const int unload = sceKernelStopUnloadModule(m, 0, 0, 0, 0, 0); final_printf("Unload result 0x%08x\n", unload); } @@ -98,7 +98,7 @@ static void loadPlugins(SceEntry* args) { if (simple_get_bool(key->value)) { - load_module(key->key, args, &info); + load_module(key->key, args, &info, 0); } key = key->next; }