├── .circleci └── config.yml ├── .clang-format ├── .gitignore ├── Makefile ├── PFishHook.h ├── StaticHook.h ├── dep.cpp ├── dep.h └── main.cpp /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | 2 | version: 2 3 | jobs: 4 | build: 5 | docker: 6 | - image: base/devel 7 | working_directory: /data/project 8 | steps: 9 | - checkout 10 | - run: 11 | name: prepare git 12 | command: pacman -Sy git cmake --noconfirm --needed 13 | - run: 14 | name: prepare zydis 15 | command: | 16 | cd /data 17 | git clone https://github.com/zyantific/zydis 18 | cd zydis 19 | mkdir build 20 | cd build 21 | echo "set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} -fPIC\")" >>../CMakeLists.txt 22 | cmake .. 23 | make 24 | make install 25 | cp ZydisExportConfig.h ../include 26 | - run: 27 | name: prepare PFishHook 28 | command: | 29 | cd /data 30 | git clone https://github.com/Menooker/PFishHook 31 | cd PFishHook 32 | make directories 33 | make lib INCLUDE=../zydis/include LIBPATH=../zydis/build 34 | cp bin/* ../project 35 | - run: 36 | name: build 37 | command: make 38 | - store_artifacts: 39 | path: /data/project/ModLoader.so 40 | 41 | workflows: 42 | version: 2 43 | build: 44 | jobs: 45 | - build -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | --- 4 | Language: Cpp 5 | # AlignAfterOpenBracket: AlwaysBreak 6 | ColumnLimit: 150 7 | AlignConsecutiveAssignments: true 8 | AlignOperands: true 9 | AlignTrailingComments: true 10 | AllowAllParametersOfDeclarationOnNextLine: false 11 | AllowShortBlocksOnASingleLine: true 12 | AllowShortCaseLabelsOnASingleLine: true 13 | AllowShortFunctionsOnASingleLine: All 14 | AllowShortIfStatementsOnASingleLine: true 15 | AllowShortLoopsOnASingleLine: true 16 | AlwaysBreakBeforeMultilineStrings: true 17 | BinPackArguments: true 18 | BinPackParameters: true 19 | BreakBeforeTernaryOperators: false 20 | BreakConstructorInitializers: BeforeComma 21 | Cpp11BracedListStyle: false 22 | FixNamespaceComments: true 23 | IndentCaseLabels: false 24 | IndentWrappedFunctionNames: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.circleci 3 | !/.gitignore 4 | !/.clang-format 5 | !*.cpp 6 | !*.h 7 | !/Makefile -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: ModLoader.so 2 | 3 | ModLoader.so: main.cpp dep.cpp dep.h PFishHook.h StaticHook.h libPFishHook.a 4 | g++ -g -std=c++2a -shared -fPIC main.cpp dep.cpp -o ModLoader.so -L . -lPFishHook -lZydis -ldl -lstdc++fs 5 | 6 | clean: 7 | -rm ModLoader.so -------------------------------------------------------------------------------- /PFishHook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | enum HookStatus 3 | { 4 | FHSuccess, 5 | FHDecodeFailed, 6 | FHMprotectFail, 7 | FHAllocFailed, 8 | FHPatchFailed, 9 | FHTooManyPatches, 10 | FHUnrecognizedRIP, 11 | }; 12 | 13 | extern "C" HookStatus HookIt(void* oldfunc, void** poutold, void* newfunc); 14 | extern "C" HookStatus UnHook(void* oldfunc, void* func); -------------------------------------------------------------------------------- /StaticHook.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern "C" int mcpelauncher_hook(void *sym, void *func, void **rev); 5 | static void *handle = dlopen(nullptr, RTLD_LAZY); 6 | 7 | struct RegisterStaticHook { 8 | 9 | RegisterStaticHook(const char *sym, void *hook, void **org) { 10 | auto r = dlsym(handle, sym); 11 | if (r == nullptr) { printf("Symbol not found: %s\n", sym); } 12 | mcpelauncher_hook(r, hook, org); 13 | } 14 | 15 | // workaround for a warning 16 | template RegisterStaticHook(const char *sym, T hook, void **org) { 17 | union { 18 | T a; 19 | void *b; 20 | } hookUnion; 21 | hookUnion.a = hook; 22 | RegisterStaticHook(sym, hookUnion.b, org); 23 | } 24 | }; 25 | 26 | #define _TInstanceHook(class_inh, pclass, iname, sym, ret, args...) \ 27 | struct _TInstanceHook_##iname class_inh { \ 28 | static ret (_TInstanceHook_##iname::*_original)(args); \ 29 | template static ret original(pclass *_this, Params &&... params) { \ 30 | return (((_TInstanceHook_##iname *)_this)->*_original)(std::forward(params)...); \ 31 | } \ 32 | ret _hook(args); \ 33 | }; \ 34 | static RegisterStaticHook _TRInstanceHook_##iname(#sym, &_TInstanceHook_##iname::_hook, (void **)&_TInstanceHook_##iname::_original); \ 35 | ret (_TInstanceHook_##iname::*_TInstanceHook_##iname::_original)(args); \ 36 | ret _TInstanceHook_##iname::_hook(args) 37 | #define _TInstanceDefHook(iname, sym, ret, type, args...) _TInstanceHook( : public type, type, iname, sym, ret, args) 38 | #define _TInstanceNoDefHook(iname, sym, ret, args...) _TInstanceHook(, void, iname, sym, ret, args) 39 | 40 | #define _TStaticHook(pclass, iname, sym, ret, args...) \ 41 | struct _TStaticHook_##iname pclass { \ 42 | static ret (*_original)(args); \ 43 | template static ret original(Params &&... params) { return (*_original)(std::forward(params)...); } \ 44 | static ret _hook(args); \ 45 | }; \ 46 | static RegisterStaticHook _TRStaticHook_##iname(#sym, &_TStaticHook_##iname::_hook, (void **)&_TStaticHook_##iname::_original); \ 47 | ret (*_TStaticHook_##iname::_original)(args); \ 48 | ret _TStaticHook_##iname::_hook(args) 49 | #define _TStaticDefHook(iname, sym, ret, type, args...) _TStaticHook( : public type, iname, sym, ret, args) 50 | #define _TStaticNoDefHook(iname, sym, ret, args...) _TStaticHook(, iname, sym, ret, args) 51 | 52 | #define THook2(iname, ret, sym, args...) _TStaticNoDefHook(iname, sym, ret, args) 53 | #define THook(ret, sym, args...) THook2(sym, ret, sym, args) 54 | #define TClasslessInstanceHook2(iname, ret, sym, args...) _TInstanceNoDefHook(iname, sym, ret, args) 55 | #define TClasslessInstanceHook(ret, sym, args...) TClasslessInstanceHook2(sym, ret, sym, args) 56 | #define TInstanceHook2(iname, ret, sym, type, args...) _TInstanceDefHook(iname, sym, ret, type, args) 57 | #define TInstanceHook(ret, sym, type, args...) TInstanceHook2(sym, ret, sym, type, args) 58 | #define TStaticHook2(iname, ret, sym, type, args...) _TStaticDefHook(iname, sym, ret, type, args) 59 | #define TStaticHook(ret, sym, type, args...) TStaticHook2(sym, ret, sym, type, args) 60 | -------------------------------------------------------------------------------- /dep.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | namespace fs = std::filesystem; 7 | 8 | std::vector getDependencies(fs::path path) { 9 | Elf64_Ehdr header; 10 | auto file = fopen(path.c_str(), "r"); 11 | if (file == nullptr) { 12 | perror("ModLoader: Failed to open mod"); 13 | return {}; 14 | } 15 | if (fread(&header, sizeof(header), 1, file) != 1) { 16 | perror("ModLoader: Failed to read mod header"); 17 | fclose(file); 18 | return {}; 19 | } 20 | fseek(file, (long)header.e_phoff, SEEK_SET); 21 | char phdr[header.e_phentsize * header.e_phnum]; 22 | if (fread(phdr, header.e_phentsize, header.e_phnum, file) != header.e_phnum) { 23 | perror("ModLoader: Failed to read phnum"); 24 | fclose(file); 25 | return {}; 26 | } 27 | Elf64_Phdr *dynamicEntry = nullptr; 28 | for (int i = 0; i < header.e_phnum; i++) { 29 | Elf64_Phdr &entry = *((Elf64_Phdr *)&phdr[header.e_phentsize * i]); 30 | if (entry.p_type == PT_DYNAMIC) dynamicEntry = &entry; 31 | } 32 | if (dynamicEntry == nullptr) { 33 | fprintf(stderr, "ModLoader: PT_DYNAMIC not found"); 34 | fclose(file); 35 | return {}; 36 | } 37 | size_t dynamicDataCount = dynamicEntry->p_filesz / sizeof(Elf64_Dyn); 38 | Elf64_Dyn dynamicData[dynamicDataCount]; 39 | fseek(file, (long)dynamicEntry->p_offset, SEEK_SET); 40 | if (fread(dynamicData, sizeof(Elf64_Dyn), dynamicDataCount, file) != dynamicDataCount) { 41 | perror("ModLoader: PT_DYNAMIC cannot be read"); 42 | fclose(file); 43 | return {}; 44 | } 45 | size_t strtabOff = 0; 46 | size_t strtabSize = 0; 47 | for (int i = 0; i < dynamicDataCount; i++) { 48 | if (dynamicData[i].d_tag == DT_STRTAB) { 49 | strtabOff = dynamicData[i].d_un.d_val; 50 | } else if (dynamicData[i].d_tag == DT_STRSZ) { 51 | strtabSize = dynamicData[i].d_un.d_val; 52 | } 53 | } 54 | if (strtabOff == 0 || strtabSize == 0) { 55 | fprintf(stderr, "ModLoader: strtab not found"); 56 | fclose(file); 57 | return {}; 58 | } 59 | std::vector strtab; 60 | strtab.resize(strtabSize); 61 | fseek(file, (long)strtabOff, SEEK_SET); 62 | if (fread(strtab.data(), 1, strtabSize, file) != strtabSize) { 63 | perror("ModLoader: strtab cannot be read"); 64 | fclose(file); 65 | return {}; 66 | } 67 | std::vector ret; 68 | for (int i = 0; i < dynamicDataCount; i++) { 69 | if (dynamicData[i].d_tag == DT_NEEDED) ret.emplace_back(&strtab[dynamicData[i].d_un.d_val]); 70 | } 71 | return ret; 72 | } -------------------------------------------------------------------------------- /dep.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | std::vector getDependencies(std::filesystem::path path); -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "PFishHook.h" 10 | #include "dep.h" 11 | 12 | #include "StaticHook.h" 13 | 14 | struct hook_defs { 15 | void *hook, **original; 16 | }; 17 | 18 | namespace fs = std::filesystem; 19 | 20 | static std::vector *mods; 21 | static std::map *hooks; 22 | 23 | struct BedrockLog { 24 | static void log(uint area, uint level, char const *tag, int prip, char const *content, ...); 25 | }; 26 | 27 | extern "C" void mcpelauncher_log(uint level, char const *tag, char const *content) { BedrockLog::log(0x800, 2, tag, level, "%s", content); } 28 | 29 | int mcpelauncher_hook_internal(void *sym, void *func, void **rev) { 30 | auto ret = HookIt(sym, rev, func); 31 | switch (ret) { 32 | case FHSuccess: return 0; 33 | case FHAllocFailed: mcpelauncher_log(5, "hook", "Hook failed: AllocFailed\n"); return -ret; 34 | case FHDecodeFailed: mcpelauncher_log(5, "hook", "Hook failed: DecodeFailed\n"); return -ret; 35 | case FHMprotectFail: mcpelauncher_log(5, "hook", "Hook failed: MProtectFailed\n"); return -ret; 36 | case FHPatchFailed: mcpelauncher_log(5, "hook", "Hook failed: PatchFailed\n"); return -ret; 37 | case FHTooManyPatches: mcpelauncher_log(5, "hook", "Hook failed: TooManyPatches\n"); return -ret; 38 | case FHUnrecognizedRIP: mcpelauncher_log(5, "hook", "Hook failed: UnrecognizedRIP\n"); return -ret; 39 | default: mcpelauncher_log(5, "hook", "Hook failed: Unknown error\n"); return -ret; 40 | } 41 | } 42 | 43 | extern "C" int mcpelauncher_hook(void *symbol, void *hook, void **original) { 44 | auto def = hooks->find(symbol); 45 | if (def == hooks->end()) { 46 | auto result = mcpelauncher_hook_internal(symbol, hook, original); 47 | hooks->insert({ symbol, { hook, original } }); 48 | return result; 49 | } else { 50 | *original = *def->second.original; 51 | *def->second.original = hook; 52 | def->second = { hook, original }; 53 | return 0; 54 | } 55 | } 56 | 57 | TClasslessInstanceHook(void, _ZN14ServerInstance17startServerThreadEv) { 58 | original(this); 59 | std::set set; 60 | for (auto mod : *mods) { 61 | auto set_server = (void (*)(void *))dlsym(mod, "mod_set_server"); 62 | if (set_server && set.count(set_server) == 0) { 63 | set_server(this); 64 | set.insert(set_server); 65 | } 66 | } 67 | } 68 | 69 | void loadMods(fs::path path, std::set &others) { 70 | static std::set set; 71 | auto deps = getDependencies(path); 72 | for (auto const &dep : deps) { 73 | auto name = path.parent_path(); 74 | name /= dep; 75 | if (others.count(name) > 0) { 76 | others.erase(name); 77 | loadMods(name, others); 78 | others.erase(name); 79 | } 80 | } 81 | printf("Loading mod: %s\n", path.stem().c_str()); 82 | void *mod = dlopen(path.c_str(), RTLD_NOW); 83 | if (!mod) { 84 | fprintf(stderr, "Failed to load %s: %s\n", path.stem().c_str(), dlerror()); 85 | return; 86 | } 87 | mods->emplace_back(mod); 88 | auto mod_init = (void (*)(void))dlsym(mod, "mod_init"); 89 | if (mod_init && set.count(mod_init) == 0) { 90 | mod_init(); 91 | set.insert(mod_init); 92 | } 93 | } 94 | 95 | void loadModsFromDirectory(fs::path base) { 96 | if (fs::exists(base) && fs::is_directory(base)) { 97 | std::set modsToLoad; 98 | for (auto mod : fs::directory_iterator{ base }) { 99 | if (mod.path().extension() == ".so") { modsToLoad.insert(mod.path()); } 100 | } 101 | while (!modsToLoad.empty()) { 102 | auto it = modsToLoad.begin(); 103 | auto path = *it; 104 | modsToLoad.erase(it); 105 | 106 | loadMods(path, modsToLoad); 107 | } 108 | std::set set; 109 | for (auto mod : *mods) { 110 | auto mod_exec = (void (*)(void))dlsym(mod, "mod_exec"); 111 | if (mod_exec && set.count(mod_exec) == 0) { 112 | mod_exec(); 113 | set.insert(mod_exec); 114 | } 115 | } 116 | } 117 | } 118 | 119 | void mod_init(void) __attribute__((constructor)); 120 | 121 | void mod_init(void) { 122 | setenv("LD_LIBRARY_PATH", "./lib:./mods:.", 1); 123 | mods = new std::vector(); 124 | hooks = new std::map(); 125 | printf("ModLoader Loading...\n"); 126 | loadModsFromDirectory("mods"); 127 | } --------------------------------------------------------------------------------