From 7703eac375d0f37f9c6d6b2d6fb00e8227f396fb Mon Sep 17 00:00:00 2001 From: Maschell Date: Mon, 2 Jan 2023 14:47:19 +0100 Subject: [PATCH] Fix restoring "dynamic" functions. Instead of predicting an unload of a RPL we now check if the replaced instruction has been changed. --- source/PatchedFunctionData.cpp | 12 ----- source/PatchedFunctionData.h | 2 - source/function_patcher.cpp | 54 ++++++++++++---------- source/main.cpp | 82 +++++++++++++++++----------------- source/utils/logger.h | 2 + source/utils/utils.cpp | 28 ++++++++++++ source/utils/utils.h | 2 + 7 files changed, 103 insertions(+), 79 deletions(-) create mode 100644 source/utils/utils.cpp diff --git a/source/PatchedFunctionData.cpp b/source/PatchedFunctionData.cpp index 2ae1f9a..ca4c798 100644 --- a/source/PatchedFunctionData.cpp +++ b/source/PatchedFunctionData.cpp @@ -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); diff --git a/source/PatchedFunctionData.h b/source/PatchedFunctionData.h index 75ba2a7..a0e1bce 100644 --- a/source/PatchedFunctionData.h +++ b/source/PatchedFunctionData.h @@ -36,8 +36,6 @@ public: return (uint32_t) this; } - [[nodiscard]] bool isDynamicFunction() const; - uint32_t *jumpToOriginal{}; uint32_t *jumpData{}; diff --git a/source/function_patcher.cpp b/source/function_patcher.cpp index 7ad9779..a2a416f 100644 --- a/source/function_patcher.cpp +++ b/source/function_patcher.cpp @@ -3,6 +3,7 @@ #include "PatchedFunctionData.h" #include "utils/CThread.h" #include "utils/logger.h" +#include "utils/utils.h" #include #include #include @@ -98,31 +99,36 @@ bool RestoreFunction(std::shared_ptr &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; } diff --git a/source/main.cpp b/source/main.cpp index bf1288c..717db80 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -6,6 +6,8 @@ #include "utils/utils.h" #include #include +#include +#include #include WUMS_MODULE_EXPORT_NAME("homebrew_functionpatcher"); @@ -37,14 +39,40 @@ void UpdateFunctionPointer() { OSDynLoad_Release(coreinitModule); } -uint32_t gDoFunctionResets; +void CheckIfPatchedFunctionsAreStillInMemory() { + std::lock_guard lock(gPatchedFunctionsMutex); + // Check if rpl has been unloaded by comparing the instruction. + std::set physicalAddressesUnchanged; + std::set 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 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 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() { diff --git a/source/utils/logger.h b/source/utils/logger.h index d520bf4..863e767 100644 --- a/source/utils/logger.h +++ b/source/utils/logger.h @@ -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); diff --git a/source/utils/utils.cpp b/source/utils/utils.cpp new file mode 100644 index 0000000..493836f --- /dev/null +++ b/source/utils/utils.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +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; +} \ No newline at end of file diff --git a/source/utils/utils.h b/source/utils/utils.h index e945dd0..e63a2e2 100644 --- a/source/utils/utils.h +++ b/source/utils/utils.h @@ -10,3 +10,5 @@ template std::shared_ptr make_shared_nothrow(Args &&...args) noexcept(noexcept(T(std::forward(args)...))) { return std::shared_ptr(new (std::nothrow) T(std::forward(args)...)); } + +bool ReadFromPhysicalAddress(uint32_t srcPhys, uint32_t *out);