mirror of
https://github.com/wiiu-env/FunctionPatcherModule.git
synced 2025-01-06 23:28:14 +01:00
Fix restoring "dynamic" functions. Instead of predicting an unload of a RPL we now check if the replaced instruction has been changed.
This commit is contained in:
parent
182c6d2f92
commit
7703eac375
@ -171,18 +171,6 @@ void PatchedFunctionData::generateReplacementJump() {
|
||||
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);
|
||||
|
@ -36,8 +36,6 @@ public:
|
||||
return (uint32_t) this;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool isDynamicFunction() const;
|
||||
|
||||
uint32_t *jumpToOriginal{};
|
||||
uint32_t *jumpData{};
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "PatchedFunctionData.h"
|
||||
#include "utils/CThread.h"
|
||||
#include "utils/logger.h"
|
||||
#include "utils/utils.h"
|
||||
#include <coreinit/cache.h>
|
||||
#include <coreinit/debug.h>
|
||||
#include <coreinit/memorymap.h>
|
||||
@ -98,31 +99,36 @@ bool RestoreFunction(std::shared_ptr<PatchedFunctionData> &patchedFunction) {
|
||||
targetAddrPhys = (uint32_t) OSEffectiveToPhysical(patchedFunction->realEffectiveFunctionAddress);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
ICInvalidateRange((void *) patchedFunction->realEffectiveFunctionAddress, 4);
|
||||
DCFlushRange((void *) patchedFunction->realEffectiveFunctionAddress, 4);
|
||||
// Check if patched instruction is still loaded.
|
||||
uint32_t currentInstruction;
|
||||
if (!ReadFromPhysicalAddress(patchedFunction->realPhysicalFunctionAddress, ¤tInstruction)) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to read instruction.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentInstruction != patchedFunction->replaceWithInstruction) {
|
||||
DEBUG_FUNCTION_LINE_WARN("Instruction is different than expected. Skip restoring. Expected: %08X Real: %08X", currentInstruction, patchedFunction->replaceWithInstruction);
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Restoring %08X to %08X [%08X]", (uint32_t) patchedFunction->replacedInstruction, patchedFunction->realEffectiveFunctionAddress, targetAddrPhys);
|
||||
auto sourceAddr = (uint32_t) &patchedFunction->replacedInstruction;
|
||||
|
||||
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);
|
||||
ICInvalidateRange((void *) patchedFunction->realEffectiveFunctionAddress, 4);
|
||||
DCFlushRange((void *) patchedFunction->realEffectiveFunctionAddress, 4);
|
||||
|
||||
patchedFunction->isPatched = false;
|
||||
return true;
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
#include "utils/utils.h"
|
||||
#include <coreinit/memdefaultheap.h>
|
||||
#include <coreinit/memexpheap.h>
|
||||
#include <ranges>
|
||||
#include <set>
|
||||
#include <wums.h>
|
||||
|
||||
WUMS_MODULE_EXPORT_NAME("homebrew_functionpatcher");
|
||||
@ -37,14 +39,40 @@ void UpdateFunctionPointer() {
|
||||
OSDynLoad_Release(coreinitModule);
|
||||
}
|
||||
|
||||
uint32_t gDoFunctionResets;
|
||||
void CheckIfPatchedFunctionsAreStillInMemory() {
|
||||
std::lock_guard<std::mutex> lock(gPatchedFunctionsMutex);
|
||||
// Check if rpl has been unloaded by comparing the instruction.
|
||||
std::set<uint32_t> physicalAddressesUnchanged;
|
||||
std::set<uint32_t> physicalAddressesChanged;
|
||||
// Restore function patches that were done after the patch we actually want to restore.
|
||||
for (auto &cur : std::ranges::reverse_view(gPatchedFunctions)) {
|
||||
if (!cur->isPatched || physicalAddressesUnchanged.contains(cur->realPhysicalFunctionAddress)) {
|
||||
continue;
|
||||
}
|
||||
if (physicalAddressesChanged.contains(cur->realPhysicalFunctionAddress)) {
|
||||
cur->isPatched = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if patched instruction is still loaded.
|
||||
uint32_t currentInstruction;
|
||||
if (!ReadFromPhysicalAddress(cur->realPhysicalFunctionAddress, ¤tInstruction)) {
|
||||
DEBUG_FUNCTION_LINE_ERR("Failed to read instruction.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentInstruction == cur->replaceWithInstruction) {
|
||||
physicalAddressesUnchanged.insert(cur->realPhysicalFunctionAddress);
|
||||
} else {
|
||||
cur->isPatched = false;
|
||||
physicalAddressesChanged.insert(cur->realPhysicalFunctionAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WUMS_INITIALIZE() {
|
||||
UpdateFunctionPointer();
|
||||
|
||||
// don't reset the patch status on the first launch.
|
||||
gDoFunctionResets = false;
|
||||
|
||||
memset(gJumpHeapData, 0, JUMP_HEAP_DATA_SIZE);
|
||||
gJumpHeapHandle = MEMCreateExpHeapEx((void *) (gJumpHeapData), JUMP_HEAP_DATA_SIZE, 1);
|
||||
if (gJumpHeapHandle == nullptr) {
|
||||
@ -82,46 +110,18 @@ WUMS_APPLICATION_STARTS() {
|
||||
gMEMFreeToDefaultHeapForThreads = MEMFreeToDefaultHeap;
|
||||
|
||||
initLogging();
|
||||
|
||||
std::lock_guard<std::mutex> lock(gPatchedFunctionsMutex);
|
||||
|
||||
// Avoid resetting the patch status of function on the first start.
|
||||
// WUMS_INITIALIZE & WUMS_APPLICATION_STARTS are called during the same application => the .rpl won't get reloaded.
|
||||
// If the .rpl won't get reloaded, old patches will still be present. This can be an issue if a module patches a
|
||||
// dynamic function in WUMS_INITIALIZE, which is called right before the first time this function will be called.
|
||||
// This reset code would mark it as unpatched, while the code is actually still patched, leading to patching an
|
||||
// already patched function.
|
||||
// To avoid this issues, the need to skip the reset status part the first time.
|
||||
if (gDoFunctionResets) {
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Reset patch status");
|
||||
// Reset all dynamic functions
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(gPatchedFunctionsMutex);
|
||||
// reset function patch status if the rpl they were patching has been unloaded from memory.
|
||||
CheckIfPatchedFunctionsAreStillInMemory();
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Patch all 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);
|
||||
}
|
||||
}
|
||||
PatchFunction(cur);
|
||||
}
|
||||
|
||||
OSMemoryBarrier();
|
||||
OSDynLoad_AddNotifyCallback(notify_callback, nullptr);
|
||||
}
|
||||
gDoFunctionResets = true;
|
||||
|
||||
OSMemoryBarrier();
|
||||
|
||||
DEBUG_FUNCTION_LINE_VERBOSE("Patch all functions");
|
||||
for (auto &cur : gPatchedFunctions) {
|
||||
PatchFunction(cur);
|
||||
}
|
||||
|
||||
OSDynLoad_AddNotifyCallback(notify_callback, nullptr);
|
||||
}
|
||||
|
||||
WUMS_APPLICATION_REQUESTS_EXIT() {
|
||||
|
@ -38,6 +38,7 @@ extern "C" {
|
||||
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) LOG(WHBLogWritef, FMT, ##ARGS)
|
||||
|
||||
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS)
|
||||
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(WHBLogPrintf, "## WARN## ", "", FMT, ##ARGS)
|
||||
|
||||
#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS);
|
||||
|
||||
@ -52,6 +53,7 @@ extern "C" {
|
||||
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0)
|
||||
|
||||
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "##ERROR## ", "\n", FMT, ##ARGS)
|
||||
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX_DEFAULT(OSReport, "## WARN## ", "\n", FMT, ##ARGS)
|
||||
|
||||
#define DEBUG_FUNCTION_LINE_ERR_LAMBDA(FILENAME, FUNCTION, LINE, FMT, ARGS...) LOG_EX(FILENAME, FUNCTION, LINE, OSReport, "##ERROR## ", "\n", FMT, ##ARGS);
|
||||
|
||||
|
28
source/utils/utils.cpp
Normal file
28
source/utils/utils.cpp
Normal file
@ -0,0 +1,28 @@
|
||||
#include <coreinit/cache.h>
|
||||
#include <coreinit/memorymap.h>
|
||||
#include <kernel/kernel.h>
|
||||
|
||||
bool ReadFromPhysicalAddress(uint32_t srcPhys, uint32_t *out) {
|
||||
if (!out) {
|
||||
return false;
|
||||
}
|
||||
// Check if patched instruction is still loaded.
|
||||
volatile uint32_t currentInstruction;
|
||||
|
||||
auto currentInstructionAddress = (uint32_t) ¤tInstruction;
|
||||
uint32_t currentInstructionAddressPhys;
|
||||
if (currentInstructionAddress < 0x00800000 || currentInstructionAddress >= 0x01000000) {
|
||||
currentInstructionAddressPhys = (uint32_t) OSEffectiveToPhysical(currentInstructionAddress);
|
||||
} else {
|
||||
currentInstructionAddressPhys = currentInstructionAddress + 0x30800000 - 0x00800000;
|
||||
}
|
||||
|
||||
if (currentInstructionAddressPhys == 0) {
|
||||
return false;
|
||||
}
|
||||
// Save the instruction we will replace.
|
||||
KernelCopyData(currentInstructionAddressPhys, srcPhys, 4);
|
||||
DCFlushRange((void *) ¤tInstruction, 4);
|
||||
*out = currentInstruction;
|
||||
return true;
|
||||
}
|
@ -10,3 +10,5 @@ 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)...));
|
||||
}
|
||||
|
||||
bool ReadFromPhysicalAddress(uint32_t srcPhys, uint32_t *out);
|
||||
|
Loading…
Reference in New Issue
Block a user