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:
Maschell 2023-01-02 14:47:19 +01:00
parent 182c6d2f92
commit 7703eac375
7 changed files with 103 additions and 79 deletions

View File

@ -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);

View File

@ -36,8 +36,6 @@ public:
return (uint32_t) this;
}
[[nodiscard]] bool isDynamicFunction() const;
uint32_t *jumpToOriginal{};
uint32_t *jumpData{};

View File

@ -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, &currentInstruction)) {
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;
}

View File

@ -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, &currentInstruction)) {
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() {

View File

@ -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
View 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) &currentInstruction;
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 *) &currentInstruction, 4);
*out = currentInstruction;
return true;
}

View File

@ -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);