get rid of dobby

looks like chaos has been waiting for us
This commit is contained in:
Chloe committed 2026-02-25 21:32:34 -07:00
1 parent 1a68cc5eb9
commit 5caa533a4a
5 files changed
+221 -198

No files matched your search

+5 -36
View File
@@ -7,41 +7,10 @@ add_library(launcherfix SHARED launcher-fix.cpp)
target_compile_features(launcherfix PUBLIC cxx_std_20)
set_target_properties(launcherfix PROPERTIES CXX_EXTENSIONS OFF)
include(FetchContent)
option(USE_TULIPHOOK "enables choice of hooking library" OFF)
if (USE_TULIPHOOK)
FetchContent_Declare(TulipHook
GIT_REPOSITORY https://github.com/geode-sdk/TulipHook.git
GIT_TAG v3.1.4
)
set(TULIP_LINK_SOURCE ON CACHE INTERNAL "")
FetchContent_MakeAvailable(TulipHook)
target_compile_definitions(launcherfix PRIVATE -DUSE_TULIPHOOK=1)
target_link_libraries(launcherfix PRIVATE TulipHook TulipHookInclude)
else()
FetchContent_Declare(dobby
# this is the last version that builds on android at all
GIT_REPOSITORY https://github.com/jmpews/Dobby
GIT_TAG 0932d69c320e786672361ab53825ba8f4245e9d3
)
FetchContent_GetProperties(dobby)
if(NOT dobby_POPULATED)
FetchContent_Populate(dobby)
# fixes Dobby build
# add_compile_definitions(typeof=__typeof__)
set(DOBBY_DEBUG OFF CACHE INTERNAL "" FORCE)
set(DOBBY_GENERATE_SHARED OFF CACHE INTERNAL "" FORCE)
add_subdirectory("${dobby_SOURCE_DIR}" ${dobby_BINARY_DIR} EXCLUDE_FROM_ALL)
target_include_directories(launcherfix PRIVATE ${dobby_SOURCE_DIR}/include)
target_link_libraries(launcherfix PRIVATE dobby)
endif()
endif()
set_target_properties(launcherfix PROPERTIES
INTERPROCEDURAL_OPTIMIZATION ON
C_VISIBILITY_PRESET hidden
CXX_VISIBILITY_PRESET hidden
)
target_link_libraries(launcherfix PRIVATE log)
+209 -151
View File
@@ -6,16 +6,11 @@
#include <cstdint>
#include <android/log.h>
#include <link.h>
#include <span>
#include <unistd.h>
#include <sys/mman.h>
#ifndef DISABLE_LAUNCHER_FIX
#ifdef USE_TULIPHOOK
#include <tulip/TulipHook.hpp>
#else
#include <dobby.h>
#endif
#endif
#define LOG_TAG "GeodeLauncher-Fix"
class DataPaths {
public:
@@ -23,6 +18,8 @@ public:
std::string data_path{};
std::string load_symbols_from{};
bool patch_exceptions{};
static DataPaths& get_instance() {
static auto paths_instance = DataPaths();
return paths_instance;
@@ -32,6 +29,22 @@ private:
DataPaths() {}
};
struct ElfDynamicState {
std::uint32_t* hash_table{};
char* str_table{};
ElfW(Sym)* sym_table{};
void* plt_table{};
std::uint64_t plt_table_size_bytes{};
bool plt_table_rela{};
std::uint64_t soname_idx{};
bool is_empty() const {
return !hash_table || !str_table || !sym_table || !plt_table;
}
};
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setDataPath(
JNIEnv *env,
@@ -74,9 +87,30 @@ JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_setOriginalDataPath(
env->ReleaseStringUTFChars(data_path, data_path_str);
}
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_enableExceptionsRenaming(JNIEnv*, jobject) {
DataPaths::get_instance().patch_exceptions = true;
}
// copied from tuliphook source :)
bool set_mprotect(std::uintptr_t address, size_t size, uint32_t protection) {
auto const page_size = getpagesize();
auto const page_mask = page_size - 1;
auto const ptr = reinterpret_cast<uintptr_t>(address);
auto const aligned_ptr = ptr & (~page_mask);
auto const begin_size = ptr - aligned_ptr;
auto const page_count = (begin_size + size + page_mask) / page_size;
auto const aligned_size = page_count * page_size;
auto status = mprotect(reinterpret_cast<void*>(aligned_ptr), aligned_size, protection);
return status == 0;
}
// i copied this function from the android linker
std::uint32_t elfhash(const char *_name) {
const unsigned char *name = reinterpret_cast<const unsigned char*>(_name);
const auto *name = reinterpret_cast<const unsigned char*>(_name);
std::uint32_t h = 0, g;
while (*name) {
@@ -108,82 +142,162 @@ bool patch_symbol(std::uint32_t* hash_table, char* str_table, ElfW(Sym)* sym_tab
if (ELF_ST_BIND(sym->st_info) == STB_GLOBAL && ELF_ST_TYPE(sym->st_info) == STT_FUNC) {
// we found it! now go rename the symbol
#ifdef USE_TULIPHOOK
auto res = tulip::hook::writeMemory(sym_name, SYMBOL_PATCH_VALUE.data(), SYMBOL_PATCH_VALUE.size());
if (!res) {
__android_log_print(ANDROID_LOG_WARN, "GeodeLauncher-fix", "failed to patch symbol: %s", res.unwrapErr().c_str());
return false;
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Disabling symbol at %p (%s)", sym_name, sym_name);
if (!set_mprotect(reinterpret_cast<std::uintptr_t>(sym_name), SYMBOL_PATCH_VALUE.size(), PROT_READ | PROT_WRITE)) {
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "failed to patch symbol: could not set writable flag");
return false;
}
memcpy(sym_name, SYMBOL_PATCH_VALUE.data(), SYMBOL_PATCH_VALUE.size());
if (!set_mprotect(reinterpret_cast<std::uintptr_t>(sym_name), SYMBOL_PATCH_VALUE.size(), PROT_READ)) {
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "failed to patch symbol: could not remove writable flag");
return false;
}
#else
DobbyCodePatch(reinterpret_cast<void*>(sym_name), SYMBOL_PATCH_VALUE.data(), SYMBOL_PATCH_VALUE.size());
#endif
return true;
}
}
}
__android_log_print(ANDROID_LOG_WARN, "GeodeLauncher-fix", "could not find symbol %s to patch (hash: %u)", orig_name, hash);
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "could not find symbol %s to patch (hash: %u)", orig_name, hash);
return false;
}
std::vector<std::string> get_symbols_listing() {
auto symbol_path = DataPaths::get_instance().load_symbols_from;
if (symbol_path.empty()) {
// this is every function that i thought would be relevant
return {
"__gxx_personality_v0",
"__cxa_throw",
"__cxa_rethrow",
"__cxa_allocate_exception",
"__cxa_end_catch",
"__cxa_begin_catch",
"__cxa_guard_abort",
"__cxa_guard_acquire",
"__cxa_guard_release",
"__cxa_free_exception"
// this is every function that i thought would be relevant
constexpr std::array remove_symbols{
"__gxx_personality_v0",
"__cxa_throw",
"__cxa_rethrow",
"__cxa_allocate_exception",
"__cxa_end_catch",
"__cxa_begin_catch",
"__cxa_guard_abort",
"__cxa_guard_acquire",
"__cxa_guard_release",
"__cxa_free_exception"
// android 10's libc doesn't have these symbols
// and the ndk is forbidden from exporting them!! so keep it for old mods...
// "_Unwind_RaiseException",
// "_Unwind_Resume"
};
// android 10's libc doesn't have these symbols
// and the ndk is forbidden from exporting them!! so keep it for old mods...
// "_Unwind_RaiseException",
// "_Unwind_Resume"
};
bool perform_symbol_patches(dl_phdr_info* info, const ElfDynamicState& state) {
// patch symbol names
auto so_name = state.str_table + state.soname_idx;
if (strcmp(so_name, "libcocos2dcpp.so") != 0) {
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "DT_SONAME gives us %s! that's not good", so_name);
}
std::ifstream symbol_file{symbol_path};
if (!symbol_file) {
__android_log_print(ANDROID_LOG_WARN, "GeodeLauncher-fix", "failed to read symbol file at %s", symbol_path.c_str());
return {};
}
std::vector<std::string> symbols_list{};
std::string current_line{};
while (std::getline(symbol_file, current_line)) {
if (!current_line.empty()) {
symbols_list.push_back(current_line);
auto has_error = false;
for (const auto& symbol : remove_symbols) {
if (!patch_symbol(state.hash_table, state.str_table, state.sym_table, symbol)) {
has_error = true;
}
}
return symbols_list;
return !has_error;
}
#if defined(__LP64__)
#define ELF_R_SYM(i) ELF64_R_SYM(i)
#define ELF_R_TYPE(i) ELF64_R_TYPE(i)
#else
#define ELF_R_SYM(i) ELF32_R_SYM(i)
#define ELF_R_TYPE(i) ELF32_R_TYPE(i)
#endif
template <typename T, typename... Types>
concept is_one_of = (std::same_as<T, Types> || ...);
template <typename T> requires is_one_of<T, ElfW(Rela), ElfW(Rel)>
bool rewrite_symbol_reloc(std::uintptr_t addr, T& r, ElfW(Sym)* sym, void* func) {
auto type = ELF_R_TYPE(r.r_info);
// we're only interested in JUMP_SLOT symbols
if (type != R_AARCH64_JUMP_SLOT && type != R_ARM_JUMP_SLOT) {
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "asked to patch unknown symbol type: %llu", type);
return false;
}
auto write_addr = reinterpret_cast<std::uintptr_t*>(addr);
auto val = reinterpret_cast<std::uintptr_t>(func);
if constexpr (std::is_same_v<T, ElfW(Rela)>) {
// reloc = S + A
val += r.r_addend;
}
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Patching relocation at vaddr %#lx (offset=%#llx): %#lx => %#lx", addr, r.r_offset, *write_addr, val);
if (!set_mprotect(addr, sizeof(std::uintptr_t), PROT_WRITE | PROT_READ)) {
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Failed to patch relocation: failed to add writable flag");
return false;
}
// why use memcpy when you can do this :)
*write_addr = val;
if (!set_mprotect(addr, sizeof(std::uintptr_t), PROT_READ)) {
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "Failed to patch relocation: failed to remove writable flag");
return false;
}
return true;
}
FILE* fopen_hook(const char* pathname, const char* mode);
int rename_hook(const char* old_path, const char* new_path);
template <typename T> requires is_one_of<T, ElfW(Rela), ElfW(Rel)>
bool perform_reloc_patches(dl_phdr_info* info, const ElfDynamicState& state) {
auto table_size = state.plt_table_size_bytes / sizeof(T);
auto failed = false;
for (auto& plt : std::span{
reinterpret_cast<T*>(state.plt_table), table_size
}) {
auto sym = ELF_R_SYM(plt.r_info);
if (sym == 0) continue;
auto sym_data = state.sym_table + sym;
auto sym_name = state.str_table + sym_data->st_name;
auto reloc_addr = info->dlpi_addr + plt.r_offset;
if (strcmp(sym_name, "fopen") == 0) {
if (!rewrite_symbol_reloc(reloc_addr, plt, sym_data, reinterpret_cast<void*>(&fopen_hook))) {
failed = true;
}
} else if (strcmp(sym_name, "rename") == 0) {
if (!rewrite_symbol_reloc(reloc_addr, plt, sym_data, reinterpret_cast<void*>(&rename_hook))) {
failed = true;
}
}
}
return !failed;
}
int on_dl_iterate(dl_phdr_info* info, size_t size, void* data) {
// this is probably going to be gd
if (info->dlpi_name != nullptr && strstr(info->dlpi_name, "libcocos2dcpp.so") != nullptr) {
__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Base address of libcocos2dcpp: %#llx", info->dlpi_addr);
// step 1: get the dynamic table
std::uintptr_t dyn_addr = 0u;
for (auto i = 0u; i < info->dlpi_phnum; i++) {
auto phdr = info->dlpi_phdr + i;
if (phdr->p_type == PT_DYNAMIC) {
dyn_addr = info->dlpi_addr + phdr->p_vaddr;
for (auto& phdr : std::span{ info->dlpi_phdr, info->dlpi_phnum}) {
if (phdr.p_type == PT_DYNAMIC) {
dyn_addr = info->dlpi_addr + phdr.p_vaddr;
break;
}
}
if (dyn_addr == 0u) {
__android_log_print(ANDROID_LOG_WARN, "GeodeLauncher-fix", "failed to find libcocos dynamic section");
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "failed to find libcocos dynamic section");
return 0;
}
@@ -191,26 +305,40 @@ int on_dl_iterate(dl_phdr_info* info, size_t size, void* data) {
auto dyn_entry = reinterpret_cast<ElfW(Dyn)*>(dyn_addr);
auto dyn_end_reached = false;
std::uintptr_t sym_table_addr = 0u;
std::uintptr_t str_table_addr = 0u;
std::uintptr_t hash_table_addr = 0u;
std::uintptr_t soname_idx = 0u;
ElfDynamicState state{};
while (!dyn_end_reached) {
auto tag = dyn_entry->d_tag;
switch (dyn_entry->d_tag) {
case DT_SYMTAB:
sym_table_addr = info->dlpi_addr + dyn_entry->d_un.d_val;
state.sym_table = reinterpret_cast<ElfW(Sym)*>(info->dlpi_addr + dyn_entry->d_un.d_val);
break;
case DT_STRTAB:
str_table_addr = info->dlpi_addr + dyn_entry->d_un.d_val;
state.str_table = reinterpret_cast<char*>(info->dlpi_addr + dyn_entry->d_un.d_val);
break;
case DT_HASH:
hash_table_addr = info->dlpi_addr + dyn_entry->d_un.d_val;
state.hash_table = reinterpret_cast<std::uint32_t*>(info->dlpi_addr + dyn_entry->d_un.d_val);
break;
case DT_SONAME:
soname_idx = dyn_entry->d_un.d_val;
state.soname_idx = dyn_entry->d_un.d_val;
break;
case DT_JMPREL:
state.plt_table = reinterpret_cast<void*>(info->dlpi_addr + dyn_entry->d_un.d_val);
break;
case DT_PLTREL: {
// for Android, arm64 = DT_RELA, arm32 = DT_REL
// could just gate this based on architecture but whatever
auto val = dyn_entry->d_un.d_val;
state.plt_table_rela = val == DT_RELA;
if (val != DT_RELA && val != DT_REL) {
__android_log_print(ANDROID_LOG_WARN, LOG_TAG, "unrecognized PLTREL type %llx", val);
}
break;
}
case DT_PLTRELSZ:
state.plt_table_size_bytes = dyn_entry->d_un.d_val;
break;
case DT_NULL:
dyn_end_reached = true;
@@ -220,48 +348,38 @@ int on_dl_iterate(dl_phdr_info* info, size_t size, void* data) {
dyn_entry++;
}
if (hash_table_addr == 0u || str_table_addr == 0u || sym_table_addr == 0u) {
__android_log_print(ANDROID_LOG_ERROR, "GeodeLauncher-fix", "failed to parse dynamic section (at least one table is null)");
if (state.is_empty()) {
__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "failed to parse dynamic section (at least one table is null)");
return -1;
}
// patch symbol names
auto hash_table = reinterpret_cast<std::uint32_t*>(hash_table_addr);
auto str_table = reinterpret_cast<char*>(str_table_addr);
auto sym_table = reinterpret_cast<ElfW(Sym)*>(sym_table_addr);
bool failed = state.plt_table_rela
? !perform_reloc_patches<ElfW(Rela)>(info, state)
: !perform_reloc_patches<ElfW(Rel)>(info, state);
auto so_name = str_table + soname_idx;
if (strcmp(so_name, "libcocos2dcpp.so") != 0) {
__android_log_print(ANDROID_LOG_WARN, "GeodeLauncher-fix", "DT_SONAME gives us %s! that's not good", so_name);
}
auto has_error = false;
auto symbols_listing = get_symbols_listing();
for (const auto& symbol : symbols_listing) {
if (!patch_symbol(hash_table, str_table, sym_table, symbol.c_str())) {
has_error = true;
if (DataPaths::get_instance().patch_exceptions) {
if (!perform_symbol_patches(info, state)) {
failed = true;
}
}
if (has_error) {
if (failed) {
__android_log_print(
ANDROID_LOG_INFO,
"GeodeLauncher-fix",
"symbol patch diagnostics\nlibrary path: %s\naddrs: base=%p sym=%p str=%p hash=%p",
info->dlpi_name, reinterpret_cast<void*>(info->dlpi_addr), sym_table, str_table, hash_table
);
LOG_TAG,
"symbol patch diagnostics\nlibrary path: %s\naddrs: base=%p sym=%p str=%p hash=%p plt=%p",
info->dlpi_name, reinterpret_cast<void*>(info->dlpi_addr), state.sym_table, state.str_table, state.hash_table, state.plt_table
);
}
return 1;
}
return 0;
}
// this should be called after gd is loaded but before geode
extern "C"
JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_performExceptionsRenaming(JNIEnv*, jobject) {
extern "C" JNIEXPORT void JNICALL Java_com_geode_launcher_LauncherFix_performPatches(JNIEnv*, jobject) {
dl_iterate_phdr(on_dl_iterate, nullptr);
}
@@ -281,15 +399,13 @@ std::optional<std::string> redirect_path(const char* pathname) {
return std::nullopt;
}
FILE* (*fopen_original)(const char *pathname, const char *mode);
FILE* fopen_hook(const char* pathname, const char* mode) {
auto path_str = redirect_path(pathname);
auto redirect = path_str.has_value() ? path_str->c_str() : pathname;
return fopen_original(redirect, mode);
return fopen(redirect, mode);
}
int (*rename_original)(const char* old_path, const char* new_path);
int rename_hook(const char* old_path, const char* new_path) {
auto old_path_str = redirect_path(old_path);
auto old_redirect = old_path_str ? old_path_str->c_str() : old_path;
@@ -297,63 +413,5 @@ int rename_hook(const char* old_path, const char* new_path) {
auto new_path_str = redirect_path(new_path);
auto new_redirect = new_path_str ? new_path_str->c_str() : new_path;
return rename_original(old_redirect, new_redirect);
}
bool hook_function(void* addr, auto* hook, auto** orig) {
#ifdef USE_TULIPHOOK
using namespace tulip::hook;
HandlerMetadata metadata = {
std::make_shared<PlatformConvention>(),
AbstractFunction::from(hook)
};
auto handler_result = createHandler(addr, metadata);
if (!handler_result) {
__android_log_print(ANDROID_LOG_WARN, "GeodeLauncher-Fix",
"failed to create handler: %s\n", handler_result.unwrapErr().c_str());
return false;
}
auto handler = *handler_result;
HookMetadata hook_metadata = {0};
createHook(handler, reinterpret_cast<void*>(hook), hook_metadata);
WrapperMetadata wrapper_metadata = {
std::make_unique<PlatformConvention>(),
AbstractFunction::from(hook)
};
auto wrapper = createWrapper(addr, std::move(wrapper_metadata));
if (!wrapper) {
__android_log_print(ANDROID_LOG_WARN, "GeodeLauncher-Fix",
"failed to create wrapper: %s\n", wrapper.unwrapErr().c_str());
return false;
}
*reinterpret_cast<void**>(orig) = *wrapper;
return true;
#else
DobbyHook(
addr,
reinterpret_cast<dobby_dummy_func_t>(hook),
reinterpret_cast<dobby_dummy_func_t*>(orig)
);
return true;
#endif
}
[[gnu::constructor]] [[gnu::used]] void setup_hooks() {
#ifndef DISABLE_LAUNCHER_FIX
auto fopen_addr = dlsym(RTLD_NEXT, "fopen");
hook_function(fopen_addr, &fopen_hook, &fopen_original);
auto rename_addr = dlsym(RTLD_NEXT, "rename");
hook_function(rename_addr, &rename_hook, &rename_original);
#endif
return rename(old_redirect, new_redirect);
}
@@ -172,19 +172,13 @@ class GeometryDashActivity : AppCompatActivity(), Cocos2dxHelper.Cocos2dxHelperL
tryLoadLibrary(gdPackageInfo, Constants.COCOS_LIB_NAME)
if (GamePackageUtils.getGameVersionCode(packageManager) >= 39L) {
/*
val customSymbols = PreferenceUtils.get(this).getBoolean(PreferenceUtils.Key.CUSTOM_SYMBOL_LIST)
if (customSymbols) {
val symbolFile = File(LaunchUtils.getBaseDirectory(this), "exception_symbols.txt")
LauncherFix.enableCustomSymbolList(symbolFile.path)
}
*/
// this fix requires geode v3, which is 2.206+
// there is a short period in which 2.206 users will still have geode v2, but whatever. ig
LauncherFix.performExceptionsRenaming()
LauncherFix.enableExceptionsRenaming()
}
LauncherFix.performPatches()
loadInternalMods()
val baseView = createView()
@@ -13,7 +13,9 @@ object LauncherFix {
external fun setOriginalDataPath(dataPath: String)
external fun performExceptionsRenaming()
external fun enableExceptionsRenaming()
external fun performPatches()
external fun enableCustomSymbolList(symbolsPath: String)
}