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 7dcd31ad84..faf2c34d6e 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 @@ -315,7 +315,8 @@ public static void launchJavaVM(final AppCompatActivity activity, final Runtime System.out.println(JVMArgs); initJavaRuntime(runtimeHome); - setupExitTrap(activity.getApplication()); + JREUtils.setupExitMethod(activity.getApplication()); + JREUtils.initializeGameExitHook(); chdir(gameDirectory == null ? Tools.DIR_GAME_NEW : gameDirectory.getAbsolutePath()); userArgs.add(0,"java"); //argv[0] is the program name according to C standard. @@ -572,10 +573,12 @@ public static int getDetectedVersion() { public static native void setLdLibraryPath(String ldLibraryPath); public static native void setupBridgeWindow(Object surface); public static native void releaseBridgeWindow(); - public static native void setupExitTrap(Context context); + public static native void initializeGameExitHook(); + public static native void setupExitMethod(Context context); // Obtain AWT screen pixels to render on Android SurfaceView public static native int[] renderAWTScreenFrame(/* Object canvas, int width, int height */); static { + System.loadLibrary("exithook"); System.loadLibrary("pojavexec"); System.loadLibrary("pojavexec_awt"); } diff --git a/app_pojavlauncher/src/main/jni/Android.mk b/app_pojavlauncher/src/main/jni/Android.mk index 3defa5f768..6619cd41ea 100644 --- a/app_pojavlauncher/src/main/jni/Android.mk +++ b/app_pojavlauncher/src/main/jni/Android.mk @@ -16,7 +16,6 @@ include $(CLEAR_VARS) LOCAL_LDLIBS := -ldl -llog -landroid # -lGLESv2 LOCAL_MODULE := pojavexec -LOCAL_SHARED_LIBRARIES := bytehook # LOCAL_CFLAGS += -DDEBUG # -DGLES_TEST LOCAL_SRC_FILES := \ @@ -43,6 +42,13 @@ LOCAL_LDLIBS += -lEGL -lGLESv2 endif include $(BUILD_SHARED_LIBRARY) +include $(CLEAR_VARS) +LOCAL_MODULE := exithook +LOCAL_LDLIBS := -ldl -llog +LOCAL_SHARED_LIBRARIES := bytehook pojavexec +LOCAL_SRC_FILES := exit_hook.c +include $(BUILD_SHARED_LIBRARY) + #ifeq ($(TARGET_ARCH_ABI),arm64-v8a) include $(CLEAR_VARS) LOCAL_MODULE := linkerhook diff --git a/app_pojavlauncher/src/main/jni/exit_hook.c b/app_pojavlauncher/src/main/jni/exit_hook.c new file mode 100644 index 0000000000..294fa5c68a --- /dev/null +++ b/app_pojavlauncher/src/main/jni/exit_hook.c @@ -0,0 +1,82 @@ +// +// Created by maks on 15.01.2025. +// + +#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(); +} + +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); +} + +static bool init_exit_hook() { + void* bytehook_handle = dlopen("libbytehook.so", RTLD_NOW); + if(bytehook_handle == NULL) { + goto dlerror; + } + + bytehook_stub_t (*bytehook_hook_all_p)(const char *callee_path_name, const char *sym_name, void *new_func, + bytehook_hooked_t hooked, void *hooked_arg); + 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) { + bytehook_stub_t stub = 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); + 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_initializeGameExitHook(JNIEnv *env, jclass clazz) { + bool hookReady = init_exit_hook(); + if(!hookReady){ + // 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/stdio_is.c b/app_pojavlauncher/src/main/jni/stdio_is.c index 2bd61aeca8..6c95641071 100644 --- a/app_pojavlauncher/src/main/jni/stdio_is.c +++ b/app_pojavlauncher/src/main/jni/stdio_is.c @@ -8,9 +8,10 @@ #include #include #include -#include #include +#include "stdio_is.h" + // // Created by maks on 17.02.21. // @@ -25,7 +26,6 @@ static pthread_t logger; static jmethodID logger_onEventLogged; static volatile jobject logListener = NULL; static int latestlog_fd = -1; -static _Atomic bool exit_tripped = false; static bool recordBuffer(char* buf, ssize_t len) { @@ -100,11 +100,6 @@ Java_net_kdt_pojavlaunch_Logger_begin(JNIEnv *env, __attribute((unused)) jclass pthread_detach(logger); } - - - -typedef void (*exit_func)(int); - _Noreturn void nominal_exit(int code, bool is_signal) { JNIEnv *env; jint errorCode = (*exitTrap_jvm)->GetEnv(exitTrap_jvm, (void**)&env, JNI_VERSION_1_6); @@ -142,48 +137,6 @@ _Noreturn void nominal_exit(int code, bool is_signal) { while(1) {} } -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(); -} - -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); -} - -JNIEXPORT void JNICALL Java_net_kdt_pojavlaunch_utils_JREUtils_setupExitTrap(JNIEnv *env, __attribute((unused)) jclass clazz, jobject context) { - exitTrap_ctx = (*env)->NewGlobalRef(env,context); - (*env)->GetJavaVM(env,&exitTrap_jvm); - exitTrap_exitClass = (*env)->NewGlobalRef(env,(*env)->FindClass(env,"net/kdt/pojavlaunch/ExitActivity")); - exitTrap_staticMethod = (*env)->GetStaticMethodID(env,exitTrap_exitClass,"showExitMessage","(Landroid/content/Context;IZ)V"); - - if(bytehook_init(BYTEHOOK_MODE_AUTOMATIC, false) == BYTEHOOK_STATUS_CODE_OK) { - bytehook_hook_all(NULL, - "exit", - &custom_exit, - NULL, - NULL); - }else { - // 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); - } -} - JNIEXPORT void JNICALL Java_net_kdt_pojavlaunch_Logger_appendToLog(JNIEnv *env, __attribute((unused)) jclass clazz, jstring text) { jsize appendStringLength = (*env)->GetStringUTFLength(env, text); char newChars[appendStringLength+2]; @@ -205,3 +158,13 @@ Java_net_kdt_pojavlaunch_Logger_setLogListener(JNIEnv *env, __attribute((unused) } if(logListenerLocal != NULL) (*env)->DeleteGlobalRef(env, logListenerLocal); } + + +JNIEXPORT void JNICALL +Java_net_kdt_pojavlaunch_utils_JREUtils_setupExitMethod(JNIEnv *env, jclass clazz, + jobject context) { + exitTrap_ctx = (*env)->NewGlobalRef(env,context); + (*env)->GetJavaVM(env,&exitTrap_jvm); + exitTrap_exitClass = (*env)->NewGlobalRef(env,(*env)->FindClass(env,"net/kdt/pojavlaunch/ExitActivity")); + exitTrap_staticMethod = (*env)->GetStaticMethodID(env,exitTrap_exitClass,"showExitMessage","(Landroid/content/Context;IZ)V"); +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/stdio_is.h b/app_pojavlauncher/src/main/jni/stdio_is.h new file mode 100644 index 0000000000..c1df115f50 --- /dev/null +++ b/app_pojavlauncher/src/main/jni/stdio_is.h @@ -0,0 +1,12 @@ +// +// Created by maks on 15.01.2025. +// + +#ifndef POJAVLAUNCHER_STDIO_IS_H +#define POJAVLAUNCHER_STDIO_IS_H + +#include + +_Noreturn void nominal_exit(int code, bool is_signal); + +#endif //POJAVLAUNCHER_STDIO_IS_H