From 6d39ab2d49696d3f5a00de06f0d9ff0b8b24bbf4 Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Sun, 5 Jan 2025 19:03:01 +0300 Subject: [PATCH] Fix[ffmpeg_plugin]: replace LD_LIBRARY_PATH/PATH for ffmpeg, switch default exec mode --- .../net/kdt/pojavlaunch/utils/JREUtils.java | 3 +- app_pojavlauncher/src/main/jni/Android.mk | 1 + .../src/main/jni/input_bridge_v3.c | 52 ----------- .../src/main/jni/java_exec_hooks.c | 90 +++++++++++++++++++ 4 files changed, 93 insertions(+), 53 deletions(-) create mode 100644 app_pojavlauncher/src/main/jni/java_exec_hooks.c 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 dde80ec37..1b9a2cd2b 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 @@ -368,7 +368,8 @@ public class JREUtils { "-Dnet.minecraft.clientmodname=" + Tools.APP_NAME, "-Dfml.earlyprogresswindow=false", //Forge 1.14+ workaround - "-Dloader.disable_forked_guis=true" + "-Dloader.disable_forked_guis=true", + "-Djdk.lang.Process.launchMechanism=FORK" // Default is POSIX_SPAWN which requires starting jspawnhelper, which doesn't work on Android )); if(LauncherPreferences.PREF_ARC_CAPES) { overridableArguments.add("-javaagent:"+new File(Tools.DIR_DATA,"arc_dns_injector/arc_dns_injector.jar").getAbsolutePath()+"=23.95.137.176"); diff --git a/app_pojavlauncher/src/main/jni/Android.mk b/app_pojavlauncher/src/main/jni/Android.mk index f0948a802..9ee43d7e7 100644 --- a/app_pojavlauncher/src/main/jni/Android.mk +++ b/app_pojavlauncher/src/main/jni/Android.mk @@ -45,6 +45,7 @@ LOCAL_SRC_FILES := \ jre_launcher.c \ utils.c \ stdio_is.c \ + java_exec_hooks.c \ driver_helper/nsbypass.c ifeq ($(TARGET_ARCH_ABI),arm64-v8a) diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index ce106f859..951050a09 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -30,8 +30,6 @@ #define EVENT_TYPE_MOUSE_BUTTON 1006 #define EVENT_TYPE_SCROLL 1007 -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); - static void registerFunctions(JNIEnv *env); jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) { @@ -230,56 +228,6 @@ void sendData(int type, int i1, int i2, int i3, int i4) { atomic_fetch_add_explicit(&pojav_environ->eventCounter, 1, memory_order_acquire); } -static jbyteArray stringToBytes(JNIEnv *env, const char* string) { - const jsize string_data_len = (jsize)(strlen(string) + 1); - jbyteArray result = (*env)->NewByteArray(env, (jsize)string_data_len); - (*env)->SetByteArrayRegion(env, result, 0, (jsize)string_data_len, (const jbyte*) string); - return result; -} - -/** - * Hooked version of java.lang.UNIXProcess.forkAndExec() - * which is used to handle the "open" command and "ffmpeg" invocations - */ -jint -hooked_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) { - const char *pProg = (char *)((*env)->GetByteArrayElements(env, prog, NULL)); - const char* pProgBaseName = basename(pProg); - const size_t basename_len = strlen(pProgBaseName); - char prog_basename[basename_len]; - memcpy(&prog_basename, pProgBaseName, basename_len + 1); - (*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0); - - if(strcmp(prog_basename, "xdg-open") == 0) { - // When invoking xdg-open, send that open command into the android half instead - Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, /* CLIPBOARD_OPEN */ 2002, argBlock); - return 0; - }else if(strcmp(prog_basename, "ffmpeg") == 0) { - // When invoking ffmpeg, always replace the program path with the path to ffmpeg from the plugin. - const char* ffmpeg_path = getenv("POJAV_FFMPEG_PATH"); - if(ffmpeg_path != NULL) { - prog = stringToBytes(env, ffmpeg_path); - } - } - return orig_ProcessImpl_forkAndExec(env, process, mode, helperpath, prog, argBlock, argc, envBlock, envc, dir, std_fds, redirectErrorStream); -} - -void hookExec() { - jclass cls; - orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_UNIXProcess_forkAndExec"); - if (!orig_ProcessImpl_forkAndExec) { - orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_ProcessImpl_forkAndExec"); - cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/ProcessImpl"); - } else { - cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/UNIXProcess"); - } - JNINativeMethod methods[] = { - {"forkAndExec", "(I[B[B[BI[BI[B[IZ)I", (void *)&hooked_ProcessImpl_forkAndExec} - }; - (*pojav_environ->runtimeJNIEnvPtr_JRE)->RegisterNatives(pojav_environ->runtimeJNIEnvPtr_JRE, cls, methods, 1); - printf("Registered forkAndExec\n"); -} - /** * Basically a verbatim implementation of ndlopen(), found at * https://github.com/PojavLauncherTeam/lwjgl3/blob/3.3.1/modules/lwjgl/core/src/generated/c/linux/org_lwjgl_system_linux_DynamicLinkLoader.c#L11 diff --git a/app_pojavlauncher/src/main/jni/java_exec_hooks.c b/app_pojavlauncher/src/main/jni/java_exec_hooks.c new file mode 100644 index 000000000..d0755a19b --- /dev/null +++ b/app_pojavlauncher/src/main/jni/java_exec_hooks.c @@ -0,0 +1,90 @@ +// +// Created by maks on 05.01.2025. +// + +#include +#include +#include +#include +#include + +#include +#include +#include + +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); + +// Turn a C-style string into a Java byte array +static jbyteArray stringToBytes(JNIEnv *env, const char* string) { + const jsize string_data_len = (jsize)(strlen(string) + 1); + jbyteArray result = (*env)->NewByteArray(env, (jsize)string_data_len); + (*env)->SetByteArrayRegion(env, result, 0, (jsize)string_data_len, (const jbyte*) string); + return result; +} + +// Replace the env block with the one that has the desired LD_LIBRARY_PATH/PATH. +// (Due to my laziness this ignores the current contents of the block) +static void replaceLibPathInEnvBlock(JNIEnv *env, jbyteArray* envBlock, jint* envc, const char* directory) { + static bool env_block_replacement_warning = false; + if(*envBlock != NULL && !env_block_replacement_warning) { + printf("exec_hooks WARN: replaceLibPathInEnvBlock does not preserve original env. Please notify PojavLauncherTeam if you need that feature\n"); + env_block_replacement_warning = true; + } + char envStr[1024]; + jsize new_envl = snprintf(envStr, sizeof(envStr) / sizeof(char), "LD_LIBRARY_PATH=%s%cPATH=%s", directory, 0 ,directory) + 1; + jbyteArray newBlock = (*env)->NewByteArray(env, new_envl); + (*env)->SetByteArrayRegion(env, newBlock, 0, new_envl, (jbyte*) envStr); + *envBlock = newBlock; + *envc = 2; +} + +/** + * Hooked version of java.lang.UNIXProcess.forkAndExec() + * which is used to handle the "open" command and "ffmpeg" invocations + */ +static jint hooked_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) { + const char *pProg = (char *)((*env)->GetByteArrayElements(env, prog, NULL)); + const char* pProgBaseName = basename(pProg); + const size_t basename_len = strlen(pProgBaseName); + char prog_basename[basename_len]; + memcpy(&prog_basename, pProgBaseName, basename_len + 1); + (*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0); + + if(strcmp(prog_basename, "xdg-open") == 0) { + // When invoking xdg-open, send the open URL into Android + Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, /* CLIPBOARD_OPEN */ 2002, argBlock); + return 0; + }else if(strcmp(prog_basename, "ffmpeg") == 0) { + // When invoking ffmpeg, always replace the program path with the path to ffmpeg from the plugin. + // This allows us to replace the executable name, which is needed because android doesn't allow + // us to put files that don't start with "lib" and end with ".so" into folders that we can execute + // from + + // Also add LD_LIBRARY_PATH and PATH for the lib in order to override the ones from the launcher, since + // they may interfere with ffmpeg dependencies. + const char* ffmpeg_path = getenv("POJAV_FFMPEG_PATH"); + prog = NULL; + if(ffmpeg_path != NULL) { + replaceLibPathInEnvBlock(env, &envBlock, &envc, dirname(ffmpeg_path)); + prog = stringToBytes(env, ffmpeg_path); + } + } + return orig_ProcessImpl_forkAndExec(env, process, mode, helperpath, prog, argBlock, argc, envBlock, envc, dir, std_fds, redirectErrorStream); +} + +// Hook the forkAndExec method in the Java runtime for custom executable overriding. +void hookExec() { + jclass cls; + orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_UNIXProcess_forkAndExec"); + if (!orig_ProcessImpl_forkAndExec) { + orig_ProcessImpl_forkAndExec = dlsym(RTLD_DEFAULT, "Java_java_lang_ProcessImpl_forkAndExec"); + cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/ProcessImpl"); + } else { + cls = (*pojav_environ->runtimeJNIEnvPtr_JRE)->FindClass(pojav_environ->runtimeJNIEnvPtr_JRE, "java/lang/UNIXProcess"); + } + JNINativeMethod methods[] = { + {"forkAndExec", "(I[B[B[BI[BI[B[IZ)I", (void *)&hooked_ProcessImpl_forkAndExec} + }; + (*pojav_environ->runtimeJNIEnvPtr_JRE)->RegisterNatives(pojav_environ->runtimeJNIEnvPtr_JRE, cls, methods, 1); + printf("Registered forkAndExec\n"); +} \ No newline at end of file