├── .gitignore ├── .gitmodules ├── src ├── main.cpp ├── elf_helper.h ├── loader.h ├── hook.cpp ├── log.cpp ├── elf_helper.cpp └── loader.cpp ├── ModLoaderConfig.cmake ├── include └── modloader │ ├── loader.h │ ├── hook.h │ ├── log.h │ └── statichook.h ├── CMakeLists.txt ├── README.md └── .github └── workflows └── build.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-build-debug 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dep/funchook"] 2 | path = dep/funchook 3 | url = ../funchook.git 4 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace modloader; 4 | 5 | __attribute__((constructor)) 6 | static void modloader_init() { 7 | ModLoader::addLibSearchDir("mods"); 8 | ModLoader::loadModsFromDirectory("mods"); 9 | } -------------------------------------------------------------------------------- /src/elf_helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace modloader { 7 | 8 | class ElfHelper { 9 | 10 | public: 11 | static std::vector getDependencies(std::string const &path); 12 | 13 | }; 14 | 15 | } -------------------------------------------------------------------------------- /ModLoaderConfig.cmake: -------------------------------------------------------------------------------- 1 | add_library(ModLoader SHARED IMPORTED) 2 | 3 | find_library(MODLOADER_LIBRARY server_modloader HINTS "${CMAKE_CURRENT_LIST_DIR}/../../../lib") 4 | set(MODLOADER_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../include/") 5 | set_target_properties(ModLoader PROPERTIES IMPORTED_LOCATION "${MODLOADER_LIBRARY}") 6 | set_target_properties(ModLoader PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${MODLOADER_INCLUDE_DIR}") -------------------------------------------------------------------------------- /src/loader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace modloader { 9 | 10 | class ModLoaderImpl { 11 | 12 | public: 13 | std::vector libDirs; 14 | std::unordered_map knownLoadedLibs; 15 | std::unordered_set mods; 16 | 17 | void addLibSearchDir(std::string const &path) { 18 | libDirs.push_back(path); 19 | } 20 | 21 | std::string findLib(std::string const& name); 22 | 23 | void *loadLib(std::string const &path); 24 | 25 | void *loadMod(std::string const &path); 26 | 27 | void loadModsFromDirectory(std::string const &path); 28 | 29 | }; 30 | 31 | } -------------------------------------------------------------------------------- /include/modloader/loader.h: -------------------------------------------------------------------------------- 1 | #ifndef MODLOADER_LOADER_H 2 | #define MODLOADER_LOADER_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | const char *modloader_version(); 9 | 10 | typedef void (*modloader_foreach_fn)(void *handle, void *userdata); 11 | 12 | void modloader_add_lib_search_path(const char* path); 13 | 14 | void *modloader_load_mod(const char* path); 15 | 16 | void modloader_load_mods_from_directory(const char* path); 17 | 18 | void modloader_iterate_mods(modloader_foreach_fn cb, void* userdata); 19 | 20 | #ifdef __cplusplus 21 | } 22 | #endif 23 | 24 | 25 | 26 | #ifdef __cplusplus 27 | 28 | #include 29 | #include 30 | 31 | namespace modloader { 32 | 33 | class ModLoader { 34 | 35 | public: 36 | static const char *getVersion(); 37 | 38 | static void addLibSearchDir(std::string const& path); 39 | 40 | static void *loadMod(std::string const& path); 41 | 42 | static void loadModsFromDirectory(std::string const &path); 43 | 44 | static void forEachMod(std::function cb); 45 | 46 | }; 47 | 48 | } 49 | #endif 50 | 51 | #endif -------------------------------------------------------------------------------- /include/modloader/hook.h: -------------------------------------------------------------------------------- 1 | #ifndef MODLOADER_HOOK_H 2 | #define MODLOADER_HOOK_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef void modloader_hook_t; 11 | 12 | void* modloader_dlsym_print_error(const char *sym); 13 | 14 | modloader_hook_t* modloader_hook(void *sym, void *hook, void **orig); 15 | 16 | void modloader_destroy_hook(modloader_hook_t*); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | 22 | 23 | #ifdef __cplusplus 24 | 25 | namespace modloader { 26 | 27 | class AutoHook { 28 | 29 | private: 30 | modloader_hook_t* hook; 31 | 32 | template 33 | static void *castToVoid(T hook) { 34 | union { 35 | T a; 36 | void *b; 37 | } hookUnion; 38 | hookUnion.a = hook; 39 | return hookUnion.b; 40 | } 41 | 42 | public: 43 | AutoHook(void *sym, void *hook, void **orig) { 44 | this->hook = modloader_hook(sym, hook, orig); 45 | } 46 | 47 | ~AutoHook() { 48 | if (hook) 49 | modloader_destroy_hook(hook); 50 | } 51 | 52 | 53 | AutoHook(const char *sym, void *hook, void **orig) : AutoHook(modloader_dlsym_print_error(sym), hook, orig) { 54 | } 55 | 56 | // workaround for a warning 57 | template 58 | AutoHook(const char *sym, T hook, void **orig) : AutoHook(sym, castToVoid(hook), orig) { 59 | } 60 | 61 | }; 62 | 63 | } 64 | 65 | #endif 66 | 67 | #endif -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(server_modloader) 3 | 4 | include(CMakePackageConfigHelpers) 5 | 6 | if (CMAKE_VERSION VERSION_LESS 3.0.0) 7 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") 8 | else() 9 | set(CMAKE_CXX_STANDARD 11) 10 | endif() 11 | 12 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 13 | 14 | #BDS is linked to libc++ since 1.20.40 15 | if(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 16 | message(NOTICE "Setting compiler to clang++") 17 | set(CMAKE_CXX_COMPILER "clang++") 18 | endif() 19 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") 20 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++") 21 | 22 | set(MODLOADER_VERSION "v0.0.1-alpha4") 23 | 24 | add_subdirectory(dep/funchook) 25 | 26 | add_library(server_modloader SHARED include/modloader/hook.h include/modloader/statichook.h include/modloader/log.h 27 | include/modloader/loader.h 28 | src/hook.cpp src/log.cpp src/loader.h src/loader.cpp src/elf_helper.cpp src/elf_helper.h src/main.cpp) 29 | target_link_libraries(server_modloader funchook) 30 | target_compile_definitions(server_modloader PRIVATE MODLOADER_VERSION="${MODLOADER_VERSION}") 31 | target_include_directories(server_modloader PUBLIC include/) 32 | 33 | install(TARGETS server_modloader DESTINATION lib) 34 | install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/ DESTINATION include) 35 | install(FILES ModLoaderConfig.cmake DESTINATION share/cmake/modloader) 36 | -------------------------------------------------------------------------------- /src/hook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern "C" { 5 | #include 6 | } 7 | 8 | using namespace modloader; 9 | 10 | extern "C" { 11 | 12 | void *modloader_dlsym_print_error(const char *sym) { 13 | void *ptr = dlsym(RTLD_DEFAULT, sym); 14 | if (!ptr) 15 | Log::error("Hook", "Unknown symbol %s", sym); 16 | return ptr; 17 | } 18 | 19 | modloader_hook_t *modloader_hook(void *sym, void *hook, void **orig) { 20 | funchook_t *h = funchook_create(); 21 | if (!h) 22 | return nullptr; 23 | *orig = sym; 24 | int r = funchook_prepare(h, orig, hook); 25 | if (r != 0) { 26 | Log::error("Hook", "Error while preparing hook: %s", funchook_error_message(h)); 27 | funchook_destroy(h); 28 | return nullptr; 29 | } 30 | r = funchook_install(h, 0); 31 | if (r != 0) { 32 | Log::error("Hook", "Error while installing hook: %s", funchook_error_message(h)); 33 | modloader_destroy_hook(h); 34 | return nullptr; 35 | } 36 | return (modloader_hook_t *) h; 37 | } 38 | 39 | void modloader_destroy_hook(modloader_hook_t *hook) { 40 | auto h = (funchook_t *) hook; 41 | int r = funchook_uninstall(h, 0); 42 | if (r != 0) 43 | Log::error("Hook", "Error while uninstalling hook: %s", funchook_error_message(h)); 44 | r = funchook_destroy(h); 45 | if (r != 0) 46 | Log::error("Hook", "Error while destroying hook: %s", funchook_error_message(h)); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ModLoader 2 | 3 | This is a tool to load mods onto the Linux version of [Bedrock Dedicated Server](https://minecraft.net/download/server/bedrock). 4 | 5 | ## Usage 6 | - Get `libserver_modloader.so`, either from [releases](https://github.com/minecraft-linux/server-modloader/releases) or [building your own](#compiling-from-source) 7 | - Put your compiled mods in a folder called `mods/` next to your moddable BDS binary 8 | - `LD_PRELOAD=path/to/libserver_modloader.so ./bedrock_server_symbols_moddable.debug` 9 | 10 | ## Compiling from source 11 | 1. `git clone https://github.com/minecraft-linux/server-modloader.git --recursive` 12 | 2. `cd server-modloader` 13 | 3. `mkdir build && cd build` 14 | 4. `cmake ..` 15 | 5. `make` 16 | 17 | You now have a `libserver_modloader.so` binary. 18 | 19 | ## Examples, usage info 20 | Check out the [wiki](https://github.com/minecraft-linux/server-modloader/wiki) for examples and more information. 21 | 22 | ## Getting mods to work on newer versions (1.16+) 23 | Newer versions of BDS don't have exported symbols anymore, so a little hacking is necessary to modify the binary and make the symbols linkable. 24 | 25 | Using [LIEF](https://github.com/lief-project/lief), you can create a patched, moddable BDS from `bedrock_server_symbols.debug` using the following Python script: 26 | 27 | ```py 28 | import lief 29 | import sys 30 | 31 | lib_symbols = lief.parse("bedrock_server_symbols.debug") 32 | for s in filter(lambda e: e.exported, lib_symbols.static_symbols): 33 | lib_symbols.add_dynamic_symbol(s) 34 | lib_symbols.write("bedrock_server_symbols_patched.debug") 35 | ``` 36 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-20.04 6 | steps: 7 | - name: Check out repository code 8 | uses: actions/checkout@v4 9 | with: 10 | submodules: recursive 11 | - name: Install deps 12 | run: sudo apt-get install -y git cmake zip libc++1-12 libc++abi-12-dev libc++-12-dev libc++abi1-12 13 | - name: Retrieve version 14 | run: | 15 | echo "TAG_NAME=$(cat CMakeLists.txt | grep -Po '(?<=MODLOADER_VERSION \")[^\"]+')" >> $GITHUB_OUTPUT 16 | id: version 17 | - name: Build 18 | run: mkdir build && cd build && cmake -DCMAKE_C_COMPILER=clang-12 -DCMAKE_CXX_COMPILER=clang++-12 -DCMAKE_INSTALL_PREFIX=../sdk/ .. && make && make install && cd .. 19 | - name: Pack the SDK and modloader 20 | run: | 21 | mkdir artifacts && cd sdk && 22 | zip -r ../artifacts/mod_sdk.zip . && cp lib/libserver_modloader.so ../artifacts && cd .. 23 | - name: Artifact - libserver_modloader.so 24 | uses: actions/upload-artifact@v3 25 | with: 26 | name: libserver_modloader.so 27 | path: artifacts/libserver_modloader.so 28 | - name: Artifact - mod_sdk.zip 29 | uses: actions/upload-artifact@v3 30 | with: 31 | name: mod_sdk.zip 32 | path: artifacts/mod_sdk.zip 33 | - name: Update release 34 | uses: softprops/action-gh-release@v1 35 | if: startsWith(github.event.head_commit.message, '[Release]') 36 | with: 37 | draft: true 38 | tag_name: ${{ steps.version.outputs.TAG_NAME }} 39 | files: | 40 | artifacts/libserver_modloader.so 41 | artifacts/mod_sdk.zip 42 | -------------------------------------------------------------------------------- /include/modloader/log.h: -------------------------------------------------------------------------------- 1 | #ifndef MODLOADER_LOG_H 2 | #define MODLOADER_LOG_H 3 | 4 | #ifdef __cplusplus 5 | #include 6 | #else 7 | #include 8 | #endif 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | typedef enum { 15 | MODLOADER_LOG_TRACE, 16 | MODLOADER_LOG_DEBUG, 17 | MODLOADER_LOG_INFO, 18 | MODLOADER_LOG_WARN, 19 | MODLOADER_LOG_ERROR 20 | } modloader_log_level; 21 | 22 | void modloader_log(modloader_log_level level, const char *tag, const char *format, ...); 23 | void modloader_vlog(modloader_log_level level, const char *tag, const char *format, va_list args); 24 | 25 | const char *modloader_log_level_str(modloader_log_level level); 26 | 27 | void modloader_logv(const char *tag, const char *format, ...); 28 | void modloader_logd(const char *tag, const char *format, ...); 29 | void modloader_logi(const char *tag, const char *format, ...); 30 | void modloader_logw(const char *tag, const char *format, ...); 31 | void modloader_loge(const char *tag, const char *format, ...); 32 | 33 | #ifdef __cplusplus 34 | } 35 | #endif 36 | 37 | 38 | #ifdef __cplusplus 39 | namespace modloader { 40 | 41 | class Log { 42 | 43 | public: 44 | using LogLevel = modloader_log_level; 45 | 46 | static const char *getLogLevelString(LogLevel level) { 47 | return modloader_log_level_str(level); 48 | } 49 | 50 | static void vlog(LogLevel level, const char* tag, const char* text, va_list args) { 51 | modloader_vlog(level, tag, text, args); 52 | } 53 | 54 | static void log(LogLevel level, const char* tag, const char* text, ...); 55 | 56 | static void verbose(const char* tag, const char* text, ...); 57 | static void debug(const char* tag, const char* text, ...); 58 | static void info(const char* tag, const char* text, ...); 59 | static void warn(const char* tag, const char* text, ...); 60 | static void error(const char* tag, const char* text, ...); 61 | 62 | }; 63 | 64 | } 65 | #endif 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/log.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace modloader; 8 | 9 | extern "C" { 10 | 11 | const char *modloader_log_level_str(modloader_log_level level) { 12 | if (level == MODLOADER_LOG_TRACE) return "Trace"; 13 | if (level == MODLOADER_LOG_DEBUG) return "Debug"; 14 | if (level == MODLOADER_LOG_INFO) return "Info"; 15 | if (level == MODLOADER_LOG_WARN) return "Warn"; 16 | if (level == MODLOADER_LOG_ERROR) return "Error"; 17 | return "?"; 18 | } 19 | 20 | void modloader_vlog(modloader_log_level level, const char *tag, const char *format, va_list args) { 21 | char buffer[4096]; 22 | int len = vsnprintf(buffer, sizeof(buffer), format, args); 23 | if (len > sizeof(buffer)) 24 | len = sizeof(buffer); 25 | while (len > 0 && (buffer[len - 1] == '\r' || buffer[len - 1] == '\n')) 26 | buffer[--len] = '\0'; 27 | 28 | char tbuf[128]; 29 | tbuf[0] = '\0'; 30 | 31 | time_t t = time(nullptr); 32 | tm tm; 33 | localtime_r(&t, &tm); 34 | strftime(tbuf, sizeof(tbuf), "%H:%M:%S", &tm); 35 | printf("%s %-5s [%s] %s\n", tbuf, modloader_log_level_str(level), tag, buffer); 36 | } 37 | 38 | void modloader_log(modloader_log_level level, const char *tag, const char *format, ...) { 39 | va_list args; 40 | va_start(args, format); 41 | modloader_vlog(level, tag, format, args); 42 | va_end(args); 43 | } 44 | 45 | } 46 | 47 | void Log::log(LogLevel level, const char* tag, const char* format, ...) { 48 | va_list args; 49 | va_start(args, format); 50 | modloader_vlog(level, tag, format, args); 51 | va_end(args); 52 | } 53 | 54 | #define LogFuncDef(name, logLevel) \ 55 | void name(const char* tag, const char* format, ...) { \ 56 | va_list args; \ 57 | va_start(args, format); \ 58 | modloader_vlog(logLevel, tag, format, args); \ 59 | va_end(args); \ 60 | } 61 | 62 | LogFuncDef(modloader_logv, MODLOADER_LOG_TRACE) 63 | LogFuncDef(modloader_logd, MODLOADER_LOG_DEBUG) 64 | LogFuncDef(modloader_logi, MODLOADER_LOG_INFO) 65 | LogFuncDef(modloader_logw, MODLOADER_LOG_WARN) 66 | LogFuncDef(modloader_loge, MODLOADER_LOG_ERROR) 67 | 68 | LogFuncDef(Log::verbose, MODLOADER_LOG_TRACE) 69 | LogFuncDef(Log::debug, MODLOADER_LOG_DEBUG) 70 | LogFuncDef(Log::info, MODLOADER_LOG_INFO) 71 | LogFuncDef(Log::warn, MODLOADER_LOG_WARN) 72 | LogFuncDef(Log::error, MODLOADER_LOG_ERROR) -------------------------------------------------------------------------------- /include/modloader/statichook.h: -------------------------------------------------------------------------------- 1 | #ifndef MODLOADER_STAICHOOK_H 2 | #define MODLOADER_STAICHOOK_H 3 | 4 | #include 5 | #include "hook.h" 6 | 7 | #define _TInstanceHook(class_inh, pclass, iname, sym, ret, args...) \ 8 | struct _TInstanceHook_##iname class_inh { \ 9 | static ret (_TInstanceHook_##iname::*_original)(args); \ 10 | template \ 11 | static ret original(pclass* _this, Params&&... params) { return (((_TInstanceHook_##iname*) _this)->*_original)(std::forward(params)...); } \ 12 | ret _hook(args); \ 13 | }; \ 14 | static modloader::AutoHook _TRInstanceHook_##iname (#sym, &_TInstanceHook_##iname::_hook, (void**) &_TInstanceHook_##iname::_original); \ 15 | ret (_TInstanceHook_##iname::*_TInstanceHook_##iname::_original)(args); \ 16 | ret _TInstanceHook_##iname::_hook(args) 17 | #define _TInstanceDefHook(iname, sym, ret, type, args...) _TInstanceHook(: public type, type, iname, sym, ret, args) 18 | #define _TInstanceNoDefHook(iname, sym, ret, args...) _TInstanceHook(, void, iname, sym, ret, args) 19 | 20 | #define _TStaticHook(pclass, iname, sym, ret, args...) \ 21 | struct _TStaticHook_##iname pclass { \ 22 | static ret (*_original)(args); \ 23 | template \ 24 | static ret original(Params&&... params) { return (*_original)(std::forward(params)...); } \ 25 | static ret _hook(args); \ 26 | }; \ 27 | static modloader::AutoHook _TRStaticHook_##iname (#sym, &_TStaticHook_##iname::_hook, (void**) &_TStaticHook_##iname::_original); \ 28 | ret (*_TStaticHook_##iname::_original)(args); \ 29 | ret _TStaticHook_##iname::_hook(args) 30 | #define _TStaticDefHook(iname, sym, ret, type, args...) _TStaticHook(: public type, iname, sym, ret, args) 31 | #define _TStaticNoDefHook(iname, sym, ret, args...) _TStaticHook(, iname, sym, ret, args) 32 | 33 | #define THook2(iname, ret, sym, args...) _TStaticNoDefHook(iname, sym, ret, args) 34 | #define THook(ret, sym, args...) THook2(sym, ret, sym, args) 35 | #define TClasslessInstanceHook2(iname, ret, sym, args...) _TInstanceNoDefHook(iname, sym, ret, args) 36 | #define TClasslessInstanceHook(ret, sym, args...) TClasslessInstanceHook2(sym, ret, sym, args) 37 | #define TInstanceHook2(iname, ret, sym, type, args...) _TInstanceDefHook(iname, sym, ret, type, args) 38 | #define TInstanceHook(ret, sym, type, args...) TInstanceHook2(sym, ret, sym, type, args) 39 | #define TStaticHook2(iname, ret, sym, type, args...) _TStaticDefHook(iname, sym, ret, type, args) 40 | #define TStaticHook(ret, sym, type, args...) TStaticHook2(sym, ret, sym, type, args) 41 | 42 | #endif -------------------------------------------------------------------------------- /src/elf_helper.cpp: -------------------------------------------------------------------------------- 1 | #include "elf_helper.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace modloader; 8 | 9 | typedef Elf64_Ehdr Elf_Ehdr; 10 | typedef Elf64_Phdr Elf_Phdr; 11 | typedef Elf64_Dyn Elf_Dyn; 12 | 13 | std::vector ElfHelper::getDependencies(std::string const &path) { 14 | Elf_Ehdr header; 15 | FILE* file = fopen(path.c_str(), "r"); 16 | if (file == nullptr) { 17 | Log::error("ModLoader", "getModDependencies: failed to open mod"); 18 | return {}; 19 | } 20 | if (fread(&header, sizeof(Elf_Ehdr), 1, file) != 1) { 21 | Log::error("ModLoader", "getModDependencies: failed to read header"); 22 | fclose(file); 23 | return {}; 24 | } 25 | 26 | fseek(file, (long) header.e_phoff, SEEK_SET); 27 | 28 | char phdr[header.e_phentsize * header.e_phnum]; 29 | if (fread(phdr, header.e_phentsize, header.e_phnum, file) != header.e_phnum) { 30 | Log::error("ModLoader", "getModDependencies: failed to read phnum"); 31 | fclose(file); 32 | return {}; 33 | } 34 | 35 | // find dynamic 36 | Elf_Phdr* dynamicEntry = nullptr; 37 | for (int i = 0; i < header.e_phnum; i++) { 38 | Elf_Phdr& entry = *((Elf_Phdr*) &phdr[header.e_phentsize * i]); 39 | if (entry.p_type == PT_DYNAMIC) 40 | dynamicEntry = &entry; 41 | } 42 | if (dynamicEntry == nullptr) { 43 | Log::error("ModLoader", "getModDependencies: couldn't find PT_DYNAMIC"); 44 | fclose(file); 45 | return {}; 46 | } 47 | size_t dynamicDataCount = dynamicEntry->p_filesz / sizeof(Elf_Dyn); 48 | Elf_Dyn dynamicData[dynamicDataCount]; 49 | fseek(file, (long) dynamicEntry->p_offset, SEEK_SET); 50 | if (fread(dynamicData, sizeof(Elf_Dyn), dynamicDataCount, file) != dynamicDataCount) { 51 | Log::error("ModLoader", "getModDependencies: failed to read PT_DYNAMIC"); 52 | fclose(file); 53 | return {}; 54 | } 55 | 56 | // find strtab 57 | size_t strtabOff = 0; 58 | size_t strtabSize = 0; 59 | for (int i = 0; i < dynamicDataCount; i++) { 60 | if (dynamicData[i].d_tag == DT_STRTAB) { 61 | strtabOff = dynamicData[i].d_un.d_val; 62 | } else if (dynamicData[i].d_tag == DT_STRSZ) { 63 | strtabSize = dynamicData[i].d_un.d_val; 64 | } 65 | } 66 | if (strtabOff == 0 || strtabSize == 0) { 67 | Log::error("ModLoader", "getModDependencies: couldn't find strtab"); 68 | fclose(file); 69 | return {}; 70 | } 71 | std::vector strtab; 72 | strtab.resize(strtabSize); 73 | fseek(file, (long) strtabOff, SEEK_SET); 74 | if (fread(strtab.data(), 1, strtabSize, file) != strtabSize) { 75 | Log::error("ModLoader", "getModDependencies: failed to read strtab"); 76 | fclose(file); 77 | return {}; 78 | } 79 | std::vector ret; 80 | for (int i = 0; i < dynamicDataCount; i++) { 81 | if (dynamicData[i].d_tag == DT_NEEDED) 82 | ret.emplace_back(&strtab[dynamicData[i].d_un.d_val]); 83 | } 84 | return ret; 85 | } -------------------------------------------------------------------------------- /src/loader.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "loader.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "elf_helper.h" 10 | 11 | using namespace modloader; 12 | 13 | static ModLoaderImpl loaderImpl; 14 | 15 | extern "C" { 16 | 17 | const char *modloader_version() { 18 | return ModLoader::getVersion(); 19 | } 20 | 21 | void modloader_add_lib_search_path(const char* path) { 22 | loaderImpl.addLibSearchDir(path); 23 | } 24 | 25 | void *modloader_load_mod(const char* path) { 26 | return loaderImpl.loadMod(path); 27 | } 28 | 29 | void modloader_load_mods_from_directory(const char* path) { 30 | loaderImpl.loadModsFromDirectory(path); 31 | } 32 | 33 | void modloader_iterate_mods(modloader_foreach_fn cb, void* userdata) { 34 | for (void* v : loaderImpl.mods) 35 | cb(v, userdata); 36 | } 37 | 38 | } 39 | 40 | const char *ModLoader::getVersion() { 41 | return MODLOADER_VERSION; 42 | } 43 | 44 | void ModLoader::addLibSearchDir(std::string const &path) { 45 | loaderImpl.addLibSearchDir(path); 46 | } 47 | 48 | void* ModLoader::loadMod(std::string const &path) { 49 | return loaderImpl.loadMod(path); 50 | } 51 | 52 | void ModLoader::loadModsFromDirectory(std::string const &path) { 53 | loaderImpl.loadModsFromDirectory(path); 54 | } 55 | 56 | void ModLoader::forEachMod(std::function cb) { 57 | for (void* v : loaderImpl.mods) 58 | cb(v); 59 | } 60 | 61 | std::string ModLoaderImpl::findLib(std::string const &name) { 62 | for (std::string const& dir : libDirs) { 63 | std::string fullPath = dir; 64 | fullPath += '/'; 65 | fullPath += name; 66 | if (access(fullPath.c_str(), R_OK) == 0) 67 | return fullPath; 68 | } 69 | return std::string(); 70 | } 71 | 72 | void *ModLoaderImpl::loadLib(std::string const &path) { 73 | auto e = knownLoadedLibs.find(path); 74 | if (e != knownLoadedLibs.end()) 75 | return e->second; 76 | 77 | auto iof = path.rfind('/'); 78 | std::string fullPath = path; 79 | if (iof == std::string::npos) 80 | fullPath = findLib(path); 81 | 82 | if (!fullPath.empty()) { 83 | Log::info("ModLoader", "Loading library: %s", fullPath.c_str()); 84 | 85 | auto deps = ElfHelper::getDependencies(fullPath); 86 | for (std::string const& dep : deps) 87 | loadLib(dep); 88 | } else { 89 | fullPath = path; 90 | } 91 | 92 | void* ret = dlopen(fullPath.c_str(), RTLD_NOW); 93 | if (!ret) { 94 | Log::error("ModLoader", "Failed loading library %s: %s", fullPath.c_str(), dlerror()); 95 | return nullptr; 96 | } 97 | std::string filename = iof != std::string::npos ? path.substr(iof + 1) : path; 98 | knownLoadedLibs[filename] = ret; 99 | return ret; 100 | } 101 | 102 | void *ModLoaderImpl::loadMod(std::string const &path) { 103 | void* ret = loadLib(path); 104 | mods.insert(ret); 105 | return ret; 106 | } 107 | 108 | void ModLoaderImpl::loadModsFromDirectory(std::string const &path) { 109 | Log::info("ModLoader", "Loading mods from directory: %s", path.c_str()); 110 | DIR* dir = opendir(path.c_str()); 111 | dirent* ent; 112 | if (dir == nullptr) { 113 | Log::info("ModLoader", "Directory does not exist"); 114 | return; 115 | } 116 | while ((ent = readdir(dir)) != nullptr) { 117 | if (ent->d_name[0] == '.') 118 | continue; 119 | std::string fileName(ent->d_name); 120 | size_t len = fileName.length(); 121 | 122 | loadMod(fileName); 123 | } 124 | closedir(dir); 125 | Log::info("ModLoader", "Loaded %li mods", mods.size()); 126 | } 127 | --------------------------------------------------------------------------------