Compare commits

...

21 Commits

Author SHA1 Message Date
Maschell
3ac4d19ad7 Update Dockerfile 2024-05-06 10:29:35 +02:00
Maschell
e9d8f970b2 Bump softprops/action-gh-release@v2 from 1 to 2 2024-05-06 10:29:35 +02:00
Maschell
6c05c4dbdc Bump version to 0.2.3 2024-04-24 19:08:04 +02:00
Maschell
df287468a1 Update Dockerfile 2024-04-24 19:08:04 +02:00
Maschell
52192d0cb4 Update .gitignore to ignore all files with .zip extension 2024-04-24 19:08:04 +02:00
Maschell
8de9c37137 Fix some jumps to original functions by placing the replaced intruction after setting CTR 2024-04-24 19:08:04 +02:00
dependabot[bot]
597788ff96 Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 22:48:19 +02:00
Maschell
e4dc696c34 Create dependabot.yml 2023-07-23 10:21:31 +02:00
Maschell
0169ae441a Bump version 2023-07-19 18:36:06 +02:00
Maschell
1c293a5e24 Update Dockerfile 2023-07-19 16:19:19 +02:00
Maschell
865d6c58c1 Update Dockerfile 2023-07-06 15:50:11 +02:00
Maschell
0d0c819f11 Update Dockerfile, fix compiling with wut 1.3.0 2023-04-23 11:39:12 +02:00
Maschell
b2b8ff2083 Change default branch to main 2023-03-28 20:38:50 +02:00
Maschell
214369da29 Bump Version 2023-03-28 20:22:29 +02:00
Maschell
fa3292fa28 memset the memory to 0 before freeing 2023-03-26 15:49:04 +02:00
Maschell
54d0f80426 Change docker registry to ghcr.io 2023-03-18 14:31:59 +01:00
Maschell
35f2c495cc Bump version to 0.2 2023-01-10 18:05:29 +01:00
Maschell
1c7a6ac333 Add support for function patcher struct version 3 2023-01-07 23:55:23 +01:00
Maschell
f6cc151506 Fix typo in ci.yml 2023-01-07 23:55:23 +01:00
Maschell
c074ca4892 Use WUMS 0.3.2 2023-01-07 23:55:23 +01:00
Maschell
1d13bc62e5 Remove unused variable from the ci.yml 2023-01-07 23:55:23 +01:00
17 changed files with 594 additions and 74 deletions

10
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"

View File

@ -3,21 +3,21 @@ name: CI-Release
on: on:
push: push:
branches: branches:
- master - main
jobs: jobs:
clang-format: clang-format:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: clang-format - name: clang-format
run: | run: |
docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./source docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source
build-binary: build-binary:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: clang-format needs: clang-format
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: create version.h - name: create version.h
run: | run: |
git_hash=$(git rev-parse --short "$GITHUB_SHA") git_hash=$(git rev-parse --short "$GITHUB_SHA")
@ -42,17 +42,16 @@ jobs:
run: | run: |
echo REPOSITORY_NAME=$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}' | sed -e "s/:refs//") >> $GITHUB_ENV echo REPOSITORY_NAME=$(echo "$GITHUB_REPOSITORY" | awk -F / '{print $2}' | sed -e "s/:refs//") >> $GITHUB_ENV
echo DATETIME=$(echo $(date '+%Y%m%d-%H%M%S')) >> $GITHUB_ENV echo DATETIME=$(echo $(date '+%Y%m%d-%H%M%S')) >> $GITHUB_ENV
echo COMMIT_MESSAGES=$(echo "${{join(github.event.commits.*.message, '||END||') }}" | sed -e 's/||END||/\r\n/g') >> $GITHUB_ENV
- uses: actions/download-artifact@master - uses: actions/download-artifact@master
with: with:
name: binary name: binary
- name: zip artifact - name: zip artifact
run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip *.wms run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip *.wms
- name: Create Release - name: Create Release
uses: "softprops/action-gh-release@v1" uses: "softprops/action-gh-release@v2"
with: with:
tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }} tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }}
draft: falsee draft: false
prerelease: true prerelease: true
generate_release_notes: true generate_release_notes: true
name: Nightly-${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }} name: Nightly-${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }}

View File

@ -6,15 +6,15 @@ jobs:
clang-format: clang-format:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: clang-format - name: clang-format
run: | run: |
docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./source docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source
check-build-with-logging: check-build-with-logging:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: clang-format needs: clang-format
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: build binary with logging - name: build binary with logging
run: | run: |
docker build . -t builder docker build . -t builder
@ -25,7 +25,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: clang-format needs: clang-format
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: create version.h - name: create version.h
run: | run: |
git_hash=$(git rev-parse --short "${{ github.event.pull_request.head.sha }}") git_hash=$(git rev-parse --short "${{ github.event.pull_request.head.sha }}")

1
.gitignore vendored
View File

@ -8,3 +8,4 @@ build/
cmake-build-debug/ cmake-build-debug/
CMakeLists.txt CMakeLists.txt
*.wms *.wms
*.zip

View File

@ -1,7 +1,7 @@
FROM wiiuenv/devkitppc:20221228 FROM ghcr.io/wiiu-env/devkitppc:20240505
COPY --from=wiiuenv/libkernel:20220904 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libkernel:20230621 /artifacts $DEVKITPRO
COPY --from=wiiuenv/libfunctionpatcher:20230106 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20230621 /artifacts $DEVKITPRO
COPY --from=wiiuenv/wiiumodulesystem:20221005 /artifacts $DEVKITPRO COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:20240424 /artifacts $DEVKITPRO
WORKDIR project WORKDIR project

View File

@ -34,4 +34,4 @@ docker run -it --rm -v ${PWD}:/project functionpatchermodule-builder make clean
## Format the code via docker ## Format the code via docker
`docker run --rm -v ${PWD}:/src wiiuenv/clang-format:13.0.0-2 -r ./source -i` `docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source -i`

View File

@ -28,7 +28,7 @@ uint32_t FunctionAddressProvider::getEffectiveAddressOfFunction(function_replace
return 0; return 0;
} }
OSDynLoad_FindExport(rpl_handle, 0, functionName, reinterpret_cast<void **>(&real_addr)); OSDynLoad_FindExport(rpl_handle, OS_DYNLOAD_EXPORT_FUNC, functionName, reinterpret_cast<void **>(&real_addr));
if (!real_addr) { if (!real_addr) {
DEBUG_FUNCTION_LINE_VERBOSE("OSDynLoad_FindExport failed for %s", functionName); DEBUG_FUNCTION_LINE_VERBOSE("OSDynLoad_FindExport failed for %s", functionName);

View File

@ -1,9 +1,69 @@
#include "PatchedFunctionData.h" #include "PatchedFunctionData.h"
#include "utils/KernelFindExport.h"
#include "utils/utils.h" #include "utils/utils.h"
#include <coreinit/mcp.h>
#include <coreinit/title.h>
#include <vector>
std::optional<std::shared_ptr<PatchedFunctionData>> PatchedFunctionData::make_shared(std::shared_ptr<FunctionAddressProvider> functionAddressProvider, std::optional<std::shared_ptr<PatchedFunctionData>> PatchedFunctionData::make_shared_v3(std::shared_ptr<FunctionAddressProvider> functionAddressProvider,
function_replacement_data_t *replacementData, function_replacement_data_v3_t *replacementData,
MEMHeapHandle heapHandle) { MEMHeapHandle heapHandle) {
if (!replacementData) {
return {};
}
auto ptr = make_shared_nothrow<PatchedFunctionData>(std::move(functionAddressProvider));
if (!ptr) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc PatchedFunctionData");
return {};
}
ptr->isPatched = false;
ptr->heapHandle = heapHandle;
ptr->replacementFunctionAddress = replacementData->replaceAddr;
ptr->realCallFunctionAddressPtr = replacementData->replaceCall;
ptr->targetProcess = replacementData->targetProcess;
ptr->type = replacementData->type;
switch (replacementData->type) {
case FUNCTION_PATCHER_REPLACE_FOR_EXECUTABLE_BY_NAME:
case FUNCTION_PATCHER_REPLACE_FOR_EXECUTABLE_BY_ADDRESS: {
ptr->library = {};
for (uint32_t i = 0; i < replacementData->ReplaceInRPX.targetTitleIdsCount; i++) {
ptr->titleIds.insert(replacementData->ReplaceInRPX.targetTitleIds[i]);
}
ptr->titleVersionMin = replacementData->ReplaceInRPX.versionMin;
ptr->titleVersionMax = replacementData->ReplaceInRPX.versionMax;
ptr->executableName = replacementData->ReplaceInRPX.executableName;
if (replacementData->type == FUNCTION_PATCHER_REPLACE_FOR_EXECUTABLE_BY_ADDRESS) {
ptr->textOffset = replacementData->ReplaceInRPX.textOffset;
} else if (replacementData->type == FUNCTION_PATCHER_REPLACE_FOR_EXECUTABLE_BY_NAME) {
ptr->functionName = replacementData->ReplaceInRPX.functionName;
}
break;
}
case FUNCTION_PATCHER_REPLACE_BY_LIB_OR_ADDRESS: {
ptr->library = replacementData->ReplaceInRPL.library;
if (replacementData->ReplaceInRPL.library != LIBRARY_OTHER) {
ptr->functionName = replacementData->ReplaceInRPL.function_name;
} else {
ptr->realEffectiveFunctionAddress = replacementData->virtualAddr;
ptr->realPhysicalFunctionAddress = replacementData->physicalAddr;
}
break;
}
}
if (!ptr->allocateDataForJumps()) {
return {};
}
return ptr;
}
std::optional<std::shared_ptr<PatchedFunctionData>> PatchedFunctionData::make_shared_v2(std::shared_ptr<FunctionAddressProvider> functionAddressProvider,
function_replacement_data_v2_t *replacementData,
MEMHeapHandle heapHandle) {
if (!replacementData) { if (!replacementData) {
return {}; return {};
} }
@ -13,6 +73,7 @@ std::optional<std::shared_ptr<PatchedFunctionData>> PatchedFunctionData::make_sh
return {}; return {};
} }
ptr->type = FUNCTION_PATCHER_REPLACE_BY_LIB_OR_ADDRESS;
ptr->isPatched = false; ptr->isPatched = false;
ptr->heapHandle = heapHandle; ptr->heapHandle = heapHandle;
ptr->library = replacementData->library; ptr->library = replacementData->library;
@ -27,47 +88,136 @@ std::optional<std::shared_ptr<PatchedFunctionData>> PatchedFunctionData::make_sh
ptr->realPhysicalFunctionAddress = replacementData->physicalAddr; ptr->realPhysicalFunctionAddress = replacementData->physicalAddr;
} }
ptr->jumpToOriginal = (uint32_t *) MEMAllocFromExpHeapEx(ptr->heapHandle, 0x5 * sizeof(uint32_t), 4); if (!ptr->allocateDataForJumps()) {
if (!ptr->jumpToOriginal) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc jump data");
return {}; return {};
} }
if (ptr->replacementFunctionAddress > 0x01FFFFFC || 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 {};
}
}
return ptr; return ptr;
} }
bool PatchedFunctionData::updateFunctionAddresses() {
if (this->library == LIBRARY_OTHER) { bool PatchedFunctionData::allocateDataForJumps() {
if (this->jumpData != nullptr && this->jumpToOriginal != nullptr) {
return true; return true;
} }
if (this->replacementFunctionAddress > 0x01FFFFFC || this->targetProcess != FP_TARGET_PROCESS_ALL) {
this->jumpDataSize = 15; // We could predict the actual size and save some memory, but at the moment we don't need it.
this->jumpData = (uint32_t *) MEMAllocFromExpHeapEx(this->heapHandle, this->jumpDataSize * sizeof(uint32_t), 4);
if (!this->functionName) { if (!this->jumpData) {
DEBUG_FUNCTION_LINE_ERR("Function name was empty. This should never happen."); DEBUG_FUNCTION_LINE_ERR("Failed to alloc jump data");
OSFatal("FunctionPatcherModule: function name was empty"); return false;
}
}
this->jumpToOriginal = (uint32_t *) MEMAllocFromExpHeapEx(this->heapHandle, 0x5 * sizeof(uint32_t), 4);
if (!this->jumpToOriginal) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc jump data");
return false;
}
return true;
}
bool PatchedFunctionData::getAddressForExecutable(uint32_t *outAddress) const {
if (!outAddress) {
return false; return false;
} }
auto real_address = functionAddressProvider->getEffectiveAddressOfFunction(library, this->functionName->c_str()); if (!executableName.has_value()) {
if (!real_address) {
DEBUG_FUNCTION_LINE("OSDynLoad_FindExport failed for %s, updating address not possible.", this->functionName->c_str());
return false; return false;
} }
uint32_t result = 0;
if (type == FUNCTION_PATCHER_REPLACE_FOR_EXECUTABLE_BY_ADDRESS) {
int num_rpls = OSDynLoad_GetNumberOfRPLs();
if (num_rpls == 0) {
DEBUG_FUNCTION_LINE_ERR("OSDynLoad_GetNumberOfRPLs failed. Missing patches?");
OSFatal("OSDynLoad_GetNumberOfRPLs failed. This shouldn't happen. Missing patches?");
return false;
}
std::vector<OSDynLoad_NotifyData> rpls;
rpls.resize(num_rpls);
bool ret = OSDynLoad_GetRPLInfo(0, num_rpls, rpls.data());
if (!ret) {
DEBUG_FUNCTION_LINE_ERR("OSDynLoad_GetRPLInfo failed. Missing patches?");
OSFatal("OSDynLoad_GetNumberOfRPLs failed. This shouldn't happen. Missing patches?");
return false;
}
bool found = false;
for (auto &rpl : rpls) {
if (std::string_view(rpl.name).ends_with(executableName.value())) {
result = rpl.textAddr + textOffset;
found = true;
break;
}
}
if (!found) {
if (executableName->ends_with(".rpx")) {
DEBUG_FUNCTION_LINE_ERR("Can't patch function. \"%s\" is not loaded.", executableName->c_str());
} else {
DEBUG_FUNCTION_LINE_WARN("Can't patch function. \"%s\" is not loaded.", executableName->c_str());
}
return false;
}
} else if (type == FUNCTION_PATCHER_REPLACE_FOR_EXECUTABLE_BY_NAME) {
if (!this->functionName) {
DEBUG_FUNCTION_LINE_ERR("Function name was empty. This should never happen.");
OSFatal("Function name was empty. This should never happen. Check logs for more information.");
return false;
}
result = KernelFindExport(executableName.value(), functionName.value());
if (result == 0) {
DEBUG_FUNCTION_LINE_WARN("Failed to find function \"%s\" in \"%s\".", functionName->c_str(), executableName->c_str());
return false;
}
} else {
DEBUG_FUNCTION_LINE_ERR("Unexpected function patching type. %d", type);
OSFatal("Unexpected function patching type.");
return false;
}
*outAddress = result;
return true;
}
bool PatchedFunctionData::updateFunctionAddresses() {
uint32_t real_address;
if (type == FUNCTION_PATCHER_REPLACE_FOR_EXECUTABLE_BY_NAME || type == FUNCTION_PATCHER_REPLACE_FOR_EXECUTABLE_BY_ADDRESS) {
if (!getAddressForExecutable(&real_address)) {
return false;
}
} else {
if (!this->library) {
DEBUG_FUNCTION_LINE_ERR("library name was empty. This should never happen.");
OSFatal("library was empty. This should never happen. Check logs for more information.");
return false;
}
if (this->library == LIBRARY_OTHER) {
// Use the provided physical/effective address!
return true;
}
if (!this->functionName) {
DEBUG_FUNCTION_LINE_ERR("Function name was empty. This should never happen.");
OSFatal("Function name was empty. This should never happen. Check logs for more information.");
return false;
}
real_address = functionAddressProvider->getEffectiveAddressOfFunction(library.value(), 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; this->realEffectiveFunctionAddress = real_address;
auto physicalFunctionAddress = (uint32_t) OSEffectiveToPhysical(real_address); auto physicalFunctionAddress = (uint32_t) OSEffectiveToPhysical(real_address);
if (!physicalFunctionAddress) { if (!physicalFunctionAddress) {
DEBUG_FUNCTION_LINE_ERR("Error. Something is wrong with the physical address"); DEBUG_FUNCTION_LINE_ERR("Error. Something is wrong with the physical address");
OSFatal("Error. Something is wrong with the physical address");
return false; return false;
} }
this->realPhysicalFunctionAddress = physicalFunctionAddress; this->realPhysicalFunctionAddress = physicalFunctionAddress;
@ -82,15 +232,15 @@ void PatchedFunctionData::generateJumpToOriginal() {
uint32_t jumpToAddress = this->realEffectiveFunctionAddress + 4; uint32_t jumpToAddress = this->realEffectiveFunctionAddress + 4;
this->jumpToOriginal[0] = this->replacedInstruction;
if (((uint32_t) jumpToAddress & 0x01FFFFFC) != (uint32_t) jumpToAddress) { if (((uint32_t) jumpToAddress & 0x01FFFFFC) != (uint32_t) jumpToAddress) {
// We need to do a long jump // We need to do a long jump
this->jumpToOriginal[1] = 0x3d600000 | ((jumpToAddress >> 16) & 0x0000FFFF); // lis r11 ,0x1234 this->jumpToOriginal[0] = 0x3d600000 | ((jumpToAddress >> 16) & 0x0000FFFF); // lis r11 ,0x1234
this->jumpToOriginal[2] = 0x616b0000 | (jumpToAddress & 0x0000ffff); // ori r11 ,r11 ,0x5678 this->jumpToOriginal[1] = 0x616b0000 | (jumpToAddress & 0x0000ffff); // ori r11 ,r11 ,0x5678
this->jumpToOriginal[3] = 0x7d6903a6; // mtspr CTR ,r11 this->jumpToOriginal[2] = 0x7d6903a6; // mtspr CTR ,r11
this->jumpToOriginal[4] = 0x4e800420; // bctr this->jumpToOriginal[3] = this->replacedInstruction;
this->jumpToOriginal[4] = 0x4e800420; // bctr
} else { } else {
this->jumpToOriginal[0] = this->replacedInstruction;
this->jumpToOriginal[1] = 0x48000002 | (jumpToAddress & 0x01FFFFFC); this->jumpToOriginal[1] = 0x48000002 | (jumpToAddress & 0x01FFFFFC);
} }
@ -181,3 +331,35 @@ PatchedFunctionData::~PatchedFunctionData() {
this->jumpData = nullptr; this->jumpData = nullptr;
} }
} }
bool PatchedFunctionData::shouldBePatched() const {
if (type == FUNCTION_PATCHER_REPLACE_FOR_EXECUTABLE_BY_NAME || type == FUNCTION_PATCHER_REPLACE_FOR_EXECUTABLE_BY_ADDRESS) {
uint64_t curTitleId = OSGetTitleID();
if (!this->titleIds.contains(curTitleId)) {
DEBUG_FUNCTION_LINE_VERBOSE("Skip function patch. Patch is not for title %016llX", curTitleId);
return false;
}
auto mcpHandle = MCP_Open();
MCPTitleListType titleInfo;
int32_t res = -1;
if ((curTitleId & 0x0000000F00000000) == 0) {
res = MCP_GetTitleInfo(mcpHandle, curTitleId | 0x0000000E00000000, &titleInfo);
}
if (res != 0) {
res = MCP_GetTitleInfo(mcpHandle, curTitleId, &titleInfo);
}
MCP_Close(mcpHandle);
if (res != 0) {
DEBUG_FUNCTION_LINE_WARN("Failed to get title version of %016llX.", curTitleId);
OSFatal("Failed to get title version. This should not happen.\n"
"Please report this with a crash log.");
return false;
}
MCP_Close(mcpHandle);
if (titleInfo.titleVersion < titleVersionMin || titleInfo.titleVersion > titleVersionMax) {
DEBUG_FUNCTION_LINE("Skipping function patch. Title version does not match: Expected >= %d && <= %d. Real version: %d", titleVersionMin, titleVersionMax, titleInfo.titleVersion);
return false;
}
}
return true;
}

View File

@ -11,6 +11,7 @@
#include <function_patcher/fpatching_defines.h> #include <function_patcher/fpatching_defines.h>
#include <memory> #include <memory>
#include <optional> #include <optional>
#include <set>
#include <string> #include <string>
#include <utility> #include <utility>
@ -22,9 +23,16 @@ public:
explicit PatchedFunctionData(std::shared_ptr<FunctionAddressProvider> functionAddressProvider) : functionAddressProvider(std::move(functionAddressProvider)) { 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, static std::optional<std::shared_ptr<PatchedFunctionData>> make_shared_v2(std::shared_ptr<FunctionAddressProvider> functionAddressProvider,
function_replacement_data_t *replacementData, function_replacement_data_v2_t *replacementData,
MEMHeapHandle heapHandle); MEMHeapHandle heapHandle);
static std::optional<std::shared_ptr<PatchedFunctionData>> make_shared_v3(std::shared_ptr<FunctionAddressProvider> functionAddressProvider,
function_replacement_data_v3_t *replacementData,
MEMHeapHandle heapHandle);
bool allocateDataForJumps();
bool getAddressForExecutable(uint32_t *outAddress) const;
bool updateFunctionAddresses(); bool updateFunctionAddresses();
@ -32,29 +40,38 @@ public:
void generateReplacementJump(); void generateReplacementJump();
[[nodiscard]] bool shouldBePatched() const;
uint32_t getHandle() { uint32_t getHandle() {
return (uint32_t) this; return (uint32_t) this;
} }
uint32_t *jumpToOriginal{}; uint32_t *jumpToOriginal = {};
uint32_t *jumpData{}; uint32_t *jumpData = {};
uint32_t realEffectiveFunctionAddress{}; uint32_t realEffectiveFunctionAddress = {};
uint32_t realPhysicalFunctionAddress{}; uint32_t realPhysicalFunctionAddress = {};
uint32_t *realCallFunctionAddressPtr{}; uint32_t *realCallFunctionAddressPtr = {};
uint32_t replacementFunctionAddress{}; uint32_t replacementFunctionAddress = {};
uint32_t replacedInstruction{}; uint32_t replacedInstruction = {};
uint32_t replaceWithInstruction{}; uint32_t replaceWithInstruction = {};
uint32_t jumpDataSize = 15; uint32_t jumpDataSize = 15;
MEMHeapHandle heapHandle = nullptr; MEMHeapHandle heapHandle = nullptr;
bool isPatched{}; FunctionPatcherFunctionType type = {};
function_replacement_library_type_t library{}; std::set<uint64_t> titleIds;
FunctionPatcherTargetProcess targetProcess{}; uint16_t titleVersionMin = 0;
std::optional<std::string> functionName = {}; uint16_t titleVersionMax = 0xFFFF;
std::shared_ptr<FunctionAddressProvider> functionAddressProvider; std::optional<std::string> executableName = {};
uint32_t textOffset = 0;
bool isPatched = {};
std::optional<function_replacement_library_type_t> library = {};
FunctionPatcherTargetProcess targetProcess = {};
std::optional<std::string> functionName = {};
std::shared_ptr<FunctionAddressProvider> functionAddressProvider = {};
}; };

View File

@ -6,17 +6,30 @@
#include <vector> #include <vector>
#include <wums/exports.h> #include <wums/exports.h>
WUT_CHECK_OFFSET(function_replacement_data_v2_t, 0x00, VERSION);
WUT_CHECK_OFFSET(function_replacement_data_v3_t, 0x00, version);
FunctionPatcherStatus FPAddFunctionPatch(function_replacement_data_t *function_data, PatchedFunctionHandle *outHandle, bool *outHasBeenPatched) { FunctionPatcherStatus FPAddFunctionPatch(function_replacement_data_t *function_data, PatchedFunctionHandle *outHandle, bool *outHasBeenPatched) {
if (function_data == nullptr) { if (function_data == nullptr) {
DEBUG_FUNCTION_LINE_ERR("function_data was NULL"); DEBUG_FUNCTION_LINE_ERR("function_data was NULL");
return FUNCTION_PATCHER_RESULT_INVALID_ARGUMENT; return FUNCTION_PATCHER_RESULT_INVALID_ARGUMENT;
} }
if (function_data->VERSION != FUNCTION_REPLACEMENT_DATA_STRUCT_VERSION) {
if (function_data->version < 2 || function_data->version > 3) {
DEBUG_FUNCTION_LINE_ERR("Failed to patch function. struct version mismatch"); DEBUG_FUNCTION_LINE_ERR("Failed to patch function. struct version mismatch");
return FUNCTION_PATCHER_RESULT_UNSUPPORTED_STRUCT_VERSION; return FUNCTION_PATCHER_RESULT_UNSUPPORTED_STRUCT_VERSION;
} }
auto functionDataOpt = PatchedFunctionData::make_shared(gFunctionAddressProvider, function_data, gJumpHeapHandle); std::optional<std::shared_ptr<PatchedFunctionData>> functionDataOpt;
if (function_data->version == 2) {
functionDataOpt = PatchedFunctionData::make_shared_v2(gFunctionAddressProvider, (function_replacement_data_v2_t *) function_data, gJumpHeapHandle);
} else if (function_data->version == 3) {
functionDataOpt = PatchedFunctionData::make_shared_v3(gFunctionAddressProvider, (function_replacement_data_v3_t *) function_data, gJumpHeapHandle);
} else {
// Should never happen.
DEBUG_FUNCTION_LINE_ERR("Unknown function_replacement_data_t struct version");
OSFatal("Unknown function patching struct version. Update FunctionPatcherModule/Aroma.");
}
if (!functionDataOpt) { if (!functionDataOpt) {
return FUNCTION_PATCHER_RESULT_UNKNOWN_ERROR; return FUNCTION_PATCHER_RESULT_UNKNOWN_ERROR;

View File

@ -36,6 +36,10 @@ bool PatchFunction(std::shared_ptr<PatchedFunctionData> &patchedFunction) {
return true; return true;
} }
if (!patchedFunction->shouldBePatched()) {
return false;
}
// The addresses of a function might change every time with run another application. // The addresses of a function might change every time with run another application.
if (!patchedFunction->updateFunctionAddresses()) { if (!patchedFunction->updateFunctionAddresses()) {
return false; return false;

View File

@ -6,12 +6,14 @@
#include "utils/utils.h" #include "utils/utils.h"
#include <coreinit/memdefaultheap.h> #include <coreinit/memdefaultheap.h>
#include <coreinit/memexpheap.h> #include <coreinit/memexpheap.h>
#include <kernel/kernel.h>
#include <ranges> #include <ranges>
#include <set> #include <set>
#include <wums.h> #include <wums.h>
WUMS_MODULE_EXPORT_NAME("homebrew_functionpatcher"); WUMS_MODULE_EXPORT_NAME("homebrew_functionpatcher");
WUMS_MODULE_INIT_BEFORE_RELOCATION_DONE_HOOK(); WUMS_MODULE_INIT_BEFORE_RELOCATION_DONE_HOOK();
WUMS_DEPENDS_ON(homebrew_kernel);
void UpdateFunctionPointer() { void UpdateFunctionPointer() {
// We need the real MEMAllocFromDefaultHeapEx/MEMFreeToDefaultHeap function pointer to force-allocate memory on the default heap. // We need the real MEMAllocFromDefaultHeapEx/MEMFreeToDefaultHeap function pointer to force-allocate memory on the default heap.
@ -23,11 +25,11 @@ void UpdateFunctionPointer() {
} }
/* Memory allocation functions */ /* Memory allocation functions */
uint32_t *allocPtr, *freePtr; uint32_t *allocPtr, *freePtr;
if (OSDynLoad_FindExport(coreinitModule, true, "MEMAllocFromDefaultHeapEx", reinterpret_cast<void **>(&allocPtr)) != OS_DYNLOAD_OK) { if (OSDynLoad_FindExport(coreinitModule, OS_DYNLOAD_EXPORT_DATA, "MEMAllocFromDefaultHeapEx", reinterpret_cast<void **>(&allocPtr)) != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE_ERR("OSDynLoad_FindExport for MEMAllocFromDefaultHeapEx"); DEBUG_FUNCTION_LINE_ERR("OSDynLoad_FindExport for MEMAllocFromDefaultHeapEx");
OSFatal("FunctionPatcherModule: OSDynLoad_FindExport for MEMAllocFromDefaultHeapEx"); OSFatal("FunctionPatcherModule: OSDynLoad_FindExport for MEMAllocFromDefaultHeapEx");
} }
if (OSDynLoad_FindExport(coreinitModule, true, "MEMFreeToDefaultHeap", reinterpret_cast<void **>(&freePtr)) != OS_DYNLOAD_OK) { if (OSDynLoad_FindExport(coreinitModule, OS_DYNLOAD_EXPORT_DATA, "MEMFreeToDefaultHeap", reinterpret_cast<void **>(&freePtr)) != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE_ERR("OSDynLoad_FindExport for MEMFreeToDefaultHeap"); DEBUG_FUNCTION_LINE_ERR("OSDynLoad_FindExport for MEMFreeToDefaultHeap");
OSFatal("FunctionPatcherModule: OSDynLoad_FindExport for MEMFreeToDefaultHeap"); OSFatal("FunctionPatcherModule: OSDynLoad_FindExport for MEMFreeToDefaultHeap");
} }
@ -69,8 +71,44 @@ void CheckIfPatchedFunctionsAreStillInMemory() {
} }
} }
bool PatchInstruction(void *instr, uint32_t original, uint32_t replacement) {
uint32_t current = *(uint32_t *) instr;
if (current != original) {
return current == replacement;
}
KernelCopyData(OSEffectiveToPhysical((uint32_t) instr), OSEffectiveToPhysical((uint32_t) &replacement), sizeof(replacement));
DCFlushRange(instr, 4);
ICInvalidateRange(instr, 4);
current = *(uint32_t *) instr;
return true;
}
bool PatchDynLoadFunctions() {
uint32_t *patch1 = ((uint32_t *) &OSDynLoad_GetNumberOfRPLs) + 6;
uint32_t *patch2 = ((uint32_t *) &OSDynLoad_GetRPLInfo) + 22;
if (!PatchInstruction(patch1, 0x41820038 /* beq +38 */, 0x60000000 /*nop*/)) {
return false;
}
if (!PatchInstruction(patch2, 0x41820100 /* beq +100 */, 0x60000000 /*nop*/)) {
return false;
}
return true;
}
WUMS_INITIALIZE() { WUMS_INITIALIZE() {
UpdateFunctionPointer(); UpdateFunctionPointer();
initLogging();
if (!PatchDynLoadFunctions()) {
DEBUG_FUNCTION_LINE_ERR("Failed to patch OSDynLoad_GetRPLInfo or OSDynLoad_GetNumberOfRPLs");
OSFatal("Failed to patch OSDynLoad_GetRPLInfo or OSDynLoad_GetNumberOfRPLs");
}
memset(gJumpHeapData, 0, JUMP_HEAP_DATA_SIZE); memset(gJumpHeapData, 0, JUMP_HEAP_DATA_SIZE);
gJumpHeapHandle = MEMCreateExpHeapEx((void *) (gJumpHeapData), JUMP_HEAP_DATA_SIZE, 1); gJumpHeapHandle = MEMCreateExpHeapEx((void *) (gJumpHeapData), JUMP_HEAP_DATA_SIZE, 1);
@ -100,7 +138,7 @@ void notify_callback(OSDynLoad_Module module,
auto library = gFunctionAddressProvider->getTypeForHandle(module); auto library = gFunctionAddressProvider->getTypeForHandle(module);
if (library != LIBRARY_OTHER) { if (library != LIBRARY_OTHER) {
for (auto &cur : gPatchedFunctions) { for (auto &cur : gPatchedFunctions) {
if (cur->library == library) { if (cur->type == FUNCTION_PATCHER_REPLACE_BY_LIB_OR_ADDRESS && cur->library.has_value() && cur->library == library) {
cur->isPatched = false; cur->isPatched = false;
} }
} }

View File

@ -28,10 +28,11 @@ public:
typedef void (*Callback)(CThread *thread, void *arg); typedef void (*Callback)(CThread *thread, void *arg);
//! constructor //! constructor
explicit CThread(int32_t iAttr, int32_t iPriority = 16, int32_t iStackSize = 0x8000, CThread::Callback callback = nullptr, void *callbackArg = nullptr) explicit CThread(int32_t iAttr, int32_t iPriority = 16, int32_t stacksize = 0x8000, CThread::Callback callback = nullptr, void *callbackArg = nullptr)
: pThread(nullptr), pThreadStack(nullptr), pCallback(callback), pCallbackArg(callbackArg) { : pThread(nullptr), pThreadStack(nullptr), pCallback(callback), pCallbackArg(callbackArg) {
//! save attribute assignment //! save attribute assignment
iAttributes = iAttr; iAttributes = iAttr;
iStackSize = stacksize;
//! allocate the thread on the default Cafe OS heap //! allocate the thread on the default Cafe OS heap
pThread = (OSThread *) gMEMAllocFromDefaultHeapExForThreads(sizeof(OSThread), 0x10); pThread = (OSThread *) gMEMAllocFromDefaultHeapExForThreads(sizeof(OSThread), 0x10);
//! allocate the stack on the default Cafe OS heap //! allocate the stack on the default Cafe OS heap
@ -123,11 +124,15 @@ public:
} }
OSJoinThread(pThread, nullptr); OSJoinThread(pThread, nullptr);
} }
// Some games (e.g. Minecraft) expect the default heap to be empty.
// Make sure to clean up the memory after using it
//! free the thread stack buffer //! free the thread stack buffer
if (pThreadStack) { if (pThreadStack) {
memset(pThreadStack, 0, iStackSize);
gMEMFreeToDefaultHeapForThreads(pThreadStack); gMEMFreeToDefaultHeapForThreads(pThreadStack);
} }
if (pThread) { if (pThread) {
memset(pThread, 0, sizeof(OSThread));
gMEMFreeToDefaultHeapForThreads(pThread); gMEMFreeToDefaultHeapForThreads(pThread);
} }
pThread = nullptr; pThread = nullptr;
@ -151,6 +156,7 @@ private:
return 0; return 0;
} }
uint32_t iStackSize;
int32_t iAttributes; int32_t iAttributes;
OSThread *pThread; OSThread *pThread;
uint8_t *pThreadStack; uint8_t *pThreadStack;

View File

@ -0,0 +1,94 @@
#include "KernelFindExport.h"
#include <coreinit/cache.h>
#include <elf.h>
#include <kernel/kernel.h>
#include <string_view>
#define KernelGetLoadedRPL ((LOADED_RPL * (*) (uint32_t))(0xfff13524))
#define KernelGetRAMPID ((int32_t(*)(uint32_t *))(0xfff10ea0))
#define KernelSetRAMPID ((uint32_t(*)(uint32_t, uint32_t))(0xfff10cc0))
#define ELF_ST_TYPE(i) ((i) &0xf)
/*
* Based on the findClosestSymbol implementation. See:
* https://github.com/decaf-emu/decaf-emu/blob/e8c9af3057a7d94f6e970406eb1ba1c37c87b4d1/src/libdecaf/src/cafe/kernel/cafe_kernel_loader.cpp#L251
*/
uint32_t FindExportKernel(const char *rplName, const char *functionName) {
if (rplName == nullptr || functionName == nullptr) {
return 0;
}
uint32_t result = 0;
uint32_t currentRamPID;
auto err = KernelGetRAMPID(&currentRamPID);
if (err != -1) {
// Switch to loader address space view.
KernelSetRAMPID(err, 2);
for (auto rpl = KernelGetLoadedRPL(0); rpl != nullptr; rpl = rpl->nextLoadedRpl) {
if (std::string_view(rpl->moduleNameBuffer) != rplName) {
continue;
}
uint32_t textSectionIndex = 0xFFFFFFFF;
for (auto i = 0u; i < rpl->elfHeader.shnum; ++i) {
auto sectionAddress = rpl->sectionAddressBuffer[i];
if (!sectionAddress) {
continue;
}
auto sectionHeader = (ElfSectionHeader *) (((uint32_t) (rpl->sectionHeaderBuffer)) + rpl->elfHeader.shentsize * i);
if (auto shstrndx = rpl->elfHeader.shstrndx) {
auto shStrSection = (char *) (rpl->sectionAddressBuffer[shstrndx]);
auto sectionName = (shStrSection + sectionHeader->name);
if (std::string_view(sectionName) == ".text") {
textSectionIndex = i;
}
}
}
for (auto i = 0u; i < rpl->elfHeader.shnum; ++i) {
auto sectionAddress = rpl->sectionAddressBuffer[i];
if (!sectionAddress) {
continue;
}
auto sectionHeader = (ElfSectionHeader *) (((uint32_t) (rpl->sectionHeaderBuffer)) + rpl->elfHeader.shentsize * i);
if (sectionHeader->type == SHT_SYMTAB) {
auto strTab = rpl->sectionAddressBuffer[sectionHeader->link];
auto symTabEntSize = sectionHeader->entsize ? static_cast<uint32_t>(sectionHeader->entsize) : sizeof(ElfSymbol);
auto numSymbols = sectionHeader->size / symTabEntSize;
bool found = false;
for (auto j = 0u; j < numSymbols; ++j) {
auto symbol = (ElfSymbol *) (sectionAddress + j * symTabEntSize);
if (symbol->shndx == textSectionIndex && ELF_ST_TYPE(symbol->info) == STT_FUNC) {
auto symbolName = (const char *) (strTab + symbol->name);
if (std::string_view(symbolName) == functionName) {
result = symbol->value;
found = true;
break;
}
}
}
if (found) {
break;
}
}
}
break;
}
// Switch back to "old" space address view
KernelSetRAMPID(err, currentRamPID);
}
return result;
}
extern "C" uint32_t SC_0x51(const char *rplname, const char *functionName);
uint32_t KernelFindExport(const std::string_view &rplName, const std::string_view &functionName) {
KernelPatchSyscall(0x51, (uint32_t) &FindExportKernel);
OSMemoryBarrier();
if (rplName.ends_with(".rpx") || rplName.ends_with(".rpl")) {
auto pureRPLName = std::string(rplName).substr(0, rplName.length() - 4);
return SC_0x51(pureRPLName.c_str(), functionName.data());
}
return SC_0x51(rplName.data(), functionName.data());
}

View File

@ -0,0 +1,151 @@
#pragma once
#include <cstdint>
#include <string>
#include <wut.h>
/*
* https://github.com/decaf-emu/decaf-emu/blob/e8c9af3057a7d94f6e970406eb1ba1c37c87b4d1/src/libdecaf/src/cafe/loader/cafe_loader_rpl.h#L150
*/
struct ElfHeader {
uint8_t magic[4]; // File identification.
uint8_t fileClass; // File class.
uint8_t encoding; // Data encoding.
uint8_t elfVersion; // File version.
uint8_t abi; // OS/ABI identification.
uint8_t abiVersion; // OS/ABI version.
uint8_t pad[7];
uint16_t type; // Type of file (ET_*)
uint16_t machine; // Required architecture for this file (EM_*)
uint32_t version; // Must be equal to 1
uint32_t entry; // Address to jump to in order to start program
uint32_t phoff; // Program header table's file offset, in bytes
uint32_t shoff; // Section header table's file offset, in bytes
uint32_t flags; // Processor-specific flags
uint16_t ehsize; // Size of ELF header, in bytes
uint16_t phentsize; // Size of an entry in the program header table
uint16_t phnum; // Number of entries in the program header table
uint16_t shentsize; // Size of an entry in the section header table
uint16_t shnum; // Number of entries in the section header table
uint16_t shstrndx; // Sect hdr table index of sect name string table
};
WUT_CHECK_OFFSET(ElfHeader, 0x00, magic);
WUT_CHECK_OFFSET(ElfHeader, 0x04, fileClass);
WUT_CHECK_OFFSET(ElfHeader, 0x05, encoding);
WUT_CHECK_OFFSET(ElfHeader, 0x06, elfVersion);
WUT_CHECK_OFFSET(ElfHeader, 0x07, abi);
WUT_CHECK_OFFSET(ElfHeader, 0x08, abiVersion);
WUT_CHECK_OFFSET(ElfHeader, 0x10, type);
WUT_CHECK_OFFSET(ElfHeader, 0x12, machine);
WUT_CHECK_OFFSET(ElfHeader, 0x14, version);
WUT_CHECK_OFFSET(ElfHeader, 0x18, entry);
WUT_CHECK_OFFSET(ElfHeader, 0x1C, phoff);
WUT_CHECK_OFFSET(ElfHeader, 0x20, shoff);
WUT_CHECK_OFFSET(ElfHeader, 0x24, flags);
WUT_CHECK_OFFSET(ElfHeader, 0x28, ehsize);
WUT_CHECK_OFFSET(ElfHeader, 0x2A, phentsize);
WUT_CHECK_OFFSET(ElfHeader, 0x2C, phnum);
WUT_CHECK_OFFSET(ElfHeader, 0x2E, shentsize);
WUT_CHECK_OFFSET(ElfHeader, 0x30, shnum);
WUT_CHECK_OFFSET(ElfHeader, 0x32, shstrndx);
WUT_CHECK_SIZE(ElfHeader, 0x34);
/*
* https://github.com/decaf-emu/decaf-emu/blob/e8c9af3057a7d94f6e970406eb1ba1c37c87b4d1/src/libdecaf/src/cafe/loader/cafe_loader_rpl.h#L216
*/
struct ElfSectionHeader {
//! Section name (index into string table)
uint32_t name;
//! Section type (SHT_*)
uint32_t type;
//! Section flags (SHF_*)
uint32_t flags;
//! Address where section is to be loaded
uint32_t addr;
//! File offset of section data, in bytes
uint32_t offset;
//! Size of section, in bytes
uint32_t size;
//! Section type-specific header table index link
uint32_t link;
//! Section type-specific extra information
uint32_t info;
//! Section address alignment
uint32_t addralign;
//! Size of records contained within the section
uint32_t entsize;
};
WUT_CHECK_OFFSET(ElfSectionHeader, 0x00, name);
WUT_CHECK_OFFSET(ElfSectionHeader, 0x04, type);
WUT_CHECK_OFFSET(ElfSectionHeader, 0x08, flags);
WUT_CHECK_OFFSET(ElfSectionHeader, 0x0C, addr);
WUT_CHECK_OFFSET(ElfSectionHeader, 0x10, offset);
WUT_CHECK_OFFSET(ElfSectionHeader, 0x14, size);
WUT_CHECK_OFFSET(ElfSectionHeader, 0x18, link);
WUT_CHECK_OFFSET(ElfSectionHeader, 0x1C, info);
WUT_CHECK_OFFSET(ElfSectionHeader, 0x20, addralign);
WUT_CHECK_OFFSET(ElfSectionHeader, 0x24, entsize);
WUT_CHECK_SIZE(ElfSectionHeader, 0x28);
/*
* https://github.com/decaf-emu/decaf-emu/blob/e8c9af3057a7d94f6e970406eb1ba1c37c87b4d1/src/libdecaf/src/cafe/loader/cafe_loader_rpl.h#L260
*/
struct ElfSymbol {
//! Symbol name (index into string table)
uint32_t name;
//! Value or address associated with the symbol
uint32_t value;
//! Size of the symbol
uint32_t size;
//! Symbol's type and binding attributes
uint8_t info;
//! Must be zero; reserved
uint8_t other;
//! Which section (header table index) it's defined in (SHN_*)
uint16_t shndx;
};
WUT_CHECK_OFFSET(ElfSymbol, 0x00, name);
WUT_CHECK_OFFSET(ElfSymbol, 0x04, value);
WUT_CHECK_OFFSET(ElfSymbol, 0x08, size);
WUT_CHECK_OFFSET(ElfSymbol, 0x0C, info);
WUT_CHECK_OFFSET(ElfSymbol, 0x0D, other);
WUT_CHECK_OFFSET(ElfSymbol, 0x0E, shndx);
WUT_CHECK_SIZE(ElfSymbol, 0x10);
/*
* https://github.com/decaf-emu/decaf-emu/blob/6feb1be1db3938e6da2d4a65fc0a7a8599fc8dd6/src/libdecaf/src/cafe/loader/cafe_loader_loaded_rpl.h#L26
*/
struct LOADED_RPL {
WUT_UNKNOWN_BYTES(0x08);
char *moduleNameBuffer;
WUT_UNKNOWN_BYTES(0x10);
ElfHeader elfHeader;
void *sectionHeaderBuffer;
WUT_UNKNOWN_BYTES(0xA0);
uint32_t *sectionAddressBuffer;
WUT_UNKNOWN_BYTES(0x1C);
LOADED_RPL *nextLoadedRpl;
};
WUT_CHECK_OFFSET(LOADED_RPL, 0x08, moduleNameBuffer);
WUT_CHECK_OFFSET(LOADED_RPL, 0x1C, elfHeader);
WUT_CHECK_OFFSET(LOADED_RPL, 0x50, sectionHeaderBuffer);
WUT_CHECK_OFFSET(LOADED_RPL, 0xF4, sectionAddressBuffer);
WUT_CHECK_OFFSET(LOADED_RPL, 0x114, nextLoadedRpl);
WUT_CHECK_SIZE(LOADED_RPL, 0x118);
uint32_t KernelFindExport(const std::string_view &rplName, const std::string_view &functioName);

View File

@ -5,7 +5,7 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#define MODULE_VERSION "v0.1" #define MODULE_VERSION "v0.2.3"
#define MODULE_VERSION_FULL MODULE_VERSION MODULE_VERSION_EXTRA #define MODULE_VERSION_FULL MODULE_VERSION MODULE_VERSION_EXTRA
#define JUMP_HEAP_DATA_SIZE (32 * 1024) #define JUMP_HEAP_DATA_SIZE (32 * 1024)
@ -17,4 +17,4 @@ extern std::mutex gPatchedFunctionsMutex;
extern std::vector<std::shared_ptr<PatchedFunctionData>> gPatchedFunctions; extern std::vector<std::shared_ptr<PatchedFunctionData>> gPatchedFunctions;
extern void *(*gMEMAllocFromDefaultHeapExForThreads)(uint32_t size, int align); extern void *(*gMEMAllocFromDefaultHeapExForThreads)(uint32_t size, int align);
extern void (*gMEMFreeToDefaultHeapForThreads)(void *ptr); extern void (*gMEMFreeToDefaultHeapForThreads)(void *ptr);

5
source/utils/syscall.s Normal file
View File

@ -0,0 +1,5 @@
.global SC_0x51
SC_0x51:
li %r0, 0x5100
sc
blr