Rewrite module loading to load some module into custom memory mapped heap.

This commit is contained in:
Maschell 2026-03-08 21:10:31 +01:00
parent b024464ca7
commit a94111fa36
67 changed files with 3111 additions and 2199 deletions

View File

@ -42,7 +42,7 @@ IndentPPDirectives: None
IndentWidth: 4
KeepEmptyLinesAtTheStartOfBlocks: true
MaxEmptyLinesToKeep: 2
NamespaceIndentation: All
NamespaceIndentation: None
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PointerAlignment: Right

View File

@ -1,5 +1,5 @@
#include "kernel.h"
#include "../wumsloader/src/globals.h"
#include "../wumsloader/src/defines.h"
#include "ElfUtils.h"
#include "wumsloader_elf.h"
#include <coreinit/cache.h>

View File

@ -1,7 +1,9 @@
#include "../wumsloader/src/globals.h"
#include "../wumsloader/src/defines.h"
#include "kernel.h"
#include "utils/logger.h"
#include <nn/act/client_cpp.h>
#include <string>
#include <sysapp/launch.h>
extern "C" void __fini();
@ -20,13 +22,10 @@ int main(int argc, char **argv) {
"See https://wiiu.hacks.guide/ for instructions on how to update!");
}
memcpy(MEMORY_REGION_USABLE_MEM_REGION_END_VALUE_PTR, &argv[3], sizeof(uint32_t));
memcpy(reinterpret_cast<uint32_t *>(MEMORY_REGION_USABLE_MEM_REGION_END_VALUE_PTR), &argv[3], sizeof(uint32_t));
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#pragma GCC diagnostic ignored "-Wstringop-overflow="
strncpy(ENVRIONMENT_STRING, basePath.c_str(), ENVIRONMENT_PATH_LENGTH - 1);
#pragma GCC diagnostic pop
auto envBuffer = getEnvironmentStringBuffer();
snprintf(envBuffer.data(), envBuffer.size(), "%s", basePath.c_str());
DEBUG_FUNCTION_LINE("Setup wumsloader");
SetupWUMSLoader();

2
wumsloader/.clangd Normal file
View File

@ -0,0 +1,2 @@
CompileFlags:
Add: [-m32]

35
wumsloader/src/defines.h Normal file
View File

@ -0,0 +1,35 @@
#pragma once
#include <cstdint>
#include <span>
#include <string_view>
constexpr std::uint32_t MEMORY_REGION_START = 0x00800000;
constexpr std::uint32_t MEMORY_REGION_SIZE = 0x00800000;
// Maximum size of the wumsloader, needs to match the one defined in link.ld
constexpr std::uint32_t MEMORY_REGION_RELOCATOR_SIZE = 0x67000;
// Length of the EnvironmentPath.
constexpr std::uint32_t MEMORY_REGION_USABLE_MEM_REGION_END_LENGTH = sizeof(std::uint32_t); // 0x04
constexpr std::uint32_t MEMORY_REGION_ENVIRONMENT_PATH_LENGTH = 0x100;
constexpr std::uint32_t MEMORY_REGION_ENVIRONMENT_STRING_ADRR = MEMORY_REGION_START + MEMORY_REGION_RELOCATOR_SIZE;
constexpr std::uint32_t MEMORY_REGION_USABLE_MEM_REGION_END_VALUE_PTR = MEMORY_REGION_ENVIRONMENT_STRING_ADRR + MEMORY_REGION_ENVIRONMENT_PATH_LENGTH;
#define MEMORY_REGION_USABLE_MEM_REGION_END_VALUE (*MEMORY_REGION_USABLE_MEM_REGION_END_VALUE_PTR)
// Technically we overwrite the CustomRPXLoader that is still loaded at 0x00800000...
// We can get away with it because the EnvironmentLoader exits instead of returning to the CustomRPXLoader.
constexpr std::uint32_t MEMORY_REGION_USABLE_HEAP_START = MEMORY_REGION_USABLE_MEM_REGION_END_VALUE_PTR + MEMORY_REGION_USABLE_MEM_REGION_END_LENGTH;
constexpr std::uint32_t WUMS_LOADER_SETUP_MAGIC_WORD = 0x13371337;
inline std::span<uint8_t> getMemoryForHeap() {
return std::span(reinterpret_cast<uint8_t *>(MEMORY_REGION_USABLE_HEAP_START), (*reinterpret_cast<uint32_t *>(MEMORY_REGION_USABLE_MEM_REGION_END_VALUE_PTR)) - MEMORY_REGION_USABLE_HEAP_START);
}
inline std::span<char> getEnvironmentStringBuffer() {
return std::span(
reinterpret_cast<char *>(MEMORY_REGION_ENVIRONMENT_STRING_ADRR),
MEMORY_REGION_ENVIRONMENT_PATH_LENGTH);
}

View File

@ -1,28 +1,28 @@
#include "entry.h"
#include "fs/DirList.h"
#include "defines.h"
#include "globals.h"
#include "module/ModuleDataFactory.h"
#include "module/HooksManagement.h"
#include "module/ModuleDataPersistence.h"
#include "utils/ElfUtils.h"
#include "utils/RelocationUtils.h"
#include "utils/dynamic.h"
#include "utils/hooks.h"
#include "utils/logger.h"
#include "utils/utils.h"
#include "module/ModuleManagement.h"
#include "version.h"
#include "module/RelocationUtils.h"
#include "utils/SegmentedTimer.h"
#include "utils/dynamic.h"
#include "utils/logger.h"
#include <coreinit/debug.h>
#include <coreinit/interrupts.h>
#include <coreinit/kernel.h>
#include <coreinit/memexpheap.h>
#include <coreinit/thread.h>
#include <string>
#include <cstdint>
#include <list>
#define VERSION "v0.2.9"
void CallInitHooksForModule(const std::shared_ptr<ModuleData> &curModule);
bool CheckModulesByDependencies(const std::vector<std::shared_ptr<ModuleData>> &loadedModules);
std::vector<std::shared_ptr<ModuleData>> OrderModulesByDependencies(const std::vector<std::shared_ptr<ModuleData>> &loadedModules);
#define VERSION "v0.3.0"
void wipeStack(void *stackEnd) {
volatile char current_stack_marker = 0;
@ -50,7 +50,8 @@ extern "C" int _start(int argc, char **argv) {
static uint8_t ucSetupRequired = 1;
if (ucSetupRequired) {
gHeapHandle = MEMCreateExpHeapEx((void *) (MEMORY_REGION_USABLE_HEAP_START), MEMORY_REGION_USABLE_HEAP_END - MEMORY_REGION_USABLE_HEAP_START, 1);
const auto heapSpan = getMemoryForHeap();
gHeapHandle = MEMCreateExpHeapEx(heapSpan.data(), heapSpan.size_bytes(), 1);
if (!gHeapHandle) {
OSFatal("Failed to alloc heap");
}
@ -85,27 +86,10 @@ extern "C" int _start(int argc, char **argv) {
return ((int (*)(int, char **))(*(unsigned int *) 0x1005E040))(argc, argv);
}
void SaveLoadedRPLsInGlobalInformation(module_information_t *globalInformation,
std::map<std::string, OSDynLoad_Module> &usedRPls) {
// free previous allocations.
if (globalInformation->acquired_rpls) {
free(globalInformation->acquired_rpls);
}
globalInformation->number_acquired_rpls = usedRPls.size();
globalInformation->acquired_rpls = (uint32_t *) malloc(usedRPls.size() * sizeof(uint32_t));
if (!globalInformation->acquired_rpls) {
OSFatal("Failed to allocate memory");
}
uint32_t i = 0;
for (auto &rpl : usedRPls) {
globalInformation->acquired_rpls[i] = (uint32_t) rpl.second;
++i;
}
}
extern "C" uint32_t OSGetBootPMFlags(void);
void doStart(int argc, char **argv) {
using namespace WUMSLoader;
uint32_t bootFlags = OSGetBootPMFlags();
/*
* Bit 13 - OS relaunch (OSForceFullRelaunch()).
@ -119,6 +103,11 @@ void doStart(int argc, char **argv) {
OSReport("Running WUMSLoader " VERSION VERSION_EXTRA "\n");
#ifdef WUMS_ENABLE_PROFILING
Utils::SegmentedTimer mainTimer("WUMSLoader Main");
Utils::SegmentedTimer::globalTimer = &mainTimer;
#endif
gUsedRPLs.clear();
// If an allocated rpl was not released properly (e.g. if something else calls OSDynload_Acquire without releasing it)
// memory gets leaked. Let's clean this up!
@ -129,226 +118,32 @@ void doStart(int argc, char **argv) {
gAllocatedAddresses.clear();
if (!gInitCalled) {
gInitCalled = 1;
PROFILE_BLOCK("Init modules");
gInitCalled = 1;
gModuleInformation = Modules::PersistedModuleData::create();
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstringop-overread"
std::string basePath = ENVRIONMENT_STRING;
#pragma GCC diagnostic pop
DEBUG_FUNCTION_LINE("We need to load the modules. basePath %s", basePath.c_str());
DirList modules(basePath + "/modules", ".wms", DirList::Files, 1);
modules.SortList();
for (int i = 0; i < modules.GetFilecount(); i++) {
std::string_view asView(modules.GetFilename(i));
if (asView.starts_with('.') || asView.starts_with('_')) {
DEBUG_FUNCTION_LINE_WARN("Skip file %s", modules.GetFilename(i));
continue;
}
DEBUG_FUNCTION_LINE("Loading module %s", modules.GetFilepath(i));
if (auto moduleData = ModuleDataFactory::load(modules.GetFilepath(i))) {
DEBUG_FUNCTION_LINE("Successfully loaded %s", modules.GetFilepath(i));
gLoadedModules.push_back(std::move(moduleData.value()));
} else {
DEBUG_FUNCTION_LINE_ERR("Failed to load %s", modules.GetFilepath(i));
}
}
gModuleInformation = {.version = MODULE_INFORMATION_VERSION};
ModuleDataPersistence::saveModuleData(&gModuleInformation, gLoadedModules);
// Order modules list by dependencies.
gLoadedModules = OrderModulesByDependencies(gLoadedModules);
{
// make sure the plugin backend module is at the end.
const auto it = std::ranges::find_if(gLoadedModules,
[](auto &cur) { return std::string_view(cur->getExportName()) == "homebrew_wupsbackend"; });
if (it != gLoadedModules.end()) {
auto module = *it;
gLoadedModules.erase(it);
gLoadedModules.push_back(module);
}
}
bool aromaBaseModuleLoaded = false;
for (auto &curModule : gLoadedModules) {
if (std::string_view(curModule->getExportName()) == "homebrew_basemodule") {
DEBUG_FUNCTION_LINE_VERBOSE("We have AromaBaseModule!");
aromaBaseModuleLoaded = true;
break;
}
}
// Make sure aromaBaseModuleLoaded is loaded when WUMS_HOOK_APPLICATION_ENDS and WUMS_HOOK_FINI_WUT are called
for (auto &curModule : gLoadedModules) {
for (auto &curHook : curModule->getHookDataList()) {
if (curHook->getType() == WUMS_HOOK_APPLICATION_ENDS || curHook->getType() == WUMS_HOOK_FINI_WUT_DEVOPTAB) {
if (!aromaBaseModuleLoaded) {
DEBUG_FUNCTION_LINE_ERR("%s requires module homebrew_basemodule", curModule->getExportName().c_str());
OSFatal("module requires module homebrew_basemodule");
}
}
}
}
// Make sure the base module is called first of the "regular" modules (except the "InitBeforeRelocationDoneHook" hooks)
if (aromaBaseModuleLoaded) {
// Create a copy of all modules which do not call init before RelocationDoneHook
std::list<std::shared_ptr<ModuleData>> gLoadedModulesCopy;
for (auto &curModule : gLoadedModules) {
if (!curModule->isInitBeforeRelocationDoneHook()) {
gLoadedModulesCopy.push_back(curModule);
}
}
// move homebrew_basemodule to the front
const auto it = std::ranges::find_if(gLoadedModulesCopy,
[](auto &cur) { return std::string_view(cur->getExportName()) == "homebrew_basemodule"; });
if (it != gLoadedModulesCopy.end()) {
auto module = *it;
gLoadedModulesCopy.erase(it);
gLoadedModulesCopy.push_front(module);
}
// Move all modules which do not call init before RelocationDoneHook to the end, but keep homebrew_basemodule at the front.
for (auto &curModule : gLoadedModulesCopy) {
if (remove_first_if(gLoadedModules, [curModule](auto &cur) { return cur->getExportName() == curModule->getExportName(); })) {
gLoadedModules.push_back(curModule);
}
}
}
// Check if dependencies are still resolved.
if (!CheckModulesByDependencies(gLoadedModules)) {
OSFatal("Module order is impossible");
}
#ifdef VERBOSE_DEBUG
DEBUG_FUNCTION_LINE_VERBOSE("Final order of modules");
for (auto &curModule : gLoadedModules) {
DEBUG_FUNCTION_LINE_VERBOSE("%s", curModule->getExportName().c_str());
}
#endif
DEBUG_FUNCTION_LINE_VERBOSE("Resolve relocations without loading additonal rpls");
ResolveRelocations(gLoadedModules, true, gUsedRPLs);
for (auto &curModule : gLoadedModules) {
if (curModule->isInitBeforeRelocationDoneHook()) {
CallInitHooksForModule(curModule);
}
}
// Hacky workaround for modules to allow register a custom rpl allocator function
CallHook(gLoadedModules, WUMS_HOOK_GET_CUSTOM_RPL_ALLOCATOR);
// Now we can load unloaded rpls.
ResolveRelocations(gLoadedModules, false, gUsedRPLs);
DEBUG_FUNCTION_LINE_VERBOSE("Call Relocations done hook");
CallHook(gLoadedModules, WUMS_HOOK_RELOCATIONS_DONE);
for (auto &curModule : gLoadedModules) {
if (!curModule->isInitBeforeRelocationDoneHook()) {
CallInitHooksForModule(curModule);
}
}
std::string modulesBasePath = std::string(getEnvironmentStringBuffer().data()) + "/modules";
DEBUG_FUNCTION_LINE("Trying to load module from: %s", modulesBasePath.c_str());
gLoadedModules = Modules::ModuleManagement::loadAndLinkModules(modulesBasePath, gUsedRPLs, gHeapHandle, *gModuleInformation);
} else {
PROFILE_BLOCK("Relocate modules");
DEBUG_FUNCTION_LINE("Resolve relocations and load unloaded rpls functions");
CallHook(gLoadedModules, WUMS_HOOK_CLEAR_ALLOCATED_RPL_MEMORY);
ResolveRelocations(gLoadedModules, false, gUsedRPLs);
CallHook(gLoadedModules, WUMS_HOOK_RELOCATIONS_DONE);
Modules::HooksManagement::CallHook(gLoadedModules, WUMS_HOOK_CLEAR_ALLOCATED_RPL_MEMORY);
Modules::RelocationUtils::ResolveRelocations(gLoadedModules, Modules::RelocationUtils::ExternalRPLLoadingStrategy::LOAD_EXTERNAL_RPLS, gUsedRPLs);
Modules::HooksManagement::CallHook(gLoadedModules, WUMS_HOOK_RELOCATIONS_DONE);
}
SaveLoadedRPLsInGlobalInformation(&gModuleInformation, gUsedRPLs);
DEBUG_FUNCTION_LINE_VERBOSE("MEMGetTotalFreeSizeForExpHeap(gHeapHandle): %f KiB", MEMGetTotalFreeSizeForExpHeap(gHeapHandle) / 1024.0);
DEBUG_FUNCTION_LINE_VERBOSE("MEMGetAllocatableSizeForExpHeapEx(gHeapHandle,0x40): %f KiB", MEMGetAllocatableSizeForExpHeapEx(gHeapHandle, 0x40) / 1024.0);
CallHook(gLoadedModules, WUMS_HOOK_INIT_WUT_DEVOPTAB);
CallHook(gLoadedModules, WUMS_HOOK_INIT_WUT_SOCKETS);
CallHook(gLoadedModules, WUMS_HOOK_APPLICATION_STARTS);
CallHook(gLoadedModules, WUMS_HOOK_ALL_APPLICATION_STARTS_DONE);
gModuleInformation->setUsedRPLsList(gUsedRPLs);
Modules::HooksManagement::CallHook(gLoadedModules, WUMS_HOOK_INIT_WUT_DEVOPTAB);
Modules::HooksManagement::CallHook(gLoadedModules, WUMS_HOOK_INIT_WUT_SOCKETS);
Modules::HooksManagement::CallHook(gLoadedModules, WUMS_HOOK_APPLICATION_STARTS);
Modules::HooksManagement::CallHook(gLoadedModules, WUMS_HOOK_ALL_APPLICATION_STARTS_DONE);
deinitLogging();
fini_wut();
}
void CallInitHooksForModule(const std::shared_ptr<ModuleData> &curModule) {
CallHook(curModule, WUMS_HOOK_INIT_WUT_THREAD);
CallHook(curModule, WUMS_HOOK_INIT_WUT_MALLOC);
CallHook(curModule, WUMS_HOOK_INIT_WUT_NEWLIB);
CallHook(curModule, WUMS_HOOK_INIT_WUT_STDCPP);
CallHook(curModule, WUMS_HOOK_INIT_WUT_DEVOPTAB);
CallHook(curModule, WUMS_HOOK_INIT_WUT_SOCKETS);
CallHook(curModule, WUMS_HOOK_INIT_WRAPPER, !curModule->isSkipInitFini());
CallHook(curModule, WUMS_HOOK_INIT);
}
bool CheckModulesByDependencies(const std::vector<std::shared_ptr<ModuleData>> &loadedModules) {
std::set<std::string> loaderModuleNames;
for (auto const &curModule : loadedModules) {
DEBUG_FUNCTION_LINE_VERBOSE("Check if we can load %s", curModule->getExportName().c_str());
for (auto &curRPL : curModule->getDependencies()) {
if (!curRPL.starts_with("homebrew")) {
continue;
}
if (curRPL == "homebrew_wupsbackend") {
OSFatal("Error: module depends on homebrew_wupsbackend, this is not supported");
}
if (!loaderModuleNames.contains(curRPL)) {
DEBUG_FUNCTION_LINE_VERBOSE("%s requires %s which is not loaded yet", curModule->getExportName().c_str(), curRPL.c_str());
return false;
} else {
DEBUG_FUNCTION_LINE_VERBOSE("Used %s, but it's already loaded", curRPL.c_str());
}
}
loaderModuleNames.insert(curModule->getExportName());
}
return true;
}
std::vector<std::shared_ptr<ModuleData>> OrderModulesByDependencies(const std::vector<std::shared_ptr<ModuleData>> &loadedModules) {
std::vector<std::shared_ptr<ModuleData>> finalOrder;
std::set<std::string> loadedModulesExportNames;
std::set<uint32_t> loadedModulesEntrypoints;
while (true) {
bool canBreak = true;
bool weDidSomething = false;
for (auto const &curModule : loadedModules) {
if (loadedModulesEntrypoints.contains(curModule->getEntrypoint())) {
// DEBUG_FUNCTION_LINE("%s [%08X] is already loaded" curModule->getExportName().c_str(), curModule->getEntrypoint());
continue;
}
canBreak = false;
DEBUG_FUNCTION_LINE_VERBOSE("Check if we can load %s", curModule->getExportName().c_str());
bool canLoad = true;
for (auto &curImportRPL : curModule->getDependencies()) {
if (!curImportRPL.starts_with("homebrew")) {
continue;
}
if (curImportRPL == "homebrew_wupsbackend") {
OSFatal("Error: module depends on homebrew_wupsbackend, this is not supported");
}
if (!loadedModulesExportNames.contains(curImportRPL)) {
DEBUG_FUNCTION_LINE_VERBOSE("We can't load the module, because %s is not loaded yet", curImportRPL.c_str());
canLoad = false;
break;
}
}
if (canLoad) {
weDidSomething = true;
DEBUG_FUNCTION_LINE_VERBOSE("We can load: %s", curModule->getExportName().c_str());
finalOrder.push_back(curModule);
loadedModulesExportNames.insert(curModule->getExportName());
loadedModulesEntrypoints.insert(curModule->getEntrypoint());
}
}
if (canBreak) {
break;
} else if (!weDidSomething) {
OSFatal("Failed to resolve dependencies.");
}
}
return finalOrder;
}

View File

@ -1,218 +0,0 @@
/****************************************************************************
* Copyright (C) 2010
* by Dimok
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any
* damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any
* purpose, including commercial applications, and to alter it and
* redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you
* must not claim that you wrote the original software. If you use
* this software in a product, an acknowledgment in the product
* documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and
* must not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*
* DirList Class
* for WiiXplorer 2010
***************************************************************************/
#include <algorithm>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <strings.h>
#include <sys/dirent.h>
#include <sys/stat.h>
#include <fs/DirList.h>
#include <utils/StringTools.h>
DirList::DirList() {
Flags = 0;
Filter = 0;
Depth = 0;
}
DirList::DirList(const std::string &path, const char *filter, uint32_t flags, uint32_t maxDepth) {
this->LoadPath(path, filter, flags, maxDepth);
this->SortList();
}
DirList::~DirList() {
ClearList();
}
BOOL DirList::LoadPath(const std::string &folder, const char *filter, uint32_t flags, uint32_t maxDepth) {
if (folder.empty())
return false;
Flags = flags;
Filter = filter;
Depth = maxDepth;
std::string folderpath(folder);
uint32_t length = folderpath.size();
//! clear path of double slashes
StringTools::RemoveDoubleSlashs(folderpath);
//! remove last slash if exists
if (length > 0 && folderpath[length - 1] == '/')
folderpath.erase(length - 1);
//! add root slash if missing
if (folderpath.find('/') == std::string::npos) {
folderpath += '/';
}
return InternalLoadPath(folderpath);
}
BOOL DirList::InternalLoadPath(std::string &folderpath) {
if (folderpath.size() < 3)
return false;
struct dirent *dirent = NULL;
DIR *dir = NULL;
dir = opendir(folderpath.c_str());
if (dir == NULL)
return false;
while ((dirent = readdir(dir)) != 0) {
BOOL isDir = dirent->d_type & DT_DIR;
const char *filename = dirent->d_name;
if (isDir) {
if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0)
continue;
if ((Flags & CheckSubfolders) && (Depth > 0)) {
int32_t length = folderpath.size();
if (length > 2 && folderpath[length - 1] != '/') {
folderpath += '/';
}
folderpath += filename;
Depth--;
InternalLoadPath(folderpath);
folderpath.erase(length);
Depth++;
}
if (!(Flags & Dirs))
continue;
} else if (!(Flags & Files)) {
continue;
}
if (Filter) {
char *fileext = strrchr(filename, '.');
if (!fileext)
continue;
if (StringTools::strtokcmp(fileext, Filter, ",") == 0)
AddEntrie(folderpath, filename, isDir);
} else {
AddEntrie(folderpath, filename, isDir);
}
}
closedir(dir);
return true;
}
void DirList::AddEntrie(const std::string &filepath, const char *filename, BOOL isDir) {
if (!filename)
return;
int32_t pos = FileInfo.size();
FileInfo.resize(pos + 1);
FileInfo[pos].FilePath = (char *) malloc(filepath.size() + strlen(filename) + 2);
if (!FileInfo[pos].FilePath) {
FileInfo.resize(pos);
return;
}
sprintf(FileInfo[pos].FilePath, "%s/%s", filepath.c_str(), filename);
FileInfo[pos].isDir = isDir;
}
void DirList::ClearList() {
for (uint32_t i = 0; i < FileInfo.size(); ++i) {
if (FileInfo[i].FilePath) {
free(FileInfo[i].FilePath);
FileInfo[i].FilePath = NULL;
}
}
FileInfo.clear();
std::vector<DirEntry>().swap(FileInfo);
}
const char *DirList::GetFilename(int32_t ind) const {
if (!valid(ind))
return "";
return StringTools::FullpathToFilename(FileInfo[ind].FilePath);
}
static BOOL SortCallback(const DirEntry &f1, const DirEntry &f2) {
if (f1.isDir && !(f2.isDir))
return true;
if (!(f1.isDir) && f2.isDir)
return false;
if (f1.FilePath && !f2.FilePath)
return true;
if (!f1.FilePath)
return false;
if (strcasecmp(f1.FilePath, f2.FilePath) > 0)
return false;
return true;
}
void DirList::SortList() {
if (FileInfo.size() > 1)
std::sort(FileInfo.begin(), FileInfo.end(), SortCallback);
}
void DirList::SortList(BOOL (*SortFunc)(const DirEntry &a, const DirEntry &b)) {
if (FileInfo.size() > 1)
std::sort(FileInfo.begin(), FileInfo.end(), SortFunc);
}
uint64_t DirList::GetFilesize(int32_t index) const {
struct stat st;
const char *path = GetFilepath(index);
if (!path || stat(path, &st) != 0)
return 0;
return st.st_size;
}
int32_t DirList::GetFileIndex(const char *filename) const {
if (!filename)
return -1;
for (uint32_t i = 0; i < FileInfo.size(); ++i) {
if (strcasecmp(GetFilename(i), filename) == 0)
return i;
}
return -1;
}

View File

@ -1,122 +0,0 @@
/****************************************************************************
* Copyright (C) 2010
* by Dimok
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any
* damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any
* purpose, including commercial applications, and to alter it and
* redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you
* must not claim that you wrote the original software. If you use
* this software in a product, an acknowledgment in the product
* documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and
* must not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*
* DirList Class
* for WiiXplorer 2010
***************************************************************************/
#ifndef ___DIRLIST_H_
#define ___DIRLIST_H_
#include <string>
#include <vector>
#include <wut_types.h>
typedef struct {
char *FilePath;
BOOL isDir;
} DirEntry;
class DirList {
public:
//!Constructor
DirList(void);
//!\param path Path from where to load the filelist of all files
//!\param filter A fileext that needs to be filtered
//!\param flags search/filter flags from the enum
DirList(const std::string &path, const char *filter = NULL, uint32_t flags = Files | Dirs, uint32_t maxDepth = 0xffffffff);
//!Destructor
virtual ~DirList();
//! Load all the files from a directory
BOOL LoadPath(const std::string &path, const char *filter = NULL, uint32_t flags = Files | Dirs, uint32_t maxDepth = 0xffffffff);
//! Get a filename of the list
//!\param list index
const char *GetFilename(int32_t index) const;
//! Get the a filepath of the list
//!\param list index
const char *GetFilepath(int32_t index) const {
if (!valid(index))
return "";
else
return FileInfo[index].FilePath;
}
//! Get the a filesize of the list
//!\param list index
uint64_t GetFilesize(int32_t index) const;
//! Is index a dir or a file
//!\param list index
BOOL IsDir(int32_t index) const {
if (!valid(index))
return false;
return FileInfo[index].isDir;
};
//! Get the filecount of the whole list
int32_t GetFilecount() const {
return FileInfo.size();
};
//! Sort list by filepath
void SortList();
//! Custom sort command for custom sort functions definitions
void SortList(BOOL (*SortFunc)(const DirEntry &a, const DirEntry &b));
//! Get the index of the specified filename
int32_t GetFileIndex(const char *filename) const;
//! Enum for search/filter flags
enum {
Files = 0x01,
Dirs = 0x02,
CheckSubfolders = 0x08,
};
protected:
// Internal parser
BOOL InternalLoadPath(std::string &path);
//!Add a list entrie
void AddEntrie(const std::string &filepath, const char *filename, BOOL isDir);
//! Clear the list
void ClearList();
//! Check if valid pos is requested
inline BOOL valid(uint32_t pos) const {
return (pos < FileInfo.size());
};
uint32_t Flags;
uint32_t Depth;
const char *Filter;
std::vector<DirEntry> FileInfo;
};
#endif

View File

@ -1,62 +0,0 @@
#include "utils/logger.h"
#include "utils/utils.h"
#include <coreinit/memdefaultheap.h>
#include <cstdint>
#include <fcntl.h>
#include <malloc.h>
#include <string>
#include <unistd.h>
int32_t LoadFileToMem(const std::string &filepath, uint8_t **inbuffer, uint32_t *size) {
//! always initialze input
*inbuffer = nullptr;
if (size) {
*size = 0;
}
int32_t iFd = open(filepath.c_str(), O_RDONLY);
if (iFd < 0) {
return -1;
}
uint32_t filesize = lseek(iFd, 0, SEEK_END);
lseek(iFd, 0, SEEK_SET);
auto *buffer = (uint8_t *) MEMAllocFromDefaultHeapEx(ROUNDUP(filesize, 0x40), 0x40);
if (buffer == nullptr) {
close(iFd);
return -2;
}
uint32_t blocksize = 0x20000;
uint32_t done = 0;
int32_t readBytes = 0;
while (done < filesize) {
if (done + blocksize > filesize) {
blocksize = filesize - done;
}
readBytes = read(iFd, buffer + done, blocksize);
if (readBytes <= 0)
break;
done += readBytes;
}
::close(iFd);
if (done != filesize) {
memset(buffer, 0, ROUNDUP(filesize, 0x40));
free(buffer);
buffer = nullptr;
return -3;
}
*inbuffer = buffer;
//! sign is optional input
if (size) {
*size = filesize;
}
return filesize;
}

View File

@ -1,4 +0,0 @@
#pragma once
#include <stdint.h>
int32_t LoadFileToMem(const std::string &filepath, uint8_t **inbuffer, uint32_t *size);

View File

@ -1,13 +1,15 @@
#include "globals.h"
#include "module/ModuleData.h"
#include "module/ModuleContainer.h"
#include "module/ModuleDataPersistence.h"
MEMHeapHandle gHeapHandle __attribute__((section(".data"))) = nullptr;
uint8_t gInitCalled __attribute__((section(".data"))) = 0;
module_information_t gModuleInformation __attribute__((section(".data")));
std::vector<std::shared_ptr<ModuleData>> gLoadedModules __attribute__((section(".data")));
std::unique_ptr<WUMSLoader::Modules::PersistedModuleData> gModuleInformation __attribute__((section(".data")));
std::vector<WUMSLoader::Modules::ModuleContainer> gLoadedModules __attribute__((section(".data")));
std::unique_ptr<module_information_single_t[]> gModuleDataInfo __attribute__((section(".data")));
std::map<std::string, OSDynLoad_Module> gUsedRPLs __attribute__((section(".data")));
std::map<std::string, OSDynLoad_Module, std::less<>> gUsedRPLs __attribute__((section(".data")));
std::vector<void *> gAllocatedAddresses __attribute__((section(".data")));
WUMSRPLAllocatorAllocFn gCustomRPLAllocatorAllocFn __attribute__((section(".data"))) = nullptr;

View File

@ -1,45 +1,30 @@
#pragma once
#include "module/ModuleContainer.h"
#include "utils/SegmentedTimer.h"
#include <wums/hooks.h>
#include <coreinit/dynload.h>
#include <coreinit/memheap.h>
#include <map>
#include <memory>
#include <vector>
#include <map>
#include <cstdint>
class ModuleData;
namespace WUMSLoader::Modules {
class PersistedModuleData;
}
extern uint8_t gInitCalled;
extern MEMHeapHandle gHeapHandle;
extern module_information_t gModuleInformation;
extern std::vector<std::shared_ptr<ModuleData>> gLoadedModules;
extern std::unique_ptr<WUMSLoader::Modules::PersistedModuleData> gModuleInformation;
extern std::vector<WUMSLoader::Modules::ModuleContainer> gLoadedModules;
extern std::unique_ptr<module_information_single_t[]> gModuleDataInfo;
extern std::map<std::string, OSDynLoad_Module> gUsedRPLs;
extern std::map<std::string, OSDynLoad_Module, std::less<>> gUsedRPLs;
extern std::vector<void *> gAllocatedAddresses;
extern WUMSRPLAllocatorAllocFn gCustomRPLAllocatorAllocFn;
extern WUMSRPLAllocatorFreeFn gCustomRPLAllocatorFreeFn;
#define MEMORY_REGION_START 0x00800000
#define MEMORY_REGION_SIZE 0x00800000
#define RELOCATOR_SIZE 0x50000 // Maximum size of the wumsloader, needs to match the one defined in link.ld
#define ENVIRONMENT_PATH_LENGTH 0x100 // Length of the EnvironmentPath.
#define MEMORY_REGION_USABLE_MEM_REGION_END_LENGTH 0x04 // sizeof(uint32_t)
#define MEMORY_REGION_ENVIRONMENT_STRING_ADRR (MEMORY_REGION_START + RELOCATOR_SIZE)
#define MEMORY_REGION_USABLE_MEM_REGION_END_VALUE_PTR ((uint32_t *) (MEMORY_REGION_ENVIRONMENT_STRING_ADRR + ENVIRONMENT_PATH_LENGTH))
#define MEMORY_REGION_USABLE_MEM_REGION_END_VALUE (*MEMORY_REGION_USABLE_MEM_REGION_END_VALUE_PTR)
// Technically we overwrite the CustomRPXLoader that is still loaded at 0x00800000...
// We can get away with it because the EnvironmentLoader exits instead of returning to the CustomRPXLoader.
#define MEMORY_REGION_USABLE_HEAP_START ((uint32_t) MEMORY_REGION_USABLE_MEM_REGION_END_VALUE_PTR + MEMORY_REGION_USABLE_MEM_REGION_END_LENGTH)
#define MEMORY_REGION_USABLE_HEAP_END MEMORY_REGION_USABLE_MEM_REGION_END_VALUE
#define ENVRIONMENT_STRING ((char *) MEMORY_REGION_ENVIRONMENT_STRING_ADRR)
#define WUMS_LOADER_SETUP_MAGIC_WORD 0x13371337
extern WUMSRPLAllocatorFreeFn gCustomRPLAllocatorFreeFn;

View File

@ -92,4 +92,4 @@ SECTIONS {
}
}
ASSERT((SIZEOF(.text) + SIZEOF(.data) + SIZEOF(.rodata) + SIZEOF(.eh_frame) + SIZEOF(.bss)) < 0x50000, "Memory overlapping with modules.");
ASSERT((SIZEOF(.text) + SIZEOF(.data) + SIZEOF(.rodata) + SIZEOF(.eh_frame) + SIZEOF(.bss)) < 0x67000, "Memory overlapping with modules. Update the link.ld and defines.h with new limits");

View File

@ -1,12 +1,16 @@
#include <coreinit/cache.h>
#include <valarray>
#include "ElfUtils.h"
#include "utils/logger.h"
#include <span>
#include <cstdlib>
#include <cstring>
namespace WUMSLoader::Modules::ElfUtils {
// See https://github.com/decaf-emu/decaf-emu/blob/43366a34e7b55ab9d19b2444aeb0ccd46ac77dea/src/libdecaf/src/cafe/loader/cafe_loader_reloc.cpp#L144
bool ElfUtils::elfLinkOne(char type, size_t offset, int32_t addend, uint32_t destination, uint32_t symbol_addr, relocation_trampoline_entry_t *trampoline_data, uint32_t trampoline_data_length,
RelocationType reloc_type) {
bool elfLinkOne(char type, size_t offset, int32_t addend, uint32_t destination, uint32_t symbol_addr,
std::span<relocation_trampoline_entry_t> trampolineData, RelocationType reloc_type) {
if (type == R_PPC_NONE) {
return true;
}
@ -75,31 +79,29 @@ bool ElfUtils::elfLinkOne(char type, size_t offset, int32_t addend, uint32_t des
// }
auto distance = static_cast<int32_t>(value) - static_cast<int32_t>(target);
if (distance > 0x1FFFFFC || distance < -0x1FFFFFC) {
if (trampoline_data == nullptr) {
if (trampolineData.empty()) {
DEBUG_FUNCTION_LINE_ERR("***24-bit relative branch cannot hit target. Trampoline isn't provided");
DEBUG_FUNCTION_LINE_ERR("***value %08X - target %08X = distance %08X", value, target, distance);
return false;
} else {
relocation_trampoline_entry_t *freeSlot = nullptr;
for (uint32_t i = 0; i < trampoline_data_length; i++) {
for (auto &cur : trampolineData) {
// We want to override "old" relocations of imports
// Pending relocations have the status RELOC_TRAMP_IMPORT_IN_PROGRESS.
// When all relocations are done successfully, they will be turned into RELOC_TRAMP_IMPORT_DONE
// so they can be overridden/updated/reused on the next application launch.
//
// Relocations that won't change will have the status RELOC_TRAMP_FIXED and are set to free when the module is unloaded.
if (trampoline_data[i].status == RELOC_TRAMP_FREE ||
trampoline_data[i].status == RELOC_TRAMP_IMPORT_DONE) {
freeSlot = &(trampoline_data[i]);
// Relocations that won't change will have the status RELOC_TRAMP_FIXED and are set to free when the plugin is unloaded.
if (cur.status == RELOC_TRAMP_FREE) {
freeSlot = &cur;
break;
}
}
if (freeSlot == nullptr) {
DEBUG_FUNCTION_LINE_ERR("***24-bit relative branch cannot hit target. Trampoline data list is full");
DEBUG_FUNCTION_LINE_ERR("***value %08X - target %08X = distance %08X", value, target, target - (uint32_t) & (freeSlot->trampoline[0]));
DEBUG_FUNCTION_LINE_ERR("***value %08X - target %08X = distance %08X", value, target, target - reinterpret_cast<uint32_t>(&(freeSlot->trampoline[0])));
return false;
}
auto symbolValue = (uint32_t) & (freeSlot->trampoline[0]);
auto symbolValue = reinterpret_cast<uint32_t>(&(freeSlot->trampoline[0]));
auto newValue = symbolValue + addend;
auto newDistance = static_cast<int32_t>(newValue) - static_cast<int32_t>(target);
if (newDistance > 0x1FFFFFC || newDistance < -0x1FFFFFC) {
@ -116,8 +118,6 @@ bool ElfUtils::elfLinkOne(char type, size_t offset, int32_t addend, uint32_t des
freeSlot->trampoline[1] = 0x616B0000 | (((uint32_t) value) & 0x0000ffff); // ori r11, r11, real_addr@l
freeSlot->trampoline[2] = 0x7D6903A6; // mtctr r11
freeSlot->trampoline[3] = 0x4E800420; // bctr
DCFlushRange((void *) freeSlot->trampoline, sizeof(freeSlot->trampoline));
ICInvalidateRange((unsigned char *) freeSlot->trampoline, sizeof(freeSlot->trampoline));
if (reloc_type == RELOC_TYPE_FIXED) {
freeSlot->status = RELOC_TRAMP_FIXED;
@ -144,14 +144,14 @@ bool ElfUtils::elfLinkOne(char type, size_t offset, int32_t addend, uint32_t des
return false;
}
*(int32_t *) target = (*(int32_t *) target & 0xfc000003) | (distance & 0x03fffffc);
*reinterpret_cast<int32_t *>(target) = (*reinterpret_cast<int32_t *>(target) & 0xfc000003) | (distance & 0x03fffffc);
break;
}
default:
DEBUG_FUNCTION_LINE_ERR("***ERROR: Unsupported Relocation_Add Type (%08X):", type);
return false;
}
ICInvalidateRange(reinterpret_cast<void *>(target), 4);
DCFlushRange(reinterpret_cast<void *>(target), 4);
return true;
}
} // namespace WUMSLoader::Modules::ElfUtils

View File

@ -0,0 +1,40 @@
#pragma once
#include <wums/defines/relocation_defines.h>
#include <cstdint>
#include <span>
namespace WUMSLoader::Modules::ElfUtils {
static constexpr uint32_t R_PPC_NONE = 0;
static constexpr uint32_t R_PPC_ADDR32 = 1;
static constexpr uint32_t R_PPC_ADDR16_LO = 4;
static constexpr uint32_t R_PPC_ADDR16_HI = 5;
static constexpr uint32_t R_PPC_ADDR16_HA = 6;
static constexpr uint32_t R_PPC_REL24 = 10;
static constexpr uint32_t R_PPC_REL14 = 11;
static constexpr uint32_t R_PPC_DTPMOD32 = 68;
static constexpr uint32_t R_PPC_DTPREL32 = 78;
static constexpr uint32_t R_PPC_EMB_SDA21 = 109;
static constexpr uint32_t R_PPC_EMB_RELSDA = 116;
static constexpr uint32_t R_PPC_DIAB_SDA21_LO = 180;
static constexpr uint32_t R_PPC_DIAB_SDA21_HI = 181;
static constexpr uint32_t R_PPC_DIAB_SDA21_HA = 182;
static constexpr uint32_t R_PPC_DIAB_RELSDA_LO = 183;
static constexpr uint32_t R_PPC_DIAB_RELSDA_HI = 184;
static constexpr uint32_t R_PPC_DIAB_RELSDA_HA = 185;
static constexpr uint32_t R_PPC_GHS_REL16_HA = 251;
static constexpr uint32_t R_PPC_GHS_REL16_HI = 252;
static constexpr uint32_t R_PPC_GHS_REL16_LO = 253;
// Masks for manipulating Power PC relocation targets
static constexpr uint32_t PPC_WORD32 = 0xFFFFFFFF;
static constexpr uint32_t PPC_WORD30 = 0xFFFFFFFC;
static constexpr uint32_t PPC_LOW24 = 0x03FFFFFC;
static constexpr uint32_t PPC_LOW14 = 0x0020FFFC;
static constexpr uint16_t PPC_HALF16 = 0xFFFF;
bool elfLinkOne(char type, size_t offset, int32_t addend, uint32_t destination, uint32_t symbol_addr, std::span<relocation_trampoline_entry_t> trampolineData, RelocationType reloc_type);
} // namespace WUMSLoader::Modules::ElfUtils

View File

@ -1,31 +1,34 @@
#pragma once
#include <wums/exports.h>
#include <string>
#include <wums.h>
namespace WUMSLoader::Modules {
class ExportData {
public:
ExportData(wums_entry_type_t type, std::string name, const void *address) {
this->type = type;
this->name = std::move(name);
this->address = address;
ExportData(const wums_entry_type_t type, std::string name, const void *address) : mType(type),
mName(std::move(name)),
mAddress(address) {}
[[nodiscard]] constexpr wums_entry_type_t getType() const {
return mType;
}
[[nodiscard]] wums_entry_type_t getType() const {
return type;
}
[[nodiscard]] const void *getAddress() const {
return address;
[[nodiscard]] constexpr const void *getAddress() const {
return mAddress;
}
[[nodiscard]] const std::string &getName() const {
return name;
return mName;
}
private:
wums_entry_type_t type;
std::string name;
const void *address;
};
wums_entry_type_t mType;
std::string mName;
const void *mAddress;
};
} // namespace WUMSLoader::Modules

View File

@ -1,26 +0,0 @@
#include "FunctionSymbolData.h"
FunctionSymbolData::FunctionSymbolData(const FunctionSymbolData &o2) = default;
FunctionSymbolData::FunctionSymbolData(std::string_view name, void *address, uint32_t size) : mName(name),
mAddress(address),
mSize(size) {
}
bool FunctionSymbolData::operator<(const FunctionSymbolData &rhs) const {
return reinterpret_cast<uint32_t>(mAddress) < reinterpret_cast<uint32_t>(rhs.mAddress);
}
FunctionSymbolData::~FunctionSymbolData() = default;
[[nodiscard]] const std::string &FunctionSymbolData::getName() const {
return mName;
}
[[nodiscard]] void *FunctionSymbolData::getAddress() const {
return mAddress;
}
[[nodiscard]] uint32_t FunctionSymbolData::getSize() const {
return mSize;
}

View File

@ -22,25 +22,32 @@
#include <cstdint>
namespace WUMSLoader::Modules {
class FunctionSymbolData {
public:
FunctionSymbolData(const FunctionSymbolData &o2);
FunctionSymbolData(std::string name, void *address, uint32_t size)
: mName(std::move(name)), mAddress(address), mSize(size) {}
FunctionSymbolData(std::string_view name, void *address, uint32_t size);
bool operator<(const FunctionSymbolData &rhs) const {
return mAddress < rhs.mAddress;
}
bool operator<(const FunctionSymbolData &rhs) const;
[[nodiscard]] constexpr void *getAddress() const {
return mAddress;
}
virtual ~FunctionSymbolData();
[[nodiscard]] constexpr uint32_t getSize() const {
return mSize;
}
[[nodiscard]] const std::string &getName() const;
[[nodiscard]] void *getAddress() const;
[[nodiscard]] uint32_t getSize() const;
[[nodiscard]] const std::string &getName() const {
return mName;
}
private:
std::string mName;
void *mAddress;
uint32_t mSize;
};
};
} // namespace WUMSLoader::Modules

View File

@ -1,16 +0,0 @@
#include "HookData.h"
HookData::HookData(void *functionPointer, const wums_hook_type_t type) {
this->mFunctionPointer = functionPointer;
this->mType = type;
}
HookData::~HookData() = default;
[[nodiscard]] void *HookData::getFunctionPointer() const {
return mFunctionPointer;
}
[[nodiscard]] wums_hook_type_t HookData::getType() const {
return mType;
}

View File

@ -19,18 +19,25 @@
#include <wums/hooks.h>
namespace WUMSLoader::Modules {
class HookData {
public:
HookData(void *functionPointer, wums_hook_type_t type);
constexpr HookData(void *functionPointer, wums_hook_type_t type) {
this->mFunctionPointer = functionPointer;
this->mType = type;
}
~HookData();
[[nodiscard]] constexpr void *getFunctionPointer() const {
return mFunctionPointer;
}
[[nodiscard]] void *getFunctionPointer() const;
[[nodiscard]] wums_hook_type_t getType() const;
[[nodiscard]] constexpr wums_hook_type_t getType() const {
return mType;
}
private:
void *mFunctionPointer;
wums_hook_type_t mType;
};
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,167 @@
#include "HooksManagement.h"
#include "ModuleContainer.h"
#include "ModuleDataPersistence.h"
#include "globals.h"
#include "utils/logger.h"
#include <coreinit/memexpheap.h>
namespace WUMSLoader::Modules::HooksManagement {
namespace {
#ifdef DEBUG
constexpr std::string_view hookNameToStr(wums_hook_type_t hook) {
switch (hook) {
case WUMS_HOOK_INIT_WUT_MALLOC:
return "WUMS_HOOK_INIT_WUT_MALLOC";
case WUMS_HOOK_FINI_WUT_MALLOC:
return "WUMS_HOOK_FINI_WUT_MALLOC";
case WUMS_HOOK_INIT_WUT_NEWLIB:
return "WUMS_HOOK_INIT_WUT_NEWLIB";
case WUMS_HOOK_FINI_WUT_NEWLIB:
return "WUMS_HOOK_FINI_WUT_NEWLIB";
case WUMS_HOOK_INIT_WUT_STDCPP:
return "WUMS_HOOK_INIT_WUT_STDCPP";
case WUMS_HOOK_FINI_WUT_STDCPP:
return "WUMS_HOOK_FINI_WUT_STDCPP";
case WUMS_HOOK_INIT_WUT_DEVOPTAB:
return "WUMS_HOOK_INIT_WUT_DEVOPTAB";
case WUMS_HOOK_FINI_WUT_DEVOPTAB:
return "WUMS_HOOK_FINI_WUT_DEVOPTAB";
case WUMS_HOOK_INIT_WUT_SOCKETS:
return "WUMS_HOOK_INIT_WUT_SOCKETS";
case WUMS_HOOK_FINI_WUT_SOCKETS:
return "WUMS_HOOK_FINI_WUT_SOCKETS";
case WUMS_HOOK_INIT_WRAPPER:
return "WUMS_HOOK_INIT_WRAPPER";
case WUMS_HOOK_FINI_WRAPPER:
return "WUMS_HOOK_FINI_WRAPPER";
case WUMS_HOOK_INIT:
return "WUMS_HOOK_INIT";
case WUMS_HOOK_APPLICATION_STARTS:
return "WUMS_HOOK_APPLICATION_STARTS";
case WUMS_HOOK_APPLICATION_ENDS:
return "WUMS_HOOK_APPLICATION_ENDS";
case WUMS_HOOK_RELOCATIONS_DONE:
return "WUMS_HOOK_RELOCATIONS_DONE";
case WUMS_HOOK_APPLICATION_REQUESTS_EXIT:
return "WUMS_HOOK_APPLICATION_REQUESTS_EXIT";
case WUMS_HOOK_DEINIT:
return "WUMS_HOOK_DEINIT";
case WUMS_HOOK_ALL_APPLICATION_STARTS_DONE:
return "WUMS_HOOK_ALL_APPLICATION_STARTS_DONE";
case WUMS_HOOK_ALL_APPLICATION_ENDS_DONE:
return "WUMS_HOOK_ALL_APPLICATION_ENDS_DONE";
case WUMS_HOOK_ALL_APPLICATION_REQUESTS_EXIT_DONE:
return "WUMS_HOOK_ALL_APPLICATION_REQUESTS_EXIT_DONE";
case WUMS_HOOK_GET_CUSTOM_RPL_ALLOCATOR:
return "WUMS_HOOK_GET_CUSTOM_RPL_ALLOCATOR";
case WUMS_HOOK_CLEAR_ALLOCATED_RPL_MEMORY:
return "WUMS_HOOK_CLEAR_ALLOCATED_RPL_MEMORY";
case WUMS_HOOK_INIT_WUT_THREAD:
return "WUMS_HOOK_INIT_WUT_THREAD";
}
return "";
}
#endif
} // namespace
void CallHook(const std::vector<ModuleContainer> &modules, wums_hook_type_t type, bool condition) {
if (condition) {
CallHook(modules, type);
}
}
void CallHook(const std::vector<ModuleContainer> &modules, wums_hook_type_t type) {
DEBUG_FUNCTION_LINE_VERBOSE("Calling hook of type %s [%d] for all modules", hookNameToStr(type).data(), type);
for (auto &curModule : modules) {
CallHook(curModule, type);
}
}
void CallHook(const ModuleContainer &module, wums_hook_type_t type, bool condition) {
if (condition) {
CallHook(module, type);
}
}
void CallHook(const ModuleContainer &module, wums_hook_type_t type) {
if (!module.isLinkedAndLoaded()) {
return;
}
PROFILE_FUNCTION();
#ifdef DEBUG
bool foundHook = false;
#endif
for (auto &curHook : module.getLinkInformation().getHookDataList()) {
auto func_ptr = (uint32_t) curHook.getFunctionPointer();
if (func_ptr == 0) {
DEBUG_FUNCTION_LINE_ERR("Module %s: hook ptr was NULL", module.getMetaInformation().getExportName().c_str());
break;
}
if (type == curHook.getType()) {
#ifdef DEBUG
foundHook = true;
#endif
if ((type == WUMS_HOOK_APPLICATION_STARTS ||
type == WUMS_HOOK_APPLICATION_ENDS ||
type == WUMS_HOOK_INIT_WUT_MALLOC ||
type == WUMS_HOOK_FINI_WUT_MALLOC ||
type == WUMS_HOOK_INIT_WUT_NEWLIB ||
type == WUMS_HOOK_FINI_WUT_NEWLIB ||
type == WUMS_HOOK_INIT_WUT_STDCPP ||
type == WUMS_HOOK_FINI_WUT_STDCPP ||
type == WUMS_HOOK_INIT_WUT_DEVOPTAB ||
type == WUMS_HOOK_FINI_WUT_DEVOPTAB ||
type == WUMS_HOOK_INIT_WUT_SOCKETS ||
type == WUMS_HOOK_FINI_WUT_SOCKETS ||
type == WUMS_HOOK_INIT_WRAPPER ||
type == WUMS_HOOK_FINI_WRAPPER ||
type == WUMS_HOOK_DEINIT ||
type == WUMS_HOOK_ALL_APPLICATION_STARTS_DONE ||
type == WUMS_HOOK_ALL_APPLICATION_ENDS_DONE ||
type == WUMS_HOOK_CLEAR_ALLOCATED_RPL_MEMORY ||
type == WUMS_HOOK_APPLICATION_REQUESTS_EXIT ||
type == WUMS_HOOK_ALL_APPLICATION_REQUESTS_EXIT_DONE ||
type == WUMS_HOOK_INIT_WUT_THREAD)) {
DEBUG_FUNCTION_LINE_VERBOSE("Calling hook of type %s [%d] %d for %s: %p", hookNameToStr(type).data(), type, curHook.getType(), module.getMetaInformation().getExportName().c_str(), curHook.getFunctionPointer());
((void (*)())((uint32_t *) func_ptr))();
break;
} else if (type == WUMS_HOOK_INIT ||
type == WUMS_HOOK_RELOCATIONS_DONE) {
DEBUG_FUNCTION_LINE_VERBOSE("Calling hook of type %s [%d] %d for %s: %p", hookNameToStr(type).data(), type, curHook.getType(), module.getMetaInformation().getExportName().c_str(), curHook.getFunctionPointer());
wums_app_init_args_t args;
args.module_information = gModuleInformation->getModuleInformation();
((void (*)(wums_app_init_args_t *))((uint32_t *) func_ptr))(&args);
} else if (type == WUMS_HOOK_GET_CUSTOM_RPL_ALLOCATOR) {
DEBUG_FUNCTION_LINE_VERBOSE("Calling hook of type %s [%d] %d for %s: %p", hookNameToStr(type).data(), type, curHook.getType(), module.getMetaInformation().getExportName().c_str(), curHook.getFunctionPointer());
const auto [allocFn, freeFn] = reinterpret_cast<wums_internal_custom_rpl_allocator_t (*)()>(reinterpret_cast<uint32_t *>(func_ptr))();
gCustomRPLAllocatorAllocFn = allocFn;
gCustomRPLAllocatorFreeFn = freeFn;
} else {
DEBUG_FUNCTION_LINE_ERR("#########################################");
DEBUG_FUNCTION_LINE_ERR("#########HOOK NOT IMPLEMENTED %d#########", type);
DEBUG_FUNCTION_LINE_ERR("#########################################");
}
break;
}
}
#ifdef DEBUG
if (foundHook && !MEMCheckExpHeap(MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM2), MEM_EXP_HEAP_CHECK_FLAGS_LOG_ERRORS)) {
DEBUG_FUNCTION_LINE_ERR("MEM2 default heap is corrupted while calling hook %s for module %s", hookNameToStr(type).data(), module.getMetaInformation().getExportName().c_str());
OSFatal("WUMSLoader: MEM2 default heap is corrupted. \n Please restart the console.");
}
#endif
}
} // namespace WUMSLoader::Modules::HooksManagement

View File

@ -0,0 +1,22 @@
#pragma once
#include <wums/hooks.h>
#include <vector>
namespace WUMSLoader::Modules {
class ModuleContainer;
namespace HooksManagement {
void CallHook(const std::vector<ModuleContainer> &modules, wums_hook_type_t type, bool condition);
void CallHook(const std::vector<ModuleContainer> &modules, wums_hook_type_t type);
void CallHook(const ModuleContainer &module, wums_hook_type_t type, bool condition);
void CallHook(const ModuleContainer &module, wums_hook_type_t type);
} // namespace HooksManagement
} // namespace WUMSLoader::Modules

View File

@ -1,7 +1,7 @@
#include "ImportRPLInformation.h"
#include <cstring>
ImportRPLInformation::ImportRPLInformation(std::string_view name) {
namespace WUMSLoader::Modules {
ImportRPLInformation::ImportRPLInformation(const std::string_view name) {
this->mName = name;
}
@ -11,10 +11,18 @@ ImportRPLInformation::~ImportRPLInformation() = default;
return mName;
}
[[nodiscard]] std::string ImportRPLInformation::getRPLName() const {
return mName.substr(strlen(".dimport_"));
[[nodiscard]] std::string_view ImportRPLInformation::getRPLName() const {
static constexpr std::string_view prefix = "._import_";
if (mName.length() > prefix.length()) {
return std::string_view(mName).substr(prefix.length());
}
return mName;
}
[[nodiscard]] bool ImportRPLInformation::isData() const {
return mName.starts_with(".dimport_");
}
static constexpr std::string_view prefix = ".dimport_";
return mName.starts_with(prefix);
}
} // namespace WUMSLoader::Modules

View File

@ -20,6 +20,7 @@
#include <string>
#include <string_view>
namespace WUMSLoader::Modules {
class ImportRPLInformation {
public:
@ -29,10 +30,11 @@ public:
[[nodiscard]] const std::string &getName() const;
[[nodiscard]] std::string getRPLName() const;
[[nodiscard]] std::string_view getRPLName() const;
[[nodiscard]] bool isData() const;
private:
std::string mName;
};
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,60 @@
#include "ModuleAllocator.h"
#include "module/ModuleContainer.h"
#include "module/ModuleDataPersistence.h"
#include "utils/logger.h"
#include <coreinit/memdefaultheap.h>
#include <coreinit/memexpheap.h>
#include <optional>
namespace WUMSLoader::Modules {
bool ModuleAllocator::isValid() const {
return alloc != nullptr && free != nullptr;
}
ModuleAllocator ModuleAllocator::FromExpHeap(MEMHeapHandle handle) {
return ModuleAllocator{
[handle](size_t size, int32_t align) { return MEMAllocFromExpHeapEx(handle, size, align); },
[handle](void *ptr) { MEMFreeToExpHeap(handle, ptr); }};
}
std::optional<ModuleAllocator> ModuleAllocator::FromMappedMemory(const std::vector<ModuleContainer> &modules) {
MEMAllocFromDefaultHeapExFn *allocExPtr = nullptr;
MEMFreeToDefaultHeapFn *freePtr = nullptr;
for (const auto &module : modules) {
if (!module.isLinkedAndLoaded()) {
continue;
}
if (module.getMetaInformation().getExportName() != "homebrew_memorymapping") {
continue;
}
for (const auto &exportData : module.getLinkInformation().getExportDataList()) {
if (exportData.getType() != WUMS_DATA_EXPORT) {
continue;
}
if (exportData.getName() == "MEMAllocFromMappedMemoryEx") {
allocExPtr = (MEMAllocFromDefaultHeapExFn *) exportData.getAddress();
} else if (exportData.getName() == "MEMFreeToMappedMemory") {
freePtr = (MEMFreeToDefaultHeapFn *) exportData.getAddress();
}
}
break;
}
if (allocExPtr && freePtr) {
const auto allocExFunc = *allocExPtr;
const auto freeFunc = *freePtr;
return ModuleAllocator{
[allocExFunc](size_t size, int32_t align) -> void * {
return allocExFunc(size, align);
},
[freeFunc](void *ptr) {
freeFunc(ptr);
}};
}
return std::nullopt;
}
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,20 @@
#pragma once
#include "utils/HeapMemoryFixedSize.h"
#include <coreinit/memheap.h>
#include <optional>
namespace WUMSLoader::Modules {
class ModuleContainer;
struct ModuleAllocator {
HeapMemoryFixedSizePool::AllocFunc alloc;
HeapMemoryFixedSizePool::FreeFunc free;
[[nodiscard]] bool isValid() const;
static ModuleAllocator FromExpHeap(MEMHeapHandle handle);
static std::optional<ModuleAllocator> FromMappedMemory(const std::vector<ModuleContainer> &modules);
};
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,35 @@
#include "ModuleContainer.h"
namespace WUMSLoader::Modules {
ModuleContainer::ModuleContainer(ModuleMetaInformation &&metaInformation, ModuleLinkInformation &&linkInformation)
: mEntrypoint(nullptr),
mMetaInformation(std::move(metaInformation)),
mLinkInformation(std::move(linkInformation)) {
// Abuse this as a stable handle that references itself and survives std::move
*mHandle = reinterpret_cast<uint32_t>(mHandle.get());
}
const ModuleMetaInformation &ModuleContainer::getMetaInformation() const {
return this->mMetaInformation;
}
const ModuleLinkInformation &ModuleContainer::getLinkInformation() const {
return this->mLinkInformation;
}
ModuleLinkInformation &ModuleContainer::getLinkInformation() {
return this->mLinkInformation;
}
uint32_t ModuleContainer::getHandle() const {
return *mHandle;
}
bool ModuleContainer::isLinkedAndLoaded() const {
return mLinkInformation.hasValidData();
}
void ModuleContainer::setLinkInformation(ModuleLinkInformation &&linkInformation) {
this->mLinkInformation = std::move(linkInformation);
}
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,41 @@
#pragma once
#include "ModuleLinkInformation.h"
#include "ModuleMetaInformation.h"
#include <cstdint>
#include <memory>
namespace WUMSLoader::Modules {
class ModuleContainer {
public:
ModuleContainer(ModuleMetaInformation &&metaInformation, ModuleLinkInformation &&linkInformation);
ModuleContainer(const ModuleContainer &) = delete;
ModuleContainer(ModuleContainer &&src) = default;
ModuleContainer &operator=(ModuleContainer &&src) = default;
[[nodiscard]] const ModuleMetaInformation &getMetaInformation() const;
[[nodiscard]] const ModuleLinkInformation &getLinkInformation() const;
[[nodiscard]] ModuleLinkInformation &getLinkInformation();
[[nodiscard]] uint32_t getHandle() const;
[[nodiscard]] bool isLinkedAndLoaded() const;
void setLinkInformation(ModuleLinkInformation &&linkInformation);
private:
void *mEntrypoint;
ModuleMetaInformation mMetaInformation;
ModuleLinkInformation mLinkInformation;
std::unique_ptr<uint32_t> mHandle = std::make_unique<uint32_t>();
};
} // namespace WUMSLoader::Modules

View File

@ -1,197 +0,0 @@
/****************************************************************************
* Copyright (C) 2018-2021 Maschell
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
#pragma once
#include "ExportData.h"
#include "FunctionSymbolData.h"
#include "HookData.h"
#include "RelocationData.h"
#include "SectionInfo.h"
#include <map>
#include <optional>
#include <set>
#include <string>
#include <vector>
struct FunctionSymbolDataComparator {
bool operator()(const std::shared_ptr<FunctionSymbolData> &lhs,
const std::shared_ptr<FunctionSymbolData> &rhs) const {
return (uint32_t) lhs->getAddress() < (uint32_t) rhs->getAddress();
}
};
class ModuleData {
public:
ModuleData() = default;
~ModuleData() = default;
void setBSSLocation(uint32_t addr, uint32_t size) {
this->bssAddr = addr;
this->bssSize = size;
}
void setSBSSLocation(uint32_t addr, uint32_t size) {
this->sbssAddr = addr;
this->sbssSize = size;
}
void setEntrypoint(uint32_t addr) {
this->entrypoint = addr;
}
void addRelocationData(std::unique_ptr<RelocationData> relocation_data) {
addDependency(relocation_data->getImportRPLInformation().getRPLName());
relocation_data_list.push_back(std::move(relocation_data));
}
[[nodiscard]] const std::vector<std::unique_ptr<RelocationData>> &getRelocationDataList() const {
return relocation_data_list;
}
void addExportData(std::unique_ptr<ExportData> data) {
export_data_list.push_back(std::move(data));
}
[[nodiscard]] const std::vector<std::unique_ptr<ExportData>> &getExportDataList() const {
return export_data_list;
}
void addHookData(std::unique_ptr<HookData> data) {
hook_data_list.push_back(std::move(data));
}
[[nodiscard]] const std::vector<std::unique_ptr<HookData>> &getHookDataList() const {
return hook_data_list;
}
void addDependency(std::string module_name) {
dependency_list.insert(std::move(module_name));
}
[[nodiscard]] const std::set<std::string> &getDependencies() const {
return dependency_list;
}
void addSectionInfo(std::shared_ptr<SectionInfo> sectionInfo) {
section_info_list[sectionInfo->getName()] = std::move(sectionInfo);
}
void addFunctionSymbolData(std::shared_ptr<FunctionSymbolData> symbol_data) {
symbol_data_list.insert(std::move(symbol_data));
}
[[nodiscard]] const std::set<std::shared_ptr<FunctionSymbolData>, FunctionSymbolDataComparator> &getFunctionSymbolDataList() const {
return symbol_data_list;
}
[[nodiscard]] const std::map<std::string_view, std::shared_ptr<SectionInfo>> &getSectionInfoList() const {
return section_info_list;
}
[[nodiscard]] std::optional<std::shared_ptr<SectionInfo>> getSectionInfo(const std::string &sectionName) const {
if (getSectionInfoList().count(sectionName) > 0) {
return section_info_list.at(sectionName);
}
return {};
}
[[nodiscard]] uint32_t getBSSAddress() const {
return bssAddr;
}
[[nodiscard]] uint32_t getBSSSize() const {
return bssSize;
}
[[nodiscard]] uint32_t getSBSSAddress() const {
return sbssAddr;
}
[[nodiscard]] uint32_t getSBSSSize() const {
return sbssSize;
}
[[nodiscard]] uint32_t getStartAddress() const {
return reinterpret_cast<uint32_t>(dataPtr.get());
}
[[nodiscard]] uint32_t getEndAddress() const {
return reinterpret_cast<uint32_t>(dataPtr.get()) + this->totalSize;
}
[[nodiscard]] uint32_t getEntrypoint() const {
return entrypoint;
}
void setExportName(std::string name) {
this->export_name = std::move(name);
}
[[nodiscard]] const std::string &getExportName() const {
return this->export_name;
}
[[nodiscard]] bool isInitBeforeRelocationDoneHook() const {
return this->initBeforeRelocationDoneHook;
}
void setInitBeforeRelocationDoneHook(bool value) {
this->initBeforeRelocationDoneHook = value;
}
[[nodiscard]] bool isSkipInitFini() const {
return this->skipInitFini;
}
void setSkipInitFini(bool value) {
this->skipInitFini = value;
}
void setDataPtr(std::unique_ptr<uint8_t[]> ptr, uint32_t size) {
this->dataPtr = std::move(ptr);
this->totalSize = size;
}
std::unique_ptr<hook_data_t[]> hookDataStruct;
std::unique_ptr<export_data_t[]> exportDataStruct;
std::unique_ptr<module_function_symbol_data_t[]> functionSymbolDataStruct;
std::unique_ptr<dyn_linking_import_t[]> rplDataStruct;
std::unique_ptr<dyn_linking_relocation_entry_t[]> relocationDataStruct;
private:
std::vector<std::unique_ptr<RelocationData>> relocation_data_list;
std::vector<std::unique_ptr<ExportData>> export_data_list;
std::vector<std::unique_ptr<HookData>> hook_data_list;
std::set<std::string> dependency_list;
std::set<std::shared_ptr<FunctionSymbolData>, FunctionSymbolDataComparator> symbol_data_list;
std::map<std::string_view, std::shared_ptr<SectionInfo>> section_info_list;
std::unique_ptr<uint8_t[]> dataPtr;
std::string export_name;
uint32_t bssAddr = 0;
uint32_t bssSize = 0;
uint32_t sbssAddr = 0;
uint32_t sbssSize = 0;
uint32_t entrypoint = 0;
uint32_t totalSize = 0;
bool initBeforeRelocationDoneHook = false;
bool skipInitFini = false;
};

View File

@ -1,498 +0,0 @@
/****************************************************************************
* Copyright (C) 2022 Maschell
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
#include "ModuleDataFactory.h"
#include "HookData.h"
#include "fs/FileUtils.h"
#include "utils/ElfUtils.h"
#include "utils/OnLeavingScope.h"
#include "utils/logger.h"
#include "utils/utils.h"
#include "utils/wiiu_zlib.hpp"
#include <coreinit/cache.h>
#include <coreinit/memdefaultheap.h>
#include <map>
#include <optional>
#include <string>
#include <wums.h>
using namespace ELFIO;
std::optional<std::shared_ptr<ModuleData>> ModuleDataFactory::load(const std::string &path) {
elfio reader(new wiiu_zlib);
auto moduleData = make_shared_nothrow<ModuleData>();
if (!moduleData) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc module data");
return {};
}
uint8_t *buffer = nullptr;
uint32_t fsize = 0;
if (LoadFileToMem(path, &buffer, &fsize) < 0) {
DEBUG_FUNCTION_LINE_ERR("Failed to load file");
return {};
}
auto cleanupBuffer = onLeavingScope([buffer, fsize]() {
// Some games (e.g. Minecraft) expect the default heap to be empty.
// Make sure to clean up the memory after using it
memset(buffer, 0, ROUNDUP(fsize, 0x40));
MEMFreeToDefaultHeap(buffer);
});
// Load ELF data
if (!reader.load(reinterpret_cast<char *>(buffer), fsize)) {
DEBUG_FUNCTION_LINE_ERR("Can't find or process %s", path.c_str());
return {};
}
uint32_t sec_num = reader.sections.size();
bool checkedVersion = false;
for (uint32_t i = 0; i < sec_num; ++i) {
auto *psec = reader.sections[i];
if (psec->get_name() == ".wums.meta") {
char *curEntry = (char *) psec->get_data();
while ((uint32_t) curEntry < (uint32_t) psec->get_data() + psec->get_size()) {
if (*curEntry == '\0') {
curEntry++;
continue;
}
auto firstFound = std::string(curEntry).find_first_of('=');
if (firstFound != std::string::npos) {
curEntry[firstFound] = '\0';
std::string key(curEntry);
std::string value(curEntry + firstFound + 1);
if (key == "export_name") {
moduleData->setExportName(value);
} else if (key == "skipInitFini") {
if (value == "true") {
DEBUG_FUNCTION_LINE("skipInitFini = %s", value.c_str());
moduleData->setSkipInitFini(true);
} else {
moduleData->setSkipInitFini(false);
}
} else if (key == "initBeforeRelocationDoneHook") {
if (value == "true") {
DEBUG_FUNCTION_LINE("initBeforeRelocationDoneHook = %s", value.c_str());
moduleData->setInitBeforeRelocationDoneHook(true);
} else {
moduleData->setInitBeforeRelocationDoneHook(false);
}
} else if (key == "wums" || key == "wum") {
checkedVersion = true;
if (value != "0.3.1" && value != "0.3.2" && value != "0.3.3" && value != "0.3.4" && value != "0.3.5") {
DEBUG_FUNCTION_LINE_WARN("Ignoring module - Unsupported WUMS version: %s.", value.c_str());
return std::nullopt;
}
}
}
curEntry += strlen(curEntry) + 1;
}
break;
}
}
if (!checkedVersion) {
DEBUG_FUNCTION_LINE_ERR("Failed to check version. Ignoring module");
return {};
}
auto destinations = make_unique_nothrow<uint8_t *[]>(sec_num);
if (!destinations) {
DEBUG_FUNCTION_LINE_ERR("Failed alloc memory for destinations array");
return {};
}
uint32_t totalSize = 0;
uint32_t text_size = 0;
uint32_t data_size = 0;
for (uint32_t i = 0; i < sec_num; ++i) {
auto *psec = reader.sections[i];
if (psec->get_type() == 0x80000002) {
continue;
}
if ((psec->get_type() == SHT_PROGBITS || psec->get_type() == SHT_NOBITS) && (psec->get_flags() & SHF_ALLOC)) {
uint32_t sectionSize = psec->get_size();
auto address = (uint32_t) psec->get_address();
if ((address >= 0x02000000) && address < 0x10000000) {
text_size += sectionSize;
} else if ((address >= 0x10000000) && address < 0xC0000000) {
data_size += sectionSize;
}
if (psec->get_name().starts_with(".wums.")) {
data_size += sectionSize;
}
}
}
auto data = make_unique_nothrow<uint8_t[]>(text_size + data_size);
if (!data) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc memory for the .text section (%d bytes)", text_size);
return {};
}
DEBUG_FUNCTION_LINE("Allocated %d kb", (text_size + data_size) / 1024);
void *text_data = data.get();
void *data_data = (void *) ((uint32_t) data.get() + text_size);
auto baseOffset = (uint32_t) data.get();
uint32_t entrypoint = (uint32_t) text_data + (uint32_t) reader.get_entry() - 0x02000000;
for (uint32_t i = 0; i < sec_num; ++i) {
section *psec = reader.sections[i];
if (psec->get_type() == 0x80000002 || psec->get_name() == ".wut_load_bounds") {
continue;
}
if ((psec->get_type() == SHT_PROGBITS || psec->get_type() == SHT_NOBITS) && (psec->get_flags() & SHF_ALLOC)) {
uint32_t sectionSize = psec->get_size();
totalSize += sectionSize;
auto address = (uint32_t) psec->get_address();
destinations[psec->get_index()] = (uint8_t *) baseOffset;
uint32_t destination = baseOffset + address;
if ((address >= 0x02000000) && address < 0x10000000) {
destination -= 0x02000000;
destinations[psec->get_index()] -= 0x02000000;
baseOffset += sectionSize;
} else if ((address >= 0x10000000) && address < 0xC0000000) {
destination -= 0x10000000;
destinations[psec->get_index()] -= 0x10000000;
} else if (address >= 0xC0000000) {
DEBUG_FUNCTION_LINE_ERR("Loading section from 0xC0000000 is NOT supported");
return std::nullopt;
} else {
DEBUG_FUNCTION_LINE_ERR("Unhandled case");
return std::nullopt;
}
const char *p = reader.sections[i]->get_data();
if (destination + sectionSize > (uint32_t) data.get() + text_size + data_size) {
DEBUG_FUNCTION_LINE_ERR("Tried to overflow buffer. %08X > %08X", destination + sectionSize, (uint32_t) data.get() + text_size + data_size);
OSFatal("WUMSLoader: Tried to overflow buffer");
}
if (psec->get_type() == SHT_NOBITS) {
DEBUG_FUNCTION_LINE("memset section %s %08X to 0 (%d bytes)", psec->get_name().c_str(), destination, sectionSize);
memset((void *) destination, 0, sectionSize);
} else if (psec->get_type() == SHT_PROGBITS) {
DEBUG_FUNCTION_LINE("Copy section %s %p -> %08X (%d bytes)", psec->get_name().c_str(), p, destination, sectionSize);
memcpy((void *) destination, p, sectionSize);
}
if (psec->get_name() == ".bss") {
moduleData->setBSSLocation(destination, sectionSize);
memset(reinterpret_cast<void *>(destination), 0, sectionSize);
} else if (psec->get_name() == ".sbss") {
moduleData->setSBSSLocation(destination, sectionSize);
memset(reinterpret_cast<void *>(destination), 0, sectionSize);
}
auto sectionInfo = make_shared_nothrow<SectionInfo>(psec->get_name(), destination, sectionSize);
if (!sectionInfo) {
DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory for section info");
return {};
}
moduleData->addSectionInfo(sectionInfo);
DEBUG_FUNCTION_LINE("Saved %s section info. Location: %08X size: %08X", psec->get_name().c_str(), destination, sectionSize);
DCFlushRange((void *) destination, sectionSize);
ICInvalidateRange((void *) destination, sectionSize);
}
}
for (uint32_t i = 0; i < sec_num; ++i) {
section *psec = reader.sections[i];
if ((psec->get_type() == SHT_PROGBITS || psec->get_type() == SHT_NOBITS) && (psec->get_flags() & SHF_ALLOC)) {
DEBUG_FUNCTION_LINE("Linking (%d)... %s", i, psec->get_name().c_str());
if (!linkSection(reader, psec->get_index(), (uint32_t) destinations[psec->get_index()], (uint32_t) text_data, (uint32_t) data_data, nullptr, 0)) {
DEBUG_FUNCTION_LINE_ERR("elfLink failed");
return std::nullopt;
}
}
}
getImportRelocationData(moduleData, reader, destinations.get());
auto secInfo = moduleData->getSectionInfo(".wums.exports");
if (secInfo && secInfo.value()->getSize() > 0) {
size_t entries_count = secInfo.value()->getSize() / sizeof(wums_entry_t);
auto *entries = (wums_entry_t *) secInfo.value()->getAddress();
if (entries != nullptr) {
for (size_t j = 0; j < entries_count; j++) {
wums_entry_t *exp = &entries[j];
DEBUG_FUNCTION_LINE("Saving export of type %d, name %s, target: %p", exp->type, exp->name, exp->address);
auto exportData = make_unique_nothrow<ExportData>(exp->type, exp->name, exp->address);
if (!exportData) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc ExportData");
return {};
}
moduleData->addExportData(std::move(exportData));
}
}
}
secInfo = moduleData->getSectionInfo(".wums.dependencies");
if (secInfo && secInfo.value()->getSize() > 0) {
if (secInfo.value()->getAddress() != 0) {
char *curEntry = (char *) secInfo.value()->getAddress();
while ((uint32_t) curEntry < (uint32_t) secInfo.value()->getAddress() + secInfo.value()->getSize()) {
if (*curEntry == '\0') {
curEntry++;
continue;
}
moduleData->addDependency(curEntry);
curEntry += strlen(curEntry) + 1;
}
}
}
secInfo = moduleData->getSectionInfo(".wums.hooks");
if (secInfo && secInfo.value()->getSize() > 0) {
size_t entries_count = secInfo.value()->getSize() / sizeof(wums_hook_t);
auto *hooks = (wums_hook_t *) secInfo.value()->getAddress();
if (hooks != nullptr) {
for (size_t j = 0; j < entries_count; j++) {
wums_hook_t *hook = &hooks[j];
DEBUG_FUNCTION_LINE("Saving hook of type %08X, target: %p", hook->type, hook->target);
auto hookData = make_unique_nothrow<HookData>(const_cast<void *>(hook->target), hook->type);
if (!hookData) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc HookData");
return {};
}
moduleData->addHookData(std::move(hookData));
}
}
}
// Get the symbol for functions.
Elf_Half n = reader.sections.size();
for (Elf_Half i = 0; i < n; ++i) {
section *sec = reader.sections[i];
if (SHT_SYMTAB == sec->get_type()) {
symbol_section_accessor symbols(reader, sec);
auto sym_no = (uint32_t) symbols.get_symbols_num();
if (sym_no > 0) {
for (Elf_Half j = 0; j < sym_no; ++j) {
std::string name;
Elf64_Addr value = 0;
Elf_Xword size = 0;
unsigned char bind = 0;
unsigned char type = 0;
Elf_Half section = 0;
unsigned char other = 0;
if (symbols.get_symbol(j, name, value, size, bind, type, section, other)) {
if (type == STT_FUNC) { // We only care about functions.
auto sectionVal = reader.sections[section];
auto offsetVal = value - sectionVal->get_address();
auto sectionOpt = moduleData->getSectionInfo(sectionVal->get_name());
if (!sectionOpt.has_value()) {
continue;
}
auto finalAddress = offsetVal + sectionOpt.value()->getAddress();
auto functionSymbolData = make_shared_nothrow<FunctionSymbolData>(name, (void *) finalAddress, (uint32_t) size);
if (!functionSymbolData) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc FunctionSymbolData");
return std::nullopt;
} else {
moduleData->addFunctionSymbolData(std::move(functionSymbolData));
}
}
}
}
break;
}
}
}
DCFlushRange((void *) text_data, text_size);
ICInvalidateRange((void *) text_data, text_size);
DCFlushRange((void *) data_data, data_size);
ICInvalidateRange((void *) data_data, data_size);
if (totalSize > text_size + data_size) {
DEBUG_FUNCTION_LINE_ERR("We didn't allocate enough memory!!");
return std::nullopt;
}
DEBUG_FUNCTION_LINE("Saved entrypoint as %08X", entrypoint);
DEBUG_FUNCTION_LINE("Saved startAddress as %08X", (uint32_t) data.get());
DEBUG_FUNCTION_LINE("Saved endAddress as %08X", (uint32_t) data.get() + totalSize);
moduleData->setEntrypoint(entrypoint);
moduleData->setDataPtr(std::move(data), totalSize);
DEBUG_FUNCTION_LINE("Loaded %s size: %d kilobytes", path.c_str(), totalSize / 1024);
return moduleData;
}
bool ModuleDataFactory::getImportRelocationData(std::shared_ptr<ModuleData> &moduleData, ELFIO::elfio &reader, uint8_t **destinations) {
std::map<uint32_t, std::shared_ptr<ImportRPLInformation>> infoMap;
uint32_t sec_num = reader.sections.size();
for (uint32_t i = 0; i < sec_num; ++i) {
auto *psec = reader.sections[i];
if (psec->get_type() == 0x80000002) {
auto info = make_shared_nothrow<ImportRPLInformation>(psec->get_name());
if (!info) {
DEBUG_FUNCTION_LINE_ERR("Failed too allocate ImportRPLInformation");
return false;
}
infoMap[i] = std::move(info);
}
}
for (uint32_t i = 0; i < sec_num; ++i) {
section *psec = reader.sections[i];
if (psec->get_type() == SHT_RELA || psec->get_type() == SHT_REL) {
relocation_section_accessor rel(reader, psec);
for (uint32_t j = 0; j < (uint32_t) rel.get_entries_num(); ++j) {
Elf_Word symbol = 0;
Elf64_Addr offset;
Elf_Word type;
Elf_Sxword addend;
std::string sym_name;
Elf64_Addr sym_value;
if (!rel.get_entry(j, offset, symbol, type, addend)) {
DEBUG_FUNCTION_LINE_ERR("Failed to get relocation");
return false;
}
symbol_section_accessor symbols(reader, reader.sections[(Elf_Half) psec->get_link()]);
// Find the symbol
Elf_Xword size;
unsigned char bind;
unsigned char symbolType;
Elf_Half sym_section_index;
unsigned char other;
if (!symbols.get_symbol(symbol, sym_name, sym_value, size,
bind, symbolType, sym_section_index, other)) {
DEBUG_FUNCTION_LINE_ERR("Failed to get symbol");
return false;
}
auto adjusted_sym_value = (uint32_t) sym_value;
if (adjusted_sym_value < 0xC0000000) {
continue;
}
uint32_t section_index = psec->get_info();
if (!infoMap.contains(sym_section_index)) {
DEBUG_FUNCTION_LINE_ERR("Relocation is referencing a unknown section. %d destination: %p sym_name %s", section_index, destinations[section_index], sym_name.c_str());
OSFatal("Relocation is referencing a unknown section.");
}
auto relocationData = make_unique_nothrow<RelocationData>(type,
offset - 0x02000000,
addend,
(void *) (destinations[section_index] + 0x02000000),
sym_name,
infoMap[sym_section_index]);
if (!relocationData) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc relocation data");
return false;
}
moduleData->addRelocationData(std::move(relocationData));
}
}
}
return true;
}
bool ModuleDataFactory::linkSection(elfio &reader, uint32_t section_index, uint32_t destination, uint32_t base_text, uint32_t base_data, relocation_trampoline_entry_t *trampoline_data,
uint32_t trampoline_data_length) {
uint32_t sec_num = reader.sections.size();
for (uint32_t i = 0; i < sec_num; ++i) {
section *psec = reader.sections[i];
if (psec->get_info() == section_index) {
DEBUG_FUNCTION_LINE_VERBOSE("Found relocation section %s", psec->get_name().c_str());
relocation_section_accessor rel(reader, psec);
for (uint32_t j = 0; j < (uint32_t) rel.get_entries_num(); ++j) {
Elf_Word symbol = 0;
Elf64_Addr offset;
Elf_Word type;
Elf_Sxword addend;
std::string sym_name;
Elf64_Addr sym_value;
if (!rel.get_entry(j, offset, symbol, type, addend)) {
DEBUG_FUNCTION_LINE_ERR("Failed to get relocation");
return false;
}
symbol_section_accessor symbols(reader, reader.sections[(Elf_Half) psec->get_link()]);
// Find the symbol
Elf_Xword size;
unsigned char bind;
unsigned char symbolType;
Elf_Half sym_section_index;
unsigned char other;
if (!symbols.get_symbol(symbol, sym_name, sym_value, size,
bind, symbolType, sym_section_index, other)) {
DEBUG_FUNCTION_LINE_ERR("Failed to get symbol");
return false;
}
auto adjusted_sym_value = (uint32_t) sym_value;
if ((adjusted_sym_value >= 0x02000000) && adjusted_sym_value < 0x10000000) {
adjusted_sym_value -= 0x02000000;
adjusted_sym_value += base_text;
} else if ((adjusted_sym_value >= 0x10000000) && adjusted_sym_value < 0xC0000000) {
adjusted_sym_value -= 0x10000000;
adjusted_sym_value += base_data;
} else if (adjusted_sym_value >= 0xC0000000) {
// Skip imports
continue;
} else if (adjusted_sym_value == 0x0) {
//
} else {
DEBUG_FUNCTION_LINE_ERR("Unhandled case %08X", adjusted_sym_value);
return false;
}
if (sym_section_index == SHN_ABS) {
//
} else if (sym_section_index > SHN_LORESERVE) {
DEBUG_FUNCTION_LINE_ERR("NOT IMPLEMENTED: %04X", sym_section_index);
return false;
}
if (!ElfUtils::elfLinkOne(type, offset, addend, destination, adjusted_sym_value, trampoline_data, trampoline_data_length, RELOC_TYPE_FIXED)) {
DEBUG_FUNCTION_LINE_ERR("Link failed");
return false;
}
}
}
}
return true;
}

View File

@ -1,213 +1,224 @@
#include "ModuleDataPersistence.h"
#include "ImportRPLInformation.h"
#include "ModuleContainer.h"
#include "globals.h"
#include "utils/logger.h"
#include "utils/utils.h"
#include <algorithm>
#include <coreinit/cache.h>
bool ModuleDataPersistence::saveModuleData(module_information_t *moduleInformation, const std::vector<std::shared_ptr<ModuleData>> &moduleList) {
auto module_data_list = make_unique_nothrow<module_information_single_t[]>(moduleList.size());
if (!module_data_list) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc memory to persist module data");
return false;
#include <map>
namespace WUMSLoader::Modules {
module_information_t *PersistedModuleData::getModuleInformation() {
return &mRootInfo;
}
void PersistedModuleData::setUsedRPLsList(const std::map<std::string, OSDynLoad_Module, std::less<>> &usedRPls) {
PROFILE_FUNCTION();
// free previous allocations.
if (mRootInfo.acquired_rpls) {
free(mRootInfo.acquired_rpls);
mRootInfo.acquired_rpls = nullptr;
}
mRootInfo.number_acquired_rpls = usedRPls.size();
if (usedRPls.empty()) {
mRootInfo.acquired_rpls = nullptr;
return;
}
moduleInformation->modules = module_data_list.get();
mRootInfo.acquired_rpls = static_cast<uint32_t *>(malloc(usedRPls.size() * sizeof(uint32_t)));
if (!mRootInfo.acquired_rpls) {
DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory for mRootInfo.acquired_rpls");
OSFatal("Failed to allocate memory");
}
uint32_t i = 0;
for (auto &module : moduleList) {
auto &module_data = module_data_list[i];
module_data = {};
if (!saveModuleData(module_data, module)) {
module->relocationDataStruct.reset();
module->hookDataStruct.reset();
module->exportDataStruct.reset();
module->functionSymbolDataStruct.reset();
module->rplDataStruct.reset();
DEBUG_FUNCTION_LINE_ERR("Failed to persist data for module. No memory?");
OSFatal("Failed to persist data for module. No memory?");
continue;
} else {
moduleInformation->number_modules++;
i++;
}
for (auto &rpl : usedRPls) {
mRootInfo.acquired_rpls[i] = reinterpret_cast<uint32_t>(rpl.second);
++i;
}
}
void PersistedModuleData::build(const std::vector<ModuleContainer> &moduleList) {
PROFILE_FUNCTION();
clearModuleInfo();
if (moduleList.empty()) {
return;
}
gModuleDataInfo = std::move(module_data_list);
// Pre-allocate the exact number of modules needed
mModules.resize(moduleList.size());
for (size_t i = 0; i < moduleList.size(); ++i) {
buildModuleData(moduleList[i], &mModules[i]);
}
// Wire up the root info AFTER the vector is fully populated
// to ensure the .data() pointer doesn't invalidate.
mRootInfo.modules = mModules.data();
mRootInfo.number_modules = mModules.size();
OSMemoryBarrier();
return true;
}
bool ModuleDataPersistence::saveModuleData(module_information_single_t &module_data, const std::shared_ptr<ModuleData> &module) {
if (!saveRelocationDataForModule(module_data, module)) {
DEBUG_FUNCTION_LINE_ERR("Failed to store relocation data of module");
return false;
}
if (!saveHookDataForModule(module_data, module)) {
DEBUG_FUNCTION_LINE_ERR("Failed to store hook data of module");
return false;
}
if (!saveExportDataForModule(module_data, module)) {
DEBUG_FUNCTION_LINE_ERR("Failed to store export data of module");
return false;
}
if (!saveFunctionSymbolDataForModule(module_data, module)) {
DEBUG_FUNCTION_LINE_ERR("Failed to store function symbol data of module");
return false;
}
module_data.module_export_name = (char *) module->getExportName().c_str();
module_data.bssAddr = module->getBSSAddress();
module_data.bssSize = module->getBSSSize();
module_data.sbssAddr = module->getSBSSAddress();
module_data.sbssSize = module->getSBSSSize();
module_data.startAddress = module->getStartAddress();
module_data.endAddress = module->getEndAddress();
module_data.entrypoint = module->getEntrypoint();
module_data.skipInitFini = module->isSkipInitFini();
module_data.initBeforeRelocationDoneHook = module->isInitBeforeRelocationDoneHook();
return true;
std::unique_ptr<PersistedModuleData> PersistedModuleData::create() {
auto persistedData = std::unique_ptr<PersistedModuleData>(new PersistedModuleData());
persistedData->clear();
return persistedData;
}
bool ModuleDataPersistence::saveFunctionSymbolDataForModule(module_information_single_t &module_data, const std::shared_ptr<ModuleData> &module) {
uint32_t entryCount = module->getFunctionSymbolDataList().size();
auto function_symbol_data = make_unique_nothrow<module_function_symbol_data_t[]>(entryCount);
if (!function_symbol_data) {
DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory for the function symbol data.");
return false;
}
uint32_t i = 0;
for (auto &curFuncSym : module->getFunctionSymbolDataList()) {
if (i >= entryCount) {
DEBUG_FUNCTION_LINE_ERR("We tried to write more entries than we have space for.");
OSFatal("We tried to write more entries than we have space for.");
}
function_symbol_data[i].address = curFuncSym->getAddress();
function_symbol_data[i].name = (char *) curFuncSym->getName().c_str();
function_symbol_data[i].size = curFuncSym->getSize();
i++;
}
module_data.function_symbol_entries = function_symbol_data.get();
module_data.number_function_symbols = i;
module->functionSymbolDataStruct = std::move(function_symbol_data);
return true;
void PersistedModuleData::clearModuleInfo() {
mRootInfo.number_modules = 0;
mRootInfo.modules = nullptr;
mModules.clear();
mHookDataList.clear();
mExportDataList.clear();
mFunctionSymbolDataList.clear();
mRelocationDataList.clear();
mRplDataList.clear();
}
bool ModuleDataPersistence::saveExportDataForModule(module_information_single_t &module_data, const std::shared_ptr<ModuleData> &module) {
auto export_data = make_unique_nothrow<export_data_t[]>(module->getExportDataList().size());
if (!export_data) {
DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory for the export data.");
return false;
void PersistedModuleData::clearRPLInfo() {
mRootInfo.number_acquired_rpls = 0;
if (mRootInfo.acquired_rpls) {
free(mRootInfo.acquired_rpls);
}
uint32_t exportCount = 0;
for (auto const &export_ : module->getExportDataList()) {
if (exportCount >= module->getExportDataList().size()) {
DEBUG_FUNCTION_LINE_ERR("We tried to write more entries than we have space for.");
OSFatal("We tried to write more entries than we have space for.");
}
auto *curExport = &export_data[exportCount++];
curExport->type = export_->getType();
curExport->name = export_->getName().c_str();
curExport->address = (uint32_t) export_->getAddress();
}
module_data.export_entries = export_data.get();
module_data.number_export_entries = exportCount;
module->exportDataStruct = std::move(export_data);
return true;
mRootInfo.acquired_rpls = nullptr;
}
bool ModuleDataPersistence::saveHookDataForModule(module_information_single_t &module_data, const std::shared_ptr<ModuleData> &module) {
auto hook_data = make_unique_nothrow<hook_data_t[]>(module->getHookDataList().size());
if (!hook_data) {
DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory for the hook data.");
return false;
}
uint32_t hookCount = 0;
for (auto const &hook : module->getHookDataList()) {
if (hookCount >= module->getHookDataList().size()) {
DEBUG_FUNCTION_LINE_ERR("We tried to write more entries than we have space for.");
OSFatal("We tried to write more entries than we have space for.");
}
auto *curHook = &hook_data[hookCount++];
curHook->type = hook->getType();
curHook->target = (uint32_t) hook->getFunctionPointer();
}
module_data.hook_entries = hook_data.get();
module_data.number_hook_entries = hookCount;
module->hookDataStruct = std::move(hook_data);
return true;
void PersistedModuleData::clear() {
mRootInfo.version = MODULE_INFORMATION_VERSION;
clearModuleInfo();
clearRPLInfo();
}
bool ModuleDataPersistence::saveRelocationDataForModule(module_information_single_t &module_data, const std::shared_ptr<ModuleData> &module) {
auto relocation_data = make_unique_nothrow<dyn_linking_relocation_entry_t[]>(module->getRelocationDataList().size());
if (!relocation_data) {
DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory for the relocation data.");
return false;
void PersistedModuleData::buildModuleData(const ModuleContainer &module, module_information_single_t *module_data) {
if (!module_data) {
return;
}
*module_data = {}; // Zero out
// Determine how many dyn_linking_import_t entries we need.
std::set<std::string_view> rplInfoCountSet;
for (auto const &reloc : module->getRelocationDataList()) {
rplInfoCountSet.insert(reloc->getImportRPLInformation().getName());
}
buildRelocationData(module, module_data);
buildHookData(module, module_data);
buildExportData(module, module_data);
buildFunctionSymbolData(module, module_data);
uint32_t rplInfoTotalCount = rplInfoCountSet.size();
rplInfoCountSet.clear();
const auto &metaInfo = module.getMetaInformation();
const auto &linkInfo = module.getLinkInformation();
auto rpl_data = make_unique_nothrow<dyn_linking_import_t[]>(rplInfoTotalCount);
if (!rpl_data) {
DEBUG_FUNCTION_LINE_ERR("Failed to allocate memory for the RPLInfos.");
return false;
}
uint32_t relocationCount = 0;
uint32_t rplInfoCount = 0;
std::map<std::string_view, dyn_linking_import_t *> rplInfoMap;
for (auto const &reloc : module->getRelocationDataList()) {
if (relocationCount >= module->getRelocationDataList().size()) {
DEBUG_FUNCTION_LINE_ERR("We tried to write more entries than we have space for.");
OSFatal("We tried to write more entries than we have space for.");
}
auto *curReloc = &relocation_data[relocationCount++];
curReloc->destination = const_cast<void *>(reloc->getDestination());
curReloc->offset = reloc->getOffset();
curReloc->addend = reloc->getAddend();
curReloc->type = reloc->getType();
curReloc->functionName = reloc->getName().c_str();
auto &rplInfo = reloc->getImportRPLInformation();
auto rplIt = rplInfoMap.find(rplInfo.getName());
if (rplIt != rplInfoMap.end()) {
curReloc->importEntry = rplIt->second;
} else {
if (rplInfoCount >= rplInfoTotalCount) {
DEBUG_FUNCTION_LINE_ERR("We tried to write more entries than we have space for.");
OSFatal("We tried to write more entries than we have space for.");
}
auto *rplInfoPtr = &rpl_data[rplInfoCount++];
rplInfoPtr->isData = rplInfo.isData();
rplInfoPtr->importName = rplInfo.getName().c_str();
rplInfoMap[rplInfo.getName()] = rplInfoPtr;
curReloc->importEntry = rplInfoPtr;
}
}
module_data.linking_entries = relocation_data.get();
module_data.number_linking_entries = relocationCount;
module->relocationDataStruct = std::move(relocation_data);
module->rplDataStruct = std::move(rpl_data);
return true;
module_data->module_export_name = const_cast<char *>(metaInfo.getExportName().c_str());
module_data->bssAddr = 0;
module_data->bssSize = 0;
module_data->sbssAddr = 0;
module_data->sbssSize = 0;
module_data->startAddress = linkInfo.getStartAddress();
module_data->endAddress = linkInfo.getEndAddress();
module_data->entrypoint = reinterpret_cast<uint32_t>(linkInfo.getEntrypoint());
module_data->skipInitFini = metaInfo.isSkipInitFini();
module_data->initBeforeRelocationDoneHook = metaInfo.isInitBeforeRelocationDoneHook();
}
void PersistedModuleData::buildFunctionSymbolData(const ModuleContainer &module, module_information_single_t *module_data) {
const auto &symList = module.getLinkInformation().getFunctionSymbolDataList();
if (symList.empty()) return;
std::vector<module_function_symbol_data_t> data;
data.reserve(symList.size());
for (const auto &sym : symList) {
data.push_back({const_cast<char *>(sym.getName().c_str()), sym.getAddress(), sym.getSize()});
}
std::ranges::sort(data, [](const auto &a, const auto &b) {
return a.address < b.address;
});
mFunctionSymbolDataList.push_back(std::move(data));
module_data->function_symbol_entries = mFunctionSymbolDataList.back().data();
module_data->number_function_symbols = mFunctionSymbolDataList.back().size();
}
void PersistedModuleData::buildExportData(const ModuleContainer &module, module_information_single_t *module_data) {
const auto &exportList = module.getLinkInformation().getExportDataList();
if (exportList.empty()) return;
std::vector<export_data_t> data;
data.reserve(exportList.size());
for (const auto &exp : exportList) {
data.push_back({static_cast<uint32_t>(exp.getType()), exp.getName().c_str(), reinterpret_cast<uint32_t>(exp.getAddress())});
}
mExportDataList.push_back(std::move(data));
module_data->export_entries = mExportDataList.back().data();
module_data->number_export_entries = mExportDataList.back().size();
}
void PersistedModuleData::buildHookData(const ModuleContainer &module, module_information_single_t *module_data) {
const auto &hookList = module.getLinkInformation().getHookDataList();
if (hookList.empty()) return;
std::vector<hook_data_t> data;
data.reserve(hookList.size());
for (const auto &hook : hookList) {
data.push_back({.type = static_cast<uint32_t>(hook.getType()),
.target = reinterpret_cast<uint32_t>(hook.getFunctionPointer())});
}
mHookDataList.push_back(std::move(data));
module_data->hook_entries = mHookDataList.back().data();
module_data->number_hook_entries = mHookDataList.back().size();
}
void PersistedModuleData::buildRelocationData(const ModuleContainer &module, module_information_single_t *module_data) {
const auto &relocList = module.getLinkInformation().getRelocationDataList();
if (relocList.empty()) return;
// we need string_view and ImportRPLInformation* here to use the persistent string inside the ModuleContainer
std::map<std::string_view, const ImportRPLInformation *> uniqueRpls;
for (const auto &reloc : relocList) {
const auto &rplInfo = reloc.getImportRPLInformation();
uniqueRpls.try_emplace(rplInfo.getName(), &rplInfo);
}
std::vector<dyn_linking_import_t> rplData;
rplData.reserve(uniqueRpls.size());
// Map string to INDEX
std::map<std::string_view, size_t> rplIndexMap;
for (const auto &[name, info] : uniqueRpls) {
rplData.push_back({.importName = name.data(),
.isData = info->isData()});
rplIndexMap[name] = rplData.size() - 1;
}
mRplDataList.push_back(std::move(rplData));
// Build the Relocation data array
std::vector<dyn_linking_relocation_entry_t> relocData;
relocData.reserve(relocList.size());
for (const auto &reloc : relocList) {
const size_t targetIndex = rplIndexMap[reloc.getImportRPLInformation().getName()];
relocData.push_back({
.functionName = reloc.getName().c_str(),
.importEntry = &mRplDataList.back()[targetIndex],
.destination = const_cast<void *>(reloc.getDestination()),
.type = reloc.getType(),
.offset = reloc.getOffset(),
.addend = reloc.getAddend(),
});
}
mRelocationDataList.push_back(std::move(relocData));
// Only do this after the push_back to make sure .data() is valid!
module_data->linking_entries = mRelocationDataList.back().data();
module_data->number_linking_entries = mRelocationDataList.back().size();
}
} // namespace WUMSLoader::Modules

View File

@ -1,15 +1,48 @@
#pragma once
#include "ModuleData.h"
#include <coreinit/memheap.h>
#include <wums.h>
#include <wums/defines/module_defines.h>
class ModuleDataPersistence {
#include <coreinit/dynload.h>
#include <map>
#include <memory>
#include <string>
#include <vector>
namespace WUMSLoader::Modules {
class ModuleContainer;
class PersistedModuleData {
public:
static bool saveModuleData(module_information_t *moduleInformation, const std::vector<std::shared_ptr<ModuleData>> &moduleList);
static bool saveModuleData(module_information_single_t &module_data, const std::shared_ptr<ModuleData> &module);
static bool saveRelocationDataForModule(module_information_single_t &module_data, const std::shared_ptr<ModuleData> &module);
static bool saveExportDataForModule(module_information_single_t &module_data, const std::shared_ptr<ModuleData> &module);
static bool saveHookDataForModule(module_information_single_t &module_data, const std::shared_ptr<ModuleData> &module);
static bool saveFunctionSymbolDataForModule(module_information_single_t &module_data, const std::shared_ptr<ModuleData> &module);
[[nodiscard]] module_information_t *getModuleInformation();
void setUsedRPLsList(const std::map<std::string, OSDynLoad_Module, std::less<>> &usedRPls);
void build(const std::vector<ModuleContainer> &moduleList);
static std::unique_ptr<PersistedModuleData> create();
private:
PersistedModuleData() = default;
void clear();
void clearModuleInfo();
void clearRPLInfo();
// Internal builders
void buildModuleData(const ModuleContainer &module, module_information_single_t *module_data);
void buildRelocationData(const ModuleContainer &module, module_information_single_t *module_data);
void buildExportData(const ModuleContainer &module, module_information_single_t *module_data);
void buildHookData(const ModuleContainer &module, module_information_single_t *module_data);
void buildFunctionSymbolData(const ModuleContainer &module, module_information_single_t *module_data);
module_information_t mRootInfo = {};
// Pure standard library memory management!
std::vector<module_information_single_t> mModules;
std::vector<std::vector<hook_data_t>> mHookDataList;
std::vector<std::vector<export_data_t>> mExportDataList;
std::vector<std::vector<module_function_symbol_data_t>> mFunctionSymbolDataList;
std::vector<std::vector<dyn_linking_relocation_entry_t>> mRelocationDataList;
std::vector<std::vector<dyn_linking_import_t>> mRplDataList;
};
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,129 @@
#include "ModuleLinkInformation.h"
#include "utils/logger.h"
#include <coreinit/cache.h>
namespace WUMSLoader::Modules {
ModuleLinkInformation::ModuleLinkInformation() : mEntrypoint(nullptr) {
}
ModuleLinkInformation::ModuleLinkInformation(ModuleLinkInformation &&src) : mDependencies(std::move(src.mDependencies)),
mHookDataList(std::move(src.mHookDataList)),
mRelocationDataList(std::move(src.mRelocationDataList)),
mExportDataList(std::move(src.mExportDataList)),
mSymbolDataList(std::move(src.mSymbolDataList)),
mSectionInfoList(std::move(src.mSectionInfoList)),
mEntrypoint(src.mEntrypoint),
mModuleMemoryPool(std::move(src.mModuleMemoryPool)) {
src.mEntrypoint = nullptr;
}
ModuleLinkInformation &ModuleLinkInformation::operator=(ModuleLinkInformation &&src) noexcept {
if (this != &src) {
this->mDependencies = std::move(src.mDependencies);
this->mHookDataList = std::move(src.mHookDataList);
this->mExportDataList = std::move(src.mExportDataList);
this->mRelocationDataList = std::move(src.mRelocationDataList);
this->mSymbolDataList = std::move(src.mSymbolDataList);
this->mSectionInfoList = std::move(src.mSectionInfoList);
this->mModuleMemoryPool = std::move(src.mModuleMemoryPool);
this->mEntrypoint = src.mEntrypoint;
src.mEntrypoint = nullptr;
}
return *this;
}
void ModuleLinkInformation::addHookData(HookData hookData) {
mHookDataList.push_back(std::move(hookData));
}
const std::vector<HookData> &ModuleLinkInformation::getHookDataList() const {
return mHookDataList;
}
void ModuleLinkInformation::addExportData(ExportData exportData) {
mExportDataList.push_back(std::move(exportData));
}
const std::vector<ExportData> &ModuleLinkInformation::getExportDataList() const {
return mExportDataList;
}
void ModuleLinkInformation::addRelocationData(RelocationData relocationData) {
mRelocationDataList.push_back(std::move(relocationData));
}
const std::vector<RelocationData> &ModuleLinkInformation::getRelocationDataList() const {
return mRelocationDataList;
}
void ModuleLinkInformation::addFunctionSymbolData(FunctionSymbolData symbol_data) {
mSymbolDataList.push_back(std::move(symbol_data));
}
void ModuleLinkInformation::addSectionInfo(SectionInfo sectionInfo) {
std::string name = sectionInfo.getName();
mSectionInfoList.emplace(std::move(name), std::move(sectionInfo));
}
void ModuleLinkInformation::addDependency(std::string_view dependency) {
mDependencies.emplace(dependency);
}
void ModuleLinkInformation::setEntrypoint(void *val) {
mEntrypoint = val;
}
const std::map<std::string, SectionInfo> &ModuleLinkInformation::getSectionInfoList() const {
return mSectionInfoList;
}
std::optional<SectionInfo> ModuleLinkInformation::getSectionInfo(const std::string &sectionName) const {
if (const auto it = mSectionInfoList.find(sectionName); it != mSectionInfoList.end()) {
return it->second;
}
return std::nullopt;
}
const std::vector<FunctionSymbolData> &ModuleLinkInformation::getFunctionSymbolDataList() const {
return mSymbolDataList;
}
void *ModuleLinkInformation::getEntrypoint() const {
return mEntrypoint;
}
uint32_t ModuleLinkInformation::getStartAddress() const {
return reinterpret_cast<uint32_t>(mModuleMemoryPool.dataView().data());
}
uint32_t ModuleLinkInformation::getEndAddress() const {
return reinterpret_cast<uint32_t>(mModuleMemoryPool.dataView().data()) + mModuleMemoryPool.dataView().size();
}
const std::set<std::string> &ModuleLinkInformation::getDependencyList() const {
return mDependencies;
}
std::span<relocation_trampoline_entry_t> ModuleLinkInformation::getTrampData() const {
if (mModuleMemoryPool && mModuleMemoryPool.numberOfSegments() < MEMORY_POOL_INDEX_TRAMP + 1) {
DEBUG_FUNCTION_LINE_ERR("Failed to return trampoline data. Memory is either not valid or has not enough segments");
return {};
}
const auto &entry = mModuleMemoryPool[MEMORY_POOL_INDEX_TRAMP]; // 1 is tramp data
return std::span(static_cast<relocation_trampoline_entry_t *>(entry.data()), entry.size() / sizeof(relocation_trampoline_entry_t));
}
bool ModuleLinkInformation::hasValidData() const {
return mModuleMemoryPool && mModuleMemoryPool.numberOfSegments() >= 3;
}
void ModuleLinkInformation::flushCache() const {
if (!mModuleMemoryPool) return;
DCFlushRange((void *) mModuleMemoryPool.dataView().data(), mModuleMemoryPool.dataView().size_bytes());
ICInvalidateRange((void *) mModuleMemoryPool.dataView().data(), mModuleMemoryPool.dataView().size_bytes());
}
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,87 @@
#pragma once
#include "ExportData.h"
#include "FunctionSymbolData.h"
#include "HookData.h"
#include "RelocationData.h"
#include "SectionInfo.h"
#include "utils/HeapMemoryFixedSize.h"
#include <map>
#include <optional>
#include <set>
#include <vector>
namespace WUMSLoader::Modules {
class ModuleLinkInformation {
static constexpr uint32_t MEMORY_POOL_INDEX_TEXT = 0;
static constexpr uint32_t MEMORY_POOL_INDEX_TRAMP = 1;
static constexpr uint32_t MEMORY_POOL_INDEX_DATA = 2;
public:
ModuleLinkInformation(const ModuleLinkInformation &) = delete;
ModuleLinkInformation(ModuleLinkInformation &&src) noexcept;
ModuleLinkInformation &operator=(ModuleLinkInformation &&src) noexcept;
[[nodiscard]] const std::vector<ExportData> &getExportDataList() const;
[[nodiscard]] const std::vector<HookData> &getHookDataList() const;
[[nodiscard]] const std::vector<RelocationData> &getRelocationDataList() const;
[[nodiscard]] const std::map<std::string, SectionInfo> &getSectionInfoList() const;
[[nodiscard]] std::optional<SectionInfo> getSectionInfo(const std::string &sectionName) const;
[[nodiscard]] const std::vector<FunctionSymbolData> &getFunctionSymbolDataList() const;
[[nodiscard]] void *getEntrypoint() const;
[[nodiscard]] uint32_t getStartAddress() const;
[[nodiscard]] uint32_t getEndAddress() const;
[[nodiscard]] const std::set<std::string> &getDependencyList() const;
[[nodiscard]] std::span<relocation_trampoline_entry_t> getTrampData() const;
[[nodiscard]] bool hasValidData() const;
void flushCache() const;
private:
ModuleLinkInformation();
void addHookData(HookData hookData);
void addExportData(ExportData exportData);
void addRelocationData(RelocationData relocationData);
void addFunctionSymbolData(FunctionSymbolData symbolData);
void addSectionInfo(SectionInfo sectionInfo);
void addDependency(std::string_view dependency);
void setEntrypoint(void *val);
std::set<std::string> mDependencies;
std::vector<HookData> mHookDataList;
std::vector<RelocationData> mRelocationDataList;
std::vector<ExportData> mExportDataList;
std::vector<FunctionSymbolData> mSymbolDataList;
std::map<std::string, SectionInfo> mSectionInfoList;
void *mEntrypoint;
HeapMemoryFixedSizePool mModuleMemoryPool;
friend class ModuleLinkInformationFactory;
};
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,557 @@
/****************************************************************************
* Copyright (C) 2022 Maschell
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
#include "ModuleLinkInformationFactory.h"
#include "ExportData.h"
#include "FunctionSymbolData.h"
#include "HookData.h"
#include "ImportRPLInformation.h"
#include "ModuleAllocator.h"
#include "ModuleLinkInformation.h"
#include "SectionInfo.h"
#include "ElfUtils.h"
#include "ModuleMetaInformation.h"
#include "globals.h"
#include "utils/OnLeavingScope.h"
#include "utils/logger.h"
#include "utils/utils.h"
#include "../utils/LoadedFile.h"
#include "elfio/elfio.hpp"
#include "utils/wiiu_zlib.hpp"
#include <wums/exports.h>
#include <coreinit/memdefaultheap.h>
#include <optional>
#include <span>
#include <wums/hooks.h>
namespace WUMSLoader::Modules {
using namespace ELFIO;
namespace {
bool predictRequiredTrampolineCount(const elfio &reader, uint32_t &outTrampCount) {
for (const auto &parentSec : reader.sections) {
if ((parentSec->get_type() == SHT_PROGBITS || parentSec->get_type() == SHT_NOBITS) && (parentSec->get_flags() & SHF_ALLOC)) {
for (const auto &psec : reader.sections) {
if ((psec->get_type() == SHT_REL || psec->get_type() == SHT_RELA) &&
psec->get_info() == parentSec->get_index()) {
const relocation_section_accessor rel(reader, psec.get());
const symbol_section_accessor symbols(reader, reader.sections[static_cast<Elf_Half>(psec->get_link())]);
Elf_Word symbol = 0;
Elf64_Addr offset;
Elf_Word type;
Elf_Sxword addend;
std::string sym_name;
Elf64_Addr sym_value;
Elf_Xword size;
unsigned char bind;
unsigned char symbolType;
Elf_Half sym_section_index;
unsigned char other;
for (uint32_t j = 0; j < static_cast<uint32_t>(rel.get_entries_num()); ++j) {
if (!rel.get_entry(j, offset, symbol, type, addend)) {
DEBUG_FUNCTION_LINE_ERR("Failed to get relocation");
return false;
}
if (!symbols.get_symbol(symbol, sym_name, sym_value, size,
bind, symbolType, sym_section_index, other)) {
DEBUG_FUNCTION_LINE_ERR("Failed to get symbol");
return false;
}
if (sym_section_index == SHN_ABS) {
//
} else if (sym_section_index > SHN_LORESERVE) {
DEBUG_FUNCTION_LINE_ERR("NOT IMPLEMENTED: %04X", sym_section_index);
return false;
}
if (type == ElfUtils::R_PPC_REL24) {
const auto target = static_cast<int32_t>(offset);
const auto value = static_cast<int32_t>(sym_value + addend);
const auto newDistance = value - target;
constexpr auto maxJumpDistance = 0x1FFFFFC;
constexpr auto maxUsedJumpDistance = maxJumpDistance - 0x10000;
if (newDistance > maxUsedJumpDistance || newDistance < -maxUsedJumpDistance) {
outTrampCount++;
}
}
}
}
}
}
}
return true;
}
bool linkSection(const elfio &reader, uint32_t section_index, uint32_t destination, uint32_t base_text, uint32_t base_data, std::span<relocation_trampoline_entry_t> trampolineData) {
for (const auto &psec : reader.sections) {
if (psec->get_info() == section_index) {
DEBUG_FUNCTION_LINE_VERBOSE("Found relocation section %s", psec->get_name().c_str());
relocation_section_accessor rel(reader, psec.get());
symbol_section_accessor symbols(reader, reader.sections[(Elf_Half) psec->get_link()]);
for (uint32_t j = 0; j < (uint32_t) rel.get_entries_num(); ++j) {
Elf_Word symbol = 0;
Elf64_Addr offset;
Elf_Word type;
Elf_Sxword addend;
std::string sym_name;
Elf64_Addr sym_value;
if (!rel.get_entry(j, offset, symbol, type, addend)) {
DEBUG_FUNCTION_LINE_ERR("Failed to get relocation");
return false;
}
// Find the symbol
Elf_Xword size;
unsigned char bind;
unsigned char symbolType;
Elf_Half sym_section_index;
unsigned char other;
if (!symbols.get_symbol(symbol, sym_name, sym_value, size,
bind, symbolType, sym_section_index, other)) {
DEBUG_FUNCTION_LINE_ERR("Failed to get symbol");
return false;
}
auto adjusted_sym_value = (uint32_t) sym_value;
if ((adjusted_sym_value >= 0x02000000) && adjusted_sym_value < 0x10000000) {
adjusted_sym_value -= 0x02000000;
adjusted_sym_value += base_text;
} else if ((adjusted_sym_value >= 0x10000000) && adjusted_sym_value < 0xC0000000) {
adjusted_sym_value -= 0x10000000;
adjusted_sym_value += base_data;
} else if (adjusted_sym_value >= 0xC0000000) {
//DEBUG_FUNCTION_LINE("Skip imports");
// Skip imports
continue;
} else if (adjusted_sym_value == 0x0) {
//
} else {
DEBUG_FUNCTION_LINE_ERR("Unhandled case %08X", adjusted_sym_value);
return false;
}
auto adjusted_offset = (uint32_t) offset;
if ((offset >= 0x02000000) && offset < 0x10000000) {
adjusted_offset -= 0x02000000;
} else if ((offset >= 0x10000000) && offset < 0xC0000000) {
adjusted_offset -= 0x10000000;
} else if (offset >= 0xC0000000) {
adjusted_offset -= 0xC0000000;
}
if (sym_section_index == SHN_ABS) {
//
} else if (sym_section_index > SHN_LORESERVE) {
DEBUG_FUNCTION_LINE_ERR("NOT IMPLEMENTED: %04X", sym_section_index);
return false;
}
if (!ElfUtils::elfLinkOne(type, adjusted_offset, addend, destination, adjusted_sym_value, trampolineData, RELOC_TYPE_FIXED)) {
DEBUG_FUNCTION_LINE_ERR("Link failed");
return false;
}
}
DEBUG_FUNCTION_LINE_VERBOSE("done");
return true;
}
}
return true;
}
} // namespace
std::optional<ModuleLinkInformation> ModuleLinkInformationFactory::loadAndLink(const std::span<uint8_t> buffer, const ModuleAllocator &moduleAllocator, ModuleLinkErrors &error) {
PROFILE_FUNCTION();
error = ModuleLinkErrors::MODULE_LINK_ERROR_NONE;
// Load ELF data
elfio reader(new wiiu_zlib);
if (!reader.load(reinterpret_cast<const char *>(buffer.data()), buffer.size())) {
DEBUG_FUNCTION_LINE_ERR("Failed to parse WMS");
error = ModuleLinkErrors::MODULE_LINK_ERROR_ELFIO_PARSE_FAILED;
return {};
}
uint32_t sec_num = reader.sections.size();
ModuleLinkInformation moduleInfo;
auto destinationsData = make_unique_nothrow<uint8_t *[]>(sec_num);
if (!destinationsData) {
DEBUG_FUNCTION_LINE_ERR("Failed alloc memory for destinations array");
error = ModuleLinkErrors::MODULE_LINK_ERROR_MALLOC_FAILED;
return std::nullopt;
}
std::span destinations(destinationsData.get(), sec_num);
uint32_t totalSize = 0;
uint32_t textSize = 0;
uint32_t dataSize = 0;
for (uint32_t i = 0; i < sec_num; ++i) {
section *psec = reader.sections[i];
if (psec->get_type() == 0x80000002) {
continue;
}
if ((psec->get_type() == SHT_PROGBITS || psec->get_type() == SHT_NOBITS) && (psec->get_flags() & SHF_ALLOC)) {
uint32_t sectionSize = psec->get_size();
auto address = (uint32_t) psec->get_address();
if ((address >= 0x02000000) && address < 0x10000000) {
textSize += sectionSize + psec->get_addr_align();
} else if ((address >= 0x10000000) && address < 0xC0000000) {
dataSize += sectionSize + psec->get_addr_align();
} else if (psec->get_name().starts_with(".wums.")) {
dataSize += sectionSize + psec->get_addr_align();
}
}
}
uint32_t expectedTramps = 0;
if (!predictRequiredTrampolineCount(reader, expectedTramps)) {
DEBUG_FUNCTION_LINE_WARN("Failed to predict required trampoline count");
}
// Add 20 tramp slots by default to any plugin to be safe.
// This alone should already be more than enough from my current observations
expectedTramps += 20;
size_t trampDataSize = (expectedTramps) * sizeof(relocation_trampoline_entry_t);
// We have to have the .text section and trampolines as close as possible in memory
// Lets create a shared memory pool for both of them
HeapMemoryFixedSizePool moduleMemoryPool(moduleAllocator.alloc, moduleAllocator.free, {textSize, trampDataSize, dataSize});
if (!moduleMemoryPool || moduleMemoryPool.numberOfSegments() < 3) {
DEBUG_FUNCTION_LINE_ERR("Failed to alloc memory for the .text section (%d bytes) and tramp data (%d bytes)", textSize, trampDataSize);
error = ModuleLinkErrors::MODULE_LINK_ERROR_MALLOC_FAILED;
return std::nullopt;
}
// Segment 0 is the .text data
const auto &text_data = moduleMemoryPool[ModuleLinkInformation::MEMORY_POOL_INDEX_TEXT];
// Segment 1 the tramp data
const auto &tramp_data = moduleMemoryPool[ModuleLinkInformation::MEMORY_POOL_INDEX_TRAMP];
memset(tramp_data.data(), 0, trampDataSize);
std::span trampolineList(static_cast<relocation_trampoline_entry_t *>(tramp_data.data()), expectedTramps);
// Segment 2 the data data
const auto &data_data = moduleMemoryPool[ModuleLinkInformation::MEMORY_POOL_INDEX_DATA];
uint32_t entrypoint = reinterpret_cast<uint32_t>(text_data.data()) + static_cast<uint32_t>(reader.get_entry()) - 0x02000000;
for (const auto &psec : reader.sections) {
if (psec->get_type() == 0x80000002 || psec->get_name() == ".wut_load_bounds") {
continue;
}
if ((psec->get_type() == SHT_PROGBITS || psec->get_type() == SHT_NOBITS) && (psec->get_flags() & SHF_ALLOC)) {
uint32_t sectionSize = psec->get_size();
auto address = static_cast<uint32_t>(psec->get_address());
uint32_t destination = address;
if ((address >= 0x02000000) && address < 0x10000000) {
destination += reinterpret_cast<uint32_t>(text_data.data());
destination -= 0x02000000;
destinations[psec->get_index()] = static_cast<uint8_t *>(text_data.data());
if (destination + sectionSize > reinterpret_cast<uint32_t>(text_data.data()) + textSize) {
DEBUG_FUNCTION_LINE_ERR("Tried to overflow .text buffer. %08X > %08X", destination + sectionSize, reinterpret_cast<uint32_t>(text_data.data()) + text_data.size());
error = ModuleLinkErrors::MODULE_LINK_ERROR_OVERFLOW;
return std::nullopt;
} else if (destination < reinterpret_cast<uint32_t>(text_data.data())) {
DEBUG_FUNCTION_LINE_ERR("Tried to underflow .text buffer. %08X < %08X", destination, reinterpret_cast<uint32_t>(text_data.data()));
error = ModuleLinkErrors::MODULE_LINK_ERROR_UNDERFLOW;
return std::nullopt;
}
} else if ((address >= 0x10000000) && address < 0xC0000000) {
destination += reinterpret_cast<uint32_t>(data_data.data());
destination -= 0x10000000;
destinations[psec->get_index()] = static_cast<uint8_t *>(data_data.data());
if (destination + sectionSize > reinterpret_cast<uint32_t>(data_data.data()) + data_data.size()) {
DEBUG_FUNCTION_LINE_ERR("Tried to overflow .data buffer. %08X > %08X", destination + sectionSize, reinterpret_cast<uint32_t>(data_data.data()) + data_data.size());
error = ModuleLinkErrors::MODULE_LINK_ERROR_OVERFLOW;
return std::nullopt;
} else if (destination < reinterpret_cast<uint32_t>(data_data.data())) {
DEBUG_FUNCTION_LINE_ERR("Tried to underflow .data buffer. %08X < %08X", destination, reinterpret_cast<uint32_t>(text_data.data()));
error = ModuleLinkErrors::MODULE_LINK_ERROR_UNDERFLOW;
return std::nullopt;
}
} else if (address >= 0xC0000000) {
DEBUG_FUNCTION_LINE_ERR("Loading section from 0xC0000000 is NOT supported");
error = ModuleLinkErrors::MODULE_LINK_ERROR_UNSUPPORTED_SECTION;
return std::nullopt;
} else {
DEBUG_FUNCTION_LINE_ERR("Unhandled case");
error = ModuleLinkErrors::MODULE_LINK_ERROR_UNKNOWN;
return std::nullopt;
}
const char *p = psec->get_data();
uint32_t address_align = psec->get_addr_align();
if ((destination & (address_align - 1)) != 0) {
DEBUG_FUNCTION_LINE_WARN("Address not aligned: %08X %08X", destination, address_align);
error = ModuleLinkErrors::MODULE_LINK_ERROR_ADDRESS_UNALIGNED;
return std::nullopt;
}
if (psec->get_type() == SHT_NOBITS) {
DEBUG_FUNCTION_LINE_VERBOSE("memset section %s %08X to 0 (%d bytes)", psec->get_name().c_str(), destination, sectionSize);
memset((void *) destination, 0, sectionSize);
} else if (psec->get_type() == SHT_PROGBITS) {
DEBUG_FUNCTION_LINE_VERBOSE("Copy section %s %p -> %08X (%d bytes)", psec->get_name().c_str(), p, destination, sectionSize);
memcpy((void *) destination, p, sectionSize);
}
moduleInfo.addSectionInfo(SectionInfo(psec->get_name(), destination, sectionSize));
DEBUG_FUNCTION_LINE_VERBOSE("Saved %s section info. Location: %08X size: %08X", psec->get_name().c_str(), destination, sectionSize);
totalSize += sectionSize;
}
}
for (uint32_t i = 0; i < sec_num; ++i) {
section *psec = reader.sections[i];
if (psec && (psec->get_type() == SHT_PROGBITS || psec->get_type() == SHT_NOBITS) && (psec->get_flags() & SHF_ALLOC)) {
DEBUG_FUNCTION_LINE_VERBOSE("Linking (%d)... %s at %p", i, psec->get_name().c_str(), destinations[psec->get_index()]);
if (!linkSection(reader, psec->get_index(), reinterpret_cast<uint32_t>(destinations[psec->get_index()]), reinterpret_cast<uint32_t>(text_data.data()), reinterpret_cast<uint32_t>(data_data.data()), trampolineList)) {
DEBUG_FUNCTION_LINE_ERR("linkSection failed");
error = ModuleLinkErrors::MODULE_LINK_ERROR_LINKING_FAILED;
return std::nullopt;
}
}
}
if (!addImportRelocationData(moduleInfo, reader, destinations)) {
DEBUG_FUNCTION_LINE_ERR("addImportRelocationData failed");
error = ModuleLinkErrors::MODULE_LINK_ERROR_PARSE_RELOCATION_DATA_FAILED;
return std::nullopt;
}
auto secInfo = moduleInfo.getSectionInfo(".wums.exports");
if (secInfo && secInfo.value().getSize() > 0) {
size_t entries_count = secInfo.value().getSize() / sizeof(wums_entry_t);
auto *entries = reinterpret_cast<wums_entry_t *>(secInfo.value().getAddress());
if (entries != nullptr) {
for (size_t j = 0; j < entries_count; j++) {
wums_entry_t *exp = &entries[j];
DEBUG_FUNCTION_LINE("Saving export of type %d, name %s, target: %p", exp->type, exp->name, exp->address);
moduleInfo.addExportData(ExportData(exp->type, exp->name, exp->address));
}
}
}
secInfo = moduleInfo.getSectionInfo(".wums.dependencies");
if (secInfo && secInfo.value().getSize() > 0) {
if (secInfo.value().getAddress() != 0) {
const char *curEntry = reinterpret_cast<const char *>(secInfo.value().getAddress());
size_t remainingSize = secInfo.value().getSize();
while (remainingSize > 0) {
if (*curEntry == '\0') {
curEntry++;
remainingSize--;
continue;
}
// Use strnlen to guarantee we never read past remainingSize
size_t len = strnlen(curEntry, remainingSize);
moduleInfo.addDependency(std::string(curEntry, len));
curEntry += len;
remainingSize -= len;
// Advance past the null terminator if we aren't at the end
if (remainingSize > 0) {
curEntry++;
remainingSize--;
}
}
}
}
secInfo = moduleInfo.getSectionInfo(".wums.hooks");
if (secInfo && secInfo.value().getSize() > 0) {
size_t entries_count = secInfo.value().getSize() / sizeof(wums_hook_t);
auto *hooks = reinterpret_cast<wums_hook_t *>(secInfo.value().getAddress());
if (hooks != nullptr) {
for (size_t j = 0; j < entries_count; j++) {
wums_hook_t *hook = &hooks[j];
DEBUG_FUNCTION_LINE("Saving hook of type %08X, target: %p", hook->type, hook->target);
moduleInfo.addHookData(HookData(const_cast<void *>(hook->target), hook->type));
}
}
}
// Get the symbol for functions.
for (const auto &sec : reader.sections) {
if (SHT_SYMTAB == sec->get_type()) {
symbol_section_accessor symbols(reader, sec.get());
auto sym_no = (uint32_t) symbols.get_symbols_num();
if (sym_no > 0) {
for (Elf_Half j = 0; j < sym_no; ++j) {
std::string name;
Elf64_Addr value = 0;
Elf_Xword size = 0;
unsigned char bind = 0;
unsigned char type = 0;
Elf_Half section = 0;
unsigned char other = 0;
if (symbols.get_symbol(j, name, value, size, bind, type, section, other)) {
if (type == STT_FUNC) { // We only care about functions.
auto sectionVal = reader.sections[section];
auto offsetVal = value - sectionVal->get_address();
auto sectionInfo = moduleInfo.getSectionInfo(sectionVal->get_name());
if (!sectionInfo) {
continue;
}
auto finalAddress = offsetVal + sectionInfo->getAddress();
moduleInfo.addFunctionSymbolData(FunctionSymbolData(name, reinterpret_cast<void *>(finalAddress), static_cast<uint32_t>(size)));
}
}
}
break;
}
}
}
if (totalSize > textSize + dataSize) {
DEBUG_FUNCTION_LINE_ERR("We didn't allocate enough memory!!");
error = ModuleLinkErrors::MODULE_LINK_ERROR_OVERFLOW;
return std::nullopt;
}
DEBUG_FUNCTION_LINE("Saved entrypoint as %08X", entrypoint);
DEBUG_FUNCTION_LINE("Saved startAddress as %08X", (uint32_t) moduleMemoryPool.dataView().data());
DEBUG_FUNCTION_LINE("Saved endAddress as %08X", (uint32_t) moduleMemoryPool.dataView().data() + moduleMemoryPool.dataView().size());
moduleInfo.mEntrypoint = reinterpret_cast<void *>(entrypoint);
moduleInfo.mModuleMemoryPool = std::move(moduleMemoryPool);
DEBUG_FUNCTION_LINE("Loaded module: %d kilobytes", totalSize / 1024);
moduleInfo.flushCache();
return moduleInfo;
}
bool ModuleLinkInformationFactory::addImportRelocationData(ModuleLinkInformation &moduleInfo, const elfio &reader, const std::span<uint8_t *> destinations) {
uint32_t sec_num = reader.sections.size();
std::vector<std::shared_ptr<ImportRPLInformation>> infoMap(sec_num);
for (uint32_t i = 0; i < sec_num; ++i) {
auto *psec = reader.sections[i];
if (psec->get_type() == 0x80000002) {
auto info = make_shared_nothrow<ImportRPLInformation>(psec->get_name());
if (!info) {
DEBUG_FUNCTION_LINE_ERR("Failed to allocate ImportRPLInformation");
return false;
}
infoMap[i] = std::move(info);
}
}
for (const auto &psec : reader.sections) {
if (psec->get_type() == SHT_RELA || psec->get_type() == SHT_REL) {
DEBUG_FUNCTION_LINE_VERBOSE("Found relocation section %s", psec->get_name().c_str());
relocation_section_accessor rel(reader, psec.get());
symbol_section_accessor symbols(reader, reader.sections[static_cast<Elf_Half>(psec->get_link())]);
Elf_Word symbol = 0;
Elf64_Addr offset;
Elf_Word type;
Elf_Sxword addend;
std::string sym_name;
Elf64_Addr sym_value;
Elf_Xword size;
unsigned char bind;
unsigned char symbolType;
Elf_Half sym_section_index;
unsigned char other;
for (uint32_t j = 0; j < static_cast<uint32_t>(rel.get_entries_num()); ++j) {
if (!rel.get_entry(j, offset, symbol, type, addend)) {
DEBUG_FUNCTION_LINE_ERR("Failed to get relocation");
return false;
}
if (!symbols.get_symbol(symbol, sym_name, sym_value, size,
bind, symbolType, sym_section_index, other)) {
DEBUG_FUNCTION_LINE_ERR("Failed to get symbol");
return false;
}
auto adjusted_sym_value = static_cast<uint32_t>(sym_value);
if (adjusted_sym_value < 0xC0000000) {
continue;
}
uint32_t section_index = psec->get_info();
if (sym_section_index >= infoMap.size() || !infoMap[sym_section_index]) {
DEBUG_FUNCTION_LINE_ERR("Relocation referencing unknown/unloaded section. sym_name %s", sym_name.c_str());
return false;
}
if (section_index >= destinations.size() || destinations[section_index] == nullptr) {
DEBUG_FUNCTION_LINE_ERR("Target section was not loaded for relocation");
return false;
}
// Safely adjust the offset based on which segment the relocation lives in
auto adjusted_offset = static_cast<uint32_t>(offset);
if (adjusted_offset >= 0x02000000 && adjusted_offset < 0x10000000) {
adjusted_offset -= 0x02000000; // .text segment
} else if (adjusted_offset >= 0x10000000 && adjusted_offset < 0xC0000000) {
DEBUG_FUNCTION_LINE_WARN("adjust offset for .data relocation of module , this has not tested/seen yet");
adjusted_offset -= 0x10000000; // .data segment
} else {
DEBUG_FUNCTION_LINE_ERR("Relocation offset %08X is out of expected bounds", adjusted_offset);
return false;
}
moduleInfo.addRelocationData(RelocationData(type,
adjusted_offset,
addend,
reinterpret_cast<void *>(destinations[section_index]),
sym_name,
infoMap[sym_section_index]));
}
}
}
return true;
}
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,83 @@
/****************************************************************************
* Copyright (C) 2022 Maschell
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
#pragma once
#include <optional>
#include <span>
#include <string>
namespace ELFIO {
class elfio;
}
namespace WUMSLoader::Modules {
class ModuleMetaInformation;
struct ModuleAllocator;
class ModuleLinkInformation;
class ModuleContainer;
enum class ModuleLinkErrors {
MODULE_LINK_ERROR_NONE,
MODULE_LINK_ERROR_UNKNOWN,
MODULE_LINK_ERROR_ELFIO_PARSE_FAILED,
MODULE_LINK_ERROR_IO_ERROR,
MODULE_LINK_ERROR_MALLOC_FAILED,
MODULE_LINK_ERROR_OVERFLOW,
MODULE_LINK_ERROR_UNDERFLOW,
MODULE_LINK_ERROR_UNSUPPORTED_SECTION,
MODULE_LINK_ERROR_ADDRESS_UNALIGNED,
MODULE_LINK_ERROR_LINKING_FAILED,
MODULE_LINK_ERROR_PARSE_RELOCATION_DATA_FAILED,
};
constexpr std::string_view ModuleLinkErrorsToString(const ModuleLinkErrors state) {
switch (state) {
case ModuleLinkErrors::MODULE_LINK_ERROR_NONE:
return "MODULE_LINK_ERROR_NONE";
case ModuleLinkErrors::MODULE_LINK_ERROR_UNKNOWN:
return "MODULE_LINK_ERROR_UNKNOWN";
case ModuleLinkErrors::MODULE_LINK_ERROR_ELFIO_PARSE_FAILED:
return "MODULE_LINK_ERROR_ELFIO_PARSE_FAILED";
case ModuleLinkErrors::MODULE_LINK_ERROR_IO_ERROR:
return "MODULE_LINK_ERROR_IO_ERROR";
case ModuleLinkErrors::MODULE_LINK_ERROR_MALLOC_FAILED:
return "MODULE_LINK_ERROR_MALLOC_FAILED";
case ModuleLinkErrors::MODULE_LINK_ERROR_OVERFLOW:
return "MODULE_LINK_ERROR_OVERFLOW";
case ModuleLinkErrors::MODULE_LINK_ERROR_UNDERFLOW:
return "MODULE_LINK_ERROR_UNDERFLOW";
case ModuleLinkErrors::MODULE_LINK_ERROR_UNSUPPORTED_SECTION:
return "MODULE_LINK_ERROR_UNSUPPORTED_SECTION";
case ModuleLinkErrors::MODULE_LINK_ERROR_ADDRESS_UNALIGNED:
return "MODULE_LINK_ERROR_ADDRESS_UNALIGNED";
case ModuleLinkErrors::MODULE_LINK_ERROR_LINKING_FAILED:
return "MODULE_LINK_ERROR_LINKING_FAILED";
case ModuleLinkErrors::MODULE_LINK_ERROR_PARSE_RELOCATION_DATA_FAILED:
return "MODULE_LINK_ERROR_PARSE_RELOCATION_DATA_FAILED";
}
return "MODULE_LINK_ERROR_UNKNOWN";
}
class ModuleLinkInformationFactory {
public:
static std::optional<ModuleLinkInformation> loadAndLink(std::span<uint8_t> buffer, const ModuleAllocator &moduleAllocator, ModuleLinkErrors &error);
private:
static bool addImportRelocationData(ModuleLinkInformation &moduleInfo, const ELFIO::elfio &reader, std::span<uint8_t *> destinations);
};
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,343 @@
#include "ModuleManagement.h"
#include "HooksManagement.h"
#include "ModuleAllocator.h"
#include "ModuleContainer.h"
#include "ModuleDataPersistence.h"
#include "ModuleLinkInformationFactory.h"
#include "ModuleMetaInformation.h"
#include "ModuleMetaInformationFactory.h"
#include "RelocationUtils.h"
#include "globals.h"
#include "utils/LoadedFile.h"
#include "utils/StringTools.h"
#include "utils/logger.h"
#include <algorithm>
#include <sys/dirent.h>
#include <vector>
namespace WUMSLoader::Modules {
namespace {
struct ModuleInfoWithLoadedFileWrapper {
ModuleMetaInformation metaInfo;
Utils::LoadedFile file;
};
std::vector<std::string> getModuleFilePaths(const std::string &basePath) {
PROFILE_FUNCTION();
std::vector<std::string> result;
struct dirent *dp;
DIR *dfd;
if (basePath.empty()) {
DEBUG_FUNCTION_LINE_ERR("Failed to scan module dir: Path was empty");
return result;
}
if ((dfd = opendir(basePath.c_str())) == nullptr) {
DEBUG_FUNCTION_LINE_ERR("Couldn't open dir %s", basePath.c_str());
return result;
}
while ((dp = readdir(dfd)) != nullptr) {
if (dp->d_type == DT_DIR) {
continue;
}
std::string_view fileName(dp->d_name);
if (fileName.starts_with('.') || fileName.starts_with('_') || !fileName.ends_with(".wms")) {
DEBUG_FUNCTION_LINE_WARN("Skip file %s/%s", basePath.c_str(), dp->d_name);
continue;
}
result.push_back(basePath + "/" + dp->d_name);
}
closedir(dfd);
return result;
}
bool CheckModulesByDependencies(const std::vector<ModuleInfoWithLoadedFileWrapper> &modules) {
PROFILE_FUNCTION();
std::set<std::string, std::less<>> loaderModuleNames;
for (const auto &curModule : modules) {
const auto &exportName = curModule.metaInfo.getExportName();
DEBUG_FUNCTION_LINE_VERBOSE("Check if we can load %s", exportName.c_str());
for (const auto &curRPL : curModule.metaInfo.getDependencyList()) {
if (!curRPL.starts_with("homebrew")) {
continue;
}
if (curRPL == "homebrew_wupsbackend") {
OSFatal("Error: module depends on homebrew_wupsbackend, this is not supported");
}
if (!loaderModuleNames.contains(curRPL)) {
DEBUG_FUNCTION_LINE_VERBOSE("%s requires %s which is not loaded yet", exportName.c_str(), curRPL.c_str());
return false;
} else {
DEBUG_FUNCTION_LINE_VERBOSE("Used %s, but it's already loaded", curRPL.c_str());
}
}
loaderModuleNames.insert(exportName);
}
return true;
}
std::vector<ModuleInfoWithLoadedFileWrapper> OrderModulesMetaByDependencies(std::vector<ModuleInfoWithLoadedFileWrapper> &&modulesMetaInfo) {
PROFILE_FUNCTION();
std::vector<std::reference_wrapper<ModuleInfoWithLoadedFileWrapper>> pendingModules;
pendingModules.reserve(modulesMetaInfo.size());
for (auto &mod : modulesMetaInfo) {
pendingModules.push_back(std::ref(mod));
}
std::vector<std::reference_wrapper<ModuleInfoWithLoadedFileWrapper>> orderedRefs;
orderedRefs.reserve(modulesMetaInfo.size());
std::set<std::string, std::less<>> loadedModulesExportNames;
while (!pendingModules.empty()) {
const size_t previousSize = pendingModules.size();
std::erase_if(pendingModules, [&](auto &ref) {
auto &metaInfo = ref.get();
const bool hasUnresolvedDeps = std::ranges::any_of(
metaInfo.metaInfo.getDependencyList(),
[&](const auto &curImportRPL) {
if (!curImportRPL.starts_with("homebrew")) {
return false;
}
if (curImportRPL == "homebrew_wupsbackend") {
OSFatal("Error: module depends on homebrew_wupsbackend, this is not supported");
}
if (!loadedModulesExportNames.contains(curImportRPL)) {
DEBUG_FUNCTION_LINE_VERBOSE("We can't load the module, because %s is not loaded yet", curImportRPL.c_str());
return true;
}
return false;
});
if (!hasUnresolvedDeps) {
orderedRefs.push_back(ref);
loadedModulesExportNames.insert(metaInfo.metaInfo.getExportName());
return true;
}
return false;
});
if (pendingModules.size() == previousSize) {
OSFatal("Failed to resolve dependencies.");
}
}
std::vector<ModuleInfoWithLoadedFileWrapper> finalOrder;
finalOrder.reserve(orderedRefs.size());
for (auto &ref : orderedRefs) {
finalOrder.push_back(std::move(ref.get()));
}
return finalOrder;
}
void CallInitHooksForModule(const ModuleContainer &curModule) {
HooksManagement::CallHook(curModule, WUMS_HOOK_INIT_WUT_THREAD);
HooksManagement::CallHook(curModule, WUMS_HOOK_INIT_WUT_MALLOC);
HooksManagement::CallHook(curModule, WUMS_HOOK_INIT_WUT_NEWLIB);
HooksManagement::CallHook(curModule, WUMS_HOOK_INIT_WUT_STDCPP);
HooksManagement::CallHook(curModule, WUMS_HOOK_INIT_WUT_DEVOPTAB);
HooksManagement::CallHook(curModule, WUMS_HOOK_INIT_WUT_SOCKETS);
HooksManagement::CallHook(curModule, WUMS_HOOK_INIT_WRAPPER, !curModule.getMetaInformation().isSkipInitFini());
HooksManagement::CallHook(curModule, WUMS_HOOK_INIT);
}
std::vector<ModuleInfoWithLoadedFileWrapper> loadModuleMetaInformationForPath(std::vector<Utils::LoadedFile> &&loadedFiles) {
PROFILE_FUNCTION();
std::vector<ModuleInfoWithLoadedFileWrapper> preloadedModules;
for (auto &loadedFile : loadedFiles) {
ModuleParseErrors error = MODULE_PARSE_ERROR_NONE;
if (auto metaInfoOpt = ModuleMetaInformationFactory::loadModuleMetaInfo(std::span(loadedFile.data(), loadedFile.size()), error)) {
preloadedModules.push_back({std::move(*metaInfoOpt), std::move(loadedFile)});
} else {
auto errMsg = string_format("Failed to load module meta information");
if (error == MODULE_PARSE_ERROR_INCOMPATIBLE_VERSION) {
errMsg += ". Incompatible version.";
}
DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str());
OSFatal(errMsg.c_str());
}
}
return preloadedModules;
}
std::vector<Utils::LoadedFile> loadModulesIntoMemory(const std::string &basePath) {
PROFILE_FUNCTION();
std::vector<Utils::LoadedFile> result;
for (const auto &modulePath : getModuleFilePaths(basePath)) {
auto fileOpt = Utils::LoadFileToMem(modulePath);
if (!fileOpt) {
DEBUG_FUNCTION_LINE_ERR("Failed to load file");
return {};
}
result.emplace_back(std::move(*fileOpt));
}
return result;
}
std::pair<std::vector<ModuleInfoWithLoadedFileWrapper>, std::vector<ModuleInfoWithLoadedFileWrapper>> getModulesInCorrectOrderAndSplit(std::vector<ModuleInfoWithLoadedFileWrapper> &&metaInfosWithLoadedFiles) {
PROFILE_FUNCTION();
// Order modules list by dependencies.
metaInfosWithLoadedFiles = OrderModulesMetaByDependencies(std::move(metaInfosWithLoadedFiles));
// Make sure the plugin backend module is at the very end as module might depend on all other modules
const auto backendIt = std::ranges::find_if(metaInfosWithLoadedFiles, [](const auto &cur) {
return std::string_view(cur.metaInfo.getExportName()) == "homebrew_wupsbackend";
});
if (backendIt != metaInfosWithLoadedFiles.end()) {
std::ranges::rotate(backendIt, backendIt + 1, metaInfosWithLoadedFiles.end());
}
// Make sure the "basemodule" is the first of the "regular" modules.
auto regularModulesMetaInfoRange = std::ranges::stable_partition(metaInfosWithLoadedFiles, [](const auto &cur) {
return cur.metaInfo.isInitBeforeRelocationDoneHook();
});
// Ensure homebrew_basemodule is the first of the regular modules (if it exists)
const auto baseModuleIt = std::ranges::find_if(regularModulesMetaInfoRange, [](const auto &cur) {
return std::string_view(cur.metaInfo.getExportName()) == "homebrew_basemodule";
});
if (baseModuleIt != regularModulesMetaInfoRange.end()) {
// Rotate the base module to the start of the regular modules subrange
std::ranges::rotate(regularModulesMetaInfoRange.begin(), baseModuleIt, baseModuleIt + 1);
}
// Final sanity check that the order is still meeting dependency requirements
if (!CheckModulesByDependencies(metaInfosWithLoadedFiles)) {
OSFatal("Failed to order Aroma Modules by depenencies");
}
// split into "early and "regular" modules
std::vector<ModuleInfoWithLoadedFileWrapper> earlyModules;
std::vector<ModuleInfoWithLoadedFileWrapper> regularModules;
// Use the iterator returned from stable_partition to know exactly where the split is
const auto boundaryIt = regularModulesMetaInfoRange.begin();
// Move elements to avoid expensive copying
std::move(metaInfosWithLoadedFiles.begin(), boundaryIt, std::back_inserter(earlyModules));
std::move(boundaryIt, metaInfosWithLoadedFiles.end(), std::back_inserter(regularModules));
return {std::move(earlyModules), std::move(regularModules)};
}
} // namespace
std::vector<ModuleContainer> ModuleManagement::loadAndLinkModules(const std::string &basePath, std::map<std::string, OSDynLoad_Module, std::less<>> &usedRPls, MEMHeapHandle defaultHeapHandle, PersistedModuleData &persistedData) {
PROFILE_FUNCTION();
auto loadedModules = loadModulesIntoMemory(basePath);
auto moduleMetaInfos = loadModuleMetaInformationForPath(std::move(loadedModules));
// Sort modules to satisfy dependencies; split into two groups
auto [earlyModulesMetaInfoWithLoadedFiles, regularModuleMetaInfoWithLoadedFiles] = getModulesInCorrectOrderAndSplit(std::move(moduleMetaInfos));
std::vector<ModuleContainer> moduleContainers;
moduleContainers.reserve(earlyModulesMetaInfoWithLoadedFiles.size() + regularModuleMetaInfoWithLoadedFiles.size());
#ifdef VERBOSE_DEBUG
DEBUG_FUNCTION_LINE_VERBOSE("Final order of early modules");
for (auto &[metaInfo, file] : earlyModulesMetaInfoWithLoadedFiles) {
DEBUG_FUNCTION_LINE_VERBOSE("%s", metaInfo.getExportName().c_str());
}
DEBUG_FUNCTION_LINE_VERBOSE("Final order of regular modules");
for (auto &[metaInfo, file] : regularModuleMetaInfoWithLoadedFiles) {
DEBUG_FUNCTION_LINE_VERBOSE("%s", metaInfo.getExportName().c_str());
}
#endif
// ========================================================================
// PHASE 1: Load and link "early" modules.
// These will get loaded into this smaller heap living between 0x00800000 and 0x01000000
// ========================================================================
ModuleAllocator defaultAllocator = ModuleAllocator::FromExpHeap(defaultHeapHandle);
for (auto &[metaInfo, file] : earlyModulesMetaInfoWithLoadedFiles) {
auto error = ModuleLinkErrors::MODULE_LINK_ERROR_NONE;
if (auto linkInfo = ModuleLinkInformationFactory::loadAndLink(std::span(file.data(), file.size()), defaultAllocator, error)) {
moduleContainers.emplace_back(std::move(metaInfo), std::move(*linkInfo));
} else {
auto errMsg = string_format("Failed to load/link module %s. %s", metaInfo.getExportName().c_str(), ModuleLinkErrorsToString(error));
DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str());
OSFatal(errMsg.c_str());
}
}
DEBUG_FUNCTION_LINE_VERBOSE("Resolve relocations without loading additional rpls");
RelocationUtils::ResolveRelocations(moduleContainers, RelocationUtils::ExternalRPLLoadingStrategy::IGNORE_EXTERNAL_RPLS, usedRPls);
// Build struct passed into INIT Hook
persistedData.build(moduleContainers);
for (auto &curModule : moduleContainers) {
CallInitHooksForModule(curModule);
}
// This sets the custom rpl allocator so we properly load external RPL now via ExternalRPLLoadingStrategy::LOAD_EXTERNAL_RPLS
HooksManagement::CallHook(moduleContainers, WUMS_HOOK_GET_CUSTOM_RPL_ALLOCATOR);
// ========================================================================
// PHASE 2: LOAD REGULAR MODULES. First get a module allocator which uses the mapped memory
// then load/link "regular" modules
// ========================================================================
auto mappedAllocatorOpt = ModuleAllocator::FromMappedMemory(moduleContainers);
if (!mappedAllocatorOpt) {
DEBUG_FUNCTION_LINE_WARN("Failed to get memory module allocator, fallback to default one");
mappedAllocatorOpt = defaultAllocator;
}
for (auto &[metaInfo, file] : regularModuleMetaInfoWithLoadedFiles) {
ModuleLinkErrors error = ModuleLinkErrors::MODULE_LINK_ERROR_NONE;
if (auto linkInfo = ModuleLinkInformationFactory::loadAndLink(std::span(file.data(), file.size()), defaultAllocator, error)) {
moduleContainers.emplace_back(std::move(metaInfo), std::move(*linkInfo));
} else {
auto errMsg = string_format("Failed to load/link module %s. %s", metaInfo.getExportName().c_str(), ModuleLinkErrorsToString(error));
DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str());
OSFatal(errMsg.c_str());
}
}
// Resolve relocations for regular modules as well, this time we can actually load external RPLs
RelocationUtils::ResolveRelocations(moduleContainers, RelocationUtils::ExternalRPLLoadingStrategy::LOAD_EXTERNAL_RPLS, usedRPls);
// rebuild struct passed into INIT Hook. The pointer passed to early module is still valid! (just hope they didn't make a copy)
persistedData.build(moduleContainers);
DEBUG_FUNCTION_LINE_VERBOSE("Call Relocations done hook to replace memory alloc/free functions");
HooksManagement::CallHook(moduleContainers, WUMS_HOOK_RELOCATIONS_DONE);
DEBUG_FUNCTION_LINE("Call init hooks for regular modules");
for (auto &moduleContainer : moduleContainers) {
if (moduleContainer.getMetaInformation().isInitBeforeRelocationDoneHook()) {
// These have already been initialized
continue;
}
CallInitHooksForModule(moduleContainer);
}
DEBUG_FUNCTION_LINE("%d modules loaded successfully", moduleContainers.size());
return moduleContainers;
}
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,18 @@
#pragma once
#include <coreinit/dynload.h>
#include <coreinit/memheap.h>
#include <map>
#include <string>
#include <vector>
namespace WUMSLoader::Modules {
class PersistedModuleData;
class ModuleContainer;
class ModuleManagement {
public:
static std::vector<ModuleContainer> loadAndLinkModules(const std::string &basePath, std::map<std::string, OSDynLoad_Module, std::less<>> &usedRPls, MEMHeapHandle defaultHeapHandle, PersistedModuleData &persistedData);
};
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,59 @@
#include "ModuleMetaInformation.h"
#include "WUMSVersion.h"
#include <set>
#include <string>
#include <cstdint>
namespace WUMSLoader::Modules {
const std::string &ModuleMetaInformation::getExportName() const {
return mExportName;
}
const WUMSVersion &ModuleMetaInformation::getWUMSVersion() const {
return mWUMSVersion;
}
bool ModuleMetaInformation::isInitBeforeRelocationDoneHook() const {
return this->mInitBeforeRelocationDoneHook;
}
bool ModuleMetaInformation::isSkipInitFini() const {
return this->mSkipInitFini;
}
void ModuleMetaInformation::setExportName(std::string exportName) {
mExportName = std::move(exportName);
}
void ModuleMetaInformation::setWUMSVersion(const uint16_t major, const uint16_t minor, const uint16_t revision) {
mWUMSVersion = WUMSVersion(major, minor, revision);
}
void ModuleMetaInformation::setWUMSVersion(const WUMSVersion &wumsVersion) {
mWUMSVersion = wumsVersion;
}
void ModuleMetaInformation::setInitBeforeRelocationDoneHook(const bool value) {
this->mInitBeforeRelocationDoneHook = value;
}
void ModuleMetaInformation::setSkipInitFini(const bool value) {
this->mSkipInitFini = value;
}
void ModuleMetaInformation::setSize(size_t size) {
mSize = size;
}
void ModuleMetaInformation::addDependency(std::string dependency) {
mDependencyList.emplace(std::move(dependency));
}
const std::set<std::string> &ModuleMetaInformation::getDependencyList() const {
return mDependencyList;
}
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,40 @@
#pragma once
#include "WUMSVersion.h"
#include <set>
#include <string>
namespace WUMSLoader::Modules {
class ModuleMetaInformation {
public:
[[nodiscard]] const std::string &getExportName() const;
[[nodiscard]] const WUMSVersion &getWUMSVersion() const;
[[nodiscard]] bool isInitBeforeRelocationDoneHook() const;
[[nodiscard]] bool isSkipInitFini() const;
[[nodiscard]] const std::set<std::string> &getDependencyList() const;
private:
friend class ModuleMetaInformationFactory;
friend class ModuleLinkInformationFactory;
friend class ModuleManagement;
ModuleMetaInformation() = default;
void setExportName(std::string exportName);
void addDependency(std::string dependency);
void setWUMSVersion(uint16_t major, uint16_t minor, uint16_t revision);
void setWUMSVersion(const WUMSVersion &wumsVersion);
void setInitBeforeRelocationDoneHook(bool value);
void setSkipInitFini(bool value);
void setSize(size_t size);
std::string mExportName;
std::set<std::string> mDependencyList;
WUMSVersion mWUMSVersion = WUMSVersion(0, 0, 0);
bool mInitBeforeRelocationDoneHook = false;
bool mSkipInitFini = false;
size_t mSize = 0;
};
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,165 @@
/****************************************************************************
* Copyright (C) 2018-2020 Maschell
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
#include "ModuleMetaInformationFactory.h"
#include "ModuleMetaInformation.h"
#include "elfio/elfio.hpp"
#include "utils/logger.h"
#include "utils/wiiu_zlib.hpp"
#include <coreinit/memdefaultheap.h>
#include <optional>
namespace WUMSLoader::Modules {
std::optional<ModuleMetaInformation> ModuleMetaInformationFactory::loadModuleMetaInfo(const std::span<const uint8_t> buffer, ModuleParseErrors &error) {
if (buffer.empty()) {
error = MODULE_PARSE_ERROR_BUFFER_EMPTY;
DEBUG_FUNCTION_LINE_ERR("Buffer is empty");
return {};
}
ELFIO::elfio reader(new wiiu_zlib);
if (!reader.load(reinterpret_cast<const char *>(buffer.data()), buffer.size())) {
error = MODULE_PARSE_ERROR_ELFIO_PARSE_FAILED;
DEBUG_FUNCTION_LINE_ERR("Can't find or process ELF file");
return {};
}
size_t moduleSize = 0;
ModuleMetaInformation moduleInfo;
const uint32_t sec_num = reader.sections.size();
bool checkedVersion = false;
bool hasMetaSection = false;
for (uint32_t i = 0; i < sec_num; ++i) {
ELFIO::section *psec = reader.sections[i];
// Calculate total size:
if ((psec->get_type() == ELFIO::SHT_PROGBITS || psec->get_type() == ELFIO::SHT_NOBITS) && (psec->get_flags() & ELFIO::SHF_ALLOC)) {
uint32_t sectionSize = psec->get_size();
const auto address = static_cast<uint32_t>(psec->get_address());
if (address >= 0x02000000 && address < 0xC0000000) {
moduleSize += sectionSize;
}
}
if (psec->get_name() == ".wums.meta") {
hasMetaSection = true;
const char *curEntry = psec->get_data();
size_t remainingSize = psec->get_size();
while (remainingSize > 0) {
if (*curEntry == '\0') {
curEntry++;
remainingSize--;
continue;
}
const size_t entryLen = strnlen(curEntry, remainingSize);
std::string_view entryView(curEntry, entryLen);
auto splitPos = entryView.find('=');
if (splitPos != std::string_view::npos) {
std::string_view key = entryView.substr(0, splitPos);
std::string_view value = entryView.substr(splitPos + 1);
if (key == "export_name") {
moduleInfo.setExportName(std::string(value));
} else if (key == "skipInitFini") {
const bool flag = (value == "true");
DEBUG_FUNCTION_LINE("skipInitFini = %s", flag ? "true" : "false");
moduleInfo.setSkipInitFini(flag);
} else if (key == "initBeforeRelocationDoneHook") {
const bool flag = (value == "true");
DEBUG_FUNCTION_LINE("initBeforeRelocationDoneHook = %s", flag ? "true" : "false");
moduleInfo.setInitBeforeRelocationDoneHook(flag);
} else if (key == "wums" || key == "wum") {
checkedVersion = true;
if (value == "0.3.1") {
moduleInfo.setWUMSVersion(0, 3, 1);
} else if (value == "0.3.2") {
moduleInfo.setWUMSVersion(0, 3, 2);
} else if (value == "0.3.3") {
moduleInfo.setWUMSVersion(0, 3, 3);
} else if (value == "0.3.4") {
moduleInfo.setWUMSVersion(0, 3, 4);
} else if (value == "0.3.5") {
moduleInfo.setWUMSVersion(0, 3, 5);
} else {
error = MODULE_PARSE_ERROR_INCOMPATIBLE_VERSION;
DEBUG_FUNCTION_LINE_WARN("Ignoring module - Unsupported WUMS version: %.*s.", static_cast<int>(value.length()), value.data());
return {};
}
}
}
curEntry += entryLen;
remainingSize -= entryLen;
if (remainingSize > 0) {
curEntry++;
remainingSize--;
}
}
} else if (psec->get_name() == ".wums.dependencies") {
const char *curEntry = psec->get_data();
size_t remainingSize = psec->get_size();
if (curEntry != nullptr && remainingSize > 0) {
while (remainingSize > 0) {
if (*curEntry == '\0') {
curEntry++;
remainingSize--;
continue;
}
size_t len = strnlen(curEntry, remainingSize);
moduleInfo.addDependency(std::string(curEntry, len));
curEntry += len;
remainingSize -= len;
if (remainingSize > 0) {
curEntry++;
remainingSize--;
}
}
}
}
}
if (!hasMetaSection) {
DEBUG_FUNCTION_LINE_ERR("File has no \".wums.meta\" section");
error = MODULE_PARSE_ERROR_NO_MODULE;
return {};
}
if (!checkedVersion) {
DEBUG_FUNCTION_LINE_ERR("File has no version information in \".wums.meta\" section");
error = MODULE_PARSE_ERROR_INCOMPATIBLE_VERSION;
return {};
}
moduleInfo.setSize(moduleSize);
error = MODULE_PARSE_ERROR_NONE;
return moduleInfo;
}
} // namespace WUMSLoader::Modules

View File

@ -1,5 +1,5 @@
/****************************************************************************
* Copyright (C) 2022 Maschell
* Copyright (C) 2019-2026 Maschell
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -17,25 +17,28 @@
#pragma once
#include "ModuleData.h"
#include "elfio/elfio.hpp"
#include <coreinit/memheap.h>
#include <map>
#include <string>
#include <vector>
#include <wums.h>
#include <optional>
#include <span>
#include <string_view>
class ModuleDataFactory {
public:
static std::optional<std::shared_ptr<ModuleData>> load(const std::string &path);
#include <cstdint>
static bool linkSection(ELFIO::elfio &reader,
uint32_t section_index,
uint32_t destination,
uint32_t base_text,
uint32_t base_data,
relocation_trampoline_entry_t *trampoline_data,
uint32_t trampoline_data_length);
namespace WUMSLoader::Modules {
static bool getImportRelocationData(std::shared_ptr<ModuleData> &moduleData, ELFIO::elfio &reader, uint8_t **destinations);
class ModuleMetaInformation;
enum ModuleParseErrors {
MODULE_PARSE_ERROR_NONE,
MODULE_PARSE_ERROR_UNKNOWN,
MODULE_PARSE_ERROR_NO_MODULE,
MODULE_PARSE_ERROR_INCOMPATIBLE_VERSION,
MODULE_PARSE_ERROR_BUFFER_EMPTY,
MODULE_PARSE_ERROR_ELFIO_PARSE_FAILED,
MODULE_PARSE_ERROR_IO_ERROR,
};
class ModuleMetaInformationFactory {
public:
static std::optional<ModuleMetaInformation> loadModuleMetaInfo(std::span<const uint8_t> buffer, ModuleParseErrors &error);
};
} // namespace WUMSLoader::Modules

View File

@ -1,5 +1,9 @@
#include "RelocationData.h"
#include "ImportRPLInformation.h"
#include <string>
namespace WUMSLoader::Modules {
RelocationData::RelocationData(const char type,
const size_t offset,
const int32_t addend,
@ -39,4 +43,5 @@ RelocationData::~RelocationData() = default;
[[nodiscard]] const ImportRPLInformation &RelocationData::getImportRPLInformation() const {
return *mRPLInfo;
}
}
} // namespace WUMSLoader::Modules

View File

@ -17,13 +17,14 @@
#pragma once
#include "ImportRPLInformation.h"
#include <memory>
#include <string>
#include <cstdint>
namespace WUMSLoader::Modules {
class ImportRPLInformation;
class RelocationData {
public:
@ -31,6 +32,9 @@ public:
RelocationData(const RelocationData &o2);
RelocationData(RelocationData &&) noexcept = default;
RelocationData &operator=(RelocationData &&) noexcept = default;
virtual ~RelocationData();
[[nodiscard]] char getType() const;
@ -53,3 +57,4 @@ private:
std::string mName;
std::shared_ptr<ImportRPLInformation> mRPLInfo;
};
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,203 @@
#include "RelocationUtils.h"
#include "ElfUtils.h"
#include "ImportRPLInformation.h"
#include "globals.h"
#include "module/ModuleContainer.h"
#include "module/RelocationData.h"
#include "utils/OnLeavingScope.h"
#include "utils/StringTools.h"
#include "utils/logger.h"
#include "utils/memory.h"
#include <wums/defines/relocation_defines.h>
#include <coreinit/debug.h>
#include <coreinit/dynload.h>
#include <algorithm>
#include <map>
#include <span>
#include <string>
#include <vector>
#include <cstdint>
#include <malloc.h>
namespace WUMSLoader::Modules::RelocationUtils {
namespace {
OSDynLoad_Error CustomDynLoadAlloc(int32_t size, int32_t align, void **outAddr) {
if (!outAddr) {
return OS_DYNLOAD_INVALID_ALLOCATOR_PTR;
}
if (align < 4) {
align = 4;
}
if (!(*outAddr = memalign(align, size))) {
return OS_DYNLOAD_OUT_OF_MEMORY;
}
// keep track of allocated memory to clean it up in case the RPLs won't get unloaded properly
gAllocatedAddresses.push_back(*outAddr);
return OS_DYNLOAD_OK;
}
void CustomDynLoadFree(void *addr) {
free(addr);
// Remove from list
if (const auto it = std::ranges::find(gAllocatedAddresses, addr); it != gAllocatedAddresses.end()) {
gAllocatedAddresses.erase(it);
}
}
bool doRelocation(const std::vector<RelocationData> &relocData,
const std::map<std::string, const ModuleContainer *, std::less<>> &moduleMap,
const ExternalRPLLoadingStrategy rplLoadingStrategy,
std::span<relocation_trampoline_entry_t> trampData,
std::map<std::string, OSDynLoad_Module, std::less<>> &usedRPls) {
for (const auto &curReloc : relocData) {
const auto &functionName = curReloc.getName();
const std::string_view rplName = curReloc.getImportRPLInformation().getRPLName();
uint32_t functionAddress = 0;
if (auto it = moduleMap.find(rplName); it != moduleMap.end()) {
const auto *module = it->second;
bool found = false;
for (const auto &exportData : module->getLinkInformation().getExportDataList()) {
if (functionName == exportData.getName()) {
functionAddress = reinterpret_cast<uint32_t>(exportData.getAddress());
found = true;
break;
}
}
if (!found) {
DEBUG_FUNCTION_LINE_ERR("Failed to find export %.*s of module: %.*s",
static_cast<int>(functionName.length()), functionName.data(),
static_cast<int>(rplName.length()), rplName.data());
return false;
}
}
if (functionAddress == 0) {
if (functionName == "MEMAllocFromDefaultHeap") {
functionAddress = reinterpret_cast<uint32_t>(&MEMAlloc);
} else if (functionName == "MEMAllocFromDefaultHeapEx") {
functionAddress = reinterpret_cast<uint32_t>(&MEMAllocEx);
} else if (functionName == "MEMFreeToDefaultHeap") {
functionAddress = reinterpret_cast<uint32_t>(&MEMFree);
}
}
if (functionAddress == 0) {
const int32_t isData = curReloc.getImportRPLInformation().isData();
OSDynLoad_Module rplHandle = nullptr;
if (auto rplIt = usedRPls.find(rplName); rplIt == usedRPls.end()) {
OSDynLoad_Module tmp = nullptr;
if (OSDynLoad_IsModuleLoaded(rplName.data(), &tmp) != OS_DYNLOAD_OK || tmp == nullptr) {
if (rplLoadingStrategy == ExternalRPLLoadingStrategy::IGNORE_EXTERNAL_RPLS) {
DEBUG_FUNCTION_LINE_ERR("Relocation requires .rpl which isn't loaded and loading is not allowed");
return false;
}
}
if (OSDynLoad_Acquire(rplName.data(), &rplHandle) != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE_ERR("Failed to acquire %.*s", (int) rplName.length(), rplName.data());
return false;
}
usedRPls.emplace(std::string(rplName), rplHandle);
} else {
rplHandle = rplIt->second;
}
const auto res = OSDynLoad_FindExport(rplHandle, static_cast<OSDynLoad_ExportType>(isData),
functionName.c_str(), reinterpret_cast<void **>(&functionAddress));
if (res != OS_DYNLOAD_OK || functionAddress == 0) {
DEBUG_FUNCTION_LINE_ERR("Failed to find export %.*s of %.*s",
static_cast<int>(functionName.length()), functionName.c_str(),
static_cast<int>(rplName.length()), rplName.data());
return false;
}
}
if (!ElfUtils::elfLinkOne(curReloc.getType(), curReloc.getOffset(), curReloc.getAddend(),
reinterpret_cast<uint32_t>(curReloc.getDestination()),
functionAddress, trampData, RELOC_TYPE_IMPORT)) {
return false;
}
}
return true;
}
} // namespace
bool ResolveRelocations(const std::vector<ModuleContainer> &loadedModules, const ExternalRPLLoadingStrategy rplLoadingStrategy, std::map<std::string, OSDynLoad_Module, std::less<>> &usedRPls) {
PROFILE_FUNCTION();
bool wasSuccessful = true;
OSDynLoadAllocFn prevDynLoadAlloc = nullptr;
OSDynLoadFreeFn prevDynLoadFree = nullptr;
if (rplLoadingStrategy == ExternalRPLLoadingStrategy::LOAD_EXTERNAL_RPLS) {
OSDynLoad_GetAllocator(&prevDynLoadAlloc, &prevDynLoadFree);
if (gCustomRPLAllocatorAllocFn != nullptr && gCustomRPLAllocatorFreeFn != nullptr) {
OSDynLoad_SetAllocator(reinterpret_cast<OSDynLoadAllocFn>(gCustomRPLAllocatorAllocFn), gCustomRPLAllocatorFreeFn);
} else {
OSDynLoad_SetAllocator(CustomDynLoadAlloc, CustomDynLoadFree);
}
}
auto restoreOSDynLoadAllocator = onLeavingScope([rplLoadingStrategy, prevDynLoadAlloc, prevDynLoadFree]() {
if (rplLoadingStrategy == ExternalRPLLoadingStrategy::LOAD_EXTERNAL_RPLS) {
OSDynLoad_SetAllocator(prevDynLoadAlloc, prevDynLoadFree);
}
});
std::map<std::string, const ModuleContainer *, std::less<>> moduleMap;
for (const auto &mod : loadedModules) {
if (mod.isLinkedAndLoaded()) {
moduleMap[mod.getMetaInformation().getExportName()] = &mod;
}
}
for (auto &curModule : loadedModules) {
if (!curModule.isLinkedAndLoaded()) {
DEBUG_FUNCTION_LINE_VERBOSE("Skip doing relocations for %s as it's not linked properly", curModule.getMetaInformation().getExportName().c_str());
continue;
}
const auto &trampData = curModule.getLinkInformation().getTrampData();
for (auto &cur : trampData) {
if (cur.status == RELOC_TRAMP_IMPORT_DONE) {
cur.status = RELOC_TRAMP_FREE;
}
}
DEBUG_FUNCTION_LINE("Let's do the relocations for %s", curModule.getMetaInformation().getExportName().c_str());
const auto &relocData = curModule.getLinkInformation().getRelocationDataList();
if (!doRelocation(relocData,
moduleMap,
rplLoadingStrategy,
trampData,
usedRPls)) {
wasSuccessful = false;
const auto errMsg = string_format("Failed to do Relocations for %s", curModule.getMetaInformation().getExportName().c_str());
DEBUG_FUNCTION_LINE_ERR("%s", errMsg.c_str());
OSFatal(errMsg.c_str());
}
curModule.getLinkInformation().flushCache();
}
return wasSuccessful;
}
} // namespace WUMSLoader::Modules::RelocationUtils

View File

@ -0,0 +1,27 @@
#pragma once
#include <coreinit/dynload.h>
#include <map>
#include <string>
#include <vector>
#include <cstdint>
namespace WUMSLoader::Modules {
class ModuleContainer;
class RelocationData;
namespace RelocationUtils {
enum class ExternalRPLLoadingStrategy {
LOAD_EXTERNAL_RPLS,
IGNORE_EXTERNAL_RPLS
};
bool ResolveRelocations(const std::vector<ModuleContainer> &loadedModules,
ExternalRPLLoadingStrategy rplLoadingStrategy,
std::map<std::string, OSDynLoad_Module, std::less<>> &usedRPls);
} // namespace RelocationUtils
} // namespace WUMSLoader::Modules

View File

@ -1,5 +1,6 @@
#include "SectionInfo.h"
namespace WUMSLoader::Modules {
SectionInfo::SectionInfo(std::string name,
const uint32_t address,
const uint32_t sectionSize) : mName(std::move(name)),
@ -21,4 +22,5 @@ SectionInfo::SectionInfo(std::string name,
[[nodiscard]] uint32_t SectionInfo::isInSection(uint32_t addr) const {
return addr >= mAddress && addr < mAddress + mSectionSize;
}
}
} // namespace WUMSLoader::Modules

View File

@ -1,5 +1,5 @@
/****************************************************************************
* Copyright (C) 2019 Maschell
* Copyright (C) 2019 Maschell
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -21,6 +21,7 @@
#include <cstdint>
namespace WUMSLoader::Modules {
class SectionInfo {
public:
@ -39,3 +40,4 @@ private:
uint32_t mAddress = {};
uint32_t mSectionSize = {};
};
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,44 @@
#include "WUMSVersion.h"
#include "utils/StringTools.h"
namespace WUMSLoader::Modules {
WUMSVersion::WUMSVersion(const int major, const int minor, const int revision)
: mMajor(major), mMinor(minor), mRevision(revision) {
}
std::optional<WUMSVersion> WUMSVersion::createFromString(const std::string &versionStr) {
char *end;
errno = 0; // Initialize errno before calling strtol
const auto major = strtol(versionStr.c_str(), &end, 10);
if (errno || *end != '.') {
return std::nullopt;
}
const auto minor = strtol(end + 1, &end, 10);
if (errno || *end != '.') {
return std::nullopt;
}
const auto revision = strtol(end + 1, &end, 10);
if (errno || *end != '\0') {
return std::nullopt;
}
return WUMSVersion(static_cast<int>(major), static_cast<int>(minor), static_cast<int>(revision));
}
std::strong_ordering WUMSVersion::operator<=>(const WUMSVersion &other) const {
if (const auto cmp = mMajor <=> other.mMajor; cmp != std::strong_ordering::equal) return cmp;
if (const auto cmp = mMinor <=> other.mMinor; cmp != std::strong_ordering::equal) return cmp;
return mRevision <=> other.mRevision;
}
[[nodiscard]] std::string WUMSVersion::toString() const {
return string_format("%d.%d.%d", mMajor,
mMinor,
mRevision);
}
} // namespace WUMSLoader::Modules

View File

@ -0,0 +1,26 @@
#pragma once
#include <optional>
#include <string>
#include <cstdint>
namespace WUMSLoader::Modules {
class WUMSVersion {
public:
WUMSVersion(int major, int minor, int revision);
static std::optional<WUMSVersion> createFromString(const std::string &versionStr);
std::strong_ordering operator<=>(const WUMSVersion &other) const;
[[nodiscard]] std::string toString() const;
private:
uint32_t mMajor;
uint32_t mMinor;
uint32_t mRevision;
};
} // namespace WUMSLoader::Modules

View File

@ -1,47 +0,0 @@
#pragma once
#include <stdint.h>
#include <wums.h>
#ifdef __cplusplus
extern "C" {
#endif
#define R_PPC_NONE 0
#define R_PPC_ADDR32 1
#define R_PPC_ADDR16_LO 4
#define R_PPC_ADDR16_HI 5
#define R_PPC_ADDR16_HA 6
#define R_PPC_REL24 10
#define R_PPC_REL14 11
#define R_PPC_DTPMOD32 68
#define R_PPC_DTPREL32 78
#define R_PPC_EMB_SDA21 109
#define R_PPC_EMB_RELSDA 116
#define R_PPC_DIAB_SDA21_LO 180
#define R_PPC_DIAB_SDA21_HI 181
#define R_PPC_DIAB_SDA21_HA 182
#define R_PPC_DIAB_RELSDA_LO 183
#define R_PPC_DIAB_RELSDA_HI 184
#define R_PPC_DIAB_RELSDA_HA 185
#define R_PPC_GHS_REL16_HA 251
#define R_PPC_GHS_REL16_HI 252
#define R_PPC_GHS_REL16_LO 253
// Masks for manipulating Power PC relocation targets
#define PPC_WORD32 0xFFFFFFFF
#define PPC_WORD30 0xFFFFFFFC
#define PPC_LOW24 0x03FFFFFC
#define PPC_LOW14 0x0020FFFC
#define PPC_HALF16 0xFFFF
#ifdef __cplusplus
}
#endif
class ElfUtils {
public:
static bool elfLinkOne(char type, size_t offset, int32_t addend, uint32_t destination, uint32_t symbol_addr, relocation_trampoline_entry_t *trampoline_data, uint32_t trampoline_data_length,
RelocationType reloc_type);
};

View File

@ -0,0 +1,92 @@
#include "HeapMemoryFixedSize.h"
#include "logger.h"
#include "utils.h"
#include <cassert>
HeapMemoryFixedSizePool::MemorySegmentInfo::MemorySegmentInfo(void *data, const size_t size) : mData(data),
mSize(size) {}
void *HeapMemoryFixedSizePool::MemorySegmentInfo::data() const {
return mData;
}
size_t HeapMemoryFixedSizePool::MemorySegmentInfo::size() const {
return mSize;
}
HeapMemoryFixedSizePool::HeapMemoryFixedSizePool() = default;
HeapMemoryFixedSizePool::HeapMemoryFixedSizePool(const AllocFunc &allocFunc, FreeFunc freeFunc, const std::initializer_list<size_t> segmentSizes)
: HeapMemoryFixedSizePool(allocFunc, std::move(freeFunc), std::span(segmentSizes.begin(), segmentSizes.size())) {
}
HeapMemoryFixedSizePool::HeapMemoryFixedSizePool(const AllocFunc &allocFunc, FreeFunc freeFunc, const std::span<const std::size_t> segmentSizes) {
assert(!segmentSizes.empty());
assert(allocFunc != nullptr);
assert(freeFunc != nullptr);
size_t totalSize = 0;
for (const auto size : segmentSizes) {
totalSize += size + 0x40; // add 0x40 bytes overhead for each entry to ensure padding to 0x40
}
// Call the injected allocator!
void *raw_mem = allocFunc(totalSize, 0x40);
if (!raw_mem) {
return;
}
mData = std::unique_ptr<uint8_t[], CustomDeleter>(
static_cast<uint8_t *>(raw_mem),
CustomDeleter{std::move(freeFunc)});
mTotalSize = (mData ? totalSize : 0);
if (mData) {
auto address = reinterpret_cast<uint32_t>(mData.get());
for (const auto size : segmentSizes) {
address = ROUNDUP(static_cast<int>(address), 0x40);
assert(address >= reinterpret_cast<uint32_t>(mData.get()) && address < reinterpret_cast<uint32_t>(mData.get()) + totalSize);
mSegmentInfos.emplace_back(reinterpret_cast<void *>(address), size);
address += size;
}
}
}
HeapMemoryFixedSizePool::HeapMemoryFixedSizePool(HeapMemoryFixedSizePool &&other) noexcept
: mData(std::move(other.mData)), mTotalSize(other.mTotalSize), mSegmentInfos(std::move(other.mSegmentInfos)) {
other.mTotalSize = 0;
}
HeapMemoryFixedSizePool &HeapMemoryFixedSizePool::operator=(HeapMemoryFixedSizePool &&other) noexcept {
if (this != &other) {
mData = std::move(other.mData);
mTotalSize = other.mTotalSize;
mSegmentInfos = std::move(other.mSegmentInfos);
other.mTotalSize = 0;
other.mSegmentInfos.clear();
}
return *this;
}
uint32_t HeapMemoryFixedSizePool::numberOfSegments() const {
return mSegmentInfos.size();
}
HeapMemoryFixedSizePool::operator bool() const {
return mData != nullptr && mTotalSize > 0;
}
HeapMemoryFixedSizePool::MemorySegmentInfo HeapMemoryFixedSizePool::operator[](const size_t idx) const {
if (idx >= mSegmentInfos.size()) {
DEBUG_FUNCTION_LINE_ERR("Out of bounce access (tried to access index %d; size is %d", idx, mSegmentInfos.size());
return {nullptr, 0};
}
return mSegmentInfos[idx];
}
std::span<const uint8_t> HeapMemoryFixedSizePool::dataView() const {
return {mData.get(), mTotalSize};
}

View File

@ -0,0 +1,64 @@
#pragma once
#include <cstdint>
#include <functional>
#include <memory>
#include <span>
#include <vector>
class HeapMemoryFixedSizePool {
public:
// Define our custom allocator and deleter signatures
using AllocFunc = std::function<void *(size_t size, int32_t alignment)>;
using FreeFunc = std::function<void(void *ptr)>;
class MemorySegmentInfo {
public:
MemorySegmentInfo(void *data, size_t size);
[[nodiscard]] void *data() const;
[[nodiscard]] size_t size() const;
private:
void *mData;
size_t mSize;
};
HeapMemoryFixedSizePool();
// Updated constructors to take the custom functions
HeapMemoryFixedSizePool(const AllocFunc &allocFunc, FreeFunc freeFunc, std::initializer_list<size_t> segmentSizes);
HeapMemoryFixedSizePool(const AllocFunc &allocFunc, FreeFunc freeFunc, std::span<const std::size_t> segmentSizes);
// Delete the copy constructor and copy assignment operator
HeapMemoryFixedSizePool(const HeapMemoryFixedSizePool &) = delete;
HeapMemoryFixedSizePool &operator=(const HeapMemoryFixedSizePool &) = delete;
HeapMemoryFixedSizePool(HeapMemoryFixedSizePool &&other) noexcept;
HeapMemoryFixedSizePool &operator=(HeapMemoryFixedSizePool &&other) noexcept;
[[nodiscard]] uint32_t numberOfSegments() const;
explicit operator bool() const;
MemorySegmentInfo operator[](size_t idx) const;
[[nodiscard]] std::span<const uint8_t> dataView() const;
private:
struct CustomDeleter {
FreeFunc freeFunc;
void operator()(uint8_t *ptr) const {
if (ptr != nullptr && freeFunc) {
freeFunc(ptr);
}
}
};
// Initialize with a null pointer and an empty CustomDeleter
std::unique_ptr<uint8_t[], CustomDeleter> mData{nullptr, CustomDeleter{nullptr}};
std::size_t mTotalSize{};
std::vector<MemorySegmentInfo> mSegmentInfos;
};

View File

@ -0,0 +1,122 @@
#include "LoadedFile.h"
#include "globals.h"
#include "utils/logger.h"
#include "utils/utils.h"
#include <coreinit/memdefaultheap.h>
#include <fcntl.h>
#include <malloc.h>
#include <unistd.h>
#include <algorithm>
#include <utility>
namespace WUMSLoader::Utils {
namespace {
class ScopedFileDescriptor {
public:
explicit ScopedFileDescriptor(const int fd) : mFD(fd) {}
~ScopedFileDescriptor() {
if (mFD >= 0) {
::close(mFD);
}
}
ScopedFileDescriptor(const ScopedFileDescriptor &) = delete;
ScopedFileDescriptor &operator=(const ScopedFileDescriptor &) = delete;
[[nodiscard]] bool isValid() const {
return mFD >= 0;
}
[[nodiscard]] int get() const {
return mFD;
}
private:
int mFD = -1;
};
} // namespace
LoadedFile::LoadedFile(uint8_t *buffer, uint32_t size) : mBuffer(buffer), mSize(size) {
}
LoadedFile::~LoadedFile() {
if (mBuffer) {
memset(mBuffer, 0, ROUNDUP(static_cast<int>(mSize), 0x40));
MEMFreeToDefaultHeap(mBuffer);
}
}
LoadedFile::LoadedFile(LoadedFile &&other) noexcept : mBuffer(std::exchange(other.mBuffer, nullptr)),
mSize(std::exchange(other.mSize, 0)) {}
LoadedFile &LoadedFile::operator=(LoadedFile &&other) noexcept {
if (this != &other) {
if (mBuffer) {
memset(mBuffer, 0, ROUNDUP(static_cast<int>(mSize), 0x40));
MEMFreeToDefaultHeap(mBuffer);
}
mBuffer = std::exchange(other.mBuffer, nullptr);
mSize = std::exchange(other.mSize, 0);
}
return *this;
}
uint8_t *LoadedFile::data() {
return mBuffer;
}
const uint8_t *LoadedFile::data() const {
return mBuffer;
}
uint32_t LoadedFile::size() const {
return mSize;
}
std::optional<LoadedFile> LoadFileToMem(const std::string &filepath) {
PROFILE_FUNCTION();
const ScopedFileDescriptor fd(open(filepath.c_str(), O_RDONLY));
if (!fd.isValid()) {
return std::nullopt;
}
const off_t rawSize = lseek(fd.get(), 0, SEEK_END);
if (rawSize <= 0) {
return std::nullopt;
}
const auto filesize = static_cast<uint32_t>(rawSize);
lseek(fd.get(), 0, SEEK_SET);
auto *buffer = static_cast<uint8_t *>(MEMAllocFromDefaultHeapEx(ROUNDUP(static_cast<int>(filesize), 0x40), 0x40));
if (buffer == nullptr) {
return std::nullopt;
}
LoadedFile file(buffer, filesize);
uint32_t done = 0;
while (done < filesize) {
constexpr uint32_t blockSize = 0x20000;
uint32_t bytesToRead = std::min(blockSize, filesize - done);
const ssize_t readBytes = read(fd.get(), file.data() + done, bytesToRead);
if (readBytes <= 0) {
break;
}
done += static_cast<uint32_t>(readBytes);
}
if (done != filesize) {
return std::nullopt;
}
return file;
}
} // namespace WUMSLoader::Utils

View File

@ -0,0 +1,32 @@
#pragma once
#include <optional>
#include <string>
#include <cstdint>
namespace WUMSLoader::Utils {
class LoadedFile {
public:
LoadedFile(uint8_t *buffer, uint32_t size);
~LoadedFile();
LoadedFile(const LoadedFile &) = delete;
LoadedFile &operator=(const LoadedFile &) = delete;
LoadedFile(LoadedFile &&other) noexcept;
LoadedFile &operator=(LoadedFile &&other) noexcept;
[[nodiscard]] uint8_t *data(); // Added non-const version
[[nodiscard]] const uint8_t *data() const; // Added const version
[[nodiscard]] uint32_t size() const;
private:
uint8_t *mBuffer = nullptr;
uint32_t mSize = 0;
};
[[nodiscard]] std::optional<LoadedFile> LoadFileToMem(const std::string &filepath);
} // namespace WUMSLoader::Utils

View File

@ -106,8 +106,8 @@ private:
* from the object passed as the function argument.
*/
template<typename Func>
OnLeavingScope<typename std::decay<Func>::type> onLeavingScope(Func &&f) {
return OnLeavingScope<typename std::decay<Func>::type>(std::forward<Func>(f));
OnLeavingScope<std::decay_t<Func>> onLeavingScope(Func &&f) {
return OnLeavingScope<std::decay_t<Func>>(std::forward<Func>(f));
}
#endif // CRASCIT_ONLEAVINGSCOPE_H

View File

@ -1,160 +0,0 @@
#include "RelocationUtils.h"
#include "ElfUtils.h"
#include "globals.h"
#include "logger.h"
#include "memory.h"
#include <algorithm>
#include <coreinit/cache.h>
#include <iterator>
#include <malloc.h>
#include <vector>
static OSDynLoad_Error CustomDynLoadAlloc(int32_t size, int32_t align, void **outAddr) {
if (!outAddr) {
return OS_DYNLOAD_INVALID_ALLOCATOR_PTR;
}
if (align >= 0 && align < 4) {
align = 4;
} else if (align < 0 && align > -4) {
align = -4;
}
if (!(*outAddr = memalign(align, size))) {
return OS_DYNLOAD_OUT_OF_MEMORY;
}
// keep track of allocated memory to clean it up in case the RPLs won't get unloaded properly
gAllocatedAddresses.push_back(*outAddr);
return OS_DYNLOAD_OK;
}
static void CustomDynLoadFree(void *addr) {
free(addr);
// Remove from list
if (const auto it = std::ranges::find(gAllocatedAddresses, addr); it != gAllocatedAddresses.end()) {
gAllocatedAddresses.erase(it);
}
}
bool ResolveRelocations(const std::vector<std::shared_ptr<ModuleData>> &loadedModules, const bool skipUnloadedRpl, std::map<std::string, OSDynLoad_Module> &usedRPls) {
bool wasSuccessful = true;
OSDynLoadAllocFn prevDynLoadAlloc = nullptr;
OSDynLoadFreeFn prevDynLoadFree = nullptr;
if (!skipUnloadedRpl) {
OSDynLoad_GetAllocator(&prevDynLoadAlloc, &prevDynLoadFree);
if (gCustomRPLAllocatorAllocFn != nullptr && gCustomRPLAllocatorFreeFn != nullptr) {
OSDynLoad_SetAllocator(reinterpret_cast<OSDynLoadAllocFn>(gCustomRPLAllocatorAllocFn), gCustomRPLAllocatorFreeFn);
} else {
OSDynLoad_SetAllocator(CustomDynLoadAlloc, CustomDynLoadFree);
}
}
for (auto &curModule : loadedModules) {
DEBUG_FUNCTION_LINE("Let's do the relocations for %s", curModule->getExportName().c_str());
auto &relocData = curModule->getRelocationDataList();
if (!doRelocation(gLoadedModules, relocData, nullptr, 0, usedRPls, skipUnloadedRpl)) {
wasSuccessful = false;
DEBUG_FUNCTION_LINE_ERR("Failed to do Relocations for %s", curModule->getExportName().c_str());
OSFatal("Failed to do Relocations");
}
}
if (!skipUnloadedRpl) {
OSDynLoad_SetAllocator(prevDynLoadAlloc, prevDynLoadFree);
}
DCFlushRange((void *) MEMORY_REGION_START, MEMORY_REGION_SIZE);
ICInvalidateRange((void *) MEMORY_REGION_START, MEMORY_REGION_SIZE);
return wasSuccessful;
}
bool doRelocation(const std::vector<std::shared_ptr<ModuleData>> &moduleList,
const std::vector<std::unique_ptr<RelocationData>> &relocData,
relocation_trampoline_entry_t *tramp_data,
const uint32_t tramp_length,
std::map<std::string, OSDynLoad_Module> &usedRPls,
const bool skipUnloadedRpl) {
for (auto const &curReloc : relocData) {
auto &functionName = curReloc->getName();
std::string rplName = curReloc->getImportRPLInformation().getRPLName();
uint32_t functionAddress = 0;
for (auto &module : moduleList) {
if (rplName == module->getExportName()) {
auto found = false;
for (auto &exportData : module->getExportDataList()) {
if (functionName == exportData->getName()) {
functionAddress = (uint32_t) exportData->getAddress();
found = true;
}
}
if (!found) {
DEBUG_FUNCTION_LINE_ERR("Failed to find export %s of module: %s", functionName.c_str(), rplName.c_str());
OSFatal("Failed to find export of module.");
}
}
}
if (functionName == "MEMAllocFromDefaultHeap") {
functionAddress = reinterpret_cast<uint32_t>(&MEMAlloc);
} else if (functionName == "MEMAllocFromDefaultHeapEx") {
functionAddress = reinterpret_cast<uint32_t>(&MEMAllocEx);
} else if (functionName == "MEMFreeToDefaultHeap") {
functionAddress = reinterpret_cast<uint32_t>(&MEMFree);
}
if (functionAddress == 0) {
int32_t isData = curReloc->getImportRPLInformation().isData();
OSDynLoad_Module rplHandle = nullptr;
if (!usedRPls.contains(rplName)) {
OSDynLoad_Module tmp = nullptr;
if (OSDynLoad_IsModuleLoaded(rplName.c_str(), &tmp) != OS_DYNLOAD_OK || tmp == nullptr) {
if (skipUnloadedRpl) {
DEBUG_FUNCTION_LINE_VERBOSE("Skip acquire of %s", rplName.c_str());
continue;
}
}
// Always acquire to increase refcount and make sure it won't get unloaded while we're using it.
OSDynLoad_Error err = OSDynLoad_Acquire(rplName.c_str(), &rplHandle);
if (err != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE_ERR("Failed to acquire %s", rplName.c_str());
return false;
}
// Keep track RPLs we are using.
// They will be released on exit (See: AromaBaseModule)
usedRPls[rplName] = rplHandle;
} else {
//DEBUG_FUNCTION_LINE_VERBOSE("Use from usedRPLs cache! %s", rplName.c_str());
}
rplHandle = usedRPls[rplName];
OSDynLoad_FindExport(rplHandle, (OSDynLoad_ExportType) isData, functionName.c_str(), (void **) &functionAddress);
if (functionAddress == 0) {
DEBUG_FUNCTION_LINE_ERR("Failed to find export %s of %s", functionName.c_str(), rplName.c_str());
OSFatal("Failed to find export");
return false;
}
}
if (!ElfUtils::elfLinkOne(curReloc->getType(), curReloc->getOffset(), curReloc->getAddend(), (uint32_t) curReloc->getDestination(), functionAddress, tramp_data, tramp_length,
RELOC_TYPE_IMPORT)) {
DEBUG_FUNCTION_LINE_ERR("Relocation failed");
return false;
}
}
if (tramp_data != nullptr) {
DCFlushRange(tramp_data, tramp_length * sizeof(relocation_trampoline_entry_t));
ICInvalidateRange(tramp_data, tramp_length * sizeof(relocation_trampoline_entry_t));
}
return true;
}

View File

@ -1,16 +0,0 @@
#pragma once
#include "module/ModuleData.h"
#include <coreinit/dynload.h>
#include <map>
#include <vector>
bool ResolveRelocations(const std::vector<std::shared_ptr<ModuleData>> &loadedModules,
bool skipUnloadedRpl,
std::map<std::string, OSDynLoad_Module> &usedRPls);
bool doRelocation(const std::vector<std::shared_ptr<ModuleData>> &moduleList,
const std::vector<std::unique_ptr<RelocationData>> &relocData,
relocation_trampoline_entry_t *tramp_data,
uint32_t tramp_length,
std::map<std::string, OSDynLoad_Module> &usedRPls,
bool skipUnloadedRpl);

View File

@ -0,0 +1,134 @@
#pragma once
#include <chrono>
#include <coreinit/debug.h>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
namespace WUMSLoader::Utils {
class SegmentedTimer {
public:
inline static SegmentedTimer *globalTimer = nullptr;
void start_segment(const std::string &segment_name) {
// Only start if it isn't already running
if (!mActiveSegments.contains(segment_name)) {
mActiveSegments.insert(segment_name);
mSegmentStartTimes[segment_name] = std::chrono::high_resolution_clock::now();
}
}
void stop_segment(const std::string &segment_name) {
// Only stop if it's currently tracked as running
if (mActiveSegments.contains(segment_name)) {
auto now = std::chrono::high_resolution_clock::now();
mSegmentTotals[segment_name] += std::chrono::duration_cast<std::chrono::microseconds>(now - mSegmentStartTimes[segment_name]);
mActiveSegments.erase(segment_name);
}
}
SegmentedTimer(std::string name = "Timer")
: name_(std::move(name)),
mStartTime(std::chrono::high_resolution_clock::now()),
mAccumulatedActiveTime(0) {
}
~SegmentedTimer() {
auto now = std::chrono::high_resolution_clock::now();
// Finalize main timers and any marks still running
mAccumulatedActiveTime += std::chrono::duration_cast<std::chrono::microseconds>(now - mStartTime);
for (const auto &mark_name : mActiveSegments) {
mSegmentTotals[mark_name] += std::chrono::duration_cast<std::chrono::microseconds>(now - mSegmentStartTimes[mark_name]);
}
// Print Main Timer
long long active_us = mAccumulatedActiveTime.count();
OSReport("\n=== [%s] Profiler Results ===\n", name_.c_str());
OSReport("Total Active: %.3f ms\n", active_us / 1000.0);
// Print all Repurposed Segments (Sorted Longest to Shortest)
if (!mSegmentTotals.empty()) {
OSReport("--- Segments (Sorted by Time) ---\n");
// Copy unordered map contents to a vector for sorting
std::vector<std::pair<std::string, std::chrono::microseconds>> sorted_segments(
mSegmentTotals.begin(), mSegmentTotals.end());
// Sort descending based on duration (second element in the pair)
std::sort(sorted_segments.begin(), sorted_segments.end(),
[](const auto &a, const auto &b) {
return a.second > b.second;
});
for (const auto &pair : sorted_segments) {
long long seg_us = pair.second.count();
OSReport(" -> %8.3f ms : [%s]\n", seg_us / 1000.0, pair.first.c_str());
}
}
OSReport("=================================\n\n");
globalTimer = nullptr;
}
// --- Segment API ---
private:
std::string name_;
// Core Time points & Accumulators
std::chrono::time_point<std::chrono::high_resolution_clock> mStartTime;
std::chrono::microseconds mAccumulatedActiveTime;
// Segment Tracking Maps
std::unordered_set<std::string> mActiveSegments;
std::unordered_map<std::string, std::chrono::time_point<std::chrono::high_resolution_clock>> mSegmentStartTimes;
std::unordered_map<std::string, std::chrono::microseconds> mSegmentTotals;
};
class ScopedTimer {
public:
ScopedTimer(const char *name) : mName(name) {
if (SegmentedTimer::globalTimer) {
SegmentedTimer::globalTimer->start_segment(mName);
}
}
~ScopedTimer() {
if (SegmentedTimer::globalTimer) {
SegmentedTimer::globalTimer->stop_segment(mName);
}
}
private:
const char *mName;
};
} // namespace WUMSLoader::Utils
#ifdef WUMS_ENABLE_PROFILING
// Token pasting macros to generate unique local variable names based on the line number
#define PROFILE_CONCAT_INNER(a, b) a##b
#define PROFILE_CONCAT(a, b) PROFILE_CONCAT_INNER(a, b)
// Drops a timer for the entire function scope using the short function name
#define PROFILE_FUNCTION() \
WUMSLoader::Utils::ScopedTimer PROFILE_CONCAT(_scoped_timer_, __LINE__) { __func__ }
// Drops a timer for a specific block or loop with a custom string
#define PROFILE_BLOCK(name) \
WUMSLoader::Utils::ScopedTimer PROFILE_CONCAT(_scoped_timer_, __LINE__) { name }
#else
// When profiling is disabled, these macros expand to absolutely nothing.
// The compiler will ignore them completely (zero overhead).
#define PROFILE_FUNCTION() (void) 0
#define PROFILE_BLOCK(name) (void) 0
#endif

View File

@ -1,51 +0,0 @@
/***************************************************************************
* Copyright (C) 2010
* by Dimok
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any
* damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any
* purpose, including commercial applications, and to alter it and
* redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you
* must not claim that you wrote the original software. If you use
* this software in a product, an acknowledgment in the product
* documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and
* must not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*
* for WiiXplorer 2010
***************************************************************************/
#include <cstring>
#include <string>
#include <strings.h>
#include <utils/StringTools.h>
#include <wut_types.h>
int32_t StringTools::strtokcmp(const char *string, const char *compare, const char *separator) {
if (!string || !compare)
return -1;
char TokCopy[512];
strncpy(TokCopy, compare, sizeof(TokCopy));
TokCopy[511] = '\0';
char *strTok = strtok(TokCopy, separator);
while (strTok != nullptr) {
if (strcasecmp(string, strTok) == 0) {
return 0;
}
strTok = strtok(nullptr, separator);
}
return -1;
}

View File

@ -1,37 +1,8 @@
/***************************************************************************
* Copyright (C) 2010
* by Dimok
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any
* damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any
* purpose, including commercial applications, and to alter it and
* redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you
* must not claim that you wrote the original software. If you use
* this software in a product, an acknowledgment in the product
* documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and
* must not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
*
* for WiiXplorer 2010
***************************************************************************/
#pragma once
#include "logger.h"
#include "utils.h"
#include <coreinit/debug.h>
#include <memory>
#include <string>
#include <vector>
#include <wut_types.h>
template<typename... Args>
std::string string_format(const std::string &format, Args... args) {
@ -46,40 +17,3 @@ std::string string_format(const std::string &format, Args... args) {
std::snprintf(buf.get(), size, format.c_str(), args...);
return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}
class StringTools {
public:
static int32_t strtokcmp(const char *string, const char *compare, const char *separator);
static const char *FullpathToFilename(const char *path) {
if (!path)
return path;
const char *ptr = path;
const char *Filename = ptr;
while (*ptr != '\0') {
if (ptr[0] == '/' && ptr[1] != '\0')
Filename = ptr + 1;
++ptr;
}
return Filename;
}
static void RemoveDoubleSlashs(std::string &str) {
uint32_t length = str.size();
//! clear path of double slashes
for (uint32_t i = 1; i < length; ++i) {
if (str[i - 1] == '/' && str[i] == '/') {
str.erase(i, 1);
i--;
length--;
}
}
}
};

View File

@ -1,129 +0,0 @@
#include "hooks.h"
#include "globals.h"
#include "module/ModuleData.h"
#include "utils/logger.h"
#include <coreinit/memexpheap.h>
#include <optional>
#include <memory>
#include <wums.h>
#ifdef DEBUG
static const char **hook_names = (const char *[]){
"WUMS_HOOK_INIT_WUT_MALLOC",
"WUMS_HOOK_FINI_WUT_MALLOC",
"WUMS_HOOK_INIT_WUT_NEWLIB",
"WUMS_HOOK_FINI_WUT_NEWLIB",
"WUMS_HOOK_INIT_WUT_STDCPP",
"WUMS_HOOK_FINI_WUT_STDCPP",
"WUMS_HOOK_INIT_WUT_DEVOPTAB",
"WUMS_HOOK_FINI_WUT_DEVOPTAB",
"WUMS_HOOK_INIT_WUT_SOCKETS",
"WUMS_HOOK_FINI_WUT_SOCKETS",
"WUMS_HOOK_INIT_WRAPPER",
"WUMS_HOOK_FINI_WRAPPER",
"WUMS_HOOK_INIT",
"WUMS_HOOK_APPLICATION_STARTS",
"WUMS_HOOK_APPLICATION_ENDS",
"WUMS_HOOK_RELOCATIONS_DONE",
"WUMS_HOOK_APPLICATION_REQUESTS_EXIT",
"WUMS_HOOK_DEINIT",
"WUMS_HOOK_ALL_APPLICATION_STARTS_DONE",
"WUMS_HOOK_ALL_APPLICATION_ENDS_DONE",
"WUMS_HOOK_ALL_APPLICATION_REQUESTS_EXIT_DONE",
"WUMS_HOOK_GET_CUSTOM_RPL_ALLOCATOR",
"WUMS_HOOK_CLEAR_ALLOCATED_RPL_MEMORY",
"WUMS_HOOK_CLEAR_ALLOCATED_RPL_MEMORY",
"WUMS_HOOK_INIT_WUT_THREAD"};
#endif
void CallHook(const std::vector<std::shared_ptr<ModuleData>> &modules, wums_hook_type_t type, bool condition) {
if (condition) {
CallHook(modules, type);
}
}
void CallHook(const std::vector<std::shared_ptr<ModuleData>> &modules, wums_hook_type_t type) {
DEBUG_FUNCTION_LINE("Calling hook of type %s [%d] for all modules", hook_names[type], type);
for (auto &curModule : modules) {
CallHook(curModule, type);
}
}
void CallHook(const std::shared_ptr<ModuleData> &module, wums_hook_type_t type, bool condition) {
if (condition) {
CallHook(module, type);
}
}
void CallHook(const std::shared_ptr<ModuleData> &module, wums_hook_type_t type) {
#ifdef DEBUG
bool foundHook = false;
#endif
for (auto &curHook : module->getHookDataList()) {
auto func_ptr = (uint32_t) curHook->getFunctionPointer();
if (func_ptr == 0) {
DEBUG_FUNCTION_LINE_ERR("Module %s: hook ptr was NULL", module->getExportName().c_str());
break;
}
if (type == curHook->getType()) {
#ifdef DEBUG
foundHook = true;
#endif
if ((type == WUMS_HOOK_APPLICATION_STARTS ||
type == WUMS_HOOK_APPLICATION_ENDS ||
type == WUMS_HOOK_INIT_WUT_MALLOC ||
type == WUMS_HOOK_FINI_WUT_MALLOC ||
type == WUMS_HOOK_INIT_WUT_NEWLIB ||
type == WUMS_HOOK_FINI_WUT_NEWLIB ||
type == WUMS_HOOK_INIT_WUT_STDCPP ||
type == WUMS_HOOK_FINI_WUT_STDCPP ||
type == WUMS_HOOK_INIT_WUT_DEVOPTAB ||
type == WUMS_HOOK_FINI_WUT_DEVOPTAB ||
type == WUMS_HOOK_INIT_WUT_SOCKETS ||
type == WUMS_HOOK_FINI_WUT_SOCKETS ||
type == WUMS_HOOK_FINI_WUT_SOCKETS ||
type == WUMS_HOOK_INIT_WRAPPER ||
type == WUMS_HOOK_FINI_WRAPPER ||
type == WUMS_HOOK_DEINIT ||
type == WUMS_HOOK_ALL_APPLICATION_STARTS_DONE ||
type == WUMS_HOOK_ALL_APPLICATION_ENDS_DONE ||
type == WUMS_HOOK_CLEAR_ALLOCATED_RPL_MEMORY ||
type == WUMS_HOOK_ALL_APPLICATION_REQUESTS_EXIT_DONE ||
type == WUMS_HOOK_INIT_WUT_THREAD)) {
DEBUG_FUNCTION_LINE("Calling hook of type %s [%d] %d for %s: %p", hook_names[type], type, curHook->getType(), module->getExportName().c_str(), curHook->getTarget());
((void (*)())((uint32_t *) func_ptr))();
break;
} else if (type == WUMS_HOOK_INIT ||
type == WUMS_HOOK_RELOCATIONS_DONE) {
DEBUG_FUNCTION_LINE("Calling hook of type %s [%d] %d for %s: %p", hook_names[type], type, curHook->getType(), module->getExportName().c_str(), curHook->getTarget());
wums_app_init_args_t args;
args.module_information = &gModuleInformation;
((void (*)(wums_app_init_args_t *))((uint32_t *) func_ptr))(&args);
} else if (type == WUMS_HOOK_GET_CUSTOM_RPL_ALLOCATOR) {
DEBUG_FUNCTION_LINE("Calling hook of type %s [%d] %d for %s: %p", hook_names[type], type, curHook->getType(), module->getExportName().c_str(), curHook->getTarget());
const auto [allocFn, freeFn] = reinterpret_cast<wums_internal_custom_rpl_allocator_t (*)()>(reinterpret_cast<uint32_t *>(func_ptr))();
gCustomRPLAllocatorAllocFn = allocFn;
gCustomRPLAllocatorFreeFn = freeFn;
} else {
DEBUG_FUNCTION_LINE_ERR("#########################################");
DEBUG_FUNCTION_LINE_ERR("#########HOOK NOT IMPLEMENTED %d#########", type);
DEBUG_FUNCTION_LINE_ERR("#########################################");
}
break;
}
}
#ifdef DEBUG
if (foundHook && !MEMCheckExpHeap(MEMGetBaseHeapHandle(MEM_BASE_HEAP_MEM2), MEM_EXP_HEAP_CHECK_FLAGS_LOG_ERRORS)) {
DEBUG_FUNCTION_LINE_ERR("MEM2 default heap is corrupted while calling hook %s for module %s", hook_names[type], module->getExportName().c_str());
OSFatal("WUMSLoader: MEM2 default heap is corrupted. \n Please restart the console.");
}
#endif
}

View File

@ -1,14 +0,0 @@
#pragma once
#include "module/ModuleData.h"
#include <memory>
#include <vector>
#include <wums/hooks.h>
void CallHook(const std::vector<std::shared_ptr<ModuleData>> &modules, wums_hook_type_t type, bool condition);
void CallHook(const std::vector<std::shared_ptr<ModuleData>> &modules, wums_hook_type_t type);
void CallHook(const std::shared_ptr<ModuleData> &module, wums_hook_type_t type, bool condition);
void CallHook(const std::shared_ptr<ModuleData> &module, wums_hook_type_t type);

View File

@ -57,6 +57,8 @@ IMPORT(OSFastMutex_Unlock);
IMPORT(OSInitSpinLock);
IMPORT(OSGetAlarmUserData);
IMPORT(OSRestoreInterrupts);
IMPORT(OSGetTime);
IMPORT(OSGetSystemTime);
IMPORT(OSDisableInterrupts);
IMPORT(FSTimeToCalendarTime);

View File

@ -8,11 +8,24 @@
extern "C" {
#endif
#define LOG_APP_TYPE "M"
#define LOG_APP_NAME "WUMSLoader"
#define LOG_APP_TYPE "M"
#define LOG_APP_NAME "WUMSLoader"
#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__)
#ifdef __cplusplus
constexpr const char *ConstexprFileName(const char *path) {
const char *file = path;
for (const char *p = path; *p; ++p) {
if (*p == '/' || *p == '\\') {
file = p + 1;
}
}
return file;
}
#define __FILENAME__ ConstexprFileName(__FILE__)
#else
#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__)
#endif
#define LOG(LOG_FUNC, FMT, ARGS...) LOG_EX(LOG_FUNC, "", "", FMT, ##ARGS)
@ -37,6 +50,7 @@ extern "C" {
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX(WHBLogPrintf, "##WARN ## ", "", FMT, ##ARGS)
#define DEBUG_FUNCTION_LINE_INFO(FMT, ARGS...) LOG_EX(OSReport, "##INFO ## ", "\n", FMT, ##ARGS)
#else

View File

@ -51,8 +51,6 @@ void *MemoryAllocEx(uint32_t size, uint32_t align) {
void MemoryFree(void *ptr) {
if (ptr) {
MEMFreeToExpHeap(gHeapHandle, ptr);
} else {
OSFatal("Failed to free");
}
}

View File

@ -1,9 +1,17 @@
#pragma once
#include <memory>
#include <vector>
#define ROUNDDOWN(val, align) ((val) & ~(align - 1))
#define ROUNDUP(val, align) ROUNDDOWN(((val) + (align - 1)), align)
template<typename T, typename U>
[[nodiscard]] constexpr T ROUNDDOWN(T val, U align) {
return val & ~(static_cast<T>(align) - 1);
}
template<typename T, typename U>
[[nodiscard]] constexpr T ROUNDUP(T val, U align) {
return ROUNDDOWN(val + static_cast<T>(align - 1), align);
}
template<class T, class... Args>
std::unique_ptr<T> make_unique_nothrow(Args &&...args) noexcept(noexcept(T(std::forward<Args>(args)...))) {