#include #include #include #include #include #include "sol/sol.hpp" #include "xhook/xhook.h" #include "hook.hpp" #include "ui.hpp" #include "imgui/imgui.h" #include "imgui/imgui_impl_android.h" #include "imgui/imgui_impl_opengl3.h" #include "il2cpp.hpp" #include "dobby.h" extern ImVec2 g_window_pos; extern ImVec2 g_window_size; #define IMGUI_WND_EDGE 50 bool ImGui_IsPosInWnd(float x, float y) { auto start_x = g_window_pos.x - IMGUI_WND_EDGE; auto start_y = g_window_pos.y - IMGUI_WND_EDGE; auto end_x = g_window_pos.x + g_window_size.x + IMGUI_WND_EDGE; auto end_y = g_window_pos.y + g_window_size.y + IMGUI_WND_EDGE; if (x >= start_x && x <= end_x && y >= start_y && y <= end_y) { return true; } return false; } using eglSwapBuffers_fnT = EGLBoolean(EGLDisplay dpy, EGLSurface surface); eglSwapBuffers_fnT* old_eglSwapBuffers = nullptr; EGLint g_width = 0; EGLint g_height = 0; std::atomic g_imgui_started = false; bool g_need_ui_penetration = false; bool g_no_rander = false; EGLBoolean my_eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) { if (g_no_rander) { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); } ui_eglSwapBuffers(dpy, surface); if (g_width == 0 || g_height == 0) { eglQuerySurface(dpy, surface, EGL_WIDTH, &g_width); eglQuerySurface(dpy, surface, EGL_HEIGHT, &g_height); } if (!g_imgui_started.load()) { g_imgui_started.store(true); } return old_eglSwapBuffers(dpy, surface); } std::atomic g_use_native_input = false; #ifdef USE_32 using Input_fnT = int32_t(void* thiz, void* ex_ab, void* ex_ac); #endif #ifdef USE_64 using Input_fnT = int64_t(void* thiz, void* ex_ab, void* ex_ac); #endif Input_fnT* old_Input = nullptr; #ifdef USE_32 int32_t my_Input(void* thiz, void* ex_ab, void* ex_ac) #endif #ifdef USE_64 int64_t my_Input(void* thiz, void* ex_ab, void* ex_ac) #endif { auto ret = old_Input(thiz, ex_ab, ex_ac); if (!g_use_native_input.load()) { g_use_native_input.store(true); } if (g_imgui_started.load() && CrackerUI::get_instance().is_ui_showed() && g_need_ui_penetration) { ImGui_ImplAndroid_HandleInputEvent((AInputEvent *) thiz); } return ret; } #ifdef USE_32 using Consume_fnT = int32_t(void* thiz, void* arg1, bool arg2, long arg3, uint32_t* arg4, AInputEvent** input_event); #endif #ifdef USE_64 using Consume_fnT = int64_t(void* thiz, int64_t arg1, char arg2, int64_t arg3, uint32_t* arg4, AInputEvent** input_event); #endif Consume_fnT* old_Consume = nullptr; #ifdef USE_32 int32_t my_Consume(void* thiz, void* arg1, bool arg2, long arg3, uint32_t* arg4, AInputEvent** input_event) #endif #ifdef USE_64 int64_t my_Consume(void* thiz, int64_t arg1, char arg2, int64_t arg3, uint32_t* arg4, AInputEvent** input_event) #endif { auto result = old_Consume(thiz, arg1, arg2, arg3, arg4, input_event); if(result != 0 || *input_event == nullptr) { return result; } if (!g_use_native_input.load()) { g_use_native_input.store(true); } if (g_imgui_started.load() && CrackerUI::get_instance().is_ui_showed() && g_need_ui_penetration) { ImGui_ImplAndroid_HandleInputEvent(*input_event); } return result; } bool has_hooked = false; #define android_InputConsumer_initializeMotionEvent "_ZN7android13InputConsumer21initializeMotionEventEPNS_11MotionEventEPKNS_12InputMessageE" #define android_InputConsumer_consume "_ZN7android13InputConsumer7consumeEPNS_26InputEventFactoryInterfaceEblPjPPNS_10InputEventE" #define TOUCH_PHASE_BEGAN 0 #define TOUCH_PHASE_MOVED 1 #define TOUCH_PHASE_STATIONARY 2 #define TOUCH_PHASE_ENDED 3 #define TOUCH_PHASE_CANCELED 4 int My_ImGui_ImplAndroid_HandleInputEvent(const UnityEngine_Touch_o& touch) { auto x = touch.fields.m_Position.fields.x; auto y = g_height - touch.fields.m_Position.fields.y; if (!ImGui_IsPosInWnd(x, y)) { return 1; } auto& io = ImGui::GetIO(); // SPDLOG_INFO("Touch (RawX = {}, RawY = {}), (X = {}, Y = {}), (DeltaX = {}, DeltaY = {})", // touch.fields.m_RawPosition.fields.x, touch.fields.m_RawPosition.fields.y, // touch.fields.m_Position.fields.x, touch.fields.m_Position.fields.y, // touch.fields.m_PositionDelta.fields.x, touch.fields.m_PositionDelta.fields.y // ); auto phase = touch.fields.m_Phase; io.AddMouseSourceEvent(ImGuiMouseSource_TouchScreen); switch(phase) { case TOUCH_PHASE_BEGAN: io.AddMousePosEvent(x, y); io.AddMouseButtonEvent(0, true); io.AddMouseButtonEvent(1, true); io.AddMouseButtonEvent(2, true); break; case TOUCH_PHASE_MOVED: case TOUCH_PHASE_STATIONARY: io.AddMousePosEvent(x, y); break; case TOUCH_PHASE_ENDED: case TOUCH_PHASE_CANCELED: io.AddMousePosEvent(x, y); io.AddMouseButtonEvent(0, false); io.AddMouseButtonEvent(1, false); io.AddMouseButtonEvent(2, false); break; default: break; } return 0; } using GlobalClickEventMgr_HandlePinchOnTouch_fnT = void(void* thiz, UnityEngine_Touch_array* touches); GlobalClickEventMgr_HandlePinchOnTouch_fnT* old_GlobalClickEventMgr_HandlePinchOnTouch = nullptr; void my_GlobalClickEventMgr_HandlePinchOnTouch(void* thiz, UnityEngine_Touch_array* touches) { if (touches != nullptr && !g_use_native_input.load() && g_imgui_started.load() && CrackerUI::get_instance().is_ui_showed() && g_need_ui_penetration) { for(int i = 0; i < touches->max_length; i++) { My_ImGui_ImplAndroid_HandleInputEvent(touches->m_Items[i]); } } old_GlobalClickEventMgr_HandlePinchOnTouch(thiz, touches); } static GlobalClickEventMgr_HandlePinchOnTouch_fnT* get_GlobalClickEventMgr_HandlePinchOnTouch() { static GlobalClickEventMgr_HandlePinchOnTouch_fnT* fn = []() { Il2CppDomain* domain = il2cpp_domain_get(); size_t assembly_count = 0; Il2CppAssembly** assemblies = il2cpp_domain_get_assemblies(domain, &assembly_count); Il2CppClass* cls = nullptr; for (size_t i = 0; i < assembly_count; ++i) { Il2CppImage* image = il2cpp_assembly_get_image(assemblies[i]); Il2CppClass* klass = il2cpp_class_from_name(image, "", "GlobalClickEventMgr"); if (klass != nullptr) { cls = klass; break; } } if (cls != nullptr) { MethodInfo* method_info = il2cpp_class_get_method_from_name(cls, "HandlePinchOnTouch", 1); if (method_info != nullptr) { auto method = reinterpret_cast(method_info->methodPointer); SPDLOG_INFO("Found UnityEngine.Input.get_touches at {}", (void*)method_info->methodPointer); return method; } } SPDLOG_ERROR("Failed to get UnityEngine.Input.get_touches"); throw std::runtime_error("Failed to get UnityEngine.Input.get_touches"); }(); return fn; } using UnityEngine_Input_GetTouch_fnT = UnityEngine_Touch_o*(UnityEngine_Touch_o* ret, int32_t index); static UnityEngine_Input_GetTouch_fnT* get_UnityEngine_Input_GetTouch() { static UnityEngine_Input_GetTouch_fnT* fn = []() { Il2CppDomain* domain = il2cpp_domain_get(); size_t assembly_count = 0; Il2CppAssembly** assemblies = il2cpp_domain_get_assemblies(domain, &assembly_count); Il2CppClass* cls = nullptr; for (size_t i = 0; i < assembly_count; ++i) { Il2CppImage* image = il2cpp_assembly_get_image(assemblies[i]); Il2CppClass* klass = il2cpp_class_from_name(image, "UnityEngine", "Input"); if (klass != nullptr) { cls = klass; break; } } if (cls != nullptr) { MethodInfo* method_info = il2cpp_class_get_method_from_name(cls, "GetTouch", 1); if (method_info != nullptr) { auto method = reinterpret_cast(method_info->methodPointer); SPDLOG_INFO("Found UnityEngine.Input.GetTouch at {}", (void*)method_info->methodPointer); return method; } } SPDLOG_ERROR("Failed to get UnityEngine.Input.GetTouch"); throw std::runtime_error("Failed to get UnityEngine.Input.GetTouch"); }(); return fn; } UnityEngine_Input_GetTouch_fnT* old_UnityEngine_Input_GetTouch = nullptr; UnityEngine_Touch_o* my_UnityEngine_Input_GetTouch(UnityEngine_Touch_o* ret, int32_t index) { auto touch = old_UnityEngine_Input_GetTouch(ret, index); if (g_imgui_started.load() && !g_need_ui_penetration && CrackerUI::get_instance().is_ui_showed() && touch != nullptr) { if (My_ImGui_ImplAndroid_HandleInputEvent(*touch) == 0) { touch->fields.m_Phase = TOUCH_PHASE_CANCELED; } } return touch; } void do_ui_hook() { if (has_hooked) { return; } int dobby_hook_status = 0; dobby_hook_status = DobbyHook( reinterpret_cast(get_GlobalClickEventMgr_HandlePinchOnTouch()), reinterpret_cast(my_GlobalClickEventMgr_HandlePinchOnTouch), reinterpret_cast(&old_GlobalClickEventMgr_HandlePinchOnTouch) ); if (dobby_hook_status != 0) { SPDLOG_INFO("Hook GlobalClickEventMgr.HandlePinchOnTouch failed: {}", dobby_hook_status); } dobby_hook_status = DobbyHook( reinterpret_cast(get_UnityEngine_Input_GetTouch()), reinterpret_cast(my_UnityEngine_Input_GetTouch), reinterpret_cast(&old_UnityEngine_Input_GetTouch) ); if (dobby_hook_status != 0) { SPDLOG_INFO("Hook UnityEngine.Input.GetTouch failed: {}", dobby_hook_status); } has_hooked = true; SPDLOG_INFO("Hook ingame ui done!"); } void hook_game_setting_panel(lua_State* l) { sol::state_view lua(l); Lua::Function old = lua["SettingsNotificationPanel"]["OnItemSwitch"]; lua["SettingsNotificationPanel"]["OnItemSwitch"] = [old](sol::this_state L, Lua::VariadicArgs args) { Lua::Table config_into = args[1]; int config_id = config_into["id"]; if (config_id == 4) { bool is_enable_ui = args[2]; is_enable_ui = !is_enable_ui; if (is_enable_ui) { do_ui_hook(); CrackerUI::get_instance().show_ui(); } else { CrackerUI::get_instance().close_ui(); } } old(L, args); }; SPDLOG_INFO("SettingsNotificationPanel.OnItemSwitch hooked"); } bool g_is_game_loaded = false; bool g_is_panel_loaded = false; lua_State* g_target_L = nullptr; int (*old_luaL_loadbuffer) (lua_State *L, const char *buff, size_t sz, const char *name); int my_luaL_loadbuffer(lua_State *L, const char *buff, size_t sz,const char *name) { auto ret = old_luaL_loadbuffer(L, buff, sz, name); if (!g_is_panel_loaded) { if (name == nullptr) { return ret; } if (name[0] != '@') { return ret; } if (strstr(name, "SettingsNotificationPanel") != nullptr) { g_target_L = L; g_is_panel_loaded = true; } } return ret; } int (*old_lua_pcall) (lua_State *L, int nargs, int nresults, int errfunc); int my_lua_pcall (lua_State *L, int nargs, int nresults, int errfunc) { auto ret = old_lua_pcall(L, nargs, nresults, errfunc); if (g_is_panel_loaded && !g_is_game_loaded && g_target_L == L) { hook_game_setting_panel(L); g_is_game_loaded = true; } return ret; } void hook_game_lua_for_ingame_ui() { int xhook_status = 0; xhook_status = xhook_register( "libunity", "eglSwapBuffers", reinterpret_cast(my_eglSwapBuffers), reinterpret_cast(&old_eglSwapBuffers) ); if (xhook_status != 0) { SPDLOG_INFO("Hook eglSwapBuffers failed: {}", xhook_status); } xhook_status = xhook_register( "libtolua", "luaL_loadbuffer", reinterpret_cast(my_luaL_loadbuffer), reinterpret_cast(&old_luaL_loadbuffer) ); if (xhook_status != 0) { SPDLOG_INFO("Hook luaL_loadbuffer failed: {}", xhook_status); } xhook_status = xhook_register( "libtolua", "lua_pcall", reinterpret_cast(my_lua_pcall), reinterpret_cast(&old_lua_pcall) ); if (xhook_status != 0) { SPDLOG_INFO("Hook lua_pcall failed: {}", xhook_status); } xhook_status = xhook_register( "libinput", android_InputConsumer_initializeMotionEvent, reinterpret_cast(my_Input), reinterpret_cast(&old_Input) ); if (xhook_status != 0) { SPDLOG_INFO("Hook android::InputConsumer::initializeMotionEvent failed: {}", xhook_status); } xhook_status = xhook_register( "libinput", android_InputConsumer_consume, reinterpret_cast(my_Consume), reinterpret_cast(&old_Consume) ); if (xhook_status != 0) { SPDLOG_INFO("Hook android::InputConsumer::consume failed: {}", xhook_status); } xhook_status = xhook_refresh(1); if (xhook_status != 0) { SPDLOG_INFO("XHook commit failed: {}", xhook_status); } }