Skip to content

Commit fa8c8e8

Browse files
authored
Merge pull request #2 from TrialCarrot/main
Add Dobby to replace And64InlineHook.
2 parents a5dd6d5 + 4818a5c commit fa8c8e8

8 files changed

Lines changed: 206 additions & 9 deletions

File tree

app/src/main/jni/Android.mk

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ LOCAL_MODULE := Keystone
88
LOCAL_SRC_FILES := $(KITTYMEMORY_PATH)/Deps/Keystone/libs-android/$(TARGET_ARCH_ABI)/libkeystone.a
99
include $(PREBUILT_STATIC_LIBRARY)
1010

11+
# Dobby
12+
include $(CLEAR_VARS)
13+
LOCAL_MODULE := Dobby
14+
LOCAL_SRC_FILES := Dobby/${TARGET_ARCH_ABI}/libdobby.a
15+
include $(PREBUILT_STATIC_LIBRARY)
16+
1117
# Here is the name of your lib.
1218
# When you change the lib name, change also on System.loadLibrary("") under OnCreate method on StaticActivity.java
1319
# Both must have same name
@@ -24,6 +30,7 @@ LOCAL_ARM_MODE := arm
2430

2531
LOCAL_C_INCLUDES += $(LOCAL_PATH)
2632
LOCAL_C_INCLUDES += $(LOCAL_PATH)/Includes/
33+
LOCAL_C_INCLUDES += $(LOCAL_PATH)/Dobby/
2734

2835
# Here you add the cpp file to compile
2936
LOCAL_SRC_FILES := Main.cpp \
@@ -44,6 +51,6 @@ LOCAL_SRC_FILES := Main.cpp \
4451
KittyMemory/MemoryBackup.cpp \
4552
And64InlineHook/And64InlineHook.cpp \
4653

47-
LOCAL_STATIC_LIBRARIES := Keystone
54+
LOCAL_STATIC_LIBRARIES := Keystone Dobby
4855

4956
include $(BUILD_SHARED_LIBRARY)

app/src/main/jni/Dobby/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
The latest version of [Dobby](https://github.com/jmpews/Dobby.git) is impossiable to compile.
2+
To build Dobby, please run `git checkout 0932d69c320e786672361ab53825ba8f4245e9d3`
3+
For more details, please see https://github.com/jmpews/Dobby/issues/257.
4+
5+
These pre-compiled objects use CMake options:
6+
```cmake
7+
option(DOBBY_GENERATE_SHARED "Build shared library" OFF)
8+
option(DOBBY_DEBUG "Enable debug logging" OFF)
9+
option(NearBranch "Enable near branch trampoline" ON)
10+
option(FullFloatingPointRegisterPack "Save and pack all floating-point registers" OFF)
11+
option(Plugin.SymbolResolver "Enable symbol resolver" ON)
12+
option(Plugin.ImportTableReplace "Enable import table replace " ON)
13+
option(Plugin.Android.BionicLinkerUtil "Enable android bionic linker util" ON)
14+
option(DOBBY_BUILD_EXAMPLE "Build example" OFF)
15+
option(DOBBY_BUILD_TEST "Build test" OFF)
16+
add_subdirectory(dobby) # Compile Dobby
17+
```
1.17 MB
Binary file not shown.
818 KB
Binary file not shown.

app/src/main/jni/Dobby/dobby.h

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#ifndef dobby_h
2+
#define dobby_h
3+
4+
#ifdef __cplusplus
5+
extern "C" {
6+
#endif
7+
8+
#include <stdbool.h>
9+
#include <stdint.h>
10+
11+
typedef uintptr_t addr_t;
12+
typedef uint32_t addr32_t;
13+
typedef uint64_t addr64_t;
14+
15+
typedef void *dobby_dummy_func_t;
16+
typedef void *asm_func_t;
17+
18+
#if defined(__arm__)
19+
typedef struct {
20+
uint32_t dummy_0;
21+
uint32_t dummy_1;
22+
23+
uint32_t dummy_2;
24+
uint32_t sp;
25+
26+
union {
27+
uint32_t r[13];
28+
struct {
29+
uint32_t r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12;
30+
} regs;
31+
} general;
32+
33+
uint32_t lr;
34+
} DobbyRegisterContext;
35+
#elif defined(__arm64__) || defined(__aarch64__)
36+
#define ARM64_TMP_REG_NDX_0 17
37+
38+
typedef union _FPReg {
39+
__int128_t q;
40+
struct {
41+
double d1;
42+
double d2;
43+
} d;
44+
struct {
45+
float f1;
46+
float f2;
47+
float f3;
48+
float f4;
49+
} f;
50+
} FPReg;
51+
52+
// register context
53+
typedef struct {
54+
uint64_t dmmpy_0; // dummy placeholder
55+
uint64_t sp;
56+
57+
uint64_t dmmpy_1; // dummy placeholder
58+
union {
59+
uint64_t x[29];
60+
struct {
61+
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,
62+
x23, x24, x25, x26, x27, x28;
63+
} regs;
64+
} general;
65+
66+
uint64_t fp;
67+
uint64_t lr;
68+
69+
union {
70+
FPReg q[32];
71+
struct {
72+
FPReg q0, q1, q2, q3, q4, q5, q6, q7;
73+
// [!!! READ ME !!!]
74+
// for Arm64, can't access q8 - q31, unless you enable full floating-point register pack
75+
FPReg q8, q9, q10, q11, q12, q13, q14, q15, q16, q17, q18, q19, q20, q21, q22, q23, q24, q25, q26, q27, q28, q29,
76+
q30, q31;
77+
} regs;
78+
} floating;
79+
} DobbyRegisterContext;
80+
#elif defined(_M_IX86) || defined(__i386__)
81+
typedef struct _RegisterContext {
82+
uint32_t dummy_0;
83+
uint32_t esp;
84+
85+
uint32_t dummy_1;
86+
uint32_t flags;
87+
88+
union {
89+
struct {
90+
uint32_t eax, ebx, ecx, edx, ebp, esp, edi, esi;
91+
} regs;
92+
} general;
93+
94+
} DobbyRegisterContext;
95+
#elif defined(_M_X64) || defined(__x86_64__)
96+
typedef struct {
97+
uint64_t dummy_0;
98+
uint64_t rsp;
99+
100+
union {
101+
struct {
102+
uint64_t rax, rbx, rcx, rdx, rbp, rsp, rdi, rsi, r8, r9, r10, r11, r12, r13, r14, r15;
103+
} regs;
104+
} general;
105+
106+
uint64_t dummy_1;
107+
uint64_t flags;
108+
} DobbyRegisterContext;
109+
#endif
110+
111+
#define install_hook_name(name, fn_ret_t, fn_args_t...) \
112+
static fn_ret_t fake_##name(fn_args_t); \
113+
static fn_ret_t (*orig_##name)(fn_args_t); \
114+
/* __attribute__((constructor)) */ static void install_hook_##name(void *sym_addr) { \
115+
DobbyHook(sym_addr, (dobby_dummy_func_t)fake_##name, (dobby_dummy_func_t *)&orig_##name); \
116+
return; \
117+
} \
118+
fn_ret_t fake_##name(fn_args_t)
119+
120+
// memory code patch
121+
int DobbyCodePatch(void *address, uint8_t *buffer, uint32_t buffer_size);
122+
123+
// function inline hook
124+
int DobbyHook(void *address, dobby_dummy_func_t replace_func, dobby_dummy_func_t *origin_func);
125+
126+
// dynamic binary instruction instrument
127+
// for Arm64, can't access q8 - q31, unless enable full floating-point register pack
128+
typedef void (*dobby_instrument_callback_t)(void *address, DobbyRegisterContext *ctx);
129+
int DobbyInstrument(void *address, dobby_instrument_callback_t pre_handler);
130+
131+
// destroy and restore code patch
132+
int DobbyDestroy(void *address);
133+
134+
const char *DobbyGetVersion();
135+
136+
// symbol resolver
137+
void *DobbySymbolResolver(const char *image_name, const char *symbol_name);
138+
139+
// import table replace
140+
int DobbyImportTableReplace(char *image_name, char *symbol_name, dobby_dummy_func_t fake_func,
141+
dobby_dummy_func_t *orig_func);
142+
143+
// for arm, Arm64, try use b xxx instead of ldr absolute indirect branch
144+
// for x86, x64, always use absolute indirect jump
145+
void dobby_enable_near_branch_trampoline();
146+
void dobby_disable_near_branch_trampoline();
147+
148+
#ifdef __cplusplus
149+
}
150+
#endif
151+
152+
#endif
709 KB
Binary file not shown.
1.02 MB
Binary file not shown.

app/src/main/jni/Main.cpp

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
#include "Menu/Jni.hpp"
1818
#include "Includes/Macros.h"
1919

20+
// Dobby is a very powerful hook framework that including hook, stub, patch, and symbol resolve.
21+
// It can completely replace And64InlineHook and KittyMemory, so they are deprecated.
22+
#include "dobby.h" // https://github.com/jmpews/Dobby
23+
2024
bool noDeath;
2125
int scoreMul = 1, coinsMul = 1;
2226

@@ -136,6 +140,8 @@ void Update(void *instance) {
136140
return old_Update(instance);
137141
}
138142

143+
// This pattern of orig_xxx and hook_xxx can be completely replaced by macro `install_hook_name` from dobby.h.
144+
// You can modify it if you want.
139145
void (*old_AddScore)(void *instance, int score);
140146
void AddScore(void *instance, int score) {
141147
return old_AddScore(instance, score * scoreMul);
@@ -152,14 +158,18 @@ void AddCoins(void *instance, int count) {
152158
ElfScanner g_il2cppELF;
153159

154160
// we will run our hacks in a new thread so our while loop doesn't block process main thread
155-
void *hack_thread(void *) {
161+
void hack_thread() {
156162
LOGI(OBFUSCATE("pthread created"));
157163

158-
//Check if target lib is loaded
159-
/*do {
160-
sleep(1);
161-
} while (!isLibraryLoaded(targetLibName));*/
164+
// This loop should be always enabled in unity game
165+
// because libil2cpp.so is not loaded into memory immediately.
166+
while (!isLibraryLoaded(targetLibName)) {
167+
sleep(1); // Wait for target lib be loaded.
168+
}
162169

170+
// ElfScanner::createWithPath can actually be replaced by xdl_open() and xdl_info(),
171+
// but that's from https://github.com/hexhacking/xDL.
172+
// You can compile it if you want.
163173
do {
164174
sleep(1);
165175
// getElfBaseMap can also find lib base even if it was loaded from zipped base.apk
@@ -168,6 +178,8 @@ void *hack_thread(void *) {
168178

169179
LOGI(OBFUSCATE("%s has been loaded"), (const char *) targetLibName);
170180

181+
// In Android Studio, to switch between arm64-v8a and armeabi-v7a syntax highlighting,
182+
// You can modify the "Active ABI" in "Build Variants" to switch to another architecture for parsing.
171183
#if defined(__aarch64__)
172184
uintptr_t il2cppBase = g_il2cppELF.base();
173185

@@ -179,6 +191,9 @@ void *hack_thread(void *) {
179191
HOOK(targetLibName, str2Offset(OBFUSCATE("0x107A2FC")), AddCoins, old_AddCoins);
180192
HOOK(targetLibName, str2Offset(OBFUSCATE("0x1078C44")), Update, old_Update);
181193

194+
// This function can completely replace MemoryPatch::createWithHex:
195+
// int DobbyCodePatch(void *address, uint8_t *buffer, uint32_t buffer_size); (from dobby.h)
196+
// And it is more powerful and intuitive.
182197
gPatches.noDeath = MemoryPatch::createWithHex(il2cppBase + str2Offset(OBFUSCATE("0x1079728")), "C0 03 5F D6");
183198

184199
//HOOK(targetLibName, str2Offset(OBFUSCATE("0x1079728")), Kill, old_Kill);
@@ -195,12 +210,18 @@ void *hack_thread(void *) {
195210
#endif
196211

197212
LOGI(OBFUSCATE("Done"));
198-
return nullptr;
199213
}
200214

215+
// Functions with `__attribute__((constructor))` are executed immediately when System.loadLibrary("lib_name") is called.
216+
// If there are multiple such functions at the same time, `constructor(priority)` (the priority is an integer)
217+
// will determine the execution priority, otherwise the execution order is undefined behavior.
201218
__attribute__((constructor))
202219
void lib_main() {
203220
// Create a new thread so it does not block the main thread, means the game would not freeze
204-
pthread_t ptid;
205-
pthread_create(&ptid, NULL, hack_thread, NULL);
221+
// pthread_t ptid;
222+
// pthread_create(&ptid, NULL, hack_thread, NULL);
223+
224+
// In modern C++, you should use std::thread(yourFunction).detach() instead of pthread_create
225+
// because it is cross-platform and more intuitive.
226+
std::thread(hack_thread).detach();
206227
}

0 commit comments

Comments
 (0)