WUMSLoader/wumsloader/src/entry.cpp

308 lines
12 KiB
C++

#include "entry.h"
#include "fs/DirList.h"
#include "globals.h"
#include "module/ModuleDataFactory.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 "version.h"
#include <coreinit/debug.h>
#include <coreinit/kernel.h>
#include <coreinit/memexpheap.h>
#include <cstdint>
#include <list>
#define VERSION "v0.2.2"
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);
// We need to wrap it to make sure the main function is called AFTER our code.
// The compiler tries to optimize this otherwise and calling the main function earlier
extern "C" int _start(int argc, char **argv) {
InitFunctionPointers();
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);
if (!gHeapHandle) {
OSFatal("Failed to alloc heap");
}
__init();
ucSetupRequired = 0;
}
uint32_t upid = OSGetUPID();
if (upid == 2 || upid == 15) {
doStart(argc, argv);
}
KernelInfo0 kernelInfo0;
__KernelGetInfo0(&kernelInfo0, 0);
asm(
"mr 13,%0\n"
"mr 2,%1\n" ::"r"(kernelInfo0.sdaBase),
"r"(kernelInfo0.sda2Base)
:);
OSCheckActiveThreads();
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;
}
}
void doStart(int argc, char **argv) {
init_wut();
initLogging();
OSReport("Running WUMSLoader " VERSION VERSION_EXTRA "\n");
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!
for (auto &addr : gAllocatedAddresses) {
DEBUG_FUNCTION_LINE_WARN("Memory allocated by OSDynload was not freed properly, let's clean it up! (%08X)", addr);
free((void *) addr);
}
gAllocatedAddresses.clear();
if (!gInitCalled) {
gInitCalled = 1;
#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));
auto moduleData = ModuleDataFactory::load(modules.GetFilepath(i));
if (moduleData) {
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.
auto it = std::find_if(gLoadedModules.begin(),
gLoadedModules.end(),
[](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
auto it = std::find_if(gLoadedModulesCopy.begin(),
gLoadedModulesCopy.end(),
[](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 replacing alloc functions");
ResolveRelocations(gLoadedModules, true, gUsedRPLs);
for (auto &curModule : gLoadedModules) {
if (curModule->isInitBeforeRelocationDoneHook()) {
CallInitHooksForModule(curModule);
}
}
DEBUG_FUNCTION_LINE_VERBOSE("Call Relocations done hook");
CallHook(gLoadedModules, WUMS_HOOK_RELOCATIONS_DONE);
for (auto &curModule : gLoadedModules) {
if (!curModule->isInitBeforeRelocationDoneHook()) {
CallInitHooksForModule(curModule);
}
}
} else {
DEBUG_FUNCTION_LINE("Resolve relocations and replace alloc functions");
ResolveRelocations(gLoadedModules, false, gUsedRPLs);
CallHook(gLoadedModules, WUMS_HOOK_RELOCATIONS_DONE);
}
SaveLoadedRPLsInGlobalInformation(&gModuleInformation, gUsedRPLs);
CallHook(gLoadedModules, WUMS_HOOK_INIT_WUT_DEVOPTAB);
CallHook(gLoadedModules, WUMS_HOOK_INIT_WUT_SOCKETS);
CallHook(gLoadedModules, WUMS_HOOK_APPLICATION_STARTS);
deinitLogging();
fini_wut();
}
void CallInitHooksForModule(const std::shared_ptr<ModuleData> &curModule) {
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.begin());
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;
}