Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,19 @@ project("bepinex_android" CXX ASM)
add_subdirectory(main)
add_subdirectory(fusion)

option(FUSION_BUILD_TESTS "Build the Android ARM64 return-buffer regression executable" OFF)
option(FUSION_BUILD_TESTS "Build the Android ARM64 native hook regression executables" OFF)
if(FUSION_BUILD_TESTS)
add_executable(return_buffer_test tests/return_buffer_test.cpp)
target_compile_options(return_buffer_test PRIVATE -UNDEBUG)
target_link_libraries(return_buffer_test PRIVATE fusion)
add_executable(short_hook_test tests/short_hook_test.cpp tests/short_functions.S)
target_compile_options(short_hook_test PRIVATE -UNDEBUG)
target_link_libraries(short_hook_test PRIVATE fusion)
add_executable(chained_hook_test tests/chained_hook_test.cpp tests/chained_functions.S)
target_compile_options(chained_hook_test PRIVATE -UNDEBUG)
target_link_options(chained_hook_test PRIVATE -Wl,--no-relax)
target_link_libraries(chained_hook_test PRIVATE fusion)
add_executable(crowded_hook_test tests/crowded_hook_test.cpp)
target_compile_options(crowded_hook_test PRIVATE -UNDEBUG)
target_link_libraries(crowded_hook_test PRIVATE fusion)
endif()
150 changes: 135 additions & 15 deletions app/src/main/cpp/dobby.h
Original file line number Diff line number Diff line change
@@ -1,32 +1,152 @@
/*
* Dobby — ARM64 inline hook framework
* https://github.com/jmpews/Dobby
* MIT License
*/
#ifndef DOBBY_H
#define DOBBY_H
#ifndef dobby_h
#define dobby_h

#ifdef __cplusplus
extern "C" {
#endif

#include <stdbool.h>
#include <stdint.h>

// Hook a function. Returns 0 on success.
int DobbyHook(void* address, void* replace_call, void** origin_call);
typedef uintptr_t addr_t;
typedef uint32_t addr32_t;
typedef uint64_t addr64_t;

// Remove a hook. Returns 0 on success.
int DobbyDestroy(void* address);
typedef void *dobby_dummy_func_t;
typedef void *asm_func_t;

// Platform-specific: get the page size
int DobbyGetGlobalPageSize();
#if defined(__arm__)
typedef struct {
uint32_t dummy_0;
uint32_t dummy_1;

// Enable/disable near branch trampoline (ARM64)
uint32_t dummy_2;
uint32_t sp;

union {
uint32_t r[13];
struct {
uint32_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12;
} regs;
} general;

uint32_t lr;
} DobbyRegisterContext;
#elif defined(__arm64__) || defined(__aarch64__)
#define ARM64_TMP_REG_NDX_0 17

typedef union _FPReg {
__int128_t q;
struct {
double d1;
double d2;
} d;
struct {
float f1;
float f2;
float f3;
float f4;
} f;
} FPReg;

// register context
typedef struct {
uint64_t dmmpy_0; // dummy placeholder
uint64_t sp;

uint64_t dmmpy_1; // dummy placeholder
union {
uint64_t x[29];
struct {
uint64_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22,
x23, x24, x25, x26, x27, x28;
} regs;
} general;

uint64_t fp;
uint64_t lr;

union {
FPReg q[32];
struct {
FPReg q0, q1, q2, q3, q4, q5, q6, q7;
// [!!! READ ME !!!]
// for Arm64, can't access q8 - q31, unless you enable full floating-point register pack
FPReg q8, q9, q10, q11, q12, q13, q14, q15, q16, q17, q18, q19, q20, q21, q22, q23, q24, q25, q26, q27, q28, q29,
q30, q31;
} regs;
} floating;
} DobbyRegisterContext;
#elif defined(_M_IX86) || defined(__i386__)
typedef struct _RegisterContext {
uint32_t dummy_0;
uint32_t esp;

uint32_t dummy_1;
uint32_t flags;

union {
struct {
uint32_t eax, ebx, ecx, edx, ebp, esp, edi, esi;
} regs;
} general;

} DobbyRegisterContext;
#elif defined(_M_X64) || defined(__x86_64__)
typedef struct {
uint64_t dummy_0;
uint64_t rsp;

union {
struct {
uint64_t rax, rbx, rcx, rdx, rbp, rsp, rdi, rsi, r8, r9, r10, r11, r12, r13, r14, r15;
} regs;
} general;

uint64_t dummy_1;
uint64_t flags;
} DobbyRegisterContext;
#endif

#define install_hook_name(name, fn_ret_t, fn_args_t...) \
static fn_ret_t fake_##name(fn_args_t); \
static fn_ret_t (*orig_##name)(fn_args_t); \
/* __attribute__((constructor)) */ static void install_hook_##name(void *sym_addr) { \
DobbyHook(sym_addr, (dobby_dummy_func_t)fake_##name, (dobby_dummy_func_t *)&orig_##name); \
return; \
} \
fn_ret_t fake_##name(fn_args_t)

// memory code patch
int DobbyCodePatch(void *address, uint8_t *buffer, uint32_t buffer_size);

// function inline hook
int DobbyHook(void *address, dobby_dummy_func_t replace_func, dobby_dummy_func_t *origin_func);

// dynamic binary instruction instrument
// for Arm64, can't access q8 - q31, unless enable full floating-point register pack
typedef void (*dobby_instrument_callback_t)(void *address, DobbyRegisterContext *ctx);
int DobbyInstrument(void *address, dobby_instrument_callback_t pre_handler);

// destroy and restore code patch
int DobbyDestroy(void *address);

const char *DobbyGetVersion();

// symbol resolver
void *DobbySymbolResolver(const char *image_name, const char *symbol_name);

// import table replace
int DobbyImportTableReplace(char *image_name, char *symbol_name, dobby_dummy_func_t fake_func,
dobby_dummy_func_t *orig_func);

// for arm, Arm64, try use b xxx instead of ldr absolute indirect branch
// for x86, x64, always use absolute indirect jump
void dobby_enable_near_branch_trampoline();
void dobby_disable_near_branch_trampoline();

#ifdef __cplusplus
}
#endif

#endif // DOBBY_H
#endif
4 changes: 3 additions & 1 deletion app/src/main/cpp/fusion/include/fusion.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,13 @@ const char *il2cpp_method_get_name(void *method);
int il2cpp_init(char *domain_name);

/* Hook management */
void il2cpp_install_init_hook(void *hookCallback);
bool il2cpp_install_init_hook(void *hookCallback);
void il2cpp_destroy_init_hook();

/* SafeHook / Dobby (safehook.cpp) */
bool safehook_initialize(void *lib_handle, uintptr_t lib_base, void *(*allocator)(void *, void *, size_t));
int safehook_install(void *target, void *replacement, void **original);
bool safehook_remove(void *target);

/* libunity hooks (libunity.cpp) */
bool try_hook_libunity(const char *libUnityPath, const char *fallbackLibUnityPath);
Expand Down
10 changes: 3 additions & 7 deletions app/src/main/cpp/fusion/src/exports.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
#include <cstdint>
#include <cstdbool>
#include <cstring>
#include <mutex>
#include <sys/mman.h>
#include <unistd.h>
#include "dobby.h"
#include "fusion.h"

namespace {
std::mutex hookMutex;
thread_local void *returnBuffer = nullptr;
}

Expand Down Expand Up @@ -67,7 +65,6 @@ static void *create_return_buffer_bridge(void *detour, size_t pageSize)

void *hook(void *target, void *detour, bool specialReturnBuffer)
{
std::lock_guard<std::mutex> guard(hookMutex);
if (!target || !detour) return nullptr;
const long pageSize = sysconf(_SC_PAGESIZE);
if (pageSize <= 0) return nullptr;
Expand All @@ -78,7 +75,7 @@ void *hook(void *target, void *detour, bool specialReturnBuffer)
return nullptr;
}
void *original = nullptr;
int rc = DobbyHook(target, bridge ? bridge : detour, &original);
int rc = safehook_install(target, bridge ? bridge : detour, &original);
if (rc != 0) {
if (bridge) munmap(bridge, static_cast<size_t>(pageSize));
__android_log_print(ANDROID_LOG_ERROR, "Fusion", "DobbyHook failed at %p: %d", target, rc);
Expand All @@ -89,8 +86,7 @@ void *hook(void *target, void *detour, bool specialReturnBuffer)

bool unhook_checked(void *target)
{
std::lock_guard<std::mutex> guard(hookMutex);
return target && DobbyDestroy(target) == 0;
return safehook_remove(target);
}

void unhook(void *target) { (void)unhook_checked(target); }
Expand Down
9 changes: 6 additions & 3 deletions app/src/main/cpp/fusion/src/fusion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extern "C" {

/* Hooking */
bool il2cpp_initialize(const char *il2cppPath);
void il2cpp_install_init_hook(void *hookCallback);
bool il2cpp_install_init_hook(void *hookCallback);
void il2cpp_destroy_init_hook();
int il2cpp_init(char *domain_name);
void *il2cpp_get_handle();
Expand Down Expand Up @@ -255,15 +255,18 @@ bool fusion_bootstrap_from_libmain(JNIEnv *env)
void *il2cppHandle = il2cpp_get_handle();
uintptr_t il2cppBase = il2cpp_get_library_base();

/* Use code cave allocator for Dobby trampolines — bypasses Android W^X. */
/* SafeHook uses the standalone Dobby build with a mandatory four-byte entry branch. */
if (!safehook_initialize(il2cppHandle, il2cppBase, allocate_injected)) {
LOGE("safehook_initialize failed");
return false;
}
LOGI("SafeHook initialized");

/* 4. Install il2cpp_init hook (one-shot, will fire when Unity calls il2cpp_init) */
il2cpp_install_init_hook(reinterpret_cast<void *>(il2cpp_init_hook));
if (!il2cpp_install_init_hook(reinterpret_cast<void *>(il2cpp_init_hook))) {
LOGE("Cannot install il2cpp_init hook");
return false;
}

LOGI("Fusion bootstrap complete 鈥?waiting for il2cpp_init...");
return true;
Expand Down
17 changes: 10 additions & 7 deletions app/src/main/cpp/fusion/src/hooking/il2cpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <string>
#include <cstring>
#include <android/log.h>
#include "dobby.h"

#define TAG "FusionIL2CPP"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
Expand Down Expand Up @@ -112,31 +111,32 @@ int il2cpp_init(char *domain_name)
}

/* Install one-shot DobbyHook on il2cpp_init. Callback should call destroy_init_hook before chaining. */
void il2cpp_install_init_hook(void *hookCallback)
bool il2cpp_install_init_hook(void *hookCallback)
{
if (!g_p_il2cpp_init) {
LOGE("il2cpp_init address not resolved 鈥?call il2cpp_initialize first!");
return;
return false;
}

if (!hookCallback) {
LOGE("Hook function is null!");
return;
return false;
}

g_init_hook_fn = reinterpret_cast<il2cpp_init_t>(hookCallback);

int result = DobbyHook(
int result = safehook_install(
g_p_il2cpp_init,
hookCallback,
reinterpret_cast<void **>(&g_orig_il2cpp_init));

if (result != 0) {
LOGE("DobbyHook failed: %d", result);
return;
return false;
}

LOGI("DobbyHook installed on il2cpp_init");
return true;
}

/* Destroy the one-shot hook — il2cpp_init calls go directly to original after this. */
Expand All @@ -147,7 +147,10 @@ void il2cpp_destroy_init_hook()
return;
}

DobbyDestroy(g_p_il2cpp_init);
if (!safehook_remove(g_p_il2cpp_init)) {
LOGE("Failed to remove il2cpp_init hook");
return;
}
g_init_hook_fn = nullptr;
LOGI("DobbyHook destroyed (one-shot complete)");
}
Expand Down
5 changes: 2 additions & 3 deletions app/src/main/cpp/fusion/src/hooking/libunity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include "fusion.h"
#include "utilities/elf.h"
#include "dobby.h"
#include <dlfcn.h>
#include <string>
#include <cstring>
Expand Down Expand Up @@ -116,7 +115,7 @@ bool try_hook_libunity(const char *libUnityPath, const char *fallbackLibUnityPat

LOGI("scripting_method_invoke @ %p (base=%p, rva=0x%zx)", target, (void*)base, rva);

int ret = DobbyHook(
int ret = safehook_install(
target,
reinterpret_cast<void *>(scripting_method_invoke_hook),
reinterpret_cast<void **>(&g_original_scripting_method_invoke));
Expand All @@ -133,7 +132,7 @@ bool try_hook_libunity(const char *libUnityPath, const char *fallbackLibUnityPat

LOGI("path_check @ %p (base=%p, rva=0x%zx)", path_check_target, (void*)base, path_check_rva);

int ret2 = DobbyHook(
int ret2 = safehook_install(
path_check_target,
reinterpret_cast<void *>(path_check_hook),
reinterpret_cast<void **>(&g_original_path_check));
Expand Down
Loading
Loading