EnvironmentLoader/source/main.cpp

406 lines
15 KiB
C++

#include <cstring>
#include "utils/StringTools.h"
#include <coreinit/cache.h>
#include <coreinit/debug.h>
#include <coreinit/dynload.h>
#include <coreinit/filesystem_fsa.h>
#include <coreinit/foreground.h>
#include <coreinit/ios.h>
#include <coreinit/screen.h>
#include <coreinit/title.h>
#include <elfio/elfio.hpp>
#include <fcntl.h>
#include <gx2/state.h>
#include <malloc.h>
#include <memory>
#include <nn/act/client_cpp.h>
#include <proc_ui/procui.h>
#include <sysapp/launch.h>
#include <sysapp/title.h>
#include <vector>
#include <vpad/input.h>
#include <whb/log_cafe.h>
#include <whb/log_module.h>
#include <whb/log_udp.h>
#include "ElfUtils.h"
#include "common/module_defines.h"
#include "fs/DirList.h"
#include "kernel.h"
#include "module/ModuleDataFactory.h"
#include "utils/DrawUtils.h"
#include "utils/InputUtils.h"
#include "utils/PairUtils.h"
// clang-format off
#define MEMORY_REGION_START 0x00A00000
#define MEMORY_REGION_SIZE 0x00600000
#define MEMORY_REGION_END (MEMORY_REGION_START + MEMORY_REGION_SIZE)
#define AUTOBOOT_CONFIG_PATH "fs:/vol/external01/wiiu/environments/default.cfg"
// clang-format on
bool CheckRunning() {
switch (ProcUIProcessMessages(true)) {
case PROCUI_STATUS_EXITING: {
return false;
}
case PROCUI_STATUS_RELEASE_FOREGROUND: {
ProcUIDrawDoneRelease();
break;
}
case PROCUI_STATUS_IN_FOREGROUND: {
break;
}
case PROCUI_STATUS_IN_BACKGROUND:
default:
break;
}
return true;
}
extern "C" uint32_t textStart();
std::string EnvironmentSelectionScreen(const std::map<std::string, std::string> &payloads, int32_t autobootIndex);
std::optional<std::string> getFileContent(const std::string &path) {
DEBUG_FUNCTION_LINE_VERBOSE("Read from file %s", path.c_str());
FILE *f = fopen(path.c_str(), "r");
if (f) {
char buf[128]{};
fgets(buf, sizeof(buf), f);
fclose(f);
return std::string(buf);
}
DEBUG_FUNCTION_LINE_ERR("Failed to load %s", path.c_str());
return {};
}
bool writeFileContent(const std::string &path, const std::string &content) {
DEBUG_FUNCTION_LINE_VERBOSE("Write to file %s: %s", path.c_str(), content.c_str());
FILE *f = fopen(path.c_str(), "w");
if (f) {
fputs(content.c_str(), f);
fclose(f);
return true;
}
return false;
}
extern "C" void __fini();
extern "C" void __init_wut_malloc();
int main(int argc, char **argv) {
// We need to call __init_wut_malloc somewhere so wut_malloc will be used for the memory allocation.
__init_wut_malloc();
initLogging();
if (IOS_Open((char *) ("/dev/iosuhax"), static_cast<IOSOpenMode>(0)) >= 0) {
auto checkTiramisuHBL = fopen("fs:/vol/external01/wiiu/environments/tiramisu/modules/setup/50_hbl_installer.rpx", "r");
if (checkTiramisuHBL != nullptr) {
fclose(checkTiramisuHBL);
OSFatal("Don't run the EnvironmentLoader twice.\n\nIf you want to open the Homebrew Launcher, launch the Mii Maker\ninstead.");
} else {
OSFatal("Don't run the EnvironmentLoader twice.");
}
}
DEBUG_FUNCTION_LINE("Hello from EnvironmentLoader!");
char environmentPath[0x100];
memset(environmentPath, 0, sizeof(environmentPath));
auto handle = IOS_Open("/dev/mcp", IOS_OPEN_READ);
if (handle >= 0) {
int in = 0xF9; // IPC_CUSTOM_COPY_ENVIRONMENT_PATH
if (IOS_Ioctl(handle, 100, &in, sizeof(in), environmentPath, sizeof(environmentPath)) == IOS_ERROR_OK) {
DEBUG_FUNCTION_LINE("Boot into %s", environmentPath);
}
IOS_Close(handle);
}
// We substract 0x100 to be safe.
uint32_t textSectionStart = textStart() - 0x100;
auto gModuleData = (module_information_t *) (textSectionStart - sizeof(module_information_t));
bool noEnvironmentsFound = false;
std::string environment_path = std::string(environmentPath);
if (strncmp(environmentPath, "fs:/vol/external01/wiiu/environments/", strlen("fs:/vol/external01/wiiu/environments/")) != 0) {
DirList environmentDirs("fs:/vol/external01/wiiu/environments/", nullptr, DirList::Dirs, 1);
std::map<std::string, std::string> environmentPaths;
for (int i = 0; i < environmentDirs.GetFilecount(); i++) {
environmentPaths[environmentDirs.GetFilename(i)] = environmentDirs.GetFilepath(i);
}
bool forceMenu = true;
auto res = getFileContent(AUTOBOOT_CONFIG_PATH);
auto autobootIndex = -1;
if (res) {
DEBUG_FUNCTION_LINE_VERBOSE("Got result %s", res->c_str());
int32_t i = 0;
for (auto const &[key, val] : environmentPaths) {
if (res.value() == key) {
DEBUG_FUNCTION_LINE("Found environment %s from config at index %d", res.value().c_str(), i);
autobootIndex = i;
environment_path = val;
forceMenu = false;
break;
}
i++;
}
} else {
DEBUG_FUNCTION_LINE_ERR("No config found");
}
InputUtils::Init();
InputUtils::InputData input = InputUtils::getControllerInput();
if (forceMenu || ((input.trigger | input.hold) & VPAD_BUTTON_X) == VPAD_BUTTON_X) {
DEBUG_FUNCTION_LINE_VERBOSE("Open menu!");
environment_path = EnvironmentSelectionScreen(environmentPaths, autobootIndex);
if (environmentPaths.empty()) {
noEnvironmentsFound = true;
} else {
DEBUG_FUNCTION_LINE_VERBOSE("Selected %s", environment_path.c_str());
}
}
InputUtils::DeInit();
}
RevertMainHook();
if (!noEnvironmentsFound) {
DirList setupModules(environment_path + "/modules/setup", ".rpx", DirList::Files, 1);
setupModules.SortList();
std::map<std::string, OSDynLoad_Module> usedRPls;
for (int i = 0; i < setupModules.GetFilecount(); i++) {
//! skip hidden linux and mac files
if (setupModules.GetFilename(i)[0] == '.' || setupModules.GetFilename(i)[0] == '_') {
DEBUG_FUNCTION_LINE_ERR("Skip file %s", setupModules.GetFilepath(i));
continue;
}
// Some module may unmount the sd card on exit.
FSAInit();
auto client = FSAAddClient(nullptr);
if (client) {
FSAMount(client, "/dev/sdcard01", "/vol/external01", static_cast<FSAMountFlags>(0), nullptr, 0);
FSADelClient(client);
}
uint32_t destination_address_end = ((uint32_t) gModuleData) & 0xFFFF0000;
memset((void *) gModuleData, 0, sizeof(module_information_t));
DEBUG_FUNCTION_LINE("Trying to run %s.", setupModules.GetFilepath(i), destination_address_end, ((uint32_t) gModuleData) - MEMORY_REGION_START);
auto moduleData = ModuleDataFactory::load(setupModules.GetFilepath(i), destination_address_end, ((uint32_t) gModuleData) - MEMORY_REGION_START, gModuleData->trampolines,
DYN_LINK_TRAMPOLIN_LIST_LENGTH);
if (!moduleData) {
DEBUG_FUNCTION_LINE_ERR("Failed to load %s", setupModules.GetFilepath(i));
OSFatal("EnvironmentLoader: Failed to load module");
continue;
}
DEBUG_FUNCTION_LINE("Loaded module data");
if (!ElfUtils::doRelocation(moduleData.value()->getRelocationDataList(), gModuleData->trampolines, DYN_LINK_TRAMPOLIN_LIST_LENGTH, usedRPls)) {
DEBUG_FUNCTION_LINE_ERR("Relocations failed");
OSFatal("EnvironmentLoader: Relocations failed");
} else {
DEBUG_FUNCTION_LINE("Relocation done");
}
DCFlushRange((void *) moduleData.value()->getStartAddress(), moduleData.value()->getEndAddress() - moduleData.value()->getStartAddress());
ICInvalidateRange((void *) moduleData.value()->getStartAddress(), moduleData.value()->getEndAddress() - moduleData.value()->getStartAddress());
DEBUG_FUNCTION_LINE("Calling entrypoint @%08X", moduleData.value()->getEntrypoint());
char *arr[1];
arr[0] = (char *) environment_path.c_str();
// clang-format off
((int(*)(int, char **)) moduleData.value()->getEntrypoint())(1, arr);
// clang-format on
DEBUG_FUNCTION_LINE("Back from module");
for (auto &rpl : usedRPls) {
DEBUG_FUNCTION_LINE_VERBOSE("Release %s", rpl.first.c_str());
OSDynLoad_Release(rpl.second);
}
usedRPls.clear();
}
} else {
DEBUG_FUNCTION_LINE("Return to Wii U Menu");
ProcUIInit(OSSavesDone_ReadyToRelease);
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "void forceDefaultTitleIDToWiiUMenu(void)") == 0) {
if ((i + 1) < argc) {
i++;
DEBUG_FUNCTION_LINE_VERBOSE("call forceDefaultTitleIDToWiiUMenu");
// clang-format off
auto forceDefaultTitleIDToWiiUMenu = (void(*)()) argv[i];
// clang-format on
forceDefaultTitleIDToWiiUMenu();
}
}
}
DEBUG_FUNCTION_LINE("Launch menu");
SYSLaunchMenu();
}
ProcUIInit(OSSavesDone_ReadyToRelease);
while (CheckRunning()) {
// wait.
OSSleepTicks(OSMillisecondsToTicks(100));
}
ProcUIShutdown();
deinitLogging();
__fini();
return 0;
}
std::string EnvironmentSelectionScreen(const std::map<std::string, std::string> &payloads, int32_t autobootIndex) {
OSScreenInit();
uint32_t tvBufferSize = OSScreenGetBufferSizeEx(SCREEN_TV);
uint32_t drcBufferSize = OSScreenGetBufferSizeEx(SCREEN_DRC);
auto *screenBuffer = (uint8_t *) memalign(0x100, tvBufferSize + drcBufferSize);
if (!screenBuffer) {
OSFatal("EnvironmentLoader: Fail to allocate screenBuffer");
}
memset(screenBuffer, 0, tvBufferSize + drcBufferSize);
OSScreenSetBufferEx(SCREEN_TV, screenBuffer);
OSScreenSetBufferEx(SCREEN_DRC, screenBuffer + tvBufferSize);
OSScreenEnableEx(SCREEN_TV, TRUE);
OSScreenEnableEx(SCREEN_DRC, TRUE);
DrawUtils::initBuffers(screenBuffer, tvBufferSize, screenBuffer + tvBufferSize, drcBufferSize);
if (!DrawUtils::initFont()) {
OSFatal("EnvironmentLoader: Failed to init font");
}
uint32_t selected = autobootIndex > 0 ? autobootIndex : 0;
int autoBoot = autobootIndex;
{
PairMenu pairMenu;
while (true) {
if (pairMenu.ProcessPairScreen()) {
continue;
}
InputUtils::InputData input = InputUtils::getControllerInput();
if (input.trigger & VPAD_BUTTON_UP) {
if (selected > 0) {
selected--;
}
} else if (input.trigger & VPAD_BUTTON_DOWN) {
if (selected < payloads.size() - 1) {
selected++;
}
} else if (input.trigger & VPAD_BUTTON_A) {
break;
} else if (input.trigger & (VPAD_BUTTON_X | VPAD_BUTTON_MINUS)) {
autoBoot = -1;
} else if (input.trigger & (VPAD_BUTTON_Y | VPAD_BUTTON_PLUS)) {
autoBoot = selected;
}
DrawUtils::beginDraw();
DrawUtils::clear(COLOR_BACKGROUND);
// draw buttons
uint32_t index = 8 + 24 + 8 + 4;
uint32_t i = 0;
if (!payloads.empty()) {
for (auto const &[key, val] : payloads) {
if (i == selected) {
DrawUtils::drawRect(16, index, SCREEN_WIDTH - 16 * 2, 44, 4, COLOR_BORDER_HIGHLIGHTED);
} else {
DrawUtils::drawRect(16, index, SCREEN_WIDTH - 16 * 2, 44, 2, ((int32_t) i == autoBoot) ? COLOR_AUTOBOOT : COLOR_BORDER);
}
DrawUtils::setFontSize(24);
DrawUtils::setFontColor(((int32_t) i == autoBoot) ? COLOR_AUTOBOOT : COLOR_TEXT);
DrawUtils::print(16 * 2, index + 8 + 24, key.c_str());
index += 42 + 8;
i++;
}
} else {
DrawUtils::setFontSize(24);
DrawUtils::setFontColor(COLOR_RED);
const char *noEnvironmentsWarning = "No valid environments found. Press \ue000 to launch the Wii U Menu";
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(noEnvironmentsWarning) / 2, SCREEN_HEIGHT / 2, noEnvironmentsWarning, true);
}
DrawUtils::setFontColor(COLOR_TEXT);
// draw top bar
DrawUtils::setFontSize(24);
DrawUtils::print(16, 6 + 24, "Environment Loader");
DrawUtils::drawRectFilled(8, 8 + 24 + 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE);
// draw bottom bar
DrawUtils::drawRectFilled(8, SCREEN_HEIGHT - 24 - 8 - 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE);
DrawUtils::setFontSize(18);
if (!payloads.empty()) {
DrawUtils::print(16, SCREEN_HEIGHT - 8, "\ue07d Navigate ");
DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 8, "\ue000 Choose", true);
const char *autobootHints = "\ue002/\ue046 Clear Default / \ue003/\ue045 Select Default";
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(autobootHints) / 2, SCREEN_HEIGHT - 8, autobootHints, true);
} else {
DrawUtils::print(SCREEN_WIDTH - 20, SCREEN_HEIGHT - 8, "\ue000 Wii U Menu", true);
}
DrawUtils::endDraw();
}
}
DrawUtils::beginDraw();
DrawUtils::clear(COLOR_BLACK);
DrawUtils::endDraw();
DrawUtils::deinitFont();
// Call GX2Init to shut down OSScreen
GX2Init(nullptr);
free(screenBuffer);
if (autoBoot != autobootIndex) {
if (autoBoot == -1) {
writeFileContent(AUTOBOOT_CONFIG_PATH, "-1");
} else {
int i = 0;
for (auto const &[key, val] : payloads) {
if (i == autoBoot) {
DEBUG_FUNCTION_LINE("Save config");
writeFileContent(AUTOBOOT_CONFIG_PATH, key);
break;
}
i++;
}
}
}
uint32_t i = 0;
for (auto const &[key, val] : payloads) {
if (i == selected) {
return val;
}
i++;
}
return "";
}