Skip to content

Commit

Permalink
Fix[ffmpeg_plugin]: replace LD_LIBRARY_PATH/PATH for ffmpeg, switch d…
Browse files Browse the repository at this point in the history
…efault exec mode
  • Loading branch information
artdeell committed Jan 5, 2025
1 parent 4885012 commit cd751fa
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,8 @@ public static List<String> getJavaArgs(Context ctx, String runtimeHome, String u

"-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");
Expand Down
1 change: 1 addition & 0 deletions app_pojavlauncher/src/main/jni/Android.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
52 changes: 0 additions & 52 deletions app_pojavlauncher/src/main/jni/input_bridge_v3.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
90 changes: 90 additions & 0 deletions app_pojavlauncher/src/main/jni/java_exec_hooks.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//
// Created by maks on 05.01.2025.
//

#include <jni.h>
#include <libgen.h>
#include <string.h>
#include <stdlib.h>
#include <dlfcn.h>

#include <environ/environ.h>
#include <android/log.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);

// 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");
}

0 comments on commit cd751fa

Please sign in to comment.