From 7c5c76f41f2f6e36b5b5d8f1450811cc6b15a481 Mon Sep 17 00:00:00 2001 From: artdeell Date: Fri, 24 Jan 2025 10:33:50 +0300 Subject: [PATCH] Style[pojavexec]: move hooks to separate dirs, use logging macros --- .../net/kdt/pojavlaunch/utils/JREUtils.java | 3 +- app_pojavlauncher/src/main/jni/Android.mk | 9 +- .../src/main/jni/ctxbridges/gl_bridge.c | 38 +++--- .../src/main/jni/ctxbridges/osm_bridge.c | 15 ++- .../jni/ctxbridges/swap_interval_no_egl.c | 10 +- .../src/main/jni/driver_helper/nsbypass.c | 1 - .../src/main/jni/environ/environ.c | 9 +- app_pojavlauncher/src/main/jni/exit_hook.c | 115 ------------------ .../src/main/jni/input_bridge_v3.c | 49 ++------ .../jni/jvm_hooks/emui_iterator_fix_hook.c | 45 +++++++ .../jni/{ => jvm_hooks}/java_exec_hooks.c | 7 +- .../src/main/jni/jvm_hooks/jvm_hooks.h | 14 +++ .../jni/{ => jvm_hooks}/lwjgl_dlopen_hook.c | 15 ++- app_pojavlauncher/src/main/jni/log.h | 15 ++- .../src/main/jni/native_hooks/chmod_hook.c | 33 +++++ .../src/main/jni/native_hooks/exit_hook.c | 86 +++++++++++++ .../src/main/jni/native_hooks/native_hooks.h | 15 +++ app_pojavlauncher/src/main/jni/utils.h | 2 - 18 files changed, 266 insertions(+), 215 deletions(-) delete mode 100644 app_pojavlauncher/src/main/jni/exit_hook.c create mode 100644 app_pojavlauncher/src/main/jni/jvm_hooks/emui_iterator_fix_hook.c rename app_pojavlauncher/src/main/jni/{ => jvm_hooks}/java_exec_hooks.c (97%) create mode 100644 app_pojavlauncher/src/main/jni/jvm_hooks/jvm_hooks.h rename app_pojavlauncher/src/main/jni/{ => jvm_hooks}/lwjgl_dlopen_hook.c (85%) create mode 100644 app_pojavlauncher/src/main/jni/native_hooks/chmod_hook.c create mode 100644 app_pojavlauncher/src/main/jni/native_hooks/exit_hook.c create mode 100644 app_pojavlauncher/src/main/jni/native_hooks/native_hooks.h diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java index d121759735..166ec8ac31 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/utils/JREUtils.java @@ -104,7 +104,8 @@ public static void redirectAndPrintJRELog() { public void run() { try { if (logcatPb == null) { - logcatPb = new ProcessBuilder().command("logcat", /* "-G", "1mb", */ "-v", "brief", "-s", "jrelog:I", "LIBGL:I", "NativeInput").redirectErrorStream(true); + // No filtering by tag anymore as that relied on incorrect log levels set in log.h + logcatPb = new ProcessBuilder().command("logcat", /* "-G", "1mb", */ "-v", "brief", "-s", "jrelog", "LIBGL", "NativeInput").redirectErrorStream(true); } Log.i("jrelog-logcat","Clearing logcat"); diff --git a/app_pojavlauncher/src/main/jni/Android.mk b/app_pojavlauncher/src/main/jni/Android.mk index 24ad2e474f..0763db163b 100644 --- a/app_pojavlauncher/src/main/jni/Android.mk +++ b/app_pojavlauncher/src/main/jni/Android.mk @@ -28,12 +28,13 @@ LOCAL_SRC_FILES := \ ctxbridges/osmesa_loader.c \ ctxbridges/swap_interval_no_egl.c \ environ/environ.c \ + jvm_hooks/emui_iterator_fix_hook.c \ + jvm_hooks/java_exec_hooks.c \ + jvm_hooks/lwjgl_dlopen_hook.c \ input_bridge_v3.c \ jre_launcher.c \ utils.c \ stdio_is.c \ - java_exec_hooks.c \ - lwjgl_dlopen_hook.c \ driver_helper/nsbypass.c ifeq ($(TARGET_ARCH_ABI),arm64-v8a) @@ -45,7 +46,9 @@ include $(CLEAR_VARS) LOCAL_MODULE := exithook LOCAL_LDLIBS := -ldl -llog LOCAL_SHARED_LIBRARIES := bytehook pojavexec -LOCAL_SRC_FILES := exit_hook.c +LOCAL_SRC_FILES := \ + native_hooks/exit_hook.c \ + native_hooks/chmod_hook.c include $(BUILD_SHARED_LIBRARY) #ifeq ($(TARGET_ARCH_ABI),arm64-v8a) diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c b/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c index 8f66139735..0b9ad99a42 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c +++ b/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -11,11 +10,13 @@ #include "gl_bridge.h" #include "egl_loader.h" +#define TAG __FILE_NAME__ +#include + // // Created by maks on 17.09.2022. // -static const char* g_LogTag = "GLBridge"; static __thread gl_render_window_t* currentBundle; static EGLDisplay g_EglDisplay; @@ -23,13 +24,11 @@ bool gl_init() { if(!dlsym_EGL()) return false; g_EglDisplay = eglGetDisplay_p(EGL_DEFAULT_DISPLAY); if (g_EglDisplay == EGL_NO_DISPLAY) { - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", - "eglGetDisplay_p(EGL_DEFAULT_DISPLAY) returned EGL_NO_DISPLAY"); + LOGE("%s", "eglGetDisplay_p(EGL_DEFAULT_DISPLAY) returned EGL_NO_DISPLAY"); return false; } if (eglInitialize_p(g_EglDisplay, 0, 0) != EGL_TRUE) { - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "eglInitialize_p() failed: %04x", - eglGetError_p()); + LOGE("eglInitialize_p() failed: %04x", eglGetError_p()); return false; } return true; @@ -62,14 +61,12 @@ gl_render_window_t* gl_init_context(gl_render_window_t *share) { EGLint num_configs = 0; if (eglChooseConfig_p(g_EglDisplay, egl_attributes, NULL, 0, &num_configs) != EGL_TRUE) { - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "eglChooseConfig_p() failed: %04x", - eglGetError_p()); + LOGE("eglChooseConfig_p() failed: %04x", eglGetError_p()); free(bundle); return NULL; } if (num_configs == 0) { - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", - "eglChooseConfig_p() found no matching config"); + LOGE("%s", "eglChooseConfig_p() found no matching config"); free(bundle); return NULL; } @@ -96,8 +93,7 @@ gl_render_window_t* gl_init_context(gl_render_window_t *share) { bundle->context = eglCreateContext_p(g_EglDisplay, bundle->config, share == NULL ? EGL_NO_CONTEXT : share->context, egl_context_attributes); if (bundle->context == EGL_NO_CONTEXT) { - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "eglCreateContext_p() finished with error: %04x", - eglGetError_p()); + LOGE("eglCreateContext_p() finished with error: %04x", eglGetError_p()); free(bundle); return NULL; } @@ -110,14 +106,14 @@ void gl_swap_surface(gl_render_window_t* bundle) { } if(bundle->surface != NULL) eglDestroySurface_p(g_EglDisplay, bundle->surface); if(bundle->newNativeSurface != NULL) { - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "Switching to new native surface"); + LOGI("Switching to new native surface"); bundle->nativeSurface = bundle->newNativeSurface; bundle->newNativeSurface = NULL; ANativeWindow_acquire(bundle->nativeSurface); ANativeWindow_setBuffersGeometry(bundle->nativeSurface, 0, 0, bundle->format); bundle->surface = eglCreateWindowSurface_p(g_EglDisplay, bundle->config, bundle->nativeSurface, NULL); }else{ - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "No new native surface, switching to 1x1 pbuffer"); + LOGI("No new native surface, switching to 1x1 pbuffer"); bundle->nativeSurface = NULL; const EGLint pbuffer_attrs[] = {EGL_WIDTH, 1 , EGL_HEIGHT, 1, EGL_NONE}; bundle->surface = eglCreatePbufferSurface_p(g_EglDisplay, bundle->config, pbuffer_attrs); @@ -136,11 +132,11 @@ void gl_make_current(gl_render_window_t* bundle) { bool hasSetMainWindow = false; if(pojav_environ->mainWindowBundle == NULL) { pojav_environ->mainWindowBundle = (basic_render_window_t*)bundle; - __android_log_print(ANDROID_LOG_INFO, g_LogTag, "Main window bundle is now %p", pojav_environ->mainWindowBundle); + LOGI("Main window bundle is now %p", pojav_environ->mainWindowBundle); pojav_environ->mainWindowBundle->newNativeSurface = pojav_environ->pojavWindow; hasSetMainWindow = true; } - __android_log_print(ANDROID_LOG_INFO, g_LogTag, "Making current, surface=%p, nativeSurface=%p, newNativeSurface=%p", bundle->surface, bundle->nativeSurface, bundle->newNativeSurface); + LOGI("Making current, surface=%p, nativeSurface=%p, newNativeSurface=%p", bundle->surface, bundle->nativeSurface, bundle->newNativeSurface); if(bundle->surface == NULL) { //it likely will be on the first run gl_swap_surface(bundle); } @@ -152,7 +148,7 @@ void gl_make_current(gl_render_window_t* bundle) { gl_swap_surface((gl_render_window_t*)pojav_environ->mainWindowBundle); pojav_environ->mainWindowBundle = NULL; } - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "eglMakeCurrent returned with error: %04x", eglGetError_p()); + LOGE("eglMakeCurrent returned with error: %04x", eglGetError_p()); } } @@ -170,14 +166,14 @@ void gl_swap_buffers() { currentBundle->newNativeSurface = NULL; gl_swap_surface(currentBundle); eglMakeCurrent_p(g_EglDisplay, currentBundle->surface, currentBundle->surface, currentBundle->context); - __android_log_print(ANDROID_LOG_INFO, g_LogTag, "The window has died, awaiting window change"); + LOGI("The window has died, awaiting window change"); } } void gl_setup_window() { if(pojav_environ->mainWindowBundle != NULL) { - __android_log_print(ANDROID_LOG_INFO, g_LogTag, "Main window bundle is not NULL, changing state"); + LOGI("Main window bundle is not NULL, changing state"); pojav_environ->mainWindowBundle->state = STATE_RENDERER_NEW_WINDOW; pojav_environ->mainWindowBundle->newNativeSurface = pojav_environ->pojavWindow; } @@ -192,14 +188,14 @@ void gl_swap_interval(int swapInterval) { JNIEXPORT void JNICALL Java_org_lwjgl_opengl_PojavRendererInit_nativeInitGl4esInternals(JNIEnv *env, jclass clazz, jobject function_provider) { - __android_log_print(ANDROID_LOG_INFO, g_LogTag, "GL4ES internals initializing..."); + LOGI("GL4ES internals initializing..."); jclass funcProviderClass = (*env)->GetObjectClass(env, function_provider); jmethodID method_getFunctionAddress = (*env)->GetMethodID(env, funcProviderClass, "getFunctionAddress", "(Ljava/lang/CharSequence;)J"); #define GETSYM(N) ((*env)->CallLongMethod(env, function_provider, method_getFunctionAddress, (*env)->NewStringUTF(env, N))); void (*set_getmainfbsize)(void (*new_getMainFBSize)(int* width, int* height)) = (void*)GETSYM("set_getmainfbsize"); if(set_getmainfbsize != NULL) { - __android_log_print(ANDROID_LOG_INFO, g_LogTag, "GL4ES internals initialized dimension callback"); + LOGI("GL4ES internals initialized dimension callback"); set_getmainfbsize(gl4esi_get_display_dimensions); } diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c b/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c index 814f2e86db..8df959138d 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c +++ b/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c @@ -4,10 +4,10 @@ #include #include #include -#include #include "osm_bridge.h" +#define TAG __FILE_NAME__ +#include -static const char* g_LogTag = "GLBridge"; static __thread osm_render_window_t* currentBundle; // a tiny buffer for rendering when there's nowhere t render static char no_render_buffer[4]; @@ -49,13 +49,13 @@ void osm_set_no_render_buffer(ANativeWindow_Buffer* buffer) { void osm_swap_surfaces(osm_render_window_t* bundle) { if(bundle->nativeSurface != NULL && bundle->newNativeSurface != bundle->nativeSurface) { if(!bundle->disable_rendering) { - __android_log_print(ANDROID_LOG_INFO, g_LogTag, "Unlocking for cleanup..."); + LOGI("Unlocking for cleanup..."); ANativeWindow_unlockAndPost(bundle->nativeSurface); } ANativeWindow_release(bundle->nativeSurface); } if(bundle->newNativeSurface != NULL) { - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "Switching to new native surface"); + LOGI("Switching to new native surface"); bundle->nativeSurface = bundle->newNativeSurface; bundle->newNativeSurface = NULL; ANativeWindow_acquire(bundle->nativeSurface); @@ -63,8 +63,7 @@ void osm_swap_surfaces(osm_render_window_t* bundle) { bundle->disable_rendering = false; return; }else { - __android_log_print(ANDROID_LOG_ERROR, g_LogTag, - "No new native surface, switching to dummy framebuffer"); + LOGI("No new native surface, switching to dummy framebuffer"); bundle->nativeSurface = NULL; osm_set_no_render_buffer(&bundle->buffer); bundle->disable_rendering = true; @@ -96,7 +95,7 @@ void osm_make_current(osm_render_window_t* bundle) { currentBundle = bundle; if(pojav_environ->mainWindowBundle == NULL) { pojav_environ->mainWindowBundle = (basic_render_window_t*) bundle; - __android_log_print(ANDROID_LOG_INFO, g_LogTag, "Main window bundle is now %p", pojav_environ->mainWindowBundle); + LOGI("Main window bundle is now %p", pojav_environ->mainWindowBundle); pojav_environ->mainWindowBundle->newNativeSurface = pojav_environ->pojavWindow; hasSetMainWindow = true; } @@ -130,7 +129,7 @@ void osm_swap_buffers() { void osm_setup_window() { if(pojav_environ->mainWindowBundle != NULL) { - __android_log_print(ANDROID_LOG_INFO, g_LogTag, "Main window bundle is not NULL, changing state"); + LOGI("Main window bundle is not NULL, changing state"); pojav_environ->mainWindowBundle->state = STATE_RENDERER_NEW_WINDOW; pojav_environ->mainWindowBundle->newNativeSurface = pojav_environ->pojavWindow; } diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/swap_interval_no_egl.c b/app_pojavlauncher/src/main/jni/ctxbridges/swap_interval_no_egl.c index 580aa9e3ee..159d5a94cc 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/swap_interval_no_egl.c +++ b/app_pojavlauncher/src/main/jni/ctxbridges/swap_interval_no_egl.c @@ -5,9 +5,11 @@ #include #include #include -#include #include +#define TAG __FILE_NAME__ +#include + // Taken from https://android.googlesource.com/platform/frameworks/native/+/41abd67/include/ui/egl/android_natives.h // Might be outdated, if you can find a more recent version please add it there // region android_native_base_t definition @@ -227,17 +229,17 @@ void setNativeWindowSwapInterval(struct ANativeWindow* nativeWindow, int swapInt } struct ANativeWindow_real* nativeWindowReal = (struct ANativeWindow_real*) nativeWindow; if(nativeWindowReal->common.magic != ANDROID_NATIVE_WINDOW_MAGIC) { - __android_log_print(ANDROID_LOG_WARN, "SwapIntervalNoEGL", "ANativeWindow magic does not match. Expected %i, got %i", + LOGW("ANativeWindow magic does not match. Expected %i, got %i", ANDROID_NATIVE_WINDOW_MAGIC, nativeWindowReal->common.magic); return; } if(nativeWindowReal->common.version != sizeof(struct ANativeWindow_real)) { - __android_log_print(ANDROID_LOG_WARN, "SwapIntervalNoEGL", "ANativeWindow version does not match. Expected %i, got %i", + LOGW("ANativeWindow version does not match. Expected %i, got %i", sizeof(struct ANativeWindow_real), nativeWindowReal->common.version); return; } int error; if((error = nativeWindowReal->setSwapInterval(nativeWindow, swapInterval)) != 0) { - __android_log_print(ANDROID_LOG_WARN, "SwapIntervalNoEGL", "Failed to set swap interval: %s", strerror(-error)); + LOGW("Failed to set swap interval: %s", strerror(-error)); } } \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/driver_helper/nsbypass.c b/app_pojavlauncher/src/main/jni/driver_helper/nsbypass.c index 9d854ae25e..16fbe68068 100644 --- a/app_pojavlauncher/src/main/jni/driver_helper/nsbypass.c +++ b/app_pojavlauncher/src/main/jni/driver_helper/nsbypass.c @@ -82,7 +82,6 @@ bool linker_ns_load(const char* lib_search_path) { // load the two functions we need android_create_namespace = dlsym(ld_android_handle, "__loader_android_create_namespace"); ld_android_link_namespaces_t android_link_namespaces = dlsym(ld_android_handle, "__loader_android_link_namespaces"); - __android_log_print(ANDROID_LOG_INFO, "nsbypass", "found functions at %p %p", android_create_namespace, android_link_namespaces); if(android_create_namespace == NULL || android_link_namespaces == NULL) { dlclose(ld_android_handle); return false; diff --git a/app_pojavlauncher/src/main/jni/environ/environ.c b/app_pojavlauncher/src/main/jni/environ/environ.c index 72633d78fa..f75c2eaa9d 100644 --- a/app_pojavlauncher/src/main/jni/environ/environ.c +++ b/app_pojavlauncher/src/main/jni/environ/environ.c @@ -7,11 +7,14 @@ #include #include #include "environ.h" +#define TAG __FILE_NAME__ +#include + struct pojav_environ_s *pojav_environ; __attribute__((constructor)) void env_init() { char* strptr_env = getenv("POJAV_ENVIRON"); if(strptr_env == NULL) { - __android_log_print(ANDROID_LOG_INFO, "Environ", "No environ found, creating..."); + LOGI("No environ found, creating..."); pojav_environ = malloc(sizeof(struct pojav_environ_s)); assert(pojav_environ); memset(pojav_environ, 0 , sizeof(struct pojav_environ_s)); @@ -19,8 +22,8 @@ __attribute__((constructor)) void env_init() { setenv("POJAV_ENVIRON", strptr_env, 1); free(strptr_env); }else{ - __android_log_print(ANDROID_LOG_INFO, "Environ", "Found existing environ: %s", strptr_env); + LOGI("Found existing environ: %s", strptr_env); pojav_environ = (void*) strtoul(strptr_env, NULL, 0x10); } - __android_log_print(ANDROID_LOG_INFO, "Environ", "%p", pojav_environ); + LOGI("%p", pojav_environ); } \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/exit_hook.c b/app_pojavlauncher/src/main/jni/exit_hook.c deleted file mode 100644 index c7901744a7..0000000000 --- a/app_pojavlauncher/src/main/jni/exit_hook.c +++ /dev/null @@ -1,115 +0,0 @@ -// -// Created by maks on 15.01.2025. -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "stdio_is.h" - -static _Atomic bool exit_tripped = false; - -typedef void (*exit_func)(int); - -static void custom_exit(int code) { - // If the exit was already done (meaning it is recursive or from a different thread), pass the call through - if(exit_tripped) { - BYTEHOOK_CALL_PREV(custom_exit, exit_func, code); - BYTEHOOK_POP_STACK(); - return; - } - exit_tripped = true; - // Perform a nominal exit, as we expect. - nominal_exit(code, false); - BYTEHOOK_POP_STACK(); -} - -// Hooks for chmod and fchmod that always return success. -// This allows older Android versions to work with Java NIO zipfs inside of the Pojav folder. -typedef int (*chmod_func)(const char*, mode_t); -typedef int (*fchmod_func)(int, mode_t); - -#define TEMPLATE_HOOK(X, Y, Z, W) static int X(Y, mode_t mode) { \ - int result = BYTEHOOK_CALL_PREV(X, Z, W, mode); \ - if(result != 0) errno = 0; \ - BYTEHOOK_POP_STACK(); \ - return 0; \ -} \ - -TEMPLATE_HOOK(custom_chmod, const char* filename, chmod_func, filename) -TEMPLATE_HOOK(custom_fchmod, int fd, fchmod_func, fd) - -#undef TEMPLATE_HOOK - -static void custom_atexit() { - // Same as custom_exit, but without the code or the exit passthrough. - if(exit_tripped) { - return; - } - exit_tripped = true; - nominal_exit(0, false); -} - -typedef bytehook_stub_t (*bytehook_hook_all_t)(const char *callee_path_name, const char *sym_name, void *new_func, - bytehook_hooked_t hooked, void *hooked_arg); - -static void create_chmod_hooks(bytehook_hook_all_t bytehook_hook_all_p) { - bytehook_stub_t stub_chmod = bytehook_hook_all_p(NULL, "chmod", &custom_chmod, NULL, NULL); - bytehook_stub_t stub_fchmod = bytehook_hook_all_p(NULL, "fchmod", &custom_fchmod, NULL, NULL); - __android_log_print(ANDROID_LOG_INFO, "chmod_hook", "Successfully initialized chmod hooks, stubs: %p %p", stub_chmod, stub_fchmod); -} - -static void create_hooks(bytehook_hook_all_t bytehook_hook_all_p) { - bytehook_stub_t stub_exit = bytehook_hook_all_p(NULL, "exit", &custom_exit, NULL, NULL); - __android_log_print(ANDROID_LOG_INFO, "exit_hook", "Successfully initialized exit hook, stub: %p", stub_exit); - // TODO: figure out proper android version where these should apply - create_chmod_hooks(bytehook_hook_all_p); -} - -static bool init_hooks() { - void* bytehook_handle = dlopen("libbytehook.so", RTLD_NOW); - if(bytehook_handle == NULL) { - goto dlerror; - } - - bytehook_hook_all_t bytehook_hook_all_p; - int (*bytehook_init_p)(int mode, bool debug); - - bytehook_hook_all_p = dlsym(bytehook_handle, "bytehook_hook_all"); - bytehook_init_p = dlsym(bytehook_handle, "bytehook_init"); - - if(bytehook_hook_all_p == NULL || bytehook_init_p == NULL) { - goto dlerror; - } - int bhook_status = bytehook_init_p(BYTEHOOK_MODE_AUTOMATIC, false); - if(bhook_status == BYTEHOOK_STATUS_CODE_OK) { - create_hooks(bytehook_hook_all_p); - return true; - } else { - __android_log_print(ANDROID_LOG_INFO, "exit_hook", "bytehook_init failed (%i)", bhook_status); - dlclose(bytehook_handle); - return false; - } - - dlerror: - if(bytehook_handle != NULL) dlclose(bytehook_handle); - __android_log_print(ANDROID_LOG_ERROR, "exit_hook", "Failed to load hook library: %s", dlerror()); - return false; -} - -JNIEXPORT void JNICALL -Java_net_kdt_pojavlaunch_utils_JREUtils_initializeHooks(JNIEnv *env, jclass clazz) { - bool hooks_ready = init_hooks(); - if(!hooks_ready){ - // If we can't hook, register atexit(). This won't report a proper error code, - // but it will prevent a SIGSEGV or a SIGABRT from the depths of Dalvik that happens - // on exit(). - atexit(custom_atexit); - } -} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index 5728f2e825..d661d0e113 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -19,9 +19,11 @@ #include #include +#define TAG __FILE_NAME__ #include "log.h" #include "utils.h" #include "environ/environ.h" +#include "jvm_hooks/jvm_hooks.h" #define EVENT_TYPE_CHAR 1000 #define EVENT_TYPE_CHAR_MODS 1001 @@ -30,12 +32,11 @@ #define EVENT_TYPE_MOUSE_BUTTON 1006 #define EVENT_TYPE_SCROLL 1007 -static void installEMUIIteratorMititgation(JNIEnv *vmEnv); static void registerFunctions(JNIEnv *env); jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) { if (pojav_environ->dalvikJavaVMPtr == NULL) { - __android_log_print(ANDROID_LOG_INFO, "Native", "Saving DVM environ..."); + LOGI("Saving DVM environ..."); //Save dalvik global JavaVM pointer pojav_environ->dalvikJavaVMPtr = vm; JNIEnv *dvEnv; @@ -45,7 +46,7 @@ jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) { pojav_environ->method_onGrabStateChanged = (*dvEnv)->GetStaticMethodID(dvEnv, pojav_environ->bridgeClazz, "onGrabStateChanged", "(Z)V"); pojav_environ->isUseStackQueueCall = JNI_FALSE; } else if (pojav_environ->dalvikJavaVMPtr != vm) { - __android_log_print(ANDROID_LOG_INFO, "Native", "Saving JVM environ..."); + LOGI("Saving JVM environ..."); pojav_environ->runtimeJavaVMPtr = vm; JNIEnv *vmEnv; (*vm)->GetEnv(vm, (void**) &vmEnv, JNI_VERSION_1_4); @@ -231,42 +232,6 @@ void sendData(int type, int i1, int i2, int i3, int i4) { atomic_fetch_add_explicit(&pojav_environ->eventCounter, 1, memory_order_acquire); } -/** - * This function is meant as a substitute for SharedLibraryUtil.getLibraryPath() that just returns 0 - * (thus making the parent Java function return null). This is done to avoid using the LWJGL's default function, - * which will hang the crappy EMUI linker by dlopen()ing inside of dl_iterate_phdr(). - * @return 0, to make the parent Java function return null immediately. - * For reference: https://github.com/PojavLauncherTeam/lwjgl3/blob/fix_huawei_hang/modules/lwjgl/core/src/main/java/org/lwjgl/system/SharedLibraryUtil.java - */ -jint getLibraryPath_fix(__attribute__((unused)) JNIEnv *env, - __attribute__((unused)) jclass class, - __attribute__((unused)) jlong pLibAddress, - __attribute__((unused)) jlong sOutAddress, - __attribute__((unused)) jint bufSize){ - return 0; -} - -/** - * Install the linker hang mitigation that is meant to prevent linker hangs on old EMUI firmware. - */ -static void installEMUIIteratorMititgation(JNIEnv *env) { - if(getenv("POJAV_EMUI_ITERATOR_MITIGATE") == NULL) return; - __android_log_print(ANDROID_LOG_INFO, "EMUIIteratorFix", "Installing..."); - jclass sharedLibraryUtil = (*env)->FindClass(env, "org/lwjgl/system/SharedLibraryUtil"); - if(sharedLibraryUtil == NULL) { - __android_log_print(ANDROID_LOG_ERROR, "EMUIIteratorFix", "Failed to find the target class"); - (*env)->ExceptionClear(env); - return; - } - JNINativeMethod getLibraryPathMethod[] = { - {"getLibraryPath", "(JJI)I", &getLibraryPath_fix} - }; - if((*env)->RegisterNatives(env, sharedLibraryUtil, getLibraryPathMethod, 1) != 0) { - __android_log_print(ANDROID_LOG_ERROR, "EMUIIteratorFix", "Failed to register the mitigation method"); - (*env)->ExceptionClear(env); - } -} - void critical_set_stackqueue(jboolean use_input_stack_queue) { pojav_environ->isUseStackQueueCall = (int) use_input_stack_queue; } @@ -308,7 +273,7 @@ JNIEXPORT jboolean JNICALL JavaCritical_org_lwjgl_glfw_CallbackBridge_nativeSetI #ifdef DEBUG LOGD("Debug: Changing input state, isReady=%d, pojav_environ->isUseStackQueueCall=%d\n", inputReady, pojav_environ->isUseStackQueueCall); #endif - __android_log_print(ANDROID_LOG_INFO, "NativeInput", "Input ready: %i", inputReady); + LOGI("Input ready: %i", inputReady); pojav_environ->isInputReady = inputReady; return pojav_environ->isUseStackQueueCall; } @@ -550,9 +515,9 @@ static void registerFunctions(JNIEnv *env) { bool use_critical_cc = tryCriticalNative(env); jclass bridge_class = (*env)->FindClass(env, "org/lwjgl/glfw/CallbackBridge"); if(use_critical_cc) { - __android_log_print(ANDROID_LOG_INFO, "pojavexec", "CriticalNative is available. Enjoy the 4.6x times faster input!"); + LOGI("CriticalNative is available. Enjoy the 4.6x times faster input!"); }else{ - __android_log_print(ANDROID_LOG_INFO, "pojavexec", "CriticalNative is not available. Upgrade, maybe?"); + LOGI("CriticalNative is not available. Upgrade, maybe?"); } (*env)->RegisterNatives(env, bridge_class, diff --git a/app_pojavlauncher/src/main/jni/jvm_hooks/emui_iterator_fix_hook.c b/app_pojavlauncher/src/main/jni/jvm_hooks/emui_iterator_fix_hook.c new file mode 100644 index 0000000000..cb2825d6da --- /dev/null +++ b/app_pojavlauncher/src/main/jni/jvm_hooks/emui_iterator_fix_hook.c @@ -0,0 +1,45 @@ +// +// Created by maks on 23.01.2025. +// + +#include "jvm_hooks.h" +#include + +#define TAG __FILE_NAME__ +#include + +/** + * This function is meant as a substitute for SharedLibraryUtil.getLibraryPath() that just returns 0 + * (thus making the parent Java function return null). This is done to avoid using the LWJGL's default function, + * which will hang the crappy EMUI linker by dlopen()ing inside of dl_iterate_phdr(). + * @return 0, to make the parent Java function return null immediately. + * For reference: https://github.com/PojavLauncherTeam/lwjgl3/blob/fix_huawei_hang/modules/lwjgl/core/src/main/java/org/lwjgl/system/SharedLibraryUtil.java + */ +jint getLibraryPath_fix(__attribute__((unused)) JNIEnv *env, + __attribute__((unused)) jclass class, + __attribute__((unused)) jlong pLibAddress, + __attribute__((unused)) jlong sOutAddress, + __attribute__((unused)) jint bufSize){ + return 0; +} + +/** + * Install the linker hang mitigation that is meant to prevent linker hangs on old EMUI firmware. + */ +void installEMUIIteratorMititgation(JNIEnv *env) { + if(getenv("POJAV_EMUI_ITERATOR_MITIGATE") == NULL) return; + LOGI("Installing..."); + jclass sharedLibraryUtil = (*env)->FindClass(env, "org/lwjgl/system/SharedLibraryUtil"); + if(sharedLibraryUtil == NULL) { + LOGE("Failed to find target class"); + (*env)->ExceptionClear(env); + return; + } + JNINativeMethod getLibraryPathMethod[] = { + {"getLibraryPath", "(JJI)I", &getLibraryPath_fix} + }; + if((*env)->RegisterNatives(env, sharedLibraryUtil, getLibraryPathMethod, 1) != 0) { + LOGE("Failed to register the mitigation method"); + (*env)->ExceptionClear(env); + } +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/java_exec_hooks.c b/app_pojavlauncher/src/main/jni/jvm_hooks/java_exec_hooks.c similarity index 97% rename from app_pojavlauncher/src/main/jni/java_exec_hooks.c rename to app_pojavlauncher/src/main/jni/jvm_hooks/java_exec_hooks.c index 830f6e1bc1..cc523a4b86 100644 --- a/app_pojavlauncher/src/main/jni/java_exec_hooks.c +++ b/app_pojavlauncher/src/main/jni/jvm_hooks/java_exec_hooks.c @@ -2,15 +2,14 @@ // Created by maks on 05.01.2025. // -#include +#include "jvm_hooks.h" #include #include #include #include -#include -#include -#include +#include "environ/environ.h" +#include "utils.h" static jint (*orig_ProcessImpl_forkAndExec)(JNIEnv *env, jobject process, jint mode, jbyteArray helperpath, jbyteArray prog, jbyteArray argBlock, jint argc, jbyteArray envBlock, jint envc, jbyteArray dir, jintArray std_fds, jboolean redirectErrorStream); diff --git a/app_pojavlauncher/src/main/jni/jvm_hooks/jvm_hooks.h b/app_pojavlauncher/src/main/jni/jvm_hooks/jvm_hooks.h new file mode 100644 index 0000000000..6fd4471b15 --- /dev/null +++ b/app_pojavlauncher/src/main/jni/jvm_hooks/jvm_hooks.h @@ -0,0 +1,14 @@ +// +// Created by maks on 23.01.2025. +// + +#ifndef POJAVLAUNCHER_JVM_HOOKS_H +#define POJAVLAUNCHER_JVM_HOOKS_H + +#include + +void installEMUIIteratorMititgation(JNIEnv *env); +void installLwjglDlopenHook(JNIEnv *env); +void hookExec(JNIEnv *env); + +#endif //POJAVLAUNCHER_JVM_HOOKS_H diff --git a/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c b/app_pojavlauncher/src/main/jni/jvm_hooks/lwjgl_dlopen_hook.c similarity index 85% rename from app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c rename to app_pojavlauncher/src/main/jni/jvm_hooks/lwjgl_dlopen_hook.c index 9990d6f017..c9af60513b 100644 --- a/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c +++ b/app_pojavlauncher/src/main/jni/jvm_hooks/lwjgl_dlopen_hook.c @@ -2,16 +2,19 @@ // Created by maks on 06.01.2025. // +#include "jvm_hooks.h" + #include -#include -#include -#include +#include "environ/environ.h" #include #include #include +#define TAG __FILE_NAME__ +#include + extern void* maybe_load_vulkan(); /** @@ -46,10 +49,10 @@ static jlong ndlopen_bugfix(__attribute__((unused)) JNIEnv *env, * Install the LWJGL dlopen hook. This allows us to mitigate linker bugs and add custom library overrides. */ void installLwjglDlopenHook(JNIEnv *env) { - __android_log_print(ANDROID_LOG_INFO, "LwjglLinkerHook", "Installing LWJGL dlopen() hook"); + LOGI("Installing LWJGL dlopen() hook"); jclass dynamicLinkLoader = (*env)->FindClass(env, "org/lwjgl/system/linux/DynamicLinkLoader"); if(dynamicLinkLoader == NULL) { - __android_log_print(ANDROID_LOG_ERROR, "LwjglLinkerHook", "Failed to find the target class"); + LOGE("Failed to find the target class"); (*env)->ExceptionClear(env); return; } @@ -57,7 +60,7 @@ void installLwjglDlopenHook(JNIEnv *env) { {"ndlopen", "(JI)J", &ndlopen_bugfix} }; if((*env)->RegisterNatives(env, dynamicLinkLoader, ndlopenMethod, 1) != 0) { - __android_log_print(ANDROID_LOG_ERROR, "LwjglLinkerHook", "Failed to register the hooked method"); + LOGE("Failed to register the hooked method"); (*env)->ExceptionClear(env); } } \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/log.h b/app_pojavlauncher/src/main/jni/log.h index f5d2d16f0e..0064486260 100644 --- a/app_pojavlauncher/src/main/jni/log.h +++ b/app_pojavlauncher/src/main/jni/log.h @@ -1,6 +1,9 @@ -#ifdef __ANDROID__ +#ifndef POJAVLAUNCHER_LOG_H +#define POJAVLAUNCHER_LOG_H + #include +#ifndef TAG #define TAG "jrelog" #endif @@ -8,12 +11,14 @@ extern "C" { #endif -#define LOGE(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) -#define LOGW(...) __android_log_print(ANDROID_LOG_SILENT, TAG, __VA_ARGS__) -#define LOGI(...) __android_log_print(ANDROID_LOG_SILENT, TAG, __VA_ARGS__) -#define LOGD(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__) +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) #ifdef __cplusplus } #endif +#endif + diff --git a/app_pojavlauncher/src/main/jni/native_hooks/chmod_hook.c b/app_pojavlauncher/src/main/jni/native_hooks/chmod_hook.c new file mode 100644 index 0000000000..9f798202bc --- /dev/null +++ b/app_pojavlauncher/src/main/jni/native_hooks/chmod_hook.c @@ -0,0 +1,33 @@ +// +// Created by maks on 23.01.2025. +// + +#include "native_hooks.h" +#include +#include + +#define TAG __FILE_NAME__ +#include + +// Hooks for chmod and fchmod that always return success. +// This allows older Android versions to work with Java NIO zipfs inside of the Pojav folder. +typedef int (*chmod_func)(const char*, mode_t); +typedef int (*fchmod_func)(int, mode_t); + +#define TEMPLATE_HOOK(X, Y, Z, W) static int X(Y, mode_t mode) { \ + int result = BYTEHOOK_CALL_PREV(X, Z, W, mode); \ + if(result != 0) errno = 0; \ + BYTEHOOK_POP_STACK(); \ + return 0; \ +} \ + +TEMPLATE_HOOK(custom_chmod, const char* filename, chmod_func, filename) +TEMPLATE_HOOK(custom_fchmod, int fd, fchmod_func, fd) + +#undef TEMPLATE_HOOK + +void create_chmod_hooks(bytehook_hook_all_t bytehook_hook_all_p) { + bytehook_stub_t stub_chmod = bytehook_hook_all_p(NULL, "chmod", &custom_chmod, NULL, NULL); + bytehook_stub_t stub_fchmod = bytehook_hook_all_p(NULL, "fchmod", &custom_fchmod, NULL, NULL); + LOGI("Successfully initialized chmod hooks, stubs: %p %p", stub_chmod, stub_fchmod); +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/native_hooks/exit_hook.c b/app_pojavlauncher/src/main/jni/native_hooks/exit_hook.c new file mode 100644 index 0000000000..3fbe726f42 --- /dev/null +++ b/app_pojavlauncher/src/main/jni/native_hooks/exit_hook.c @@ -0,0 +1,86 @@ +// +// Created by maks on 15.01.2025. +// +#include "native_hooks.h" + +#include +#include +#include +#include +#include +#include "stdio_is.h" + +#define TAG __FILE_NAME__ +#include + +static _Atomic bool exit_tripped = false; + +static int exit_code = 0; + +typedef void (*exit_func)(int); +// Use the exit hook *only* to store the exit code. +static void custom_exit(int code) { + exit_code = code; + BYTEHOOK_CALL_PREV(custom_exit, exit_func, code); + BYTEHOOK_POP_STACK(); +} + +static void custom_atexit() { + if(exit_tripped) { + return; + } + exit_tripped = true; + nominal_exit(exit_code, false); +} + +static void create_hooks(bytehook_hook_all_t bytehook_hook_all_p) { + bytehook_stub_t stub_exit = bytehook_hook_all_p(NULL, "exit", &custom_exit, NULL, NULL); + LOGI("Successfully initialized exit hook, stub: %p", stub_exit); + // Only apply chmod hooks on devices where the game directory is in games/PojavLauncher + // which is below API 29 + if(android_get_device_api_level() < 29) { + create_chmod_hooks(bytehook_hook_all_p); + } +} + +static bool init_hooks() { + void* bytehook_handle = dlopen("libbytehook.so", RTLD_NOW); + if(bytehook_handle == NULL) { + goto dlerror; + } + + bytehook_hook_all_t bytehook_hook_all_p; + int (*bytehook_init_p)(int mode, bool debug); + + bytehook_hook_all_p = dlsym(bytehook_handle, "bytehook_hook_all"); + bytehook_init_p = dlsym(bytehook_handle, "bytehook_init"); + + if(bytehook_hook_all_p == NULL || bytehook_init_p == NULL) { + goto dlerror; + } + int bhook_status = bytehook_init_p(BYTEHOOK_MODE_AUTOMATIC, false); + if(bhook_status == BYTEHOOK_STATUS_CODE_OK) { + create_hooks(bytehook_hook_all_p); + return true; + } else { + LOGE("bytehook_init failed (%i)", bhook_status); + dlclose(bytehook_handle); + return false; + } + + dlerror: + if(bytehook_handle != NULL) dlclose(bytehook_handle); + LOGE("Failed to load hook library: %s", dlerror()); + return false; +} + +JNIEXPORT void JNICALL +Java_net_kdt_pojavlaunch_utils_JREUtils_initializeHooks(JNIEnv *env, jclass clazz) { + bool hooks_ready = init_hooks(); + if(!hooks_ready) { + LOGE("Failed to initialize native hooks!"); + } + // Always register atexit, because that's what we will call our exit from. + // We only use the hook to capture the exit code. + atexit(custom_atexit); +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/native_hooks/native_hooks.h b/app_pojavlauncher/src/main/jni/native_hooks/native_hooks.h new file mode 100644 index 0000000000..a99a8182ca --- /dev/null +++ b/app_pojavlauncher/src/main/jni/native_hooks/native_hooks.h @@ -0,0 +1,15 @@ +// +// Created by maks on 23.01.2025. +// + +#ifndef POJAVLAUNCHER_NATIVE_HOOKS_H +#define POJAVLAUNCHER_NATIVE_HOOKS_H + +#include + +typedef bytehook_stub_t (*bytehook_hook_all_t)(const char *callee_path_name, const char *sym_name, void *new_func, + bytehook_hooked_t hooked, void *hooked_arg); + +void create_chmod_hooks(bytehook_hook_all_t bytehook_hook_all_p); + +#endif //POJAVLAUNCHER_NATIVE_HOOKS_H diff --git a/app_pojavlauncher/src/main/jni/utils.h b/app_pojavlauncher/src/main/jni/utils.h index 494dde7173..7ebd1dedd8 100644 --- a/app_pojavlauncher/src/main/jni/utils.h +++ b/app_pojavlauncher/src/main/jni/utils.h @@ -11,8 +11,6 @@ jobjectArray convert_from_char_array(JNIEnv *env, char **charArray, int num_rows void free_char_array(JNIEnv *env, jobjectArray jstringArray, const char **charArray); jstring convertStringJVM(JNIEnv* srcEnv, JNIEnv* dstEnv, jstring srcStr); -void hookExec(JNIEnv *env); -void installLwjglDlopenHook(JNIEnv *env); JNIEnv* get_attached_env(JavaVM* jvm); JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNIEnv* env, jclass clazz, jint action, jbyteArray copySrc);