mirror of
https://github.com/wiiu-env/EnvironmentLoader.git
synced 2024-11-01 04:55:05 +01:00
Compare commits
28 Commits
Environmen
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
ed5bdca3b9 | ||
|
4ad1400f9e | ||
|
fd803278f2 | ||
|
4d9e4f73c9 | ||
|
be78693cb7 | ||
|
f81ecf0178 | ||
|
95d0ce1385 | ||
|
d9c743febd | ||
|
95014a2123 | ||
|
bdf9e079c8 | ||
|
7332dcaa1d | ||
|
44d07c0e78 | ||
|
e1d48361b6 | ||
|
5536bc79ee | ||
|
b5f62f552d | ||
|
35b8c0eeb7 | ||
|
2054d97e62 | ||
|
bd8992bc11 | ||
|
e4d2dfa5aa | ||
|
83cf3060c8 | ||
|
f60e8a340d | ||
|
10337d5fba | ||
|
7dbd371d5a | ||
|
a6f7df7764 | ||
|
5e4f93ddfa | ||
|
5da6afb310 | ||
|
0c0d73ffa3 | ||
|
b3a7150c5d |
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
|||||||
clang-format:
|
clang-format:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: clang-format
|
- name: clang-format
|
||||||
run: |
|
run: |
|
||||||
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source --exclude ./source/elfio
|
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source --exclude ./source/elfio
|
||||||
@ -17,7 +17,14 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: clang-format
|
needs: clang-format
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
- name: create version.h
|
||||||
|
run: |
|
||||||
|
git_hash=$(git rev-parse --short "$GITHUB_SHA")
|
||||||
|
cat <<EOF > ./source/version.h
|
||||||
|
#pragma once
|
||||||
|
#define ENVIRONMENT_LOADER_VERSION_EXTRA " (nightly-$git_hash)"
|
||||||
|
EOF
|
||||||
- name: build binary
|
- name: build binary
|
||||||
run: |
|
run: |
|
||||||
docker build . -t builder
|
docker build . -t builder
|
||||||
@ -42,7 +49,7 @@ jobs:
|
|||||||
- name: zip artifact
|
- name: zip artifact
|
||||||
run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip wiiu
|
run: zip -r ${{ env.REPOSITORY_NAME }}_${{ env.DATETIME }}.zip wiiu
|
||||||
- name: Create Release
|
- name: Create Release
|
||||||
uses: "softprops/action-gh-release@v1"
|
uses: "softprops/action-gh-release@v2"
|
||||||
with:
|
with:
|
||||||
tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }}
|
tag_name: ${{ env.REPOSITORY_NAME }}-${{ env.DATETIME }}
|
||||||
draft: false
|
draft: false
|
||||||
|
13
.github/workflows/pr.yml
vendored
13
.github/workflows/pr.yml
vendored
@ -6,7 +6,7 @@ jobs:
|
|||||||
clang-format:
|
clang-format:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: clang-format
|
- name: clang-format
|
||||||
run: |
|
run: |
|
||||||
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source --exclude ./source/elfio
|
docker run --rm -v ${PWD}:/src ghcr.io/wiiu-env/clang-format:13.0.0-2 -r ./source --exclude ./source/elfio
|
||||||
@ -14,7 +14,7 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: clang-format
|
needs: clang-format
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: build binary with logging
|
- name: build binary with logging
|
||||||
run: |
|
run: |
|
||||||
docker build . -t builder
|
docker build . -t builder
|
||||||
@ -25,7 +25,14 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
needs: clang-format
|
needs: clang-format
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
- name: create version.h
|
||||||
|
run: |
|
||||||
|
git_hash=$(git rev-parse --short "${{ github.event.pull_request.head.sha }}")
|
||||||
|
cat <<EOF > ./source/version.h
|
||||||
|
#pragma once
|
||||||
|
#define ENVIRONMENT_LOADER_VERSION_EXTRA " (nightly-$git_hash)"
|
||||||
|
EOF
|
||||||
- name: build binary
|
- name: build binary
|
||||||
run: |
|
run: |
|
||||||
docker build . -t builder
|
docker build . -t builder
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -12,3 +12,4 @@ cmake-build-debug/
|
|||||||
*.txt
|
*.txt
|
||||||
build1/
|
build1/
|
||||||
cmake-build-default/
|
cmake-build-default/
|
||||||
|
*.zip
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
FROM ghcr.io/wiiu-env/devkitppc:20230417
|
FROM ghcr.io/wiiu-env/devkitppc:20240704
|
||||||
|
|
||||||
WORKDIR project
|
WORKDIR project
|
||||||
|
@ -3,32 +3,39 @@
|
|||||||
#include <coreinit/cache.h>
|
#include <coreinit/cache.h>
|
||||||
#include <coreinit/debug.h>
|
#include <coreinit/debug.h>
|
||||||
#include <coreinit/dynload.h>
|
#include <coreinit/dynload.h>
|
||||||
#include <coreinit/memdefaultheap.h>
|
|
||||||
|
|
||||||
#include "ElfUtils.h"
|
#include "ElfUtils.h"
|
||||||
#include "elfio/elfio.hpp"
|
#include "elfio/elfio.hpp"
|
||||||
|
|
||||||
bool ElfUtils::doRelocation(const std::vector<std::unique_ptr<RelocationData>> &relocData, relocation_trampoline_entry_t *tramp_data, uint32_t tramp_length) {
|
bool ElfUtils::doRelocation(const std::vector<RelocationData> &relocData, relocation_trampoline_entry_t *tramp_data, uint32_t tramp_length, std::map<std::string, OSDynLoad_Module> &usedRPls) {
|
||||||
for (auto const &curReloc : relocData) {
|
for (auto const &curReloc : relocData) {
|
||||||
std::string functionName = curReloc->getName();
|
std::string functionName = curReloc.getName();
|
||||||
std::string rplName = curReloc->getImportRPLInformation()->getRPLName();
|
std::string rplName = curReloc.getImportRPLInformation()->getRPLName();
|
||||||
int32_t isData = curReloc->getImportRPLInformation()->isData();
|
int32_t isData = curReloc.getImportRPLInformation()->isData();
|
||||||
OSDynLoad_Module rplHandle = nullptr;
|
OSDynLoad_Module rplHandle = nullptr;
|
||||||
|
|
||||||
|
if (!usedRPls.contains(rplName)) {
|
||||||
auto err = OSDynLoad_IsModuleLoaded(rplName.c_str(), &rplHandle);
|
//DEBUG_FUNCTION_LINE_VERBOSE("Acquire %s", rplName.c_str());
|
||||||
if (err != OS_DYNLOAD_OK || rplHandle == nullptr) {
|
// Always acquire to increase refcount and make sure it won't get unloaded while we're using it.
|
||||||
// only acquire if not already loaded.
|
OSDynLoad_Error err = OSDynLoad_Acquire(rplName.c_str(), &rplHandle);
|
||||||
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];
|
||||||
|
|
||||||
uint32_t functionAddress = 0;
|
uint32_t functionAddress = 0;
|
||||||
OSDynLoad_FindExport(rplHandle, (OSDynLoad_ExportType) isData, functionName.c_str(), (void **) &functionAddress);
|
if ((OSDynLoad_FindExport(rplHandle, (OSDynLoad_ExportType) isData, functionName.c_str(), (void **) &functionAddress) != OS_DYNLOAD_OK) || functionAddress == 0) {
|
||||||
if (functionAddress == 0) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to find export for %s %s %d", functionName.c_str(), rplName.c_str(), isData);
|
DEBUG_FUNCTION_LINE_ERR("Failed to find export for %s %s %d", functionName.c_str(), rplName.c_str(), isData);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ElfUtils::elfLinkOne(curReloc->getType(), curReloc->getOffset(), curReloc->getAddend(), (uint32_t) curReloc->getDestination(), functionAddress, tramp_data, tramp_length,
|
if (!ElfUtils::elfLinkOne(curReloc.getType(), curReloc.getOffset(), curReloc.getAddend(), (uint32_t) curReloc.getDestination(), functionAddress, tramp_data, tramp_length,
|
||||||
RELOC_TYPE_IMPORT)) {
|
RELOC_TYPE_IMPORT)) {
|
||||||
DEBUG_FUNCTION_LINE_ERR("Relocation failed\n");
|
DEBUG_FUNCTION_LINE_ERR("Relocation failed\n");
|
||||||
return false;
|
return false;
|
||||||
@ -152,7 +159,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[1] = 0x616B0000 | (((uint32_t) value) & 0x0000ffff); // ori r11, r11, real_addr@l
|
||||||
freeSlot->trampoline[2] = 0x7D6903A6; // mtctr r11
|
freeSlot->trampoline[2] = 0x7D6903A6; // mtctr r11
|
||||||
freeSlot->trampoline[3] = 0x4E800420; // bctr
|
freeSlot->trampoline[3] = 0x4E800420; // bctr
|
||||||
DCFlushRange((void *) freeSlot->trampoline, sizeof(freeSlot->trampoline));
|
|
||||||
ICInvalidateRange((unsigned char *) freeSlot->trampoline, sizeof(freeSlot->trampoline));
|
ICInvalidateRange((unsigned char *) freeSlot->trampoline, sizeof(freeSlot->trampoline));
|
||||||
|
|
||||||
if (reloc_type == RELOC_TYPE_FIXED) {
|
if (reloc_type == RELOC_TYPE_FIXED) {
|
||||||
|
@ -2,8 +2,10 @@
|
|||||||
|
|
||||||
#include "common/relocation_defines.h"
|
#include "common/relocation_defines.h"
|
||||||
#include "module/RelocationData.h"
|
#include "module/RelocationData.h"
|
||||||
|
#include <coreinit/dynload.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@ -49,6 +51,5 @@ public:
|
|||||||
static bool elfLinkOne(char type, size_t offset, int32_t addend, uint32_t destination, uint32_t symbol_addr, relocation_trampoline_entry_t *trampolin_data, uint32_t trampolin_data_length,
|
static bool elfLinkOne(char type, size_t offset, int32_t addend, uint32_t destination, uint32_t symbol_addr, relocation_trampoline_entry_t *trampolin_data, uint32_t trampolin_data_length,
|
||||||
RelocationType reloc_type);
|
RelocationType reloc_type);
|
||||||
|
|
||||||
|
static bool doRelocation(const std::vector<RelocationData> &relocData, relocation_trampoline_entry_t *tramp_data, uint32_t tramp_length, std::map<std::string, OSDynLoad_Module> &usedRPls);
|
||||||
static bool doRelocation(const std::vector<std::unique_ptr<RelocationData>> &relocData, relocation_trampoline_entry_t *tramp_data, uint32_t tramp_length);
|
|
||||||
};
|
};
|
||||||
|
420
source/main.cpp
420
source/main.cpp
@ -7,14 +7,17 @@
|
|||||||
#include <coreinit/filesystem_fsa.h>
|
#include <coreinit/filesystem_fsa.h>
|
||||||
#include <coreinit/foreground.h>
|
#include <coreinit/foreground.h>
|
||||||
#include <coreinit/ios.h>
|
#include <coreinit/ios.h>
|
||||||
|
#include <coreinit/savedframe.h>
|
||||||
#include <coreinit/screen.h>
|
#include <coreinit/screen.h>
|
||||||
#include <coreinit/title.h>
|
#include <coreinit/title.h>
|
||||||
#include <elfio/elfio.hpp>
|
#include <elfio/elfio.hpp>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <gx2/display.h>
|
||||||
#include <gx2/state.h>
|
#include <gx2/state.h>
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <nn/act/client_cpp.h>
|
#include <nn/act/client_cpp.h>
|
||||||
|
#include <nsysccr/cdc.h>
|
||||||
#include <proc_ui/procui.h>
|
#include <proc_ui/procui.h>
|
||||||
#include <sysapp/launch.h>
|
#include <sysapp/launch.h>
|
||||||
#include <sysapp/title.h>
|
#include <sysapp/title.h>
|
||||||
@ -30,14 +33,18 @@
|
|||||||
#include "kernel.h"
|
#include "kernel.h"
|
||||||
#include "module/ModuleDataFactory.h"
|
#include "module/ModuleDataFactory.h"
|
||||||
#include "utils/DrawUtils.h"
|
#include "utils/DrawUtils.h"
|
||||||
|
#include "utils/FileUtils.h"
|
||||||
|
#include "utils/InputUtils.h"
|
||||||
|
#include "utils/OnLeavingScope.h"
|
||||||
|
#include "utils/PairUtils.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
#include "utils/wiiu_zlib.hpp"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
// clang-format off
|
#define ENVIRONMENT_LOADER_VERSION "v0.3.2"
|
||||||
#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"
|
#define MEMORY_REGION_START 0x00800000
|
||||||
// clang-format on
|
#define AUTOBOOT_CONFIG_PATH "fs:/vol/external01/wiiu/environments/default.cfg"
|
||||||
|
|
||||||
bool CheckRunning() {
|
bool CheckRunning() {
|
||||||
switch (ProcUIProcessMessages(true)) {
|
switch (ProcUIProcessMessages(true)) {
|
||||||
@ -90,6 +97,8 @@ bool writeFileContent(const std::string &path, const std::string &content) {
|
|||||||
|
|
||||||
extern "C" void __fini();
|
extern "C" void __fini();
|
||||||
extern "C" void __init_wut_malloc();
|
extern "C" void __init_wut_malloc();
|
||||||
|
void LoadAndRunModule(std::string_view filepath, std::string_view environment_path);
|
||||||
|
void ClearSavedFrameBuffers();
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
||||||
// We need to call __init_wut_malloc somewhere so wut_malloc will be used for the memory allocation.
|
// We need to call __init_wut_malloc somewhere so wut_malloc will be used for the memory allocation.
|
||||||
@ -108,28 +117,22 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
DEBUG_FUNCTION_LINE("Hello from EnvironmentLoader!");
|
DEBUG_FUNCTION_LINE("Hello from EnvironmentLoader!");
|
||||||
|
|
||||||
char environmentPath[0x100];
|
char environmentPathFromIOSU[0x100] = {};
|
||||||
memset(environmentPath, 0, sizeof(environmentPath));
|
auto handle = IOS_Open("/dev/mcp", IOS_OPEN_READ);
|
||||||
|
|
||||||
auto handle = IOS_Open("/dev/mcp", IOS_OPEN_READ);
|
|
||||||
if (handle >= 0) {
|
if (handle >= 0) {
|
||||||
int in = 0xF9; // IPC_CUSTOM_COPY_ENVIRONMENT_PATH
|
int in = 0xF9; // IPC_CUSTOM_COPY_ENVIRONMENT_PATH
|
||||||
if (IOS_Ioctl(handle, 100, &in, sizeof(in), environmentPath, sizeof(environmentPath)) == IOS_ERROR_OK) {
|
if (IOS_Ioctl(handle, 100, &in, sizeof(in), environmentPathFromIOSU, sizeof(environmentPathFromIOSU)) == IOS_ERROR_OK) {
|
||||||
DEBUG_FUNCTION_LINE("Boot into %s", environmentPath);
|
DEBUG_FUNCTION_LINE("Boot into %s", environmentPathFromIOSU);
|
||||||
}
|
}
|
||||||
|
|
||||||
IOS_Close(handle);
|
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;
|
bool noEnvironmentsFound = false;
|
||||||
|
bool shownMenu = false;
|
||||||
|
|
||||||
std::string environment_path = std::string(environmentPath);
|
std::string environmentPath = std::string(environmentPathFromIOSU);
|
||||||
if (strncmp(environmentPath, "fs:/vol/external01/wiiu/environments/", strlen("fs:/vol/external01/wiiu/environments/")) != 0) {
|
if (!environmentPath.starts_with("fs:/vol/external01/wiiu/environments/")) { // If the environment path in IOSU is empty or unexpected, read config
|
||||||
DirList environmentDirs("fs:/vol/external01/wiiu/environments/", nullptr, DirList::Dirs, 1);
|
DirList environmentDirs("fs:/vol/external01/wiiu/environments/", nullptr, DirList::Dirs, 1);
|
||||||
|
|
||||||
std::map<std::string, std::string> environmentPaths;
|
std::map<std::string, std::string> environmentPaths;
|
||||||
@ -146,9 +149,9 @@ int main(int argc, char **argv) {
|
|||||||
for (auto const &[key, val] : environmentPaths) {
|
for (auto const &[key, val] : environmentPaths) {
|
||||||
if (res.value() == key) {
|
if (res.value() == key) {
|
||||||
DEBUG_FUNCTION_LINE("Found environment %s from config at index %d", res.value().c_str(), i);
|
DEBUG_FUNCTION_LINE("Found environment %s from config at index %d", res.value().c_str(), i);
|
||||||
autobootIndex = i;
|
autobootIndex = i;
|
||||||
environment_path = val;
|
environmentPath = val;
|
||||||
forceMenu = false;
|
forceMenu = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
@ -157,29 +160,41 @@ int main(int argc, char **argv) {
|
|||||||
DEBUG_FUNCTION_LINE_ERR("No config found");
|
DEBUG_FUNCTION_LINE_ERR("No config found");
|
||||||
}
|
}
|
||||||
|
|
||||||
VPADReadError err;
|
InputUtils::Init();
|
||||||
VPADStatus vpad_data;
|
|
||||||
VPADRead(VPAD_CHAN_0, &vpad_data, 1, &err);
|
|
||||||
|
|
||||||
uint32_t btn = 0;
|
InputUtils::InputData input = InputUtils::getControllerInput();
|
||||||
if (err == VPAD_READ_SUCCESS) {
|
|
||||||
btn = vpad_data.hold | vpad_data.trigger;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceMenu || (btn & VPAD_BUTTON_X) == VPAD_BUTTON_X) {
|
if (forceMenu || ((input.trigger | input.hold) & VPAD_BUTTON_X) == VPAD_BUTTON_X) {
|
||||||
|
shownMenu = true;
|
||||||
DEBUG_FUNCTION_LINE_VERBOSE("Open menu!");
|
DEBUG_FUNCTION_LINE_VERBOSE("Open menu!");
|
||||||
environment_path = EnvironmentSelectionScreen(environmentPaths, autobootIndex);
|
environmentPath = EnvironmentSelectionScreen(environmentPaths, autobootIndex);
|
||||||
if (environmentPaths.empty()) {
|
if (environmentPaths.empty()) {
|
||||||
noEnvironmentsFound = true;
|
noEnvironmentsFound = true;
|
||||||
} else {
|
} else {
|
||||||
DEBUG_FUNCTION_LINE_VERBOSE("Selected %s", environment_path.c_str());
|
DEBUG_FUNCTION_LINE_VERBOSE("Selected %s", environmentPath.c_str());
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
}
|
}
|
||||||
|
InputUtils::DeInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!shownMenu) {
|
||||||
|
// Clear saved frame buffer to reduce screen corruption
|
||||||
|
ClearSavedFrameBuffers();
|
||||||
|
|
||||||
|
OSScreenInit();
|
||||||
|
|
||||||
|
// Call GX2Init to shut down OSScreen
|
||||||
|
GX2Init(nullptr);
|
||||||
|
|
||||||
|
GX2SetTVEnable(FALSE);
|
||||||
|
GX2SetDRCEnable(FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
RevertMainHook();
|
RevertMainHook();
|
||||||
|
|
||||||
if (!noEnvironmentsFound) {
|
if (!noEnvironmentsFound) {
|
||||||
DirList setupModules(environment_path + "/modules/setup", ".rpx", DirList::Files, 1);
|
DirList setupModules(environmentPath + "/modules/setup", ".rpx", DirList::Files, 1);
|
||||||
setupModules.SortList();
|
setupModules.SortList();
|
||||||
|
|
||||||
for (int i = 0; i < setupModules.GetFilecount(); i++) {
|
for (int i = 0; i < setupModules.GetFilecount(); i++) {
|
||||||
@ -189,43 +204,9 @@ int main(int argc, char **argv) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some module may unmount the sd card on exit.
|
LoadAndRunModule(setupModules.GetFilepath(i), environmentPath);
|
||||||
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)) {
|
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
DEBUG_FUNCTION_LINE("Return to Wii U Menu");
|
DEBUG_FUNCTION_LINE("Return to Wii U Menu");
|
||||||
ProcUIInit(OSSavesDone_ReadyToRelease);
|
ProcUIInit(OSSavesDone_ReadyToRelease);
|
||||||
@ -258,17 +239,239 @@ int main(int argc, char **argv) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define COLOR_WHITE Color(0xffffffff)
|
std::optional<HeapWrapper> GetHeapFromMappedMemory(uint32_t heapSize) {
|
||||||
#define COLOR_BLACK Color(0, 0, 0, 255)
|
void *(*MEMAllocFromDefaultHeapExForThreads)(uint32_t size, int align) = nullptr;
|
||||||
#define COLOR_RED Color(237, 28, 36, 255)
|
void (*MEMFreeToDefaultHeapForThreads)(void *ptr) = nullptr;
|
||||||
#define COLOR_BACKGROUND Color(0, 40, 100, 255)
|
|
||||||
#define COLOR_TEXT COLOR_WHITE
|
// Let's try to get the memalign and free functions from the memorymapping module.
|
||||||
#define COLOR_TEXT2 Color(0xB3ffffff)
|
OSDynLoad_Module module;
|
||||||
#define COLOR_AUTOBOOT Color(0xaeea00ff)
|
if (OSDynLoad_Acquire("homebrew_memorymapping", &module) != OS_DYNLOAD_OK) {
|
||||||
#define COLOR_BORDER Color(204, 204, 204, 255)
|
DEBUG_FUNCTION_LINE("Failed to acquire homebrew_memorymapping.");
|
||||||
#define COLOR_BORDER_HIGHLIGHTED Color(0x3478e4ff)
|
return {};
|
||||||
|
}
|
||||||
|
/* Memory allocation functions */
|
||||||
|
uint32_t *allocPtr = nullptr, *freePtr = nullptr;
|
||||||
|
if (OSDynLoad_FindExport(module, OS_DYNLOAD_EXPORT_DATA, "MEMAllocFromMappedMemoryEx", reinterpret_cast<void **>(&allocPtr)) != OS_DYNLOAD_OK) {
|
||||||
|
DEBUG_FUNCTION_LINE("OSDynLoad_FindExport for MEMAllocFromDefaultHeapEx failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (OSDynLoad_FindExport(module, OS_DYNLOAD_EXPORT_DATA, "MEMFreeToMappedMemory", reinterpret_cast<void **>(&freePtr)) != OS_DYNLOAD_OK) {
|
||||||
|
DEBUG_FUNCTION_LINE("OSDynLoad_FindExport for MEMFreeToDefaultHeap failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
MEMAllocFromDefaultHeapExForThreads = (void *(*) (uint32_t, int) ) * allocPtr;
|
||||||
|
MEMFreeToDefaultHeapForThreads = (void (*)(void *)) * freePtr;
|
||||||
|
|
||||||
|
if (!MEMAllocFromDefaultHeapExForThreads || !MEMFreeToDefaultHeapForThreads) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("MEMAllocFromDefaultHeapExForThreads or MEMFreeToDefaultHeapForThreads is null");
|
||||||
|
// the mapped memory is not available (yet)
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t size = heapSize;
|
||||||
|
auto ptr = MEMAllocFromDefaultHeapExForThreads(size, 0x4);
|
||||||
|
if (!ptr) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to alloc memory: %d bytes", size);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_FUNCTION_LINE("Let's create a memory wrapper for 0x%08X, size: %d", ptr, size);
|
||||||
|
return HeapWrapper(MemoryWrapper(ptr, size, MEMFreeToDefaultHeapForThreads));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<HeapWrapper> GetHeapForModule(uint32_t heapSize) {
|
||||||
|
// If Aroma is already loaded, we can't use the region between MEMORY_REGION_START and MEMORY_REGION_END anymore because Aroma is using.
|
||||||
|
// So instead we check before loading a module if aromas memory mapping module is already usable. If yes, we use this to load the module instead
|
||||||
|
if (auto heapWrapper = GetHeapFromMappedMemory(heapSize)) {
|
||||||
|
return heapWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If Aroma is not already loaded, we use the existing 0x00800000 - 0x01000000 memory region. This is where aroma is loaded to. Note: this region may be only mapped to the main core.
|
||||||
|
// The environment loader is loaded to the end of 0x00800000 - 0x01000000 memory region. With this helper we know the start of the .text section
|
||||||
|
uint32_t textSectionStart = textStart() - 0x100;
|
||||||
|
|
||||||
|
auto endOfUsableMemory = textSectionStart;
|
||||||
|
uint32_t startAddress = ((uint32_t) endOfUsableMemory - heapSize) & 0xFFFF0000;
|
||||||
|
uint32_t size = endOfUsableMemory - startAddress;
|
||||||
|
|
||||||
|
if (startAddress < MEMORY_REGION_START) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Not enough static memory");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_FUNCTION_LINE("Let's create a memory wrapper for 0x%08X, size: %d", startAddress, size);
|
||||||
|
auto res = HeapWrapper(MemoryWrapper((void *) startAddress, size, /* we don't need to free this memory*/ nullptr));
|
||||||
|
if ((uint32_t) res.GetHeapHandle() != startAddress) {
|
||||||
|
OSFatal("EnvironmentLoader: Unexpected address");
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetupKernelModule() {
|
||||||
|
void *(*KernelSetupDefaultSyscalls)() = nullptr;
|
||||||
|
|
||||||
|
OSDynLoad_Module module;
|
||||||
|
if (OSDynLoad_Acquire("homebrew_kernel", &module) != OS_DYNLOAD_OK) {
|
||||||
|
DEBUG_FUNCTION_LINE("Failed to acquire homebrew_kernel.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OSDynLoad_FindExport(module, OS_DYNLOAD_EXPORT_FUNC, "KernelSetupDefaultSyscalls", reinterpret_cast<void **>(&KernelSetupDefaultSyscalls)) != OS_DYNLOAD_OK) {
|
||||||
|
DEBUG_FUNCTION_LINE("OSDynLoad_FindExport for KernelSetupDefaultSyscalls failed");
|
||||||
|
OSFatal("EnvironmentLoader: KernelModule is missing the export\n"
|
||||||
|
"\"KernelSetupDefaultSyscalls\"... Please update Aroma!\n"
|
||||||
|
"\n"
|
||||||
|
"See https://wiiu.hacks.guide/ for more information.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!KernelSetupDefaultSyscalls) {
|
||||||
|
DEBUG_FUNCTION_LINE_WARN("KernelSetupDefaultSyscalls is null");
|
||||||
|
OSFatal("EnvironmentLoader: KernelModule is missing the export\n"
|
||||||
|
"\"KernelSetupDefaultSyscalls\"... Please update Aroma!\n"
|
||||||
|
"\n"
|
||||||
|
"See https://wiiu.hacks.guide/ for more information.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_FUNCTION_LINE("Call KernelSetupDefaultSyscalls");
|
||||||
|
KernelSetupDefaultSyscalls();
|
||||||
|
OSDynLoad_Release(module);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadAndRunModule(std::string_view filepath, std::string_view environment_path) {
|
||||||
|
// 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);
|
||||||
|
} else {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to add FSA client");
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_FUNCTION_LINE("Trying to load %s into memory", filepath.data());
|
||||||
|
uint8_t *buffer = nullptr;
|
||||||
|
uint32_t fsize = 0;
|
||||||
|
if (LoadFileToMem(filepath.data(), &buffer, &fsize) < 0) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to load file");
|
||||||
|
OSFatal("EnvironmentLoader: Failed to load file to memory");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cleanupBuffer = onLeavingScope([buffer]() { free(buffer); });
|
||||||
|
|
||||||
|
ELFIO::elfio reader(new wiiu_zlib);
|
||||||
|
// Load ELF data
|
||||||
|
if (!reader.load(reinterpret_cast<const char *>(buffer), fsize)) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Can't parse .wms from buffer.");
|
||||||
|
OSFatal("Can't parse .wms from buffer.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t moduleSize = ModuleDataFactory::GetSizeOfModule(reader);
|
||||||
|
DEBUG_FUNCTION_LINE_VERBOSE("Module has size: %d", moduleSize);
|
||||||
|
|
||||||
|
uint32_t requiredHeapSize = moduleSize + sizeof(module_information_t) + 0x10000; // add another 0x10000 to be safe
|
||||||
|
DEBUG_FUNCTION_LINE_VERBOSE("Allocate %d bytes for heap (%.2f KiB)", requiredHeapSize, requiredHeapSize / 1024.0f);
|
||||||
|
|
||||||
|
if (auto heapWrapperOpt = GetHeapForModule(requiredHeapSize); heapWrapperOpt.has_value()) {
|
||||||
|
// Frees automatically, must not survive the heapWrapper.
|
||||||
|
auto moduleInfoOpt = heapWrapperOpt->Alloc(sizeof(module_information_t), 0x4);
|
||||||
|
if (!moduleInfoOpt) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to alloc module information");
|
||||||
|
OSFatal("EnvironmentLoader: Failed to alloc module information");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto moduleInfo = std::move(*moduleInfoOpt);
|
||||||
|
auto moduleInfoPtr = (module_information_t *) moduleInfo.data();
|
||||||
|
*moduleInfoPtr = {};
|
||||||
|
|
||||||
|
// Frees automatically, must not survive the heapWrapper.
|
||||||
|
auto moduleData = ModuleDataFactory::load(reader, *heapWrapperOpt, moduleInfoPtr->trampolines, sizeof(moduleInfoPtr->trampolines) / sizeof(moduleInfoPtr->trampolines[0]));
|
||||||
|
if (!moduleData) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to load %s", filepath);
|
||||||
|
OSFatal("EnvironmentLoader: Failed to load module");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_FUNCTION_LINE("Loaded module data");
|
||||||
|
std::map<std::string, OSDynLoad_Module> usedRPls;
|
||||||
|
if (!ElfUtils::doRelocation(moduleData.value()->getRelocationDataList(), moduleInfoPtr->trampolines, sizeof(moduleInfoPtr->trampolines) / sizeof(moduleInfoPtr->trampolines[0]), usedRPls)) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Relocations failed");
|
||||||
|
OSFatal("EnvironmentLoader: Relocations failed");
|
||||||
|
} else {
|
||||||
|
DEBUG_FUNCTION_LINE("Relocation done");
|
||||||
|
}
|
||||||
|
|
||||||
|
char *arr[4];
|
||||||
|
arr[0] = (char *) environment_path.data();
|
||||||
|
arr[1] = (char *) "EnvironmentLoader"; //
|
||||||
|
arr[2] = (char *) 0x02; // Version
|
||||||
|
/*
|
||||||
|
* This is a hacky work around to tell Aromas Module Loader which memory region it can use safely. After using it, it's expected to expose new memory region via the
|
||||||
|
* custom rpl "homebrew_mappedmemory" (See: GetHeapFromMappedMemory). The returned memory is expected to be RWX for user and kernel.
|
||||||
|
* Once a custom memory allocator is provided, usable_mem_start and usable_mem_end are set to 0.
|
||||||
|
*/
|
||||||
|
auto usable_mem_end = (uint32_t) heapWrapperOpt->GetHeapHandle();
|
||||||
|
if (heapWrapperOpt->IsAllocated()) { // Check if you use memory which is actually allocated. This means we can't give it to the module.
|
||||||
|
DEBUG_FUNCTION_LINE("Don't give the module a usable memory region because it will be loaded on a custom memory region.");
|
||||||
|
usable_mem_end = 0;
|
||||||
|
}
|
||||||
|
arr[3] = (char *) usable_mem_end; // End of usable memory
|
||||||
|
|
||||||
|
DEBUG_FUNCTION_LINE("Calling entrypoint @%08X with: \"%s\", \"%s\", %08X, %08X", moduleData.value()->getEntrypoint(), arr[0], arr[1], arr[2], arr[3]);
|
||||||
|
// clang-format off
|
||||||
|
((int(*)(int, char **)) moduleData.value()->getEntrypoint())(sizeof(arr)/ sizeof(arr[0]), 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to create heap");
|
||||||
|
OSFatal("EnvironmentLoader: Failed to create heap");
|
||||||
|
}
|
||||||
|
|
||||||
|
// module may override the syscalls used by the Aroma KernelModule. This (tries to) re-init(s) the KernelModule after a setup module has been run.
|
||||||
|
SetupKernelModule();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClearSavedFrameBuffers() {
|
||||||
|
// If GX2 is running make sure to shut it down and free all existing memory in the saved-frame area.
|
||||||
|
if (GX2GetMainCoreId() != -1) {
|
||||||
|
GX2SetTVEnable(FALSE);
|
||||||
|
GX2SetDRCEnable(FALSE);
|
||||||
|
GX2Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
__OSClearSavedFrame(OS_SAVED_FRAME_A, OS_SAVED_FRAME_SCREEN_TV);
|
||||||
|
__OSClearSavedFrame(OS_SAVED_FRAME_A, OS_SAVED_FRAME_SCREEN_DRC);
|
||||||
|
__OSClearSavedFrame(OS_SAVED_FRAME_B, OS_SAVED_FRAME_SCREEN_TV);
|
||||||
|
__OSClearSavedFrame(OS_SAVED_FRAME_B, OS_SAVED_FRAME_SCREEN_DRC);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AbortQuickStartMenu() {
|
||||||
|
CCRCDCDrcState state = {};
|
||||||
|
CCRCDCSysGetDrcState(CCR_CDC_DESTINATION_DRC0, &state);
|
||||||
|
if (state.state == CCR_CDC_DRC_STATE_SUBACTIVE) {
|
||||||
|
state.state = CCR_CDC_DRC_STATE_ACTIVE;
|
||||||
|
CCRCDCSysSetDrcState(CCR_CDC_DESTINATION_DRC0, &state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::string EnvironmentSelectionScreen(const std::map<std::string, std::string> &payloads, int32_t autobootIndex) {
|
std::string EnvironmentSelectionScreen(const std::map<std::string, std::string> &payloads, int32_t autobootIndex) {
|
||||||
|
// Close quick start menu is selection screen is displayed
|
||||||
|
AbortQuickStartMenu();
|
||||||
|
|
||||||
|
// Clear saved frame buffer to reduce screen corruption
|
||||||
|
ClearSavedFrameBuffers();
|
||||||
|
|
||||||
OSScreenInit();
|
OSScreenInit();
|
||||||
|
|
||||||
uint32_t tvBufferSize = OSScreenGetBufferSizeEx(SCREEN_TV);
|
uint32_t tvBufferSize = OSScreenGetBufferSizeEx(SCREEN_TV);
|
||||||
@ -276,7 +479,7 @@ std::string EnvironmentSelectionScreen(const std::map<std::string, std::string>
|
|||||||
|
|
||||||
auto *screenBuffer = (uint8_t *) memalign(0x100, tvBufferSize + drcBufferSize);
|
auto *screenBuffer = (uint8_t *) memalign(0x100, tvBufferSize + drcBufferSize);
|
||||||
if (!screenBuffer) {
|
if (!screenBuffer) {
|
||||||
OSFatal("Fail to allocate screenBuffer");
|
OSFatal("EnvironmentLoader: Fail to allocate screenBuffer");
|
||||||
}
|
}
|
||||||
memset(screenBuffer, 0, tvBufferSize + drcBufferSize);
|
memset(screenBuffer, 0, tvBufferSize + drcBufferSize);
|
||||||
|
|
||||||
@ -289,38 +492,38 @@ std::string EnvironmentSelectionScreen(const std::map<std::string, std::string>
|
|||||||
DrawUtils::initBuffers(screenBuffer, tvBufferSize, screenBuffer + tvBufferSize, drcBufferSize);
|
DrawUtils::initBuffers(screenBuffer, tvBufferSize, screenBuffer + tvBufferSize, drcBufferSize);
|
||||||
|
|
||||||
if (!DrawUtils::initFont()) {
|
if (!DrawUtils::initFont()) {
|
||||||
OSFatal("Failed to init font");
|
OSFatal("EnvironmentLoader: Failed to init font");
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t selected = autobootIndex > 0 ? autobootIndex : 0;
|
uint32_t selected = autobootIndex > 0 ? autobootIndex : 0;
|
||||||
int autoBoot = autobootIndex;
|
int autoBoot = autobootIndex;
|
||||||
|
|
||||||
bool redraw = true;
|
{
|
||||||
while (true) {
|
PairMenu pairMenu;
|
||||||
VPADStatus vpad{};
|
while (true) {
|
||||||
VPADRead(VPAD_CHAN_0, &vpad, 1, nullptr);
|
if (pairMenu.ProcessPairScreen()) {
|
||||||
|
continue;
|
||||||
if (vpad.trigger & VPAD_BUTTON_UP) {
|
|
||||||
if (selected > 0) {
|
|
||||||
selected--;
|
|
||||||
redraw = true;
|
|
||||||
}
|
}
|
||||||
} else if (vpad.trigger & VPAD_BUTTON_DOWN) {
|
|
||||||
if (selected < payloads.size() - 1) {
|
|
||||||
selected++;
|
|
||||||
redraw = true;
|
|
||||||
}
|
|
||||||
} else if (vpad.trigger & VPAD_BUTTON_A) {
|
|
||||||
break;
|
|
||||||
} else if (vpad.trigger & VPAD_BUTTON_X) {
|
|
||||||
autoBoot = -1;
|
|
||||||
redraw = true;
|
|
||||||
} else if (vpad.trigger & VPAD_BUTTON_Y) {
|
|
||||||
autoBoot = selected;
|
|
||||||
redraw = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (redraw) {
|
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::beginDraw();
|
||||||
DrawUtils::clear(COLOR_BACKGROUND);
|
DrawUtils::clear(COLOR_BACKGROUND);
|
||||||
|
|
||||||
@ -354,6 +557,8 @@ std::string EnvironmentSelectionScreen(const std::map<std::string, std::string>
|
|||||||
DrawUtils::setFontSize(24);
|
DrawUtils::setFontSize(24);
|
||||||
DrawUtils::print(16, 6 + 24, "Environment Loader");
|
DrawUtils::print(16, 6 + 24, "Environment Loader");
|
||||||
DrawUtils::drawRectFilled(8, 8 + 24 + 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE);
|
DrawUtils::drawRectFilled(8, 8 + 24 + 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE);
|
||||||
|
DrawUtils::setFontSize(16);
|
||||||
|
DrawUtils::print(SCREEN_WIDTH - 16, 6 + 24, ENVIRONMENT_LOADER_VERSION ENVIRONMENT_LOADER_VERSION_EXTRA, true);
|
||||||
|
|
||||||
// draw bottom bar
|
// draw bottom bar
|
||||||
DrawUtils::drawRectFilled(8, SCREEN_HEIGHT - 24 - 8 - 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE);
|
DrawUtils::drawRectFilled(8, SCREEN_HEIGHT - 24 - 8 - 4, SCREEN_WIDTH - 8 * 2, 3, COLOR_WHITE);
|
||||||
@ -361,15 +566,13 @@ std::string EnvironmentSelectionScreen(const std::map<std::string, std::string>
|
|||||||
if (!payloads.empty()) {
|
if (!payloads.empty()) {
|
||||||
DrawUtils::print(16, SCREEN_HEIGHT - 8, "\ue07d Navigate ");
|
DrawUtils::print(16, SCREEN_HEIGHT - 8, "\ue07d Navigate ");
|
||||||
DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 8, "\ue000 Choose", true);
|
DrawUtils::print(SCREEN_WIDTH - 16, SCREEN_HEIGHT - 8, "\ue000 Choose", true);
|
||||||
const char *autobootHints = "\ue002 Clear Default / \ue003 Select Default";
|
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);
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(autobootHints) / 2, SCREEN_HEIGHT - 8, autobootHints, true);
|
||||||
} else {
|
} else {
|
||||||
DrawUtils::print(SCREEN_WIDTH - 20, SCREEN_HEIGHT - 8, "\ue000 Wii U Menu", true);
|
DrawUtils::print(SCREEN_WIDTH - 20, SCREEN_HEIGHT - 8, "\ue000 Wii U Menu", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawUtils::endDraw();
|
DrawUtils::endDraw();
|
||||||
|
|
||||||
redraw = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,6 +585,9 @@ std::string EnvironmentSelectionScreen(const std::map<std::string, std::string>
|
|||||||
// Call GX2Init to shut down OSScreen
|
// Call GX2Init to shut down OSScreen
|
||||||
GX2Init(nullptr);
|
GX2Init(nullptr);
|
||||||
|
|
||||||
|
GX2SetTVEnable(FALSE);
|
||||||
|
GX2SetDRCEnable(FALSE);
|
||||||
|
|
||||||
free(screenBuffer);
|
free(screenBuffer);
|
||||||
|
|
||||||
if (autoBoot != autobootIndex) {
|
if (autoBoot != autobootIndex) {
|
||||||
|
@ -39,7 +39,7 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] const char *getRPLName() const {
|
[[nodiscard]] const char *getRPLName() const {
|
||||||
if (name.max_size() < strlen("._import_") + 1) {
|
if (name.max_size() < strlen("._import_") + 1) {
|
||||||
OSFatal("Invalid RPLName, is too short to be valid");
|
OSFatal("EnvironmentLoader: Invalid RPLName, is too short to be valid");
|
||||||
}
|
}
|
||||||
return name.c_str() + strlen("._import_");
|
return name.c_str() + strlen("._import_");
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "RelocationData.h"
|
#include "RelocationData.h"
|
||||||
|
#include "utils/MemoryUtils.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -33,11 +34,11 @@ public:
|
|||||||
this->entrypoint = address;
|
this->entrypoint = address;
|
||||||
}
|
}
|
||||||
|
|
||||||
void addRelocationData(std::unique_ptr<RelocationData> relocation_data) {
|
void addRelocationData(RelocationData relocation_data) {
|
||||||
relocation_data_list.push_back(std::move(relocation_data));
|
relocation_data_list.push_back(std::move(relocation_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] const std::vector<std::unique_ptr<RelocationData>> &getRelocationDataList() const {
|
[[nodiscard]] const std::vector<RelocationData> &getRelocationDataList() const {
|
||||||
return relocation_data_list;
|
return relocation_data_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,25 +46,16 @@ public:
|
|||||||
return entrypoint;
|
return entrypoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStartAddress(uint32_t address) {
|
void setTextMemory(ExpHeapMemory &&memory) {
|
||||||
this->startAddress = address;
|
mTextMemory = std::move(memory);
|
||||||
}
|
}
|
||||||
|
void setDataMemory(ExpHeapMemory &&memory) {
|
||||||
void setEndAddress(uint32_t address) {
|
mDataMemory = std::move(memory);
|
||||||
this->endAddress = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] uint32_t getStartAddress() const {
|
|
||||||
return startAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] uint32_t getEndAddress() const {
|
|
||||||
return endAddress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::unique_ptr<RelocationData>> relocation_data_list;
|
std::vector<RelocationData> relocation_data_list;
|
||||||
uint32_t entrypoint = 0;
|
uint32_t entrypoint = 0;
|
||||||
uint32_t startAddress = 0;
|
ExpHeapMemory mTextMemory;
|
||||||
uint32_t endAddress = 0;
|
ExpHeapMemory mDataMemory;
|
||||||
};
|
};
|
||||||
|
@ -24,33 +24,32 @@
|
|||||||
#include <coreinit/cache.h>
|
#include <coreinit/cache.h>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
|
uint32_t ModuleDataFactory::GetSizeOfModule(const ELFIO::elfio &reader) {
|
||||||
|
uint32_t sec_num = reader.sections.size();
|
||||||
|
uint32_t sizeOfModule = 0;
|
||||||
|
for (uint32_t i = 0; i < sec_num; ++i) {
|
||||||
|
ELFIO::section *psec = reader.sections[i];
|
||||||
|
if (psec->get_type() == 0x80000002 || psec->get_name() == ".wut_load_bounds") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((psec->get_type() == ELFIO::SHT_PROGBITS || psec->get_type() == ELFIO::SHT_NOBITS) && (psec->get_flags() & ELFIO::SHF_ALLOC)) {
|
||||||
|
sizeOfModule += psec->get_size() + psec->get_addr_align();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sizeOfModule;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<std::unique_ptr<ModuleData>>
|
std::optional<std::unique_ptr<ModuleData>>
|
||||||
ModuleDataFactory::load(const std::string &path, uint32_t destination_address_end, uint32_t maximum_size, relocation_trampoline_entry_t *trampoline_data, uint32_t trampoline_data_length) {
|
ModuleDataFactory::load(const ELFIO::elfio &reader, const HeapWrapper &heapWrapper, relocation_trampoline_entry_t *trampoline_data, uint32_t trampoline_data_length) {
|
||||||
ELFIO::elfio reader(new wiiu_zlib);
|
|
||||||
auto moduleData = make_unique_nothrow<ModuleData>();
|
auto moduleData = make_unique_nothrow<ModuleData>();
|
||||||
if (!moduleData) {
|
if (!moduleData) {
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to allocate ModuleData");
|
DEBUG_FUNCTION_LINE_ERR("Failed to allocate ModuleData");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t *buffer = nullptr;
|
|
||||||
uint32_t fsize = 0;
|
|
||||||
if (LoadFileToMem(path.c_str(), &buffer, &fsize) < 0) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Failed to load file");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load ELF data
|
|
||||||
if (!reader.load(reinterpret_cast<char *>(buffer), fsize)) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Can't find or process %s", path.c_str());
|
|
||||||
free(buffer);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cleanupBuffer = onLeavingScope([buffer]() { free(buffer); });
|
|
||||||
|
|
||||||
uint32_t sec_num = reader.sections.size();
|
uint32_t sec_num = reader.sections.size();
|
||||||
|
|
||||||
auto destinations = make_unique_nothrow<uint8_t *[]>(sec_num);
|
auto destinations = make_unique_nothrow<uint8_t *[]>(sec_num);
|
||||||
@ -59,7 +58,9 @@ ModuleDataFactory::load(const std::string &path, uint32_t destination_address_en
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t sizeOfModule = 0;
|
uint32_t text_size = 0;
|
||||||
|
uint32_t data_size = 0;
|
||||||
|
|
||||||
for (uint32_t i = 0; i < sec_num; ++i) {
|
for (uint32_t i = 0; i < sec_num; ++i) {
|
||||||
ELFIO::section *psec = reader.sections[i];
|
ELFIO::section *psec = reader.sections[i];
|
||||||
if (psec->get_type() == 0x80000002) {
|
if (psec->get_type() == 0x80000002) {
|
||||||
@ -67,25 +68,31 @@ ModuleDataFactory::load(const std::string &path, uint32_t destination_address_en
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((psec->get_type() == ELFIO::SHT_PROGBITS || psec->get_type() == ELFIO::SHT_NOBITS) && (psec->get_flags() & ELFIO::SHF_ALLOC)) {
|
if ((psec->get_type() == ELFIO::SHT_PROGBITS || psec->get_type() == ELFIO::SHT_NOBITS) && (psec->get_flags() & ELFIO::SHF_ALLOC)) {
|
||||||
sizeOfModule += psec->get_size() + 1;
|
uint32_t sectionSize = psec->get_size();
|
||||||
|
auto address = (uint32_t) psec->get_address();
|
||||||
|
if ((address >= 0x02000000) && address < 0x10000000) {
|
||||||
|
text_size += sectionSize + psec->get_addr_align();
|
||||||
|
} else if ((address >= 0x10000000) && address < 0xC0000000) {
|
||||||
|
data_size += sectionSize + psec->get_addr_align();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sizeOfModule > maximum_size) {
|
auto text_dataOpt = heapWrapper.Alloc(text_size, 0x100);
|
||||||
DEBUG_FUNCTION_LINE_ERR("Module is too big.");
|
if (!text_dataOpt) {
|
||||||
return {};
|
DEBUG_FUNCTION_LINE_ERR("Failed to alloc memory for the .text section (%d bytes)", text_size);
|
||||||
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
ExpHeapMemory text_data = std::move(*text_dataOpt);
|
||||||
|
|
||||||
uint32_t baseOffset = (destination_address_end - sizeOfModule) & 0xFFFFFF00;
|
auto data_dataOpt = heapWrapper.Alloc(data_size, 0x100);
|
||||||
uint32_t startAddress = baseOffset;
|
if (!data_dataOpt) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to alloc memory for the .data section (%d bytes)", data_size);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
ExpHeapMemory data_data = std::move(*data_dataOpt);
|
||||||
|
|
||||||
uint32_t offset_text = baseOffset;
|
uint32_t entrypoint = (uint32_t) text_data.data() + (uint32_t) reader.get_entry() - 0x02000000;
|
||||||
uint32_t offset_data = offset_text;
|
|
||||||
|
|
||||||
uint32_t entrypoint = offset_text + (uint32_t) reader.get_entry() - 0x02000000;
|
|
||||||
|
|
||||||
uint32_t totalSize = 0;
|
|
||||||
uint32_t endAddress = 0;
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < sec_num; ++i) {
|
for (uint32_t i = 0; i < sec_num; ++i) {
|
||||||
ELFIO::section *psec = reader.sections[i];
|
ELFIO::section *psec = reader.sections[i];
|
||||||
@ -95,26 +102,34 @@ ModuleDataFactory::load(const std::string &path, uint32_t destination_address_en
|
|||||||
|
|
||||||
if ((psec->get_type() == ELFIO::SHT_PROGBITS || psec->get_type() == ELFIO::SHT_NOBITS) && (psec->get_flags() & ELFIO::SHF_ALLOC)) {
|
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();
|
uint32_t sectionSize = psec->get_size();
|
||||||
|
auto address = (uint32_t) psec->get_address();
|
||||||
|
|
||||||
totalSize += sectionSize;
|
uint32_t destination = address;
|
||||||
if (totalSize > maximum_size) {
|
|
||||||
DEBUG_FUNCTION_LINE_ERR("Couldn't load setup module because it's too big.");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto address = (uint32_t) psec->get_address();
|
|
||||||
|
|
||||||
destinations[psec->get_index()] = (uint8_t *) baseOffset;
|
|
||||||
|
|
||||||
uint32_t destination = baseOffset + address;
|
|
||||||
if ((address >= 0x02000000) && address < 0x10000000) {
|
if ((address >= 0x02000000) && address < 0x10000000) {
|
||||||
|
destination += (uint32_t) text_data.data();
|
||||||
destination -= 0x02000000;
|
destination -= 0x02000000;
|
||||||
destinations[psec->get_index()] -= 0x02000000;
|
destinations[psec->get_index()] = (uint8_t *) text_data.data();
|
||||||
baseOffset += sectionSize;
|
|
||||||
offset_data += sectionSize;
|
if (destination + sectionSize > (uint32_t) text_data.data() + text_size) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Tried to overflow .text buffer. %08X > %08X", destination + sectionSize, (uint32_t) text_data.data() + text_data.size());
|
||||||
|
OSFatal("EnvironmentLoader: Tried to overflow .text buffer");
|
||||||
|
} else if (destination < (uint32_t) text_data.data()) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Tried to underflow .text buffer. %08X < %08X", destination, (uint32_t) text_data.data());
|
||||||
|
OSFatal("EnvironmentLoader: Tried to underflow .text buffer");
|
||||||
|
}
|
||||||
} else if ((address >= 0x10000000) && address < 0xC0000000) {
|
} else if ((address >= 0x10000000) && address < 0xC0000000) {
|
||||||
|
destination += (uint32_t) data_data.data();
|
||||||
destination -= 0x10000000;
|
destination -= 0x10000000;
|
||||||
destinations[psec->get_index()] -= 0x10000000;
|
destinations[psec->get_index()] = (uint8_t *) data_data.data();
|
||||||
|
|
||||||
|
if (destination + sectionSize > (uint32_t) data_data.data() + data_data.size()) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Tried to overflow .data buffer. %08X > %08X", destination + sectionSize, (uint32_t) data_data.data() + data_data.size());
|
||||||
|
OSFatal("EnvironmentLoader: Tried to overflow .data buffer");
|
||||||
|
} else if (destination < (uint32_t) data_data.data()) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Tried to underflow .data buffer. %08X < %08X", destination, (uint32_t) data_data.data());
|
||||||
|
OSFatal("EnvironmentLoader: Tried to underflow .data buffer");
|
||||||
|
}
|
||||||
} else if (address >= 0xC0000000) {
|
} else if (address >= 0xC0000000) {
|
||||||
DEBUG_FUNCTION_LINE_ERR("Loading section from 0xC0000000 is NOT supported");
|
DEBUG_FUNCTION_LINE_ERR("Loading section from 0xC0000000 is NOT supported");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
@ -123,30 +138,22 @@ ModuleDataFactory::load(const std::string &path, uint32_t destination_address_en
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *p = reader.sections[i]->get_data();
|
const char *p = psec->get_data();
|
||||||
|
|
||||||
if (destination + sectionSize > (uint32_t) destination_address_end) {
|
uint32_t address_align = psec->get_addr_align();
|
||||||
DEBUG_FUNCTION_LINE_ERR("Tried to overflow buffer. %08X > %08X", destination + sectionSize, destination_address_end);
|
if ((destination & (address_align - 1)) != 0) {
|
||||||
OSFatal("EnvironmentLoader: Tried to overflow buffer");
|
DEBUG_FUNCTION_LINE_WARN("Address not aligned: %08X %08X", destination, address_align);
|
||||||
|
OSFatal("EnvironmentLoader: Address not aligned");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (psec->get_type() == ELFIO::SHT_NOBITS) {
|
if (psec->get_type() == ELFIO::SHT_NOBITS) {
|
||||||
DEBUG_FUNCTION_LINE("memset section %s %08X to 0 (%d bytes)", psec->get_name().c_str(), destination, sectionSize);
|
DEBUG_FUNCTION_LINE_VERBOSE("memset section %s %08X to 0 (%d bytes)", psec->get_name().c_str(), destination, sectionSize);
|
||||||
memset((void *) destination, 0, sectionSize);
|
memset((void *) destination, 0, sectionSize);
|
||||||
} else if (psec->get_type() == ELFIO::SHT_PROGBITS) {
|
} else if (psec->get_type() == ELFIO::SHT_PROGBITS) {
|
||||||
DEBUG_FUNCTION_LINE("Copy section %s %08X -> %08X (%d bytes)", psec->get_name().c_str(), p, destination, sectionSize);
|
DEBUG_FUNCTION_LINE_VERBOSE("Copy section %s %08X -> %08X (%d bytes)", psec->get_name().c_str(), p, destination, sectionSize);
|
||||||
memcpy((void *) destination, p, sectionSize);
|
memcpy((void *) destination, p, sectionSize);
|
||||||
}
|
}
|
||||||
|
DEBUG_FUNCTION_LINE_VERBOSE("Saved %s section info. Location: %08X size: %08X", psec->get_name().c_str(), destination, sectionSize);
|
||||||
//nextAddress = ROUNDUP(destination + sectionSize, 0x100);
|
|
||||||
if (psec->get_name() == ".bss" || psec->get_name() == ".sbss") {
|
|
||||||
DEBUG_FUNCTION_LINE("memset %s section. Location: %08X size: %08X", psec->get_name().c_str(), destination, sectionSize);
|
|
||||||
memset(reinterpret_cast<void *>(destination), 0, sectionSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endAddress < destination + sectionSize) {
|
|
||||||
endAddress = destination + sectionSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
DCFlushRange((void *) destination, sectionSize);
|
DCFlushRange((void *) destination, sectionSize);
|
||||||
ICInvalidateRange((void *) destination, sectionSize);
|
ICInvalidateRange((void *) destination, sectionSize);
|
||||||
@ -157,7 +164,7 @@ ModuleDataFactory::load(const std::string &path, uint32_t destination_address_en
|
|||||||
ELFIO::section *psec = reader.sections[i];
|
ELFIO::section *psec = reader.sections[i];
|
||||||
if ((psec->get_type() == ELFIO::SHT_PROGBITS || psec->get_type() == ELFIO::SHT_NOBITS) && (psec->get_flags() & ELFIO::SHF_ALLOC)) {
|
if ((psec->get_type() == ELFIO::SHT_PROGBITS || psec->get_type() == ELFIO::SHT_NOBITS) && (psec->get_flags() & ELFIO::SHF_ALLOC)) {
|
||||||
DEBUG_FUNCTION_LINE("Linking (%d)... %s", i, psec->get_name().c_str());
|
DEBUG_FUNCTION_LINE("Linking (%d)... %s", i, psec->get_name().c_str());
|
||||||
if (!linkSection(reader, psec->get_index(), (uint32_t) destinations[psec->get_index()], offset_text, offset_data, trampoline_data, trampoline_data_length)) {
|
if (!linkSection(reader, psec->get_index(), (uint32_t) destinations[psec->get_index()], (uint32_t) (text_data.data()), (uint32_t) (data_data.data()), trampoline_data, trampoline_data_length)) {
|
||||||
DEBUG_FUNCTION_LINE_ERR("elfLink failed");
|
DEBUG_FUNCTION_LINE_ERR("elfLink failed");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@ -165,18 +172,19 @@ ModuleDataFactory::load(const std::string &path, uint32_t destination_address_en
|
|||||||
}
|
}
|
||||||
getImportRelocationData(moduleData, reader, destinations.get());
|
getImportRelocationData(moduleData, reader, destinations.get());
|
||||||
|
|
||||||
DCFlushRange((void *) baseOffset, totalSize);
|
DCFlushRange((void *) data_data.data(), data_data.size());
|
||||||
ICInvalidateRange((void *) baseOffset, totalSize);
|
ICInvalidateRange((void *) text_data.data(), text_data.size());
|
||||||
|
|
||||||
moduleData->setStartAddress(startAddress);
|
|
||||||
moduleData->setEndAddress(endAddress);
|
|
||||||
moduleData->setEntrypoint(entrypoint);
|
moduleData->setEntrypoint(entrypoint);
|
||||||
|
moduleData->setTextMemory(std::move(text_data));
|
||||||
|
moduleData->setDataMemory(std::move(data_data));
|
||||||
|
|
||||||
DEBUG_FUNCTION_LINE("Saved entrypoint as %08X", entrypoint);
|
DEBUG_FUNCTION_LINE("Saved entrypoint as %08X", entrypoint);
|
||||||
|
|
||||||
return moduleData;
|
return moduleData;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModuleDataFactory::getImportRelocationData(std::unique_ptr<ModuleData> &moduleData, ELFIO::elfio &reader, uint8_t **destinations) {
|
bool ModuleDataFactory::getImportRelocationData(std::unique_ptr<ModuleData> &moduleData, const ELFIO::elfio &reader, uint8_t **destinations) {
|
||||||
std::map<uint32_t, std::shared_ptr<ImportRPLInformation>> infoMap;
|
std::map<uint32_t, std::shared_ptr<ImportRPLInformation>> infoMap;
|
||||||
|
|
||||||
uint32_t sec_num = reader.sections.size();
|
uint32_t sec_num = reader.sections.size();
|
||||||
@ -196,6 +204,7 @@ bool ModuleDataFactory::getImportRelocationData(std::unique_ptr<ModuleData> &mod
|
|||||||
for (uint32_t i = 0; i < sec_num; ++i) {
|
for (uint32_t i = 0; i < sec_num; ++i) {
|
||||||
ELFIO::section *psec = reader.sections[i];
|
ELFIO::section *psec = reader.sections[i];
|
||||||
if (psec->get_type() == ELFIO::SHT_RELA || psec->get_type() == ELFIO::SHT_REL) {
|
if (psec->get_type() == ELFIO::SHT_RELA || psec->get_type() == ELFIO::SHT_REL) {
|
||||||
|
DEBUG_FUNCTION_LINE_VERBOSE("Found relocation section %s", psec->get_name().c_str());
|
||||||
ELFIO::relocation_section_accessor rel(reader, psec);
|
ELFIO::relocation_section_accessor rel(reader, psec);
|
||||||
for (uint32_t j = 0; j < (uint32_t) rel.get_entries_num(); ++j) {
|
for (uint32_t j = 0; j < (uint32_t) rel.get_entries_num(); ++j) {
|
||||||
ELFIO::Elf_Word symbol = 0;
|
ELFIO::Elf_Word symbol = 0;
|
||||||
@ -232,29 +241,23 @@ bool ModuleDataFactory::getImportRelocationData(std::unique_ptr<ModuleData> &mod
|
|||||||
uint32_t section_index = psec->get_info();
|
uint32_t section_index = psec->get_info();
|
||||||
if (!infoMap.contains(sym_section_index)) {
|
if (!infoMap.contains(sym_section_index)) {
|
||||||
DEBUG_FUNCTION_LINE_ERR("Relocation is referencing a unknown section. %d destination: %08X sym_name %s", section_index, destinations[section_index], sym_name.c_str());
|
DEBUG_FUNCTION_LINE_ERR("Relocation is referencing a unknown section. %d destination: %08X sym_name %s", section_index, destinations[section_index], sym_name.c_str());
|
||||||
OSFatal("Relocation is referencing a unknown section.");
|
OSFatal("EnvironmentLoader: 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
moduleData->addRelocationData(std::move(relocationData));
|
moduleData->addRelocationData(RelocationData(type,
|
||||||
|
offset - 0x02000000,
|
||||||
|
addend,
|
||||||
|
(void *) (destinations[section_index]),
|
||||||
|
sym_name,
|
||||||
|
infoMap[sym_section_index]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ModuleDataFactory::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,
|
bool ModuleDataFactory::linkSection(const 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) {
|
uint32_t trampoline_data_length) {
|
||||||
uint32_t sec_num = reader.sections.size();
|
uint32_t sec_num = reader.sections.size();
|
||||||
|
|
||||||
@ -307,17 +310,27 @@ bool ModuleDataFactory::linkSection(ELFIO::elfio &reader, uint32_t section_index
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto adjusted_offset = (uint32_t) offset;
|
||||||
|
if ((offset >= 0x02000000) && offset < 0x10000000) {
|
||||||
|
adjusted_offset -= 0x02000000;
|
||||||
|
} else if ((adjusted_offset >= 0x10000000) && adjusted_offset < 0xC0000000) {
|
||||||
|
adjusted_offset -= 0x10000000;
|
||||||
|
} else if (adjusted_offset >= 0xC0000000) {
|
||||||
|
adjusted_offset -= 0xC0000000;
|
||||||
|
}
|
||||||
|
|
||||||
if (sym_section_index == ELFIO::SHN_ABS) {
|
if (sym_section_index == ELFIO::SHN_ABS) {
|
||||||
//
|
//
|
||||||
} else if (sym_section_index > ELFIO::SHN_LORESERVE) {
|
} else if (sym_section_index > ELFIO::SHN_LORESERVE) {
|
||||||
DEBUG_FUNCTION_LINE_ERR("NOT IMPLEMENTED: %04X", sym_section_index);
|
DEBUG_FUNCTION_LINE_ERR("NOT IMPLEMENTED: %04X", sym_section_index);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!ElfUtils::elfLinkOne(type, offset, addend, destination, adjusted_sym_value, trampoline_data, trampoline_data_length, RELOC_TYPE_FIXED)) {
|
if (!ElfUtils::elfLinkOne(type, adjusted_offset, addend, destination, adjusted_sym_value, trampoline_data, trampoline_data_length, RELOC_TYPE_FIXED)) {
|
||||||
DEBUG_FUNCTION_LINE_ERR("Link failed");
|
DEBUG_FUNCTION_LINE_ERR("Link failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -20,17 +20,20 @@
|
|||||||
#include "../common/relocation_defines.h"
|
#include "../common/relocation_defines.h"
|
||||||
#include "ModuleData.h"
|
#include "ModuleData.h"
|
||||||
#include "elfio/elfio.hpp"
|
#include "elfio/elfio.hpp"
|
||||||
|
#include "utils/MemoryUtils.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class ModuleDataFactory {
|
class ModuleDataFactory {
|
||||||
public:
|
public:
|
||||||
static std::optional<std::unique_ptr<ModuleData>>
|
static uint32_t GetSizeOfModule(const ELFIO::elfio &reader);
|
||||||
load(const std::string &path, uint32_t destination_address_end, uint32_t maximum_size, relocation_trampoline_entry_t *trampoline_data, uint32_t trampoline_data_length);
|
|
||||||
|
|
||||||
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,
|
static std::optional<std::unique_ptr<ModuleData>> load(const ELFIO::elfio &reader, const HeapWrapper &heapWrapper, relocation_trampoline_entry_t *trampoline_data, uint32_t trampoline_data_length);
|
||||||
|
|
||||||
|
static bool linkSection(const 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);
|
uint32_t trampoline_data_length);
|
||||||
|
|
||||||
static bool getImportRelocationData(std::unique_ptr<ModuleData> &moduleData, ELFIO::elfio &reader, uint8_t **destinations);
|
static bool getImportRelocationData(std::unique_ptr<ModuleData> &moduleData, const ELFIO::elfio &reader, uint8_t **destinations);
|
||||||
};
|
};
|
||||||
|
@ -3,9 +3,19 @@
|
|||||||
#include "schrift.h"
|
#include "schrift.h"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
#define COLOR_WHITE Color(0xffffffff)
|
||||||
|
#define COLOR_BLACK Color(0, 0, 0, 255)
|
||||||
|
#define COLOR_RED Color(237, 28, 36, 255)
|
||||||
|
#define COLOR_BACKGROUND Color(0, 40, 100, 255)
|
||||||
|
#define COLOR_TEXT COLOR_WHITE
|
||||||
|
#define COLOR_TEXT2 Color(0xB3ffffff)
|
||||||
|
#define COLOR_AUTOBOOT Color(0xaeea00ff)
|
||||||
|
#define COLOR_BORDER Color(204, 204, 204, 255)
|
||||||
|
#define COLOR_BORDER_HIGHLIGHTED Color(0x3478e4ff)
|
||||||
|
|
||||||
// visible screen sizes
|
// visible screen sizes
|
||||||
#define SCREEN_WIDTH 854
|
#define SCREEN_WIDTH 854
|
||||||
#define SCREEN_HEIGHT 480
|
#define SCREEN_HEIGHT 480
|
||||||
|
|
||||||
union Color {
|
union Color {
|
||||||
explicit Color(uint32_t color) {
|
explicit Color(uint32_t color) {
|
||||||
|
140
source/utils/InputUtils.cpp
Normal file
140
source/utils/InputUtils.cpp
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
#include "InputUtils.h"
|
||||||
|
#include <coreinit/thread.h>
|
||||||
|
#include <padscore/kpad.h>
|
||||||
|
#include <padscore/wpad.h>
|
||||||
|
#include <vpad/input.h>
|
||||||
|
|
||||||
|
uint32_t remapWiiMoteButtons(uint32_t buttons) {
|
||||||
|
uint32_t convButtons = 0;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_LEFT)
|
||||||
|
convButtons |= VPAD_BUTTON_LEFT;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_RIGHT)
|
||||||
|
convButtons |= VPAD_BUTTON_RIGHT;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_DOWN)
|
||||||
|
convButtons |= VPAD_BUTTON_DOWN;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_UP)
|
||||||
|
convButtons |= VPAD_BUTTON_UP;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_PLUS)
|
||||||
|
convButtons |= VPAD_BUTTON_PLUS;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_2)
|
||||||
|
convButtons |= VPAD_BUTTON_Y;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_1)
|
||||||
|
convButtons |= VPAD_BUTTON_X;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_B)
|
||||||
|
convButtons |= VPAD_BUTTON_B;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_A)
|
||||||
|
convButtons |= VPAD_BUTTON_A;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_MINUS)
|
||||||
|
convButtons |= VPAD_BUTTON_MINUS;
|
||||||
|
|
||||||
|
if (buttons & WPAD_BUTTON_HOME)
|
||||||
|
convButtons |= VPAD_BUTTON_HOME;
|
||||||
|
|
||||||
|
return convButtons;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t remapClassicButtons(uint32_t buttons) {
|
||||||
|
uint32_t convButtons = 0;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_LEFT)
|
||||||
|
convButtons |= VPAD_BUTTON_LEFT;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_RIGHT)
|
||||||
|
convButtons |= VPAD_BUTTON_RIGHT;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_DOWN)
|
||||||
|
convButtons |= VPAD_BUTTON_DOWN;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_UP)
|
||||||
|
convButtons |= VPAD_BUTTON_UP;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_PLUS)
|
||||||
|
convButtons |= VPAD_BUTTON_PLUS;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_X)
|
||||||
|
convButtons |= VPAD_BUTTON_X;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_Y)
|
||||||
|
convButtons |= VPAD_BUTTON_Y;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_B)
|
||||||
|
convButtons |= VPAD_BUTTON_B;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_A)
|
||||||
|
convButtons |= VPAD_BUTTON_A;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_MINUS)
|
||||||
|
convButtons |= VPAD_BUTTON_MINUS;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_HOME)
|
||||||
|
convButtons |= VPAD_BUTTON_HOME;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_ZR)
|
||||||
|
convButtons |= VPAD_BUTTON_ZR;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_ZL)
|
||||||
|
convButtons |= VPAD_BUTTON_ZL;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_R)
|
||||||
|
convButtons |= VPAD_BUTTON_R;
|
||||||
|
|
||||||
|
if (buttons & WPAD_CLASSIC_BUTTON_L)
|
||||||
|
convButtons |= VPAD_BUTTON_L;
|
||||||
|
|
||||||
|
return convButtons;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputUtils::InputData InputUtils::getControllerInput() {
|
||||||
|
InputData inputData = {};
|
||||||
|
VPADStatus vpadStatus = {};
|
||||||
|
VPADReadError vpadError = VPAD_READ_UNINITIALIZED;
|
||||||
|
int maxAttempts = 100;
|
||||||
|
do {
|
||||||
|
if (VPADRead(VPAD_CHAN_0, &vpadStatus, 1, &vpadError) > 0 && vpadError == VPAD_READ_SUCCESS) {
|
||||||
|
inputData.trigger = vpadStatus.trigger;
|
||||||
|
inputData.hold = vpadStatus.hold;
|
||||||
|
inputData.release = vpadStatus.release;
|
||||||
|
} else {
|
||||||
|
OSSleepTicks(OSMillisecondsToTicks(1));
|
||||||
|
}
|
||||||
|
} while (--maxAttempts > 0 && vpadError == VPAD_READ_NO_SAMPLES);
|
||||||
|
|
||||||
|
KPADStatus kpadStatus = {};
|
||||||
|
KPADError kpadError = KPAD_ERROR_UNINITIALIZED;
|
||||||
|
for (int32_t i = 0; i < 4; i++) {
|
||||||
|
if (KPADReadEx((KPADChan) i, &kpadStatus, 1, &kpadError) > 0) {
|
||||||
|
if (kpadError == KPAD_ERROR_OK && kpadStatus.extensionType != 0xFF) {
|
||||||
|
if (kpadStatus.extensionType == WPAD_EXT_CORE || kpadStatus.extensionType == WPAD_EXT_NUNCHUK) {
|
||||||
|
inputData.trigger |= remapWiiMoteButtons(kpadStatus.trigger);
|
||||||
|
inputData.hold |= remapWiiMoteButtons(kpadStatus.hold);
|
||||||
|
inputData.release |= remapWiiMoteButtons(kpadStatus.release);
|
||||||
|
} else {
|
||||||
|
inputData.trigger |= remapClassicButtons(kpadStatus.classic.trigger);
|
||||||
|
inputData.hold |= remapClassicButtons(kpadStatus.classic.hold);
|
||||||
|
inputData.release |= remapClassicButtons(kpadStatus.classic.release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return inputData;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputUtils::Init() {
|
||||||
|
KPADInit();
|
||||||
|
WPADEnableURCC(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InputUtils::DeInit() {
|
||||||
|
KPADShutdown();
|
||||||
|
}
|
17
source/utils/InputUtils.h
Normal file
17
source/utils/InputUtils.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vpad/input.h>
|
||||||
|
|
||||||
|
class InputUtils {
|
||||||
|
public:
|
||||||
|
typedef struct InputData {
|
||||||
|
uint32_t trigger = 0;
|
||||||
|
uint32_t hold = 0;
|
||||||
|
uint32_t release = 0;
|
||||||
|
} InputData;
|
||||||
|
|
||||||
|
static void Init();
|
||||||
|
static void DeInit();
|
||||||
|
|
||||||
|
static InputData getControllerInput();
|
||||||
|
};
|
199
source/utils/MemoryUtils.h
Normal file
199
source/utils/MemoryUtils.h
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "logger.h"
|
||||||
|
#include <coreinit/memexpheap.h>
|
||||||
|
#include <coreinit/memheap.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
typedef void (*FreeMemoryFn)(void *);
|
||||||
|
|
||||||
|
class MemoryWrapper {
|
||||||
|
public:
|
||||||
|
MemoryWrapper(void *ptr, uint32_t size, FreeMemoryFn freeFn) : mPtr(ptr), mSize(size), mFreeFn(freeFn) {
|
||||||
|
}
|
||||||
|
~MemoryWrapper() {
|
||||||
|
if (mPtr && mFreeFn) {
|
||||||
|
memset(mPtr, 0, mSize);
|
||||||
|
mFreeFn(mPtr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryWrapper(const MemoryWrapper &) = delete;
|
||||||
|
MemoryWrapper &operator=(const MemoryWrapper &) = delete;
|
||||||
|
|
||||||
|
MemoryWrapper(MemoryWrapper &&other) noexcept
|
||||||
|
: mPtr(other.mPtr), mSize(other.mSize), mFreeFn(other.mFreeFn) {
|
||||||
|
other.mPtr = {};
|
||||||
|
other.mSize = {};
|
||||||
|
other.mFreeFn = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryWrapper &operator=(MemoryWrapper &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
mPtr = other.mPtr;
|
||||||
|
mSize = other.mSize;
|
||||||
|
mFreeFn = other.mFreeFn;
|
||||||
|
other.mPtr = {};
|
||||||
|
other.mSize = 0;
|
||||||
|
other.mFreeFn = {};
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] void *data() const {
|
||||||
|
return mPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] uint32_t size() const {
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsAllocated() const {
|
||||||
|
return mFreeFn && mPtr && mSize > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void *mPtr = {};
|
||||||
|
uint32_t mSize = 0;
|
||||||
|
FreeMemoryFn mFreeFn = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class ExpHeapMemory {
|
||||||
|
|
||||||
|
public:
|
||||||
|
ExpHeapMemory(MEMHeapHandle heapHandle, void *data, uint32_t size) : mHeapHandle(heapHandle),
|
||||||
|
mData(data),
|
||||||
|
mSize(size) {
|
||||||
|
}
|
||||||
|
ExpHeapMemory() = default;
|
||||||
|
|
||||||
|
~ExpHeapMemory() {
|
||||||
|
if (mData) {
|
||||||
|
MEMFreeToExpHeap(mHeapHandle, mData);
|
||||||
|
}
|
||||||
|
mData = nullptr;
|
||||||
|
mSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the copy constructor and copy assignment operator
|
||||||
|
ExpHeapMemory(const ExpHeapMemory &) = delete;
|
||||||
|
ExpHeapMemory &operator=(const ExpHeapMemory &) = delete;
|
||||||
|
|
||||||
|
ExpHeapMemory(ExpHeapMemory &&other) noexcept
|
||||||
|
: mHeapHandle(other.mHeapHandle), mData(other.mData), mSize(other.mSize) {
|
||||||
|
other.mHeapHandle = {};
|
||||||
|
other.mData = {};
|
||||||
|
other.mSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExpHeapMemory &operator=(ExpHeapMemory &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
mHeapHandle = other.mHeapHandle;
|
||||||
|
mData = other.mData;
|
||||||
|
mSize = other.mSize;
|
||||||
|
other.mHeapHandle = {};
|
||||||
|
other.mData = {};
|
||||||
|
other.mSize = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool() const {
|
||||||
|
return mData != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator void *() const {
|
||||||
|
// Return the desired void* value
|
||||||
|
return mData;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] void *data() const {
|
||||||
|
return mData;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::size_t size() const {
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::optional<ExpHeapMemory> Alloc(MEMHeapHandle heapHandle, uint32_t size, int32_t alignment) {
|
||||||
|
auto *ptr = MEMAllocFromExpHeapEx(heapHandle, size, alignment);
|
||||||
|
if (!ptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpHeapMemory(heapHandle, ptr, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MEMHeapHandle mHeapHandle{};
|
||||||
|
void *mData = nullptr;
|
||||||
|
uint32_t mSize{};
|
||||||
|
};
|
||||||
|
|
||||||
|
class HeapWrapper {
|
||||||
|
public:
|
||||||
|
explicit HeapWrapper(MemoryWrapper &&memory) : mMemory(std::move(memory)) {
|
||||||
|
mHeapHandle = MEMCreateExpHeapEx(mMemory.data(), mMemory.size(), MEM_HEAP_FLAG_USE_LOCK);
|
||||||
|
if (mHeapHandle) {
|
||||||
|
mSize = mMemory.size();
|
||||||
|
mPtr = mMemory.data();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~HeapWrapper() {
|
||||||
|
if (mHeapHandle) {
|
||||||
|
MEMDestroyExpHeap(mHeapHandle);
|
||||||
|
}
|
||||||
|
if (mPtr) {
|
||||||
|
memset(mPtr, 0, mSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the copy constructor and copy assignment operator
|
||||||
|
HeapWrapper(const HeapWrapper &) = delete;
|
||||||
|
HeapWrapper &operator=(const HeapWrapper &) = delete;
|
||||||
|
|
||||||
|
HeapWrapper(HeapWrapper &&other) noexcept
|
||||||
|
: mMemory(std::move(other.mMemory)), mHeapHandle(other.mHeapHandle), mPtr(other.mPtr), mSize(other.mSize) {
|
||||||
|
other.mHeapHandle = {};
|
||||||
|
other.mPtr = {};
|
||||||
|
other.mSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
HeapWrapper &operator=(HeapWrapper &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
mMemory = std::move(other.mMemory);
|
||||||
|
mHeapHandle = other.mHeapHandle;
|
||||||
|
mPtr = other.mPtr;
|
||||||
|
mSize = other.mSize;
|
||||||
|
other.mHeapHandle = {};
|
||||||
|
other.mPtr = {};
|
||||||
|
other.mSize = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] MEMHeapHandle GetHeapHandle() const {
|
||||||
|
return mHeapHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] uint32_t GetHeapSize() const {
|
||||||
|
return mSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsAllocated() const {
|
||||||
|
return mMemory.IsAllocated();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<ExpHeapMemory> Alloc(uint32_t size, int align) const {
|
||||||
|
return ExpHeapMemory::Alloc(mHeapHandle, size, align);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
MemoryWrapper mMemory;
|
||||||
|
MEMHeapHandle mHeapHandle = {};
|
||||||
|
void *mPtr = {};
|
||||||
|
uint32_t mSize = 0;
|
||||||
|
};
|
239
source/utils/PairUtils.cpp
Normal file
239
source/utils/PairUtils.cpp
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
#include "PairUtils.h"
|
||||||
|
#include "DrawUtils.h"
|
||||||
|
#include "InputUtils.h"
|
||||||
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include <coreinit/cache.h>
|
||||||
|
#include <coreinit/thread.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <nn/ccr/sys.h>
|
||||||
|
#include <padscore/kpad.h>
|
||||||
|
#include <padscore/wpad.h>
|
||||||
|
#include <vpad/input.h>
|
||||||
|
|
||||||
|
void PairMenu::drawPairKPADScreen() const {
|
||||||
|
DrawUtils::beginDraw();
|
||||||
|
DrawUtils::clear(COLOR_BACKGROUND);
|
||||||
|
|
||||||
|
DrawUtils::setFontColor(COLOR_TEXT);
|
||||||
|
|
||||||
|
DrawUtils::setFontSize(26);
|
||||||
|
|
||||||
|
std::string textLine1 = "Press the SYNC Button on the controller you want to pair.";
|
||||||
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(textLine1.c_str()) / 2, 40, textLine1.c_str(), true);
|
||||||
|
|
||||||
|
|
||||||
|
WPADExtensionType ext{};
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
bool isConnected = WPADProbe((WPADChan) i, &ext) == 0;
|
||||||
|
std::string textLine = string_format("Slot %d: ", i + 1);
|
||||||
|
if (isConnected) {
|
||||||
|
textLine += ext == WPAD_EXT_PRO_CONTROLLER ? "Pro Controller" : "Wiimote";
|
||||||
|
} else {
|
||||||
|
textLine += "No controller";
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawUtils::print(300, 140 + (i * 30), textLine.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawUtils::setFontSize(26);
|
||||||
|
|
||||||
|
std::string gamepadSyncText1 = "If you are pairing a Wii U GamePad, press the SYNC Button";
|
||||||
|
std::string gamepadSyncText2 = "on your Wii U console one more time";
|
||||||
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(gamepadSyncText1.c_str()) / 2, SCREEN_HEIGHT - 100, gamepadSyncText1.c_str(), true);
|
||||||
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(gamepadSyncText2.c_str()) / 2, SCREEN_HEIGHT - 70, gamepadSyncText2.c_str(), true);
|
||||||
|
|
||||||
|
DrawUtils::setFontSize(16);
|
||||||
|
|
||||||
|
const char *exitHints = "Press \ue001 to return";
|
||||||
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(exitHints) / 2, SCREEN_HEIGHT - 8, exitHints, true);
|
||||||
|
|
||||||
|
DrawUtils::endDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PairMenu::drawPairScreen() const {
|
||||||
|
DrawUtils::beginDraw();
|
||||||
|
DrawUtils::clear(COLOR_BACKGROUND);
|
||||||
|
|
||||||
|
DrawUtils::setFontColor(COLOR_TEXT);
|
||||||
|
|
||||||
|
// Convert the pin to symbols and set the text
|
||||||
|
static char pinSymbols[][4] = {
|
||||||
|
"\u2660",
|
||||||
|
"\u2665",
|
||||||
|
"\u2666",
|
||||||
|
"\u2663"};
|
||||||
|
|
||||||
|
uint32_t pincode = mGamePadPincode;
|
||||||
|
|
||||||
|
std::string pin = std::string(pinSymbols[(pincode / 1000) % 10]) +
|
||||||
|
pinSymbols[(pincode / 100) % 10] +
|
||||||
|
pinSymbols[(pincode / 10) % 10] +
|
||||||
|
pinSymbols[pincode % 10];
|
||||||
|
|
||||||
|
std::string textLine1 = "Press the SYNC Button on the Wii U GamePad,";
|
||||||
|
std::string textLine2 = "and enter the four symbols shown below.";
|
||||||
|
|
||||||
|
DrawUtils::setFontSize(26);
|
||||||
|
|
||||||
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(textLine1.c_str()) / 2, 60, textLine1.c_str(), true);
|
||||||
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(textLine2.c_str()) / 2, 100, textLine2.c_str(), true);
|
||||||
|
|
||||||
|
DrawUtils::setFontSize(100);
|
||||||
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(pin.c_str()) / 2, (SCREEN_HEIGHT / 2) + 40, pin.c_str(), true);
|
||||||
|
|
||||||
|
DrawUtils::setFontSize(20);
|
||||||
|
|
||||||
|
std::string textLine3 = string_format("(%d seconds remaining) ", mGamePadSyncTimeout - (uint32_t) (OSTicksToSeconds(OSGetTime() - mSyncGamePadStartTime)));
|
||||||
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(textLine3.c_str()) / 2, SCREEN_HEIGHT - 80, textLine3.c_str(), true);
|
||||||
|
|
||||||
|
DrawUtils::setFontSize(26);
|
||||||
|
|
||||||
|
std::string textLine4 = "Press the SYNC Button on the Wii U console to exit.";
|
||||||
|
DrawUtils::print(SCREEN_WIDTH / 2 + DrawUtils::getTextWidth(textLine4.c_str()) / 2, SCREEN_HEIGHT - 40, textLine4.c_str(), true);
|
||||||
|
|
||||||
|
DrawUtils::endDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
PairMenu::PairMenu() {
|
||||||
|
CCRSysInit();
|
||||||
|
|
||||||
|
mState = STATE_WAIT;
|
||||||
|
mGamePadSyncTimeout = 120;
|
||||||
|
|
||||||
|
// Initialize IM
|
||||||
|
mIMHandle = IM_Open();
|
||||||
|
if (mIMHandle < 0) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("PairMenu: IM_Open failed");
|
||||||
|
OSFatal("EnvironmentLoader: PairMenu: IM_Open failed");
|
||||||
|
}
|
||||||
|
mIMRequest = (IMRequest *) memalign(0x40, sizeof(IMRequest));
|
||||||
|
|
||||||
|
// Allocate a separate request for IM_CancelGetEventNotify to avoid conflict with the pending IM_GetEventNotify request
|
||||||
|
mIMCancelRequest = (IMRequest *) memalign(0x40, sizeof(IMRequest));
|
||||||
|
|
||||||
|
if (!mIMRequest || !mIMCancelRequest) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("Failed to allocate im request");
|
||||||
|
OSFatal("EnvironmentLoader: PairMenu: Failed to allocate im request");
|
||||||
|
}
|
||||||
|
|
||||||
|
mIMEventMask = IM_EVENT_SYNC;
|
||||||
|
|
||||||
|
// Notify about sync button events
|
||||||
|
IM_GetEventNotify(mIMHandle, mIMRequest, &mIMEventMask, PairMenu::SyncButtonCallback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
PairMenu::~PairMenu() {
|
||||||
|
// Close IM
|
||||||
|
IM_CancelGetEventNotify(mIMHandle, mIMCancelRequest, nullptr, nullptr);
|
||||||
|
IM_Close(mIMHandle);
|
||||||
|
if (mIMCancelRequest) {
|
||||||
|
free(mIMCancelRequest);
|
||||||
|
mIMCancelRequest = {};
|
||||||
|
}
|
||||||
|
if (mIMRequest) {
|
||||||
|
free(mIMRequest);
|
||||||
|
mIMRequest = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deinit CCRSys
|
||||||
|
CCRSysExit();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PairMenu::ProcessPairScreen() {
|
||||||
|
switch (mState) {
|
||||||
|
case STATE_SYNC_WPAD: {
|
||||||
|
// WPAD syncing stops after ~18 seconds, make sure to restart it.
|
||||||
|
if ((uint32_t) OSTicksToSeconds(OSGetTime() - mSyncWPADStartTime) >= 18) {
|
||||||
|
WPADStartSyncDevice();
|
||||||
|
mSyncWPADStartTime = OSGetTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
InputUtils::InputData input = InputUtils::getControllerInput();
|
||||||
|
|
||||||
|
// Stop syncing when pressing A or B.
|
||||||
|
if (input.trigger & (VPAD_BUTTON_A | VPAD_BUTTON_B)) {
|
||||||
|
mState = STATE_WAIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STATE_SYNC_GAMEPAD: {
|
||||||
|
if (CCRSysGetPincode(&mGamePadPincode) != 0) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("CCRSysGetPincode failed");
|
||||||
|
mState = STATE_WAIT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start pairing to slot 0
|
||||||
|
if (CCRSysStartPairing(0, mGamePadSyncTimeout) != 0) {
|
||||||
|
DEBUG_FUNCTION_LINE_ERR("CCRSysStartPairing failed.");
|
||||||
|
mState = STATE_WAIT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pairing has started, save start time
|
||||||
|
mSyncGamePadStartTime = OSGetTime();
|
||||||
|
mState = STATE_PAIRING;
|
||||||
|
|
||||||
|
DEBUG_FUNCTION_LINE("Started GamePad syncing.");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STATE_PAIRING: {
|
||||||
|
// Get the current pairing state
|
||||||
|
CCRSysPairingState pairingState = CCRSysGetPairingState();
|
||||||
|
if (pairingState == CCR_SYS_PAIRING_TIMED_OUT) {
|
||||||
|
DEBUG_FUNCTION_LINE("GamePad SYNC timed out.");
|
||||||
|
// Pairing has timed out or was cancelled
|
||||||
|
CCRSysStopPairing();
|
||||||
|
mState = STATE_WAIT;
|
||||||
|
} else if (pairingState == CCR_SYS_PAIRING_FINISHED) {
|
||||||
|
DEBUG_FUNCTION_LINE("GamePad paired.");
|
||||||
|
mState = STATE_WAIT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STATE_CANCEL: {
|
||||||
|
CCRSysStopPairing();
|
||||||
|
mState = STATE_WAIT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case STATE_WAIT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch (mState) {
|
||||||
|
case STATE_WAIT: {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case STATE_SYNC_WPAD:
|
||||||
|
drawPairKPADScreen();
|
||||||
|
break;
|
||||||
|
case STATE_SYNC_GAMEPAD:
|
||||||
|
case STATE_PAIRING:
|
||||||
|
case STATE_CANCEL: {
|
||||||
|
drawPairScreen();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PairMenu::SyncButtonCallback(IOSError error, void *arg) {
|
||||||
|
auto *pairMenu = (PairMenu *) arg;
|
||||||
|
|
||||||
|
if (error == IOS_ERROR_OK && pairMenu && (pairMenu->mIMEventMask & IM_EVENT_SYNC)) {
|
||||||
|
if (pairMenu->mState == STATE_WAIT) {
|
||||||
|
pairMenu->mState = STATE_SYNC_WPAD;
|
||||||
|
// We need to restart the WPAD pairing every 18 seconds. For the timing we need to save the current time.
|
||||||
|
pairMenu->mSyncWPADStartTime = OSGetTime();
|
||||||
|
} else if (pairMenu->mState == STATE_SYNC_WPAD) {
|
||||||
|
pairMenu->mState = STATE_SYNC_GAMEPAD;
|
||||||
|
} else if (pairMenu->mState == STATE_SYNC_GAMEPAD || pairMenu->mState == STATE_PAIRING) {
|
||||||
|
pairMenu->mState = STATE_CANCEL;
|
||||||
|
}
|
||||||
|
OSMemoryBarrier();
|
||||||
|
IM_GetEventNotify(pairMenu->mIMHandle, pairMenu->mIMRequest, &pairMenu->mIMEventMask, PairMenu::SyncButtonCallback, pairMenu);
|
||||||
|
}
|
||||||
|
}
|
43
source/utils/PairUtils.h
Normal file
43
source/utils/PairUtils.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "logger.h"
|
||||||
|
#include <coreinit/cache.h>
|
||||||
|
#include <coreinit/im.h>
|
||||||
|
#include <coreinit/ios.h>
|
||||||
|
#include <coreinit/time.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <nn/ccr/sys.h>
|
||||||
|
|
||||||
|
class PairMenu {
|
||||||
|
public:
|
||||||
|
PairMenu();
|
||||||
|
|
||||||
|
~PairMenu();
|
||||||
|
|
||||||
|
bool ProcessPairScreen();
|
||||||
|
|
||||||
|
static void SyncButtonCallback(IOSError error, void *arg);
|
||||||
|
|
||||||
|
void drawPairScreen() const;
|
||||||
|
|
||||||
|
void drawPairKPADScreen() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum PairMenuState {
|
||||||
|
STATE_WAIT, // Wait for SYNC button press
|
||||||
|
STATE_SYNC_WPAD,
|
||||||
|
STATE_SYNC_GAMEPAD,
|
||||||
|
STATE_PAIRING,
|
||||||
|
STATE_CANCEL,
|
||||||
|
};
|
||||||
|
|
||||||
|
IOSHandle mIMHandle{};
|
||||||
|
IMRequest *mIMRequest{};
|
||||||
|
IMRequest *mIMCancelRequest{};
|
||||||
|
OSTime mSyncWPADStartTime = 0;
|
||||||
|
OSTime mSyncGamePadStartTime = 0;
|
||||||
|
uint32_t mGamePadPincode = 0;
|
||||||
|
PairMenuState mState = STATE_WAIT;
|
||||||
|
uint32_t mGamePadSyncTimeout = 120;
|
||||||
|
IMEventMask mIMEventMask{};
|
||||||
|
};
|
@ -34,6 +34,7 @@ extern "C" {
|
|||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) LOG(WHBLogWritef, FMT, ##ARGS)
|
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) LOG(WHBLogWritef, FMT, ##ARGS)
|
||||||
|
|
||||||
|
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX(WHBLogPrintf, "##WARN ## ", "", FMT, ##ARGS)
|
||||||
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS)
|
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX(WHBLogPrintf, "##ERROR## ", "", FMT, ##ARGS)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
@ -44,6 +45,7 @@ extern "C" {
|
|||||||
|
|
||||||
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0)
|
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...) while (0)
|
||||||
|
|
||||||
|
#define DEBUG_FUNCTION_LINE_WARN(FMT, ARGS...) LOG_EX(OSReport, "##WARN ## ", "\n", FMT, ##ARGS)
|
||||||
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX(OSReport, "##ERROR## ", "\n", FMT, ##ARGS)
|
#define DEBUG_FUNCTION_LINE_ERR(FMT, ARGS...) LOG_EX(OSReport, "##ERROR## ", "\n", FMT, ##ARGS)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -15,3 +15,16 @@ template<class T, class... Args>
|
|||||||
std::shared_ptr<T> make_shared_nothrow(Args &&...args) noexcept(noexcept(T(std::forward<Args>(args)...))) {
|
std::shared_ptr<T> make_shared_nothrow(Args &&...args) noexcept(noexcept(T(std::forward<Args>(args)...))) {
|
||||||
return std::shared_ptr<T>(new (std::nothrow) T(std::forward<Args>(args)...));
|
return std::shared_ptr<T>(new (std::nothrow) T(std::forward<Args>(args)...));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
std::string string_format(const std::string &format, Args... args) {
|
||||||
|
int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
|
||||||
|
auto size = static_cast<size_t>(size_s);
|
||||||
|
auto buf = std::make_unique<char[]>(size);
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// those work only in powers of 2
|
||||||
|
#define ROUNDDOWN(val, align) ((val) & ~(align - 1))
|
||||||
|
#define ROUNDUP(val, align) ROUNDDOWN(((val) + (align - 1)), align)
|
2
source/version.h
Normal file
2
source/version.h
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#pragma once
|
||||||
|
#define ENVIRONMENT_LOADER_VERSION_EXTRA ""
|
Loading…
Reference in New Issue
Block a user