mirror of
https://github.com/wiiu-env/FunctionPatcherModule.git
synced 2025-01-05 06:41:50 +01:00
Rewrite of Function Patcher to match libfunctionpatcher
This commit is contained in:
parent
a6eb4eeb88
commit
f95f00e6f2
@ -1,7 +1,7 @@
|
||||
FROM wiiuenv/devkitppc:20211229
|
||||
FROM wiiuenv/devkitppc:20220507
|
||||
|
||||
COPY --from=wiiuenv/libkernel:20211031 /artifacts $DEVKITPRO
|
||||
COPY --from=wiiuenv/libfunctionpatcher:20210924 /artifacts $DEVKITPRO
|
||||
COPY --from=wiiuenv/wiiumodulesystem:20220123 /artifacts $DEVKITPRO
|
||||
COPY --from=wiiuenv/libfunctionpatcher:20220507 /artifacts $DEVKITPRO
|
||||
COPY --from=wiiuenv/wiiumodulesystem:20220507 /artifacts $DEVKITPRO
|
||||
|
||||
WORKDIR project
|
65
source/FunctionAddressProvider.cpp
Normal file
65
source/FunctionAddressProvider.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include "FunctionAddressProvider.h"
|
||||
#include "utils/logger.h"
|
||||
#include <coreinit/dynload.h>
|
||||
#include <function_patcher/fpatching_defines.h>
|
||||
|
||||
uint32_t FunctionAddressProvider::getEffectiveAddressOfFunction(function_replacement_library_type_t library, const char *functionName) {
|
||||
uint32_t real_addr = 0;
|
||||
OSDynLoad_Module rpl_handle = nullptr;
|
||||
OSDynLoad_Error err = OS_DYNLOAD_OK;
|
||||
int32_t rpl_handles_size = sizeof rpl_handles / sizeof rpl_handles[0];
|
||||
|
||||
for (int32_t i = 0; i < rpl_handles_size; i++) {
|
||||
if (rpl_handles[i].library == library) {
|
||||
if (rpl_handles[i].handle == nullptr) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Lets acquire handle for rpl: %s", rpl_handles[i].rplname);
|
||||
err = OSDynLoad_IsModuleLoaded((char *) rpl_handles[i].rplname, &rpl_handles[i].handle);
|
||||
}
|
||||
if (err != OS_DYNLOAD_OK || !rpl_handles[i].handle) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("%s is not loaded yet", rpl_handles[i].rplname, err, rpl_handles[i].handle);
|
||||
return 0;
|
||||
}
|
||||
rpl_handle = rpl_handles[i].handle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rpl_handle) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to find the RPL handle for %s", functionName);
|
||||
return 0;
|
||||
}
|
||||
|
||||
OSDynLoad_FindExport(rpl_handle, 0, functionName, reinterpret_cast<void **>(&real_addr));
|
||||
|
||||
if (!real_addr) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("OSDynLoad_FindExport failed for %s", functionName);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((library == LIBRARY_NN_ACP) && (uint32_t) (*(volatile uint32_t *) (real_addr) &0x48000002) == 0x48000000) {
|
||||
auto address_diff = (uint32_t) (*(volatile uint32_t *) (real_addr) &0x03FFFFFC);
|
||||
if ((address_diff & 0x03000000) == 0x03000000) {
|
||||
address_diff |= 0xFC000000;
|
||||
}
|
||||
real_addr += (int32_t) address_diff;
|
||||
if ((uint32_t) (*(volatile uint32_t *) (real_addr) &0x48000002) == 0x48000000) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Error");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return real_addr;
|
||||
}
|
||||
|
||||
void FunctionAddressProvider::resetHandles() {
|
||||
int32_t rpl_handles_size = sizeof rpl_handles / sizeof rpl_handles[0];
|
||||
|
||||
for (int32_t i = 0; i < rpl_handles_size; i++) {
|
||||
if (rpl_handles[i].handle != nullptr) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Resetting handle for rpl: %s", rpl_handles[i].rplname);
|
||||
OSDynLoad_Release(rpl_handles[i].handle);
|
||||
}
|
||||
|
||||
rpl_handles[i].handle = nullptr;
|
||||
}
|
||||
}
|
85
source/FunctionAddressProvider.h
Normal file
85
source/FunctionAddressProvider.h
Normal file
@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include <coreinit/dynload.h>
|
||||
#include <cstdint>
|
||||
#include <function_patcher/fpatching_defines.h>
|
||||
|
||||
typedef struct rpl_handling {
|
||||
function_replacement_library_type_t library;
|
||||
const char rplname[15];
|
||||
OSDynLoad_Module handle;
|
||||
} rpl_handling;
|
||||
|
||||
class FunctionAddressProvider {
|
||||
public:
|
||||
uint32_t getEffectiveAddressOfFunction(function_replacement_library_type_t library, const char *functionName);
|
||||
void resetHandles();
|
||||
|
||||
rpl_handling rpl_handles[LIBRARY_OTHER] = {
|
||||
{LIBRARY_AVM, "avm.rpl", nullptr},
|
||||
{LIBRARY_CAMERA, "camera.rpl", nullptr},
|
||||
{LIBRARY_COREINIT, "coreinit.rpl", nullptr},
|
||||
{LIBRARY_DC, "dc.rpl", nullptr},
|
||||
{LIBRARY_DMAE, "dmae.rpl", nullptr},
|
||||
{LIBRARY_DRMAPP, "drmapp.rpl", nullptr},
|
||||
{LIBRARY_ERREULA, "erreula.rpl", nullptr},
|
||||
{LIBRARY_GX2, "gx2.rpl", nullptr},
|
||||
{LIBRARY_H264, "h264.rpl", nullptr},
|
||||
{LIBRARY_LZMA920, "lzma920.rpl", nullptr},
|
||||
{LIBRARY_MIC, "mic.rpl", nullptr},
|
||||
{LIBRARY_NFC, "nfc.rpl", nullptr},
|
||||
{LIBRARY_NIO_PROF, "nio_prof.rpl", nullptr},
|
||||
{LIBRARY_NLIBCURL, "nlibcurl.rpl", nullptr},
|
||||
{LIBRARY_NLIBNSS, "nlibnss.rpl", nullptr},
|
||||
{LIBRARY_NLIBNSS2, "nlibnss2.rpl", nullptr},
|
||||
{LIBRARY_NN_AC, "nn_ac.rpl", nullptr},
|
||||
{LIBRARY_NN_ACP, "nn_acp.rpl", nullptr},
|
||||
{LIBRARY_NN_ACT, "nn_act.rpl", nullptr},
|
||||
{LIBRARY_NN_AOC, "nn_aoc.rpl", nullptr},
|
||||
{LIBRARY_NN_BOSS, "nn_boss.rpl", nullptr},
|
||||
{LIBRARY_NN_CCR, "nn_ccr.rpl", nullptr},
|
||||
{LIBRARY_NN_CMPT, "nn_cmpt.rpl", nullptr},
|
||||
{LIBRARY_NN_DLP, "nn_dlp.rpl", nullptr},
|
||||
{LIBRARY_NN_EC, "nn_ec.rpl", nullptr},
|
||||
{LIBRARY_NN_FP, "nn_fp.rpl", nullptr},
|
||||
{LIBRARY_NN_HAI, "nn_hai.rpl", nullptr},
|
||||
{LIBRARY_NN_HPAD, "nn_hpad.rpl", nullptr},
|
||||
{LIBRARY_NN_IDBE, "nn_idbe.rpl", nullptr},
|
||||
{LIBRARY_NN_NDM, "nn_ndm.rpl", nullptr},
|
||||
{LIBRARY_NN_NETS2, "nn_nets2.rpl", nullptr},
|
||||
{LIBRARY_NN_NFP, "nn_nfp.rpl", nullptr},
|
||||
{LIBRARY_NN_NIM, "nn_nim.rpl", nullptr},
|
||||
{LIBRARY_NN_OLV, "nn_olv.rpl", nullptr},
|
||||
{LIBRARY_NN_PDM, "nn_pdm.rpl", nullptr},
|
||||
{LIBRARY_NN_SAVE, "nn_save.rpl", nullptr},
|
||||
{LIBRARY_NN_SL, "nn_sl.rpl", nullptr},
|
||||
{LIBRARY_NN_SPM, "nn_spm.rpl", nullptr},
|
||||
{LIBRARY_NN_TEMP, "nn_temp.rpl", nullptr},
|
||||
{LIBRARY_NN_UDS, "nn_uds.rpl", nullptr},
|
||||
{LIBRARY_NN_VCTL, "nn_vctl.rpl", nullptr},
|
||||
{LIBRARY_NSYSCCR, "nsysccr.rpl", nullptr},
|
||||
{LIBRARY_NSYSHID, "nsyshid.rpl", nullptr},
|
||||
{LIBRARY_NSYSKBD, "nsyskbd.rpl", nullptr},
|
||||
{LIBRARY_NSYSNET, "nsysnet.rpl", nullptr},
|
||||
{LIBRARY_NSYSUHS, "nsysuhs.rpl", nullptr},
|
||||
{LIBRARY_NSYSUVD, "nsysuvd.rpl", nullptr},
|
||||
{LIBRARY_NTAG, "ntag.rpl", nullptr},
|
||||
{LIBRARY_PADSCORE, "padscore.rpl", nullptr},
|
||||
{LIBRARY_PROC_UI, "proc_ui.rpl", nullptr},
|
||||
{LIBRARY_SNDCORE2, "sndcore2.rpl", nullptr},
|
||||
{LIBRARY_SNDUSER2, "snduser2.rpl", nullptr},
|
||||
{LIBRARY_SND_CORE, "snd_core.rpl", nullptr},
|
||||
{LIBRARY_SND_USER, "snd_user.rpl", nullptr},
|
||||
{LIBRARY_SWKBD, "swkbd.rpl", nullptr},
|
||||
{LIBRARY_SYSAPP, "sysapp.rpl", nullptr},
|
||||
{LIBRARY_TCL, "tcl.rpl", nullptr},
|
||||
{LIBRARY_TVE, "tve.rpl", nullptr},
|
||||
{LIBRARY_UAC, "uac.rpl", nullptr},
|
||||
{LIBRARY_UAC_RPL, "uac_rpl.rpl", nullptr},
|
||||
{LIBRARY_USB_MIC, "usb_mic.rpl", nullptr},
|
||||
{LIBRARY_UVC, "uvc.rpl", nullptr},
|
||||
{LIBRARY_UVD, "uvd.rpl", nullptr},
|
||||
{LIBRARY_VPAD, "vpad.rpl", nullptr},
|
||||
{LIBRARY_VPADBASE, "vpadbase.rpl", nullptr},
|
||||
{LIBRARY_ZLIB125, "zlib125.rpl", nullptr}};
|
||||
};
|
194
source/PatchedFunctionData.cpp
Normal file
194
source/PatchedFunctionData.cpp
Normal file
@ -0,0 +1,194 @@
|
||||
#include "PatchedFunctionData.h"
|
||||
#include "utils/utils.h"
|
||||
|
||||
std::optional<std::shared_ptr<PatchedFunctionData>> PatchedFunctionData::make_shared(std::shared_ptr<FunctionAddressProvider> functionAddressProvider,
|
||||
function_replacement_data_t *replacementData,
|
||||
MEMHeapHandle heapHandle) {
|
||||
if (!replacementData) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto ptr = make_shared_nothrow<PatchedFunctionData>(std::move(functionAddressProvider));
|
||||
if (!ptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ptr->isPatched = false;
|
||||
ptr->heapHandle = heapHandle;
|
||||
ptr->library = replacementData->library;
|
||||
ptr->targetProcess = replacementData->targetProcess;
|
||||
ptr->replacementFunctionAddress = replacementData->replaceAddr;
|
||||
ptr->realCallFunctionAddressPtr = replacementData->replaceCall;
|
||||
|
||||
if (replacementData->library != LIBRARY_OTHER) {
|
||||
ptr->functionName = replacementData->function_name;
|
||||
} else {
|
||||
ptr->realEffectiveFunctionAddress = replacementData->virtualAddr;
|
||||
ptr->realPhysicalFunctionAddress = replacementData->physicalAddr;
|
||||
}
|
||||
|
||||
ptr->jumpToOriginal = (uint32_t *) MEMAllocFromExpHeapEx(ptr->heapHandle, 0x5 * sizeof(uint32_t), 4);
|
||||
|
||||
if (ptr->replacementFunctionAddress > 0x03FFFFFC || ptr->targetProcess != FP_TARGET_PROCESS_ALL) {
|
||||
ptr->jumpDataSize = 15; // We could predict the actual size and save some memory, but at the moment we don't need it.
|
||||
ptr->jumpData = (uint32_t *) MEMAllocFromExpHeapEx(ptr->heapHandle, ptr->jumpDataSize * sizeof(uint32_t), 4);
|
||||
|
||||
if (!ptr->jumpData) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to alloc jump data");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (!ptr->jumpToOriginal) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to alloc jump data");
|
||||
return {};
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool PatchedFunctionData::updateFunctionAddresses() {
|
||||
if (this->library == LIBRARY_OTHER) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this->functionName) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Function name was empty. This should never happen.");
|
||||
OSFatal("function name was empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto real_address = functionAddressProvider->getEffectiveAddressOfFunction(library, this->functionName->c_str());
|
||||
if (!real_address) {
|
||||
DEBUG_FUNCTION_LINE("OSDynLoad_FindExport failed for %s, updating address not possible.", this->functionName->c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
this->realEffectiveFunctionAddress = real_address;
|
||||
auto physicalFunctionAddress = (uint32_t) OSEffectiveToPhysical(real_address);
|
||||
if (!physicalFunctionAddress) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Error. Something is wrong with the physical address");
|
||||
return false;
|
||||
}
|
||||
this->realPhysicalFunctionAddress = physicalFunctionAddress;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PatchedFunctionData::generateJumpToOriginal() {
|
||||
if (!this->jumpToOriginal) {
|
||||
DEBUG_FUNCTION_LINE_ERR("this->jumpToOriginal is not allocated");
|
||||
OSFatal("this->jumpToOriginal is not allocated");
|
||||
}
|
||||
|
||||
uint32_t jumpToAddress = this->realEffectiveFunctionAddress + 4;
|
||||
|
||||
this->jumpToOriginal[0] = this->replacedInstruction;
|
||||
if (((uint32_t) jumpToAddress & 0x03FFFFFC) != (uint32_t) jumpToAddress) {
|
||||
// We need to do a long jump
|
||||
this->jumpToOriginal[1] = 0x3d600000 | ((jumpToAddress >> 16) & 0x0000FFFF); // lis r11 ,0x1234
|
||||
this->jumpToOriginal[2] = 0x616b0000 | (jumpToAddress & 0x0000ffff); // ori r11 ,r11 ,0x5678
|
||||
this->jumpToOriginal[3] = 0x7d6903a6; // mtspr CTR ,r11
|
||||
this->jumpToOriginal[4] = 0x4e800420; // bctr
|
||||
} else {
|
||||
this->jumpToOriginal[1] = 0x48000002 | (jumpToAddress & 0x03FFFFFC);
|
||||
}
|
||||
|
||||
DCFlushRange((void *) this->jumpToOriginal, sizeof(uint32_t) * 5);
|
||||
ICInvalidateRange((void *) this->jumpToOriginal, sizeof(uint32_t) * 5);
|
||||
|
||||
*(this->realCallFunctionAddressPtr) = (uint32_t) this->jumpToOriginal;
|
||||
OSMemoryBarrier();
|
||||
}
|
||||
|
||||
void PatchedFunctionData::generateReplacementJump() {
|
||||
//setting jump back
|
||||
this->replaceWithInstruction = 0x48000002 | (this->replacementFunctionAddress & 0x03FFFFFC);
|
||||
|
||||
// If the jump is too big or we want only patch for certain processes we need a trampoline
|
||||
if (this->replacementFunctionAddress > 0x03FFFFFC || this->targetProcess != FP_TARGET_PROCESS_ALL) {
|
||||
if (!this->jumpData) {
|
||||
DEBUG_FUNCTION_LINE_ERR("jumpData was not allocated");
|
||||
OSFatal("jumpData was not allocated");
|
||||
}
|
||||
uint32_t offset = 0;
|
||||
if (this->targetProcess != FP_TARGET_PROCESS_ALL) {
|
||||
auto originalFunctionAddrWithOffset = this->realEffectiveFunctionAddress + 4;
|
||||
bool shortBranchToOriginalPossible = ((uint32_t) originalFunctionAddrWithOffset & 0x03FFFFFC) == (uint32_t) originalFunctionAddrWithOffset;
|
||||
// Only use patched function if OSGetUPID matches function_data->targetProcess
|
||||
this->jumpData[offset++] = 0x3d600000 | (((uint32_t *) OSGetUPID)[0] & 0x0000FFFF); // lis r11 ,0x0
|
||||
this->jumpData[offset++] = 0x816b0000 | (((uint32_t *) OSGetUPID)[1] & 0x0000FFFF); // lwz r11 ,0x0(r11)
|
||||
if (this->targetProcess == FP_TARGET_PROCESS_GAME_AND_MENU) {
|
||||
this->jumpData[offset++] = 0x2c0b0000 | FP_TARGET_PROCESS_WII_U_MENU; // cmpwi r11 ,FP_TARGET_PROCESS_WII_U_MENU
|
||||
this->jumpData[offset++] = 0x41820000 | (shortBranchToOriginalPossible ? 0x00000014 : 0x00000020); // beq myfunc
|
||||
this->jumpData[offset++] = 0x2c0b0000 | FP_TARGET_PROCESS_GAME; // cmpwi r11 ,FP_TARGET_PROCESS_GAME
|
||||
this->jumpData[offset++] = 0x41820000 | (shortBranchToOriginalPossible ? 0x0000000C : 0x00000018); // beq myfunc
|
||||
} else {
|
||||
this->jumpData[offset++] = 0x2c0b0000 | this->targetProcess; // cmpwi r11 ,function_data->targetProcess
|
||||
this->jumpData[offset++] = 0x41820000 | (shortBranchToOriginalPossible ? 0x0000000C : 0x00000018); // beq myfunc
|
||||
}
|
||||
|
||||
this->jumpData[offset++] = this->replacedInstruction;
|
||||
if (((uint32_t) originalFunctionAddrWithOffset & 0x03FFFFFC) != (uint32_t) originalFunctionAddrWithOffset) {
|
||||
this->jumpData[offset++] = 0x3d600000 | (((this->realEffectiveFunctionAddress + 4) >> 16) & 0x0000FFFF); // lis r11 ,(real_addr + 4)@hi
|
||||
this->jumpData[offset++] = 0x616b0000 | ((this->realEffectiveFunctionAddress + 4) & 0x0000ffff); // ori r11 ,(real_addr + 4)@lo
|
||||
this->jumpData[offset++] = 0x7d6903a6; // mtspr CTR ,r11
|
||||
this->jumpData[offset++] = 0x4e800420; // bctr
|
||||
} else {
|
||||
this->jumpData[offset++] = 0x48000002 | (originalFunctionAddrWithOffset & 0x03FFFFFC);
|
||||
}
|
||||
}
|
||||
// myfunc:
|
||||
if (((uint32_t) this->replacementFunctionAddress & 0x03FFFFFC) != (uint32_t) this->replacementFunctionAddress) {
|
||||
this->jumpData[offset++] = 0x3d600000 | (((this->replacementFunctionAddress) >> 16) & 0x0000FFFF); // lis r11 ,repl_addr@hi
|
||||
this->jumpData[offset++] = 0x616b0000 | ((this->replacementFunctionAddress) & 0x0000ffff); // ori r11 ,r11 ,repl_addr@lo
|
||||
this->jumpData[offset++] = 0x7d6903a6; // mtspr CTR ,r11
|
||||
this->jumpData[offset] = 0x4e800420; // bctr
|
||||
} else {
|
||||
this->jumpData[offset] = 0x48000002 | (replacementFunctionAddress & 0x03FFFFFC);
|
||||
}
|
||||
|
||||
if (offset >= this->jumpDataSize) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Tried to overflow buffer. offset: %08X vs array size: %08X", offset, this->jumpDataSize);
|
||||
OSFatal("Wrote too much data");
|
||||
}
|
||||
|
||||
// Make sure the trampoline itself is usable.
|
||||
if (((uint32_t) this->jumpData & 0x03FFFFFC) != (uint32_t) this->jumpData) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Jump is impossible");
|
||||
OSFatal("Jump is impossible");
|
||||
}
|
||||
|
||||
this->replaceWithInstruction = 0x48000002 | ((uint32_t) this->jumpData & 0x03FFFFFC);
|
||||
|
||||
DCFlushRange((void *) this->jumpData, sizeof(uint32_t) * 15);
|
||||
ICInvalidateRange((void *) this->jumpData, sizeof(uint32_t) * 15);
|
||||
}
|
||||
|
||||
DCFlushRange((void *) &replaceWithInstruction, 4);
|
||||
ICInvalidateRange((void *) &replaceWithInstruction, 4);
|
||||
|
||||
OSMemoryBarrier();
|
||||
}
|
||||
|
||||
bool PatchedFunctionData::isDynamicFunction() const {
|
||||
if (this->library == LIBRARY_OTHER) {
|
||||
return false;
|
||||
}
|
||||
if ((this->realPhysicalFunctionAddress & 0x80000000) == 0x80000000 && (this->realPhysicalFunctionAddress & 0xFF000000) != 0xFF000000) {
|
||||
if (this->targetProcess == FP_TARGET_PROCESS_GAME_AND_MENU || this->targetProcess == FP_TARGET_PROCESS_GAME || this->targetProcess == FP_TARGET_PROCESS_WII_U_MENU) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
PatchedFunctionData::~PatchedFunctionData() {
|
||||
if (this->jumpToOriginal) {
|
||||
MEMFreeToExpHeap(this->heapHandle, this->jumpToOriginal);
|
||||
this->jumpToOriginal = nullptr;
|
||||
}
|
||||
if (this->jumpData) {
|
||||
MEMFreeToExpHeap(this->heapHandle, this->jumpData);
|
||||
this->jumpData = nullptr;
|
||||
}
|
||||
}
|
62
source/PatchedFunctionData.h
Normal file
62
source/PatchedFunctionData.h
Normal file
@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "FunctionAddressProvider.h"
|
||||
#include "PatchedFunctionData.h"
|
||||
#include "utils/logger.h"
|
||||
#include <coreinit/cache.h>
|
||||
#include <coreinit/debug.h>
|
||||
#include <coreinit/memexpheap.h>
|
||||
#include <coreinit/memorymap.h>
|
||||
#include <cstdint>
|
||||
#include <function_patcher/fpatching_defines.h>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
class PatchedFunctionData {
|
||||
|
||||
public:
|
||||
~PatchedFunctionData();
|
||||
|
||||
explicit PatchedFunctionData(std::shared_ptr<FunctionAddressProvider> functionAddressProvider) : functionAddressProvider(std::move(functionAddressProvider)) {
|
||||
}
|
||||
|
||||
static std::optional<std::shared_ptr<PatchedFunctionData>> make_shared(std::shared_ptr<FunctionAddressProvider> functionAddressProvider,
|
||||
function_replacement_data_t *replacementData,
|
||||
MEMHeapHandle heapHandle);
|
||||
|
||||
bool updateFunctionAddresses();
|
||||
|
||||
void generateJumpToOriginal();
|
||||
|
||||
void generateReplacementJump();
|
||||
|
||||
uint32_t getHandle() {
|
||||
return (uint32_t) this;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isDynamicFunction() const;
|
||||
|
||||
uint32_t *jumpToOriginal{};
|
||||
uint32_t *jumpData{};
|
||||
|
||||
uint32_t realEffectiveFunctionAddress{};
|
||||
uint32_t realPhysicalFunctionAddress{};
|
||||
|
||||
uint32_t *realCallFunctionAddressPtr{};
|
||||
|
||||
uint32_t replacementFunctionAddress{};
|
||||
|
||||
uint32_t replacedInstruction{};
|
||||
|
||||
uint32_t replaceWithInstruction{};
|
||||
uint32_t jumpDataSize = 15;
|
||||
MEMHeapHandle heapHandle = nullptr;
|
||||
|
||||
bool isPatched{};
|
||||
function_replacement_library_type_t library{};
|
||||
FunctionPatcherTargetProcess targetProcess{};
|
||||
std::optional<std::string> functionName = {};
|
||||
std::shared_ptr<FunctionAddressProvider> functionAddressProvider;
|
||||
};
|
83
source/export.cpp
Normal file
83
source/export.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include "export.h"
|
||||
#include "PatchedFunctionData.h"
|
||||
#include "function_patcher.h"
|
||||
#include "utils/globals.h"
|
||||
#include <ranges>
|
||||
#include <vector>
|
||||
|
||||
bool FunctionPatcherPatchFunction(function_replacement_data_t *function_data, PatchedFunctionHandle *outHandle) {
|
||||
if (function_data->VERSION != FUNCTION_REPLACEMENT_DATA_STRUCT_VERSION) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to patch function. struct version mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto functionDataOpt = PatchedFunctionData::make_shared(gFunctionAddressProvider, function_data, gJumpHeapHandle);
|
||||
|
||||
if (!functionDataOpt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto functionData = functionDataOpt.value();
|
||||
|
||||
std::lock_guard<std::mutex> lock(gPatchedFunctionsMutex);
|
||||
|
||||
if (!PatchFunction(functionData)) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to patch function");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outHandle) {
|
||||
*outHandle = functionData->getHandle();
|
||||
}
|
||||
|
||||
gPatchedFunctions.push_back(std::move(functionData));
|
||||
|
||||
OSMemoryBarrier();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FunctionPatcherRestoreFunction(PatchedFunctionHandle handle) {
|
||||
std::lock_guard<std::mutex> lock(gPatchedFunctionsMutex);
|
||||
std::vector<std::shared_ptr<PatchedFunctionData>> toBeTempRestored;
|
||||
bool found = false;
|
||||
int32_t erasePosition = 0;
|
||||
std::shared_ptr<PatchedFunctionData> toRemoved;
|
||||
for (auto &cur : gPatchedFunctions) {
|
||||
if (cur->getHandle() == handle) {
|
||||
toRemoved = cur;
|
||||
found = true;
|
||||
continue;
|
||||
}
|
||||
// Check if something else patched the same function afterwards.
|
||||
if (found) {
|
||||
if (cur->realPhysicalFunctionAddress == toRemoved->realPhysicalFunctionAddress) {
|
||||
toBeTempRestored.push_back(cur);
|
||||
}
|
||||
} else {
|
||||
erasePosition++;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to find PatchedFunctionData by handle %08X", handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Restore function patches that were done after the patch we actually want to restore.
|
||||
for (auto &cur : std::ranges::reverse_view(toBeTempRestored)) {
|
||||
RestoreFunction(cur);
|
||||
}
|
||||
|
||||
// Restore the function we actually want to restore
|
||||
RestoreFunction(toRemoved);
|
||||
|
||||
gPatchedFunctions.erase(gPatchedFunctions.begin() + erasePosition);
|
||||
|
||||
// Apply the other patches again
|
||||
for (auto &cur : toBeTempRestored) {
|
||||
PatchFunction(cur);
|
||||
}
|
||||
|
||||
OSMemoryBarrier();
|
||||
return true;
|
||||
}
|
6
source/export.h
Normal file
6
source/export.h
Normal file
@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
#include <function_patcher/fpatching_defines.h>
|
||||
|
||||
bool FunctionPatcherPatchFunction(function_replacement_data_t *function_data, PatchedFunctionHandle *outHandle);
|
||||
|
||||
bool FunctionPatcherRestoreFunction(PatchedFunctionHandle handle);
|
@ -1,25 +1,23 @@
|
||||
#include "function_patcher.h"
|
||||
#include "CThread.h"
|
||||
#include "FunctionAddressProvider.h"
|
||||
#include "PatchedFunctionData.h"
|
||||
#include "utils/CThread.h"
|
||||
#include "utils/logger.h"
|
||||
#include <coreinit/cache.h>
|
||||
#include <coreinit/debug.h>
|
||||
#include <coreinit/dynload.h>
|
||||
#include <coreinit/memorymap.h>
|
||||
#include <kernel/kernel.h>
|
||||
#include <memory>
|
||||
|
||||
void writeDataAndFlushIC(CThread *thread, void *arg) {
|
||||
auto *data = (uint32_t *) arg;
|
||||
static void writeDataAndFlushIC(CThread *thread, void *arg) {
|
||||
auto *data = (PatchedFunctionData *) arg;
|
||||
|
||||
DCFlushRange(data, sizeof(uint32_t) * 3);
|
||||
|
||||
uint32_t replace_instruction = data[0];
|
||||
uint32_t physical_address = data[1];
|
||||
uint32_t effective_address = data[2];
|
||||
uint32_t replace_instruction = data->replaceWithInstruction;
|
||||
uint32_t physical_address = data->realPhysicalFunctionAddress;
|
||||
uint32_t effective_address = data->realEffectiveFunctionAddress;
|
||||
DCFlushRange(&replace_instruction, 4);
|
||||
DCFlushRange(&physical_address, 4);
|
||||
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Write instruction %08X to %08X [%08X] on core %d", replace_instruction, effective_address, physical_address, OSGetThreadAffinity(OSGetCurrentThread()) / 2);
|
||||
|
||||
auto replace_instruction_physical = (uint32_t) &replace_instruction;
|
||||
|
||||
if (replace_instruction_physical < 0x00800000 || replace_instruction_physical >= 0x01000000) {
|
||||
@ -32,362 +30,99 @@ void writeDataAndFlushIC(CThread *thread, void *arg) {
|
||||
ICInvalidateRange((void *) (effective_address), 4);
|
||||
}
|
||||
|
||||
void FunctionPatcherRestoreDynamicFunctions(function_replacement_data_t *replacements, uint32_t size) {
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
function_replacement_data_t *function_data = &replacements[i];
|
||||
if (function_data->VERSION != FUNCTION_REPLACEMENT_DATA_STRUCT_VERSION) {
|
||||
OSFatal("Failed to patch function. struct version mismatch");
|
||||
}
|
||||
|
||||
uint32_t targetAddrPhys = function_data->physicalAddr;
|
||||
if (function_data->library != LIBRARY_OTHER) {
|
||||
targetAddrPhys = (uint32_t) OSEffectiveToPhysical(function_data->realAddr);
|
||||
}
|
||||
if (isDynamicFunction(targetAddrPhys)) {
|
||||
DEBUG_FUNCTION_LINE("Setting alreadyPatched to false for %s because it's a dynamic function", function_data->function_name);
|
||||
function_data->alreadyPatched = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FunctionPatcherPatchFunction(function_replacement_data_t *replacements, uint32_t size) {
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
function_replacement_data_t *function_data = &replacements[i];
|
||||
if (function_data->VERSION != FUNCTION_REPLACEMENT_DATA_STRUCT_VERSION) {
|
||||
OSFatal("Failed to patch function. struct version mismatch");
|
||||
}
|
||||
/* Patch branches to it. */
|
||||
volatile uint32_t *space = function_data->replace_data;
|
||||
|
||||
if (function_data->alreadyPatched == 1) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Skipping %s, its already patched\n", function_data->function_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Patching %s ...", function_data->function_name);
|
||||
|
||||
uint32_t physical = function_data->physicalAddr;
|
||||
auto repl_addr = (uint32_t) function_data->replaceAddr;
|
||||
auto call_addr = (uint32_t) function_data->replaceCall;
|
||||
|
||||
uint32_t real_addr = function_data->virtualAddr;
|
||||
if (function_data->library != LIBRARY_OTHER) {
|
||||
real_addr = getAddressOfFunction(function_data->function_name, function_data->library);
|
||||
}
|
||||
|
||||
if (!real_addr) {
|
||||
DEBUG_FUNCTION_LINE("OSDynLoad_FindExport failed for %s", function_data->function_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("%s is located at %08X!", function_data->function_name, real_addr);
|
||||
|
||||
if (function_data->library != LIBRARY_OTHER) {
|
||||
physical = (uint32_t) OSEffectiveToPhysical(real_addr);
|
||||
}
|
||||
|
||||
if (!physical) {
|
||||
DEBUG_FUNCTION_LINE("Error. Something is wrong with the physical address");
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("%s physical is located at %08X!", function_data->function_name, physical);
|
||||
|
||||
*(volatile uint32_t *) (call_addr) = (uint32_t) (space);
|
||||
|
||||
auto targetAddr = (uint32_t) space;
|
||||
if (targetAddr < 0x00800000 || targetAddr >= 0x01000000) {
|
||||
targetAddr = (uint32_t) OSEffectiveToPhysical(targetAddr);
|
||||
} else {
|
||||
targetAddr = targetAddr + 0x30800000 - 0x00800000;
|
||||
}
|
||||
|
||||
KernelCopyData(targetAddr, physical, 4);
|
||||
|
||||
ICInvalidateRange((void *) (space), 4);
|
||||
DCFlushRange((void *) (space), 4);
|
||||
|
||||
space++;
|
||||
|
||||
// fill the restore instruction section
|
||||
function_data->realAddr = real_addr;
|
||||
function_data->restoreInstruction = space[-1];
|
||||
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("function_data->realAddr = %08X!", function_data->realAddr);
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("function_data->restoreInstruction = %08X!", function_data->restoreInstruction);
|
||||
|
||||
/*
|
||||
00808cfc 3d601234 lis r11 ,0x1234
|
||||
00808d00 616b5678 ori r11 ,r11 ,0x5678
|
||||
00808d04 7d6903a6 mtspr CTR ,r11
|
||||
00808d08 4e800420 bctr
|
||||
*/
|
||||
|
||||
*space = 0x3d600000 | (((real_addr + 4) >> 16) & 0x0000FFFF);
|
||||
space++; // lis r11 ,0x1234
|
||||
*space = 0x616b0000 | ((real_addr + 4) & 0x0000ffff);
|
||||
space++; // ori r11 ,r11 ,0x5678
|
||||
*space = 0x7d6903a6;
|
||||
space++; // mtspr CTR ,r11
|
||||
*space = 0x4e800420;
|
||||
space++; // bctr
|
||||
|
||||
//setting jump back
|
||||
uint32_t replace_instr = 0x48000002 | (repl_addr & 0x03FFFFFC);
|
||||
|
||||
// If the jump is too big or we want only patch for certain processes we need a trampoline
|
||||
if (repl_addr > 0x03FFFFFC || function_data->targetProcess != FP_TARGET_PROCESS_ALL) {
|
||||
auto repl_addr_test = (uint32_t) space;
|
||||
if (function_data->targetProcess != FP_TARGET_PROCESS_ALL) {
|
||||
// Only use patched function if OSGetUPID matches function_data->targetProcess
|
||||
*space = 0x3d600000 | (((uint32_t *) OSGetUPID)[0] & 0x0000FFFF);
|
||||
space++; // lis r11 ,0x0
|
||||
*space = 0x816b0000 | (((uint32_t *) OSGetUPID)[1] & 0x0000FFFF);
|
||||
space++; // lwz r11 ,0x0(r11)
|
||||
if (function_data->targetProcess == FP_TARGET_PROCESS_GAME_AND_MENU) {
|
||||
*space = 0x2c0b0000 | FP_TARGET_PROCESS_WII_U_MENU;
|
||||
space++; // cmpwi r11 ,FP_TARGET_PROCESS_WII_U_MENU
|
||||
*space = 0x41820000 | 0x00000020;
|
||||
space++; // beq myfunc
|
||||
*space = 0x2c0b0000 | FP_TARGET_PROCESS_GAME;
|
||||
space++; // cmpwi r11 ,FP_TARGET_PROCESS_GAME
|
||||
*space = 0x41820000 | 0x00000018;
|
||||
space++; // beq myfunc
|
||||
} else {
|
||||
*space = 0x2c0b0000 | function_data->targetProcess;
|
||||
space++; // cmpwi r11 ,function_data->targetProcess
|
||||
*space = 0x41820000 | 0x00000018;
|
||||
space++; // beq myfunc
|
||||
}
|
||||
*space = 0x3d600000 | (((real_addr + 4) >> 16) & 0x0000FFFF);
|
||||
space++; // lis r11 ,(real_addr + 4)@hi
|
||||
*space = 0x616b0000 | ((real_addr + 4) & 0x0000ffff);
|
||||
space++; // ori r11 ,(real_addr + 4)@lo
|
||||
*space = 0x7d6903a6;
|
||||
space++; // mtspr CTR ,r11
|
||||
*space = function_data->restoreInstruction;
|
||||
space++; //
|
||||
*space = 0x4e800420;
|
||||
space++; // bctr
|
||||
}
|
||||
// myfunc:
|
||||
*space = 0x3d600000 | (((repl_addr) >> 16) & 0x0000FFFF);
|
||||
space++; // lis r11 ,repl_addr@hi
|
||||
*space = 0x616b0000 | ((repl_addr) &0x0000ffff);
|
||||
space++; // ori r11 ,r11 ,repl_addr@lo
|
||||
*space = 0x7d6903a6;
|
||||
space++; // mtspr CTR ,r11
|
||||
*space = 0x4e800420;
|
||||
space++; // bctr
|
||||
|
||||
// Make sure the trampoline itself is usable.
|
||||
if ((repl_addr_test & 0x03FFFFFC) != repl_addr_test) {
|
||||
OSFatal("Jump is impossible");
|
||||
}
|
||||
|
||||
replace_instr = 0x48000002 | (repl_addr_test & 0x03FFFFFC);
|
||||
}
|
||||
|
||||
if (space > &function_data->replace_data[FUNCTION_PATCHER_METHOD_STORE_SIZE]) {
|
||||
OSFatal("The replacement data is too long.");
|
||||
}
|
||||
|
||||
DCFlushRange((void *) function_data->replace_data, FUNCTION_PATCHER_METHOD_STORE_SIZE * 4);
|
||||
ICInvalidateRange((void *) function_data->replace_data, FUNCTION_PATCHER_METHOD_STORE_SIZE * 4);
|
||||
|
||||
uint32_t data[] = {
|
||||
replace_instr,
|
||||
physical,
|
||||
real_addr};
|
||||
CThread::runOnAllCores(writeDataAndFlushIC, data);
|
||||
|
||||
function_data->alreadyPatched = 1;
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("done with patching %s!", function_data->function_name);
|
||||
}
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Done with patching given functions!");
|
||||
}
|
||||
|
||||
void FunctionPatcherRestoreFunctions(function_replacement_data_t *replacements, uint32_t size) {
|
||||
DEBUG_FUNCTION_LINE("Restoring given functions!");
|
||||
for (uint32_t i = 0; i < size; i++) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Restoring %s... ", replacements[i].function_name);
|
||||
if (replacements[i].restoreInstruction == 0 || replacements[i].realAddr == 0) {
|
||||
DEBUG_FUNCTION_LINE("I dont have the information for the restore =( skip");
|
||||
continue;
|
||||
}
|
||||
|
||||
auto targetAddrPhys = (uint32_t) replacements[i].physicalAddr;
|
||||
|
||||
if (replacements[i].library != LIBRARY_OTHER) {
|
||||
targetAddrPhys = (uint32_t) OSEffectiveToPhysical(replacements[i].realAddr);
|
||||
}
|
||||
|
||||
if (isDynamicFunction(targetAddrPhys) &&
|
||||
// Other processes than the wii u menu and game one seem to keep their rpl's loaded.
|
||||
replacements[i].targetProcess != FP_TARGET_PROCESS_GAME_AND_MENU &&
|
||||
replacements[i].targetProcess != FP_TARGET_PROCESS_GAME &&
|
||||
replacements[i].targetProcess != FP_TARGET_PROCESS_WII_U_MENU) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Its a dynamic function. We don't need to restore it!", replacements[i].function_name);
|
||||
replacements[i].alreadyPatched = false;
|
||||
} else {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("\nRestoring %08X to %08X [%08X]", (uint32_t) replacements[i].restoreInstruction, replacements[i].realAddr, targetAddrPhys);
|
||||
auto sourceAddr = (uint32_t) &replacements[i].restoreInstruction;
|
||||
|
||||
auto sourceAddrPhys = (uint32_t) OSEffectiveToPhysical(sourceAddr);
|
||||
|
||||
// These hardcoded values should be replaced with something more dynamic.
|
||||
if (sourceAddrPhys == 0 && (sourceAddr >= 0x00800000 && sourceAddr < 0x01000000)) {
|
||||
sourceAddrPhys = sourceAddr + (0x30800000 - 0x00800000);
|
||||
}
|
||||
|
||||
if (sourceAddrPhys == 0) {
|
||||
OSFatal("Failed to get physical address");
|
||||
}
|
||||
|
||||
KernelCopyData(targetAddrPhys, sourceAddrPhys, 4);
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("\nICInvalidateRange %08X", (void *) replacements[i].realAddr);
|
||||
ICInvalidateRange((void *) replacements[i].realAddr, 4);
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("done");
|
||||
}
|
||||
replacements[i].alreadyPatched = 0; // In case a
|
||||
}
|
||||
DEBUG_FUNCTION_LINE("Done with restoring given functions!");
|
||||
}
|
||||
|
||||
bool isDynamicFunction(uint32_t physicalAddress) {
|
||||
if ((physicalAddress & 0x80000000) == 0x80000000 && (physicalAddress & 0xFF000000) != 0xFF000000) {
|
||||
bool PatchFunction(std::shared_ptr<PatchedFunctionData> &patchedFunction) {
|
||||
// The addresses of a function might change every time with run another application.
|
||||
if (!patchedFunction->updateFunctionAddresses()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
if (patchedFunction->isPatched) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (patchedFunction->functionName) {
|
||||
DEBUG_FUNCTION_LINE("Patching function %s...", patchedFunction->functionName->c_str());
|
||||
} else {
|
||||
DEBUG_FUNCTION_LINE("Patching function @ %08X", patchedFunction->realEffectiveFunctionAddress);
|
||||
}
|
||||
|
||||
volatile uint32_t replacedInstruction;
|
||||
|
||||
auto targetAddress = (uint32_t) &replacedInstruction;
|
||||
if (targetAddress < 0x00800000 || targetAddress >= 0x01000000) {
|
||||
targetAddress = (uint32_t) OSEffectiveToPhysical(targetAddress);
|
||||
} else {
|
||||
targetAddress = targetAddress + 0x30800000 - 0x00800000;
|
||||
}
|
||||
|
||||
if (targetAddress == 0) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to get physical address");
|
||||
OSFatal("Failed to get physical address");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save the instruction we will replace.
|
||||
KernelCopyData(targetAddress, patchedFunction->realPhysicalFunctionAddress, 4);
|
||||
DCFlushRange((void *) &replacedInstruction, 4);
|
||||
patchedFunction->replacedInstruction = replacedInstruction;
|
||||
|
||||
// Generate a jump to the original function so the unpatched function can still be called
|
||||
patchedFunction->generateJumpToOriginal();
|
||||
|
||||
// Generate a code that is run when somebody calls the patched function.
|
||||
// If the correct process calls this, it'll jump the function replacement, otherwise the original function will be called.
|
||||
patchedFunction->generateReplacementJump();
|
||||
|
||||
// Write this->replaceWithInstruction to the first instruction of the function we want to replace.
|
||||
CThread::runOnAllCores(writeDataAndFlushIC, patchedFunction.get());
|
||||
|
||||
// Set patch status
|
||||
patchedFunction->isPatched = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
rpl_handling rpl_handles[] __attribute__((section(".data"))) = {
|
||||
{LIBRARY_AVM, "avm.rpl", nullptr},
|
||||
{LIBRARY_CAMERA, "camera.rpl", nullptr},
|
||||
{LIBRARY_COREINIT, "coreinit.rpl", nullptr},
|
||||
{LIBRARY_DC, "dc.rpl", nullptr},
|
||||
{LIBRARY_DMAE, "dmae.rpl", nullptr},
|
||||
{LIBRARY_DRMAPP, "drmapp.rpl", nullptr},
|
||||
{LIBRARY_ERREULA, "erreula.rpl", nullptr},
|
||||
{LIBRARY_GX2, "gx2.rpl", nullptr},
|
||||
{LIBRARY_H264, "h264.rpl", nullptr},
|
||||
{LIBRARY_LZMA920, "lzma920.rpl", nullptr},
|
||||
{LIBRARY_MIC, "mic.rpl", nullptr},
|
||||
{LIBRARY_NFC, "nfc.rpl", nullptr},
|
||||
{LIBRARY_NIO_PROF, "nio_prof.rpl", nullptr},
|
||||
{LIBRARY_NLIBCURL, "nlibcurl.rpl", nullptr},
|
||||
{LIBRARY_NLIBNSS, "nlibnss.rpl", nullptr},
|
||||
{LIBRARY_NLIBNSS2, "nlibnss2.rpl", nullptr},
|
||||
{LIBRARY_NN_AC, "nn_ac.rpl", nullptr},
|
||||
{LIBRARY_NN_ACP, "nn_acp.rpl", nullptr},
|
||||
{LIBRARY_NN_ACT, "nn_act.rpl", nullptr},
|
||||
{LIBRARY_NN_AOC, "nn_aoc.rpl", nullptr},
|
||||
{LIBRARY_NN_BOSS, "nn_boss.rpl", nullptr},
|
||||
{LIBRARY_NN_CCR, "nn_ccr.rpl", nullptr},
|
||||
{LIBRARY_NN_CMPT, "nn_cmpt.rpl", nullptr},
|
||||
{LIBRARY_NN_DLP, "nn_dlp.rpl", nullptr},
|
||||
{LIBRARY_NN_EC, "nn_ec.rpl", nullptr},
|
||||
{LIBRARY_NN_FP, "nn_fp.rpl", nullptr},
|
||||
{LIBRARY_NN_HAI, "nn_hai.rpl", nullptr},
|
||||
{LIBRARY_NN_HPAD, "nn_hpad.rpl", nullptr},
|
||||
{LIBRARY_NN_IDBE, "nn_idbe.rpl", nullptr},
|
||||
{LIBRARY_NN_NDM, "nn_ndm.rpl", nullptr},
|
||||
{LIBRARY_NN_NETS2, "nn_nets2.rpl", nullptr},
|
||||
{LIBRARY_NN_NFP, "nn_nfp.rpl", nullptr},
|
||||
{LIBRARY_NN_NIM, "nn_nim.rpl", nullptr},
|
||||
{LIBRARY_NN_OLV, "nn_olv.rpl", nullptr},
|
||||
{LIBRARY_NN_PDM, "nn_pdm.rpl", nullptr},
|
||||
{LIBRARY_NN_SAVE, "nn_save.rpl", nullptr},
|
||||
{LIBRARY_NN_SL, "nn_sl.rpl", nullptr},
|
||||
{LIBRARY_NN_SPM, "nn_spm.rpl", nullptr},
|
||||
{LIBRARY_NN_TEMP, "nn_temp.rpl", nullptr},
|
||||
{LIBRARY_NN_UDS, "nn_uds.rpl", nullptr},
|
||||
{LIBRARY_NN_VCTL, "nn_vctl.rpl", nullptr},
|
||||
{LIBRARY_NSYSCCR, "nsysccr.rpl", nullptr},
|
||||
{LIBRARY_NSYSHID, "nsyshid.rpl", nullptr},
|
||||
{LIBRARY_NSYSKBD, "nsyskbd.rpl", nullptr},
|
||||
{LIBRARY_NSYSNET, "nsysnet.rpl", nullptr},
|
||||
{LIBRARY_NSYSUHS, "nsysuhs.rpl", nullptr},
|
||||
{LIBRARY_NSYSUVD, "nsysuvd.rpl", nullptr},
|
||||
{LIBRARY_NTAG, "ntag.rpl", nullptr},
|
||||
{LIBRARY_PADSCORE, "padscore.rpl", nullptr},
|
||||
{LIBRARY_PROC_UI, "proc_ui.rpl", nullptr},
|
||||
{LIBRARY_SNDCORE2, "sndcore2.rpl", nullptr},
|
||||
{LIBRARY_SNDUSER2, "snduser2.rpl", nullptr},
|
||||
{LIBRARY_SND_CORE, "snd_core.rpl", nullptr},
|
||||
{LIBRARY_SND_USER, "snd_user.rpl", nullptr},
|
||||
{LIBRARY_SWKBD, "swkbd.rpl", nullptr},
|
||||
{LIBRARY_SYSAPP, "sysapp.rpl", nullptr},
|
||||
{LIBRARY_TCL, "tcl.rpl", nullptr},
|
||||
{LIBRARY_TVE, "tve.rpl", nullptr},
|
||||
{LIBRARY_UAC, "uac.rpl", nullptr},
|
||||
{LIBRARY_UAC_RPL, "uac_rpl.rpl", nullptr},
|
||||
{LIBRARY_USB_MIC, "usb_mic.rpl", nullptr},
|
||||
{LIBRARY_UVC, "uvc.rpl", nullptr},
|
||||
{LIBRARY_UVD, "uvd.rpl", nullptr},
|
||||
{LIBRARY_VPAD, "vpad.rpl", nullptr},
|
||||
{LIBRARY_VPADBASE, "vpadbase.rpl", nullptr},
|
||||
{LIBRARY_ZLIB125, "zlib125.rpl", nullptr}};
|
||||
bool RestoreFunction(std::shared_ptr<PatchedFunctionData> &patchedFunction) {
|
||||
if (!patchedFunction->isPatched) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Skip restoring function because it's not patched");
|
||||
return true;
|
||||
}
|
||||
if (patchedFunction->replacedInstruction == 0 || patchedFunction->realEffectiveFunctionAddress == 0) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to restore function, information is missing.");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t getAddressOfFunction(char *functionName, function_replacement_library_type_t library) {
|
||||
uint32_t real_addr = 0;
|
||||
auto targetAddrPhys = (uint32_t) patchedFunction->realPhysicalFunctionAddress;
|
||||
|
||||
OSDynLoad_Module rpl_handle = nullptr;
|
||||
if (patchedFunction->library != LIBRARY_OTHER) {
|
||||
targetAddrPhys = (uint32_t) OSEffectiveToPhysical(patchedFunction->realEffectiveFunctionAddress);
|
||||
}
|
||||
|
||||
OSDynLoad_Error err = OS_DYNLOAD_OK;
|
||||
if (patchedFunction->isDynamicFunction() &&
|
||||
// Other processes than the wii u menu and game one seem to keep their rpl's loaded.
|
||||
patchedFunction->targetProcess != FP_TARGET_PROCESS_GAME_AND_MENU &&
|
||||
patchedFunction->targetProcess != FP_TARGET_PROCESS_GAME &&
|
||||
patchedFunction->targetProcess != FP_TARGET_PROCESS_WII_U_MENU) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Its a dynamic function. We don't need to restore it!");
|
||||
} else {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Restoring %08X to %08X [%08X]", (uint32_t) patchedFunction->replacedInstruction, patchedFunction->realEffectiveFunctionAddress, targetAddrPhys);
|
||||
auto sourceAddr = (uint32_t) &patchedFunction->replacedInstruction;
|
||||
|
||||
int32_t rpl_handles_size = sizeof rpl_handles / sizeof rpl_handles[0];
|
||||
auto sourceAddrPhys = (uint32_t) OSEffectiveToPhysical(sourceAddr);
|
||||
|
||||
for (int32_t i = 0; i < rpl_handles_size; i++) {
|
||||
if (rpl_handles[i].library == library) {
|
||||
if (rpl_handles[i].handle == nullptr) {
|
||||
//DEBUG_FUNCTION_LINE("Lets acquire handle for rpl: %s", rpl_handles[i].rplname);
|
||||
err = OSDynLoad_IsModuleLoaded((char *) rpl_handles[i].rplname, &rpl_handles[i].handle);
|
||||
}
|
||||
if (err != OS_DYNLOAD_OK || !rpl_handles[i].handle) {
|
||||
DEBUG_FUNCTION_LINE_WRITE("%s failed to acquire %d %08X\n", rpl_handles[i].rplname, err, rpl_handles[i].handle);
|
||||
return 0;
|
||||
}
|
||||
rpl_handle = rpl_handles[i].handle;
|
||||
break;
|
||||
// These hardcoded values should be replaced with something more dynamic.
|
||||
if (sourceAddrPhys == 0 && (sourceAddr >= 0x00800000 && sourceAddr < 0x01000000)) {
|
||||
sourceAddrPhys = sourceAddr + (0x30800000 - 0x00800000);
|
||||
}
|
||||
}
|
||||
|
||||
if (!rpl_handle) {
|
||||
DEBUG_FUNCTION_LINE("Failed to find the RPL handle for %s", functionName);
|
||||
return 0;
|
||||
}
|
||||
|
||||
OSDynLoad_FindExport(rpl_handle, 0, functionName, reinterpret_cast<void **>(&real_addr));
|
||||
|
||||
if (!real_addr) {
|
||||
DEBUG_FUNCTION_LINE("OSDynLoad_FindExport failed for %s", functionName);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((library == LIBRARY_NN_ACP) && (uint32_t) (*(volatile uint32_t *) (real_addr) &0x48000002) == 0x48000000) {
|
||||
auto address_diff = (uint32_t) (*(volatile uint32_t *) (real_addr) &0x03FFFFFC);
|
||||
if ((address_diff & 0x03000000) == 0x03000000) {
|
||||
address_diff |= 0xFC000000;
|
||||
if (sourceAddrPhys == 0) {
|
||||
OSFatal("Failed to get physical address");
|
||||
}
|
||||
real_addr += (int32_t) address_diff;
|
||||
if ((uint32_t) (*(volatile uint32_t *) (real_addr) &0x48000002) == 0x48000000) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return real_addr;
|
||||
}
|
||||
|
||||
|
||||
void FunctionPatcherResetLibHandles() {
|
||||
int32_t rpl_handles_size = sizeof rpl_handles / sizeof rpl_handles[0];
|
||||
|
||||
for (int32_t i = 0; i < rpl_handles_size; i++) {
|
||||
if (rpl_handles[i].handle != nullptr) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Resetting handle for rpl: %s", rpl_handles[i].rplname);
|
||||
}
|
||||
rpl_handles[i].handle = nullptr;
|
||||
// Release handle?
|
||||
}
|
||||
KernelCopyData(targetAddrPhys, sourceAddrPhys, 4);
|
||||
ICInvalidateRange((void *) patchedFunction->realEffectiveFunctionAddress, 4);
|
||||
DCFlushRange((void *) patchedFunction->realEffectiveFunctionAddress, 4);
|
||||
}
|
||||
patchedFunction->isPatched = false;
|
||||
return true;
|
||||
}
|
||||
|
@ -1,29 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "PatchedFunctionData.h"
|
||||
#include <coreinit/dynload.h>
|
||||
#include <function_patcher/fpatching_defines.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct rpl_handling {
|
||||
function_replacement_library_type_t library;
|
||||
const char rplname[15];
|
||||
OSDynLoad_Module handle;
|
||||
} rpl_handling;
|
||||
|
||||
void FunctionPatcherRestoreDynamicFunctions(function_replacement_data_t *replacements, uint32_t size);
|
||||
|
||||
void FunctionPatcherPatchFunction(function_replacement_data_t *replacements, uint32_t size);
|
||||
|
||||
void FunctionPatcherRestoreFunctions(function_replacement_data_t *replacements, uint32_t size);
|
||||
|
||||
void FunctionPatcherResetLibHandles();
|
||||
|
||||
uint32_t getAddressOfFunction(char *functionName, function_replacement_library_type_t type);
|
||||
|
||||
bool isDynamicFunction(uint32_t physicalAddress);
|
||||
bool PatchFunction(std::shared_ptr<PatchedFunctionData> &patchedFunction);
|
||||
bool RestoreFunction(std::shared_ptr<PatchedFunctionData> &patchedFunction);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
103
source/main.cpp
103
source/main.cpp
@ -1,21 +1,110 @@
|
||||
#include "FunctionAddressProvider.h"
|
||||
#include "export.h"
|
||||
#include "function_patcher.h"
|
||||
#include "utils/globals.h"
|
||||
#include "utils/logger.h"
|
||||
#include "utils/utils.h"
|
||||
#include <coreinit/memexpheap.h>
|
||||
#include <wums.h>
|
||||
|
||||
#include "function_patcher.h"
|
||||
#include "utils/logger.h"
|
||||
|
||||
WUMS_MODULE_EXPORT_NAME("homebrew_functionpatcher");
|
||||
WUMS_MODULE_SKIP_INIT_FINI();
|
||||
WUMS_MODULE_INIT_BEFORE_RELOCATION_DONE_HOOK();
|
||||
|
||||
WUMS_INITIALIZE() {
|
||||
// We need the real MEMAllocFromDefaultHeapEx/MEMFreeToDefaultHeap function pointer to force-allocate memory on the default heap.
|
||||
// Our custom heap doesn't work (yet) for threads and causes an app panic.
|
||||
OSDynLoad_Module coreinitModule;
|
||||
if (OSDynLoad_Acquire("coreinit", &coreinitModule) != OS_DYNLOAD_OK) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to acquire coreinit.rpl");
|
||||
OSFatal("Failed to acquire coreinit.rpl");
|
||||
}
|
||||
/* Memory allocation functions */
|
||||
uint32_t *allocPtr, *freePtr;
|
||||
/* Memory allocation functions */
|
||||
if (OSDynLoad_FindExport(coreinitModule, true, "MEMAllocFromDefaultHeapEx", reinterpret_cast<void **>(&allocPtr)) != OS_DYNLOAD_OK) {
|
||||
DEBUG_FUNCTION_LINE_ERR("OSDynLoad_FindExport for MEMAllocFromDefaultHeapEx");
|
||||
OSFatal("OSDynLoad_FindExport for MEMAllocFromDefaultHeapEx");
|
||||
}
|
||||
if (OSDynLoad_FindExport(coreinitModule, true, "MEMFreeToDefaultHeap", reinterpret_cast<void **>(&freePtr)) != OS_DYNLOAD_OK) {
|
||||
DEBUG_FUNCTION_LINE_ERR("OSDynLoad_FindExport for MEMFreeToDefaultHeap");
|
||||
OSFatal("OSDynLoad_FindExport for MEMFreeToDefaultHeap");
|
||||
}
|
||||
|
||||
gRealMEMAllocFromDefaultHeapEx = (void *(*) (uint32_t, int) ) * allocPtr;
|
||||
gMEMFreeToDefaultHeap = (void (*)(void *)) * freePtr;
|
||||
OSDynLoad_Release(coreinitModule);
|
||||
|
||||
memset(gJumpHeapData, 0, JUMP_HEAP_DATA_SIZE);
|
||||
gJumpHeapHandle = MEMCreateExpHeapEx((void *) (gJumpHeapData), JUMP_HEAP_DATA_SIZE, 1);
|
||||
if (gJumpHeapHandle == nullptr) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to create heap for jump data");
|
||||
OSFatal("Failed to create heap for jump data");
|
||||
}
|
||||
|
||||
gFunctionAddressProvider = make_shared_nothrow<FunctionAddressProvider>();
|
||||
if (!gFunctionAddressProvider) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to create gFunctionAddressProvider");
|
||||
OSFatal("Failed to create gFunctionAddressProvider");
|
||||
}
|
||||
}
|
||||
|
||||
void notify_callback(OSDynLoad_Module module,
|
||||
void *userContext,
|
||||
OSDynLoad_NotifyReason reason,
|
||||
OSDynLoad_NotifyData *infos) {
|
||||
if (reason == OS_DYNLOAD_NOTIFY_LOADED) {
|
||||
std::lock_guard<std::mutex> lock(gPatchedFunctionsMutex);
|
||||
for (auto &cur : gPatchedFunctions) {
|
||||
PatchFunction(cur);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WUMS_APPLICATION_STARTS() {
|
||||
uint32_t upid = OSGetUPID();
|
||||
if (upid != 2 && upid != 15) {
|
||||
return;
|
||||
}
|
||||
|
||||
initLogging();
|
||||
FunctionPatcherResetLibHandles();
|
||||
|
||||
std::lock_guard<std::mutex> lock(gPatchedFunctionsMutex);
|
||||
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Reset patch status");
|
||||
// Reset all dynamic functions
|
||||
for (auto &cur : gPatchedFunctions) {
|
||||
if (cur->isDynamicFunction()) {
|
||||
if (cur->functionName) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("%s is dynamic, reset patched status", cur->functionName->c_str());
|
||||
} else {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("is dynamic, reset patched status");
|
||||
}
|
||||
cur->isPatched = false;
|
||||
} else {
|
||||
if (cur->functionName) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Skip %s for targetProcess %d", cur->functionName->c_str(), cur->targetProcess);
|
||||
} else {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Skip %08X for targetProcess %d", cur->realEffectiveFunctionAddress, cur->targetProcess);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OSMemoryBarrier();
|
||||
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Patch all functions");
|
||||
for (auto &cur : gPatchedFunctions) {
|
||||
PatchFunction(cur);
|
||||
}
|
||||
|
||||
OSDynLoad_AddNotifyCallback(notify_callback, nullptr);
|
||||
}
|
||||
|
||||
WUMS_APPLICATION_REQUESTS_EXIT() {
|
||||
deinitLogging();
|
||||
}
|
||||
WUMS_APPLICATION_ENDS() {
|
||||
gFunctionAddressProvider->resetHandles();
|
||||
}
|
||||
|
||||
WUMS_EXPORT_FUNCTION(FunctionPatcherPatchFunction);
|
||||
WUMS_EXPORT_FUNCTION(FunctionPatcherRestoreFunctions);
|
||||
WUMS_EXPORT_FUNCTION(FunctionPatcherRestoreDynamicFunctions);
|
||||
WUMS_EXPORT_FUNCTION(FunctionPatcherRestoreFunction);
|
||||
|
@ -16,6 +16,8 @@
|
||||
****************************************************************************/
|
||||
#pragma once
|
||||
|
||||
#include "globals.h"
|
||||
#include "logger.h"
|
||||
#include <coreinit/thread.h>
|
||||
#include <cstdint>
|
||||
#include <malloc.h>
|
||||
@ -26,14 +28,14 @@ public:
|
||||
typedef void (*Callback)(CThread *thread, void *arg);
|
||||
|
||||
//! constructor
|
||||
explicit CThread(int32_t iAttr, int32_t iPriority = 16, int32_t iStackSize = 0x8000, CThread::Callback callback = NULL, void *callbackArg = NULL)
|
||||
explicit CThread(int32_t iAttr, int32_t iPriority = 16, int32_t iStackSize = 0x8000, CThread::Callback callback = nullptr, void *callbackArg = nullptr)
|
||||
: pThread(nullptr), pThreadStack(nullptr), pCallback(callback), pCallbackArg(callbackArg) {
|
||||
//! save attribute assignment
|
||||
iAttributes = iAttr;
|
||||
//! allocate the thread
|
||||
pThread = (OSThread *) memalign(0x10, sizeof(OSThread));
|
||||
//! allocate the stack
|
||||
pThreadStack = (uint8_t *) memalign(0x20, iStackSize);
|
||||
//! allocate the thread on the default Cafe OS heap
|
||||
pThread = (OSThread *) gRealMEMAllocFromDefaultHeapEx(sizeof(OSThread), 0x10);
|
||||
//! allocate the stack on the default Cafe OS heap
|
||||
pThreadStack = (uint8_t *) gRealMEMAllocFromDefaultHeapEx(iStackSize, 0x20);
|
||||
//! create the thread
|
||||
if (pThread && pThreadStack) {
|
||||
// clang-format off
|
||||
@ -53,7 +55,6 @@ public:
|
||||
|
||||
static void runOnAllCores(CThread::Callback callback, void *callbackArg, int32_t iAttr = 0, int32_t iPriority = 16, int32_t iStackSize = 0x8000) {
|
||||
int32_t aff[] = {CThread::eAttributeAffCore2, CThread::eAttributeAffCore1, CThread::eAttributeAffCore0};
|
||||
|
||||
for (int i : aff) {
|
||||
CThread thread(iAttr | i, iPriority, iStackSize, callback, callbackArg);
|
||||
thread.resumeThread();
|
||||
@ -80,7 +81,9 @@ public:
|
||||
//! Resume thread
|
||||
virtual void resumeThread() {
|
||||
if (!isThreadSuspended()) return;
|
||||
if (pThread) OSResumeThread(pThread);
|
||||
if (pThread) {
|
||||
OSResumeThread(pThread);
|
||||
}
|
||||
}
|
||||
|
||||
//! Set thread priority
|
||||
@ -122,10 +125,10 @@ public:
|
||||
}
|
||||
//! free the thread stack buffer
|
||||
if (pThreadStack) {
|
||||
free(pThreadStack);
|
||||
gMEMFreeToDefaultHeap(pThreadStack);
|
||||
}
|
||||
if (pThread) {
|
||||
free(pThread);
|
||||
gMEMFreeToDefaultHeap(pThread);
|
||||
}
|
||||
pThread = nullptr;
|
||||
pThreadStack = nullptr;
|
11
source/utils/globals.cpp
Normal file
11
source/utils/globals.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include "globals.h"
|
||||
|
||||
char gJumpHeapData[JUMP_HEAP_DATA_SIZE] __attribute__((section(".data")));
|
||||
MEMHeapHandle gJumpHeapHandle __attribute__((section(".data")));
|
||||
|
||||
std::shared_ptr<FunctionAddressProvider> gFunctionAddressProvider;
|
||||
std::mutex gPatchedFunctionsMutex;
|
||||
std::vector<std::shared_ptr<PatchedFunctionData>> gPatchedFunctions;
|
||||
|
||||
void *(*gRealMEMAllocFromDefaultHeapEx)(uint32_t size, int align);
|
||||
void (*gMEMFreeToDefaultHeap)(void *ptr);
|
16
source/utils/globals.h
Normal file
16
source/utils/globals.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "../PatchedFunctionData.h"
|
||||
#include <coreinit/memheap.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#define JUMP_HEAP_DATA_SIZE (32 * 1024)
|
||||
extern char gJumpHeapData[];
|
||||
extern MEMHeapHandle gJumpHeapHandle;
|
||||
|
||||
extern std::shared_ptr<FunctionAddressProvider> gFunctionAddressProvider;
|
||||
extern std::mutex gPatchedFunctionsMutex;
|
||||
extern std::vector<std::shared_ptr<PatchedFunctionData>> gPatchedFunctions;
|
||||
|
||||
extern void *(*gRealMEMAllocFromDefaultHeapEx)(uint32_t size, int align);
|
||||
extern void (*gMEMFreeToDefaultHeap)(void *ptr);
|
12
source/utils/utils.h
Normal file
12
source/utils/utils.h
Normal file
@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
template<class T, class... Args>
|
||||
std::unique_ptr<T> make_unique_nothrow(Args &&...args) noexcept(noexcept(T(std::forward<Args>(args)...))) {
|
||||
return std::unique_ptr<T>(new (std::nothrow) T(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
template<class T, class... Args>
|
||||
std::shared_ptr<T> make_shared_nothrow(Args &&...args) noexcept(noexcept(T(std::forward<Args>(args)...))) {
|
||||
return std::shared_ptr<T>(new (std::nothrow) T(std::forward<Args>(args)...));
|
||||
}
|
Loading…
Reference in New Issue
Block a user