From a72451c9e7735df2d7000358489311784c896697 Mon Sep 17 00:00:00 2001 From: Mathias-Boulay Date: Tue, 7 Jan 2025 20:21:59 +0100 Subject: [PATCH 1/8] fix(gesture): Right click not working Turns out if you were perfectly still, it would fail. For a quick tap, it was fairly easy to create. --- .../pojavlaunch/customcontrols/mouse/RightClickGesture.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java index 3ca4c02ea3..84c5efd183 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/customcontrols/mouse/RightClickGesture.java @@ -17,14 +17,15 @@ public RightClickGesture(Handler mHandler) { public final void inputEvent() { if(!mGestureEnabled) return; if(submit()) { - mGestureStartX = CallbackBridge.mouseX; - mGestureStartY = CallbackBridge.mouseY; + mGestureStartX = mGestureEndX = CallbackBridge.mouseX; + mGestureStartY = mGestureEndY = CallbackBridge.mouseY; mGestureEnabled = false; mGestureValid = true; } } public void setMotion(float deltaX, float deltaY) { + System.out.println("set motion called"); mGestureEndX += deltaX; mGestureEndY += deltaY; } @@ -49,6 +50,7 @@ public void onGestureCancelled(boolean isSwitching) { mGestureEnabled = true; if(!mGestureValid || isSwitching) return; boolean fingerStill = LeftClickGesture.isFingerStill(mGestureStartX, mGestureStartY, mGestureEndX, mGestureEndY, LeftClickGesture.FINGER_STILL_THRESHOLD); + System.out.println("Right click: " + fingerStill); if(!fingerStill) return; CallbackBridge.sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, true); CallbackBridge.sendMouseButton(LwjglGlfwKeycode.GLFW_MOUSE_BUTTON_RIGHT, false); From 041838725b1c5938c7611fa465a3e6959caccea7 Mon Sep 17 00:00:00 2001 From: artdeell Date: Sun, 5 Jan 2025 12:05:09 +0300 Subject: [PATCH 2/8] Fix[ffmpeg_plugin]: better FFmpeg plugin insertion NOTE: requires a different version of the FFmpeg plugin which I haven't made yet --- .../kdt/pojavlaunch/plugins/FFmpegPlugin.java | 8 ++++- .../net/kdt/pojavlaunch/utils/JREUtils.java | 2 +- .../src/main/jni/input_bridge_v3.c | 35 +++++++++++++------ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/plugins/FFmpegPlugin.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/plugins/FFmpegPlugin.java index bd1b308124..514a176555 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/plugins/FFmpegPlugin.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/plugins/FFmpegPlugin.java @@ -5,15 +5,21 @@ import android.content.pm.PackageManager; import android.util.Log; +import java.io.File; + public class FFmpegPlugin { public static boolean isAvailable = false; public static String libraryPath; + public static String executablePath; public static void discover(Context context) { PackageManager manager = context.getPackageManager(); try { PackageInfo ffmpegPluginInfo = manager.getPackageInfo("net.kdt.pojavlaunch.ffmpeg", PackageManager.GET_SHARED_LIBRARY_FILES); libraryPath = ffmpegPluginInfo.applicationInfo.nativeLibraryDir; - isAvailable = true; + File ffmpegExecutable = new File(libraryPath, "libffmpeg.so"); + executablePath = ffmpegExecutable.getAbsolutePath(); + // Older plugin versions still have the old executable location + isAvailable = ffmpegExecutable.exists(); }catch (Exception e) { Log.i("FFmpegPlugin", "Failed to discover plugin", e); } 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 521c91de84..dde80ec37c 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 @@ -215,7 +215,7 @@ public static void setJavaEnvironment(Activity activity, String jreHome) throws envMap.put("LD_LIBRARY_PATH", LD_LIBRARY_PATH); envMap.put("PATH", jreHome + "/bin:" + Os.getenv("PATH")); if(FFmpegPlugin.isAvailable) { - envMap.put("PATH", FFmpegPlugin.libraryPath+":"+envMap.get("PATH")); + envMap.put("POJAV_FFMPEG_PATH", FFmpegPlugin.executablePath); } if(LOCAL_RENDERER != null) { diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index 03bb79f627..ce106f859c 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -230,23 +230,38 @@ 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. + * 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) { - char *pProg = (char *)((*env)->GetByteArrayElements(env, prog, NULL)); - - // Here we only handle the "xdg-open" command - if (strcmp(basename(pProg), "xdg-open") != 0) { - (*env)->ReleaseByteArrayElements(env, prog, (jbyte *)pProg, 0); - return orig_ProcessImpl_forkAndExec(env, process, mode, helperpath, prog, argBlock, argc, envBlock, envc, dir, std_fds, 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); - Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, /* CLIPBOARD_OPEN */ 2002, argBlock); - return 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() { From 6d39ab2d49696d3f5a00de06f0d9ff0b8b24bbf4 Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Sun, 5 Jan 2025 19:03:01 +0300 Subject: [PATCH 3/8] 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 dde80ec37c..1b9a2cd2b4 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 static List 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"); diff --git a/app_pojavlauncher/src/main/jni/Android.mk b/app_pojavlauncher/src/main/jni/Android.mk index f0948a8026..9ee43d7e75 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 ce106f859c..951050a093 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 0000000000..d0755a19b3 --- /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 From aadb91dc989225f84e319f48dbb0fee267aecc59 Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Sun, 5 Jan 2025 19:11:30 +0300 Subject: [PATCH 4/8] Whoops[exec_hooks]: do not null program unconditionally --- app_pojavlauncher/src/main/jni/java_exec_hooks.c | 1 - 1 file changed, 1 deletion(-) diff --git a/app_pojavlauncher/src/main/jni/java_exec_hooks.c b/app_pojavlauncher/src/main/jni/java_exec_hooks.c index d0755a19b3..0972b3c73d 100644 --- a/app_pojavlauncher/src/main/jni/java_exec_hooks.c +++ b/app_pojavlauncher/src/main/jni/java_exec_hooks.c @@ -63,7 +63,6 @@ static jint hooked_ProcessImpl_forkAndExec(JNIEnv *env, jobject process, jint mo // 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); From ebe5314f28df7d94c76384cb6ec463ff505ad0a0 Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Mon, 6 Jan 2025 20:32:54 +0300 Subject: [PATCH 5/8] Feat[lwjgl]: add vulkan to lwjgl dlopen hook, move hook to new file --- app_pojavlauncher/src/main/jni/Android.mk | 1 + app_pojavlauncher/src/main/jni/egl_bridge.c | 14 ++-- .../src/main/jni/input_bridge_v3.c | 41 ------------ .../src/main/jni/lwjgl_dlopen_hook.c | 64 +++++++++++++++++++ 4 files changed, 74 insertions(+), 46 deletions(-) create mode 100644 app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c diff --git a/app_pojavlauncher/src/main/jni/Android.mk b/app_pojavlauncher/src/main/jni/Android.mk index 9ee43d7e75..a820a902a8 100644 --- a/app_pojavlauncher/src/main/jni/Android.mk +++ b/app_pojavlauncher/src/main/jni/Android.mk @@ -46,6 +46,7 @@ LOCAL_SRC_FILES := \ utils.c \ stdio_is.c \ java_exec_hooks.c \ + lwjgl_dlopen_hook.c \ driver_helper/nsbypass.c ifeq ($(TARGET_ARCH_ABI),arm64-v8a) diff --git a/app_pojavlauncher/src/main/jni/egl_bridge.c b/app_pojavlauncher/src/main/jni/egl_bridge.c index 30b89631bb..8b133a30b2 100644 --- a/app_pojavlauncher/src/main/jni/egl_bridge.c +++ b/app_pojavlauncher/src/main/jni/egl_bridge.c @@ -258,14 +258,18 @@ EXTERNAL_API void* pojavCreateContext(void* contextSrc) { return br_init_context((basic_render_window_t*)contextSrc); } -EXTERNAL_API JNIEXPORT jlong JNICALL -Java_org_lwjgl_vulkan_VK_getVulkanDriverHandle(ABI_COMPAT JNIEnv *env, ABI_COMPAT jclass thiz) { - printf("EGLBridge: LWJGL-side Vulkan loader requested the Vulkan handle\n"); - // The code below still uses the env var because +void* maybe_load_vulkan() { + // We use the env var because // 1. it's easier to do that // 2. it won't break if something will try to load vulkan and osmesa simultaneously if(getenv("VULKAN_PTR") == NULL) load_vulkan(); - return strtoul(getenv("VULKAN_PTR"), NULL, 0x10); + return (void*) strtoul(getenv("VULKAN_PTR"), NULL, 0x10); +} + +EXTERNAL_API JNIEXPORT jlong JNICALL +Java_org_lwjgl_vulkan_VK_getVulkanDriverHandle(ABI_COMPAT JNIEnv *env, ABI_COMPAT jclass thiz) { + printf("EGLBridge: LWJGL-side Vulkan loader requested the Vulkan handle\n"); + return (jlong) maybe_load_vulkan(); } EXTERNAL_API void pojavSwapInterval(int interval) { diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index 951050a093..e1d406fa8c 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -228,47 +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); } -/** - * 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 - * The idea is that since, on Android 10 and earlier, the linker doesn't really do namespace nesting. - * It is not a problem as most of the libraries are in the launcher path, but when you try to run - * VulkanMod which loads shaderc outside of the default jni libs directory through this method, - * it can't load it because the path is not in the allowed paths for the anonymous namesapce. - * This method fixes the issue by being in libpojavexec, and thus being in the classloader namespace - */ -jlong ndlopen_bugfix(__attribute__((unused)) JNIEnv *env, - __attribute__((unused)) jclass class, - jlong filename_ptr, - jint jmode) { - const char* filename = (const char*) filename_ptr; - int mode = (int)jmode; - return (jlong) dlopen(filename, mode); -} - -/** - * Install the linker bug mitigation for Android 10 and lower. Fixes VulkanMod crashing on these - * Android versions due to missing namespace nesting. - */ -void installLinkerBugMitigation() { - if(android_get_device_api_level() >= 30) return; - __android_log_print(ANDROID_LOG_INFO, "Api29LinkerFix", "API < 30 detected, installing linker bug mitigation"); - JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE; - jclass dynamicLinkLoader = (*env)->FindClass(env, "org/lwjgl/system/linux/DynamicLinkLoader"); - if(dynamicLinkLoader == NULL) { - __android_log_print(ANDROID_LOG_ERROR, "Api29LinkerFix", "Failed to find the target class"); - (*env)->ExceptionClear(env); - return; - } - JNINativeMethod ndlopenMethod[] = { - {"ndlopen", "(JI)J", &ndlopen_bugfix} - }; - if((*env)->RegisterNatives(env, dynamicLinkLoader, ndlopenMethod, 1) != 0) { - __android_log_print(ANDROID_LOG_ERROR, "Api29LinkerFix", "Failed to register the bugfix method"); - (*env)->ExceptionClear(env); - } -} - /** * 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, diff --git a/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c b/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c new file mode 100644 index 0000000000..96c6333b47 --- /dev/null +++ b/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c @@ -0,0 +1,64 @@ +// +// Created by maks on 06.01.2025. +// + +#include +#include +#include + +#include + +#include +#include +#include + +extern void* maybe_load_vulkan(); + +/** + * 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 + * but with our own additions for stuff like vulkanmod. + */ +static jlong ndlopen_bugfix(__attribute__((unused)) JNIEnv *env, + __attribute__((unused)) jclass class, + jlong filename_ptr, + jint jmode) { + const char* filename = (const char*) filename_ptr; + + // Oveeride vulkan loading to let us load vulkan ourselves + if(strstr(filename, "libvulkan.so") == filename) { + printf("LWJGL linkerhook: replacing load for libvulkan.so with custom driver\n"); + return (jlong) maybe_load_vulkan(); + } + + // This hook also serves the task of mitigating a bug: the idea is that since, on Android 10 and + // earlier, the linker doesn't really do namespace nesting. + // It is not a problem as most of the libraries are in the launcher path, but when you try to run + // VulkanMod which loads shaderc outside of the default jni libs directory through this method, + // it can't load it because the path is not in the allowed paths for the anonymous namesapce. + // This method fixes the issue by being in libpojavexec, and thus being in the classloader namespace + + int mode = (int)jmode; + return (jlong) dlopen(filename, mode); +} + +/** + * Install the LWJGL dlopen hook. This allows us to mitigate linker bugs and add custom library overrides. + */ +void installLinkerBugMitigation() { + __android_log_print(ANDROID_LOG_INFO, "LwjglLinkerHook", "API < 30 detected, installing linker bug mitigation"); + JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE; + 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"); + (*env)->ExceptionClear(env); + return; + } + JNINativeMethod ndlopenMethod[] = { + {"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"); + (*env)->ExceptionClear(env); + } +} \ No newline at end of file From 208b92be83b5b1202070335a94dacbad13e5bb14 Mon Sep 17 00:00:00 2001 From: Maksim Belov Date: Wed, 8 Jan 2025 00:00:48 +0300 Subject: [PATCH 6/8] Style[exec_hooks]: change function name, log lines, add clipboard constants --- app_pojavlauncher/src/main/jni/input_bridge_v3.c | 2 +- app_pojavlauncher/src/main/jni/java_exec_hooks.c | 2 +- app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c | 4 ++-- app_pojavlauncher/src/main/jni/utils.h | 6 ++++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index e1d406fa8c..93c1b9041b 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -57,7 +57,7 @@ jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) { jobject mouseDownBufferJ = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetStaticObjectField(pojav_environ->runtimeJNIEnvPtr_JRE, pojav_environ->vmGlfwClass, field_mouseDownBuffer); pojav_environ->mouseDownBuffer = (*pojav_environ->runtimeJNIEnvPtr_JRE)->GetDirectBufferAddress(pojav_environ->runtimeJNIEnvPtr_JRE, mouseDownBufferJ); hookExec(); - installLinkerBugMitigation(); + installLwjglDlopenHook(); installEMUIIteratorMititgation(); } diff --git a/app_pojavlauncher/src/main/jni/java_exec_hooks.c b/app_pojavlauncher/src/main/jni/java_exec_hooks.c index 0972b3c73d..4469ba117d 100644 --- a/app_pojavlauncher/src/main/jni/java_exec_hooks.c +++ b/app_pojavlauncher/src/main/jni/java_exec_hooks.c @@ -52,7 +52,7 @@ static jint hooked_ProcessImpl_forkAndExec(JNIEnv *env, jobject process, jint mo 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); + Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(env, NULL, CLIPBOARD_OPEN, 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. diff --git a/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c b/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c index 96c6333b47..8694613d14 100644 --- a/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c +++ b/app_pojavlauncher/src/main/jni/lwjgl_dlopen_hook.c @@ -45,8 +45,8 @@ 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 installLinkerBugMitigation() { - __android_log_print(ANDROID_LOG_INFO, "LwjglLinkerHook", "API < 30 detected, installing linker bug mitigation"); +void installLwjglDlopenHook() { + __android_log_print(ANDROID_LOG_INFO, "LwjglLinkerHook", "Installing LWJGL dlopen() hook"); JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE; jclass dynamicLinkLoader = (*env)->FindClass(env, "org/lwjgl/system/linux/DynamicLinkLoader"); if(dynamicLinkLoader == NULL) { diff --git a/app_pojavlauncher/src/main/jni/utils.h b/app_pojavlauncher/src/main/jni/utils.h index 4a03c726a2..69583c44eb 100644 --- a/app_pojavlauncher/src/main/jni/utils.h +++ b/app_pojavlauncher/src/main/jni/utils.h @@ -2,7 +2,9 @@ #include - +#define CLIPBOARD_COPY 2000 +#define CLIPBOARD_PASTE 2001 +#define CLIPBOARD_OPEN 2002 char** convert_to_char_array(JNIEnv *env, jobjectArray jstringArray); jobjectArray convert_from_char_array(JNIEnv *env, char **charArray, int num_rows); @@ -10,7 +12,7 @@ void free_char_array(JNIEnv *env, jobjectArray jstringArray, const char **charAr jstring convertStringJVM(JNIEnv* srcEnv, JNIEnv* dstEnv, jstring srcStr); void hookExec(); -void installLinkerBugMitigation(); +void installLwjglDlopenHook(); void installEMUIIteratorMititgation(); JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNIEnv* env, jclass clazz, jint action, jbyteArray copySrc); From 3c616b9d90c94afcfb40b5e2e5ff4deb67221b06 Mon Sep 17 00:00:00 2001 From: Mathias-Boulay Date: Mon, 13 Jan 2025 00:25:15 +0100 Subject: [PATCH 7/8] tweak(ui): inline the delete profile button Makes the main ui slightly cleaner --- .../java/com/kdt/mcgui/mcAccountSpinner.java | 59 ++++++++++++++----- .../net/kdt/pojavlaunch/LauncherActivity.java | 11 +--- .../layout-land/activity_pojav_launcher.xml | 10 ---- .../res/layout/activity_pojav_launcher.xml | 13 ---- .../res/layout/item_minecraft_account.xml | 40 ++++++++++--- 5 files changed, 77 insertions(+), 56 deletions(-) diff --git a/app_pojavlauncher/src/main/java/com/kdt/mcgui/mcAccountSpinner.java b/app_pojavlauncher/src/main/java/com/kdt/mcgui/mcAccountSpinner.java index 9280aa2aed..0c576ade2f 100644 --- a/app_pojavlauncher/src/main/java/com/kdt/mcgui/mcAccountSpinner.java +++ b/app_pojavlauncher/src/main/java/com/kdt/mcgui/mcAccountSpinner.java @@ -18,12 +18,13 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.BaseAdapter; +import android.widget.ImageView; import android.widget.Toast; import androidx.annotation.Keep; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.AppCompatSpinner; import androidx.core.content.res.ResourcesCompat; @@ -188,7 +189,10 @@ protected void onDraw(Canvas canvas) { } public void removeCurrentAccount(){ - int position = getSelectedItemPosition(); + removeAccount(getSelectedItemPosition()); + } + + private void removeAccount(int position) { if(position == 0) return; File accountFile = new File(Tools.DIR_ACCOUNT_NEW, mAccountList.get(position)+".json"); if(accountFile.exists()) accountFile.delete(); @@ -321,8 +325,9 @@ private void setImageFromSelectedAccount(){ BitmapDrawable oldBitmapDrawable = mHeadDrawable; if(mSelectecAccount != null){ - ExtendedTextView view = ((ExtendedTextView) getSelectedView()); - if(view != null){ + View layout = getSelectedView(); + if(layout != null){ + ExtendedTextView view = layout.findViewById(R.id.account_item); Bitmap bitmap = mSelectecAccount.getSkinFace(); if(bitmap != null) { mHeadDrawable = new BitmapDrawable(getResources(), bitmap); @@ -339,8 +344,7 @@ private void setImageFromSelectedAccount(){ } } - - private static class AccountAdapter extends ArrayAdapter { + private class AccountAdapter extends ArrayAdapter { private final HashMap mImageCache = new HashMap<>(); public AccountAdapter(@NonNull Context context, int resource, @NonNull String[] objects) { @@ -349,20 +353,19 @@ public AccountAdapter(@NonNull Context context, int resource, @NonNull String[] @Override public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { - return getView(position, convertView, parent); - } - - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { if(convertView == null){ convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_minecraft_account, parent, false); } - ExtendedTextView textview = (ExtendedTextView) convertView; + + ExtendedTextView textview = convertView.findViewById(R.id.account_item); + ImageView deleteButton = convertView.findViewById(R.id.delete_account_button); textview.setText(super.getItem(position)); // Handle the "Add account section" - if(position == 0) textview.setCompoundDrawables(ResourcesCompat.getDrawable(parent.getResources(), R.drawable.ic_add, null), null, null, null); + if(position == 0) { + textview.setCompoundDrawables(ResourcesCompat.getDrawable(parent.getResources(), R.drawable.ic_add, null), null, null, null); + deleteButton.setVisibility(View.GONE); + } else { String username = super.getItem(position); Drawable accountHead = mImageCache.get(username); @@ -371,9 +374,37 @@ public View getView(int position, View convertView, @NonNull ViewGroup parent) { mImageCache.put(username, accountHead); } textview.setCompoundDrawables(accountHead, null, null, null); + + deleteButton.setVisibility(View.VISIBLE); + deleteButton.setOnClickListener(v -> { + showDeleteDialog(getContext(), position); + }); } return convertView; } + + + + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + View view = getDropDownView(position, convertView, parent); + view.findViewById(R.id.delete_account_button).setVisibility(View.GONE); + return view; + } + + private void showDeleteDialog(Context context, int position) { + new AlertDialog.Builder(context) + .setMessage(R.string.warning_remove_account) + .setPositiveButton(android.R.string.cancel, null) + .setNeutralButton(R.string.global_delete, (dialog, which) -> { + onDetachedFromWindow(); + removeAccount(position); + }) + .show(); + } } + + } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java index 00cc8e53be..44a873b47c 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/LauncherActivity.java @@ -59,7 +59,7 @@ public class LauncherActivity extends BaseActivity { private mcAccountSpinner mAccountSpinner; private FragmentContainerView mFragmentView; - private ImageButton mSettingsButton, mDeleteAccountButton; + private ImageButton mSettingsButton; private ProgressLayout mProgressLayout; private ProgressServiceKeeper mProgressServiceKeeper; private ModloaderInstallTracker mInstallTracker; @@ -101,13 +101,6 @@ public void onFragmentResumed(@NonNull FragmentManager fm, @NonNull Fragment f) } }; - /* Listener for account deletion */ - private final View.OnClickListener mAccountDeleteButtonListener = v -> new AlertDialog.Builder(this) - .setMessage(R.string.warning_remove_account) - .setPositiveButton(android.R.string.cancel, null) - .setNeutralButton(R.string.global_delete, (dialog, which) -> mAccountSpinner.removeCurrentAccount()) - .show(); - private final ExtraListener mLaunchGameListener = (key, value) -> { if(mProgressLayout.hasProcesses()){ Toast.makeText(this, R.string.tasks_ongoing, Toast.LENGTH_LONG).show(); @@ -200,7 +193,6 @@ protected void onCreate(Bundle savedInstanceState) { ProgressKeeper.addTaskCountListener((mProgressServiceKeeper = new ProgressServiceKeeper(this))); mSettingsButton.setOnClickListener(mSettingButtonListener); - mDeleteAccountButton.setOnClickListener(mAccountDeleteButtonListener); ProgressKeeper.addTaskCountListener(mProgressLayout); ExtraCore.addExtraListener(ExtraConstants.BACK_PREFERENCE, mBackPreferenceListener); ExtraCore.addExtraListener(ExtraConstants.SELECT_AUTH_METHOD, mSelectAuthMethod); @@ -343,7 +335,6 @@ public void askForNotificationPermission(Runnable onSuccessRunnable) { private void bindViews(){ mFragmentView = findViewById(R.id.container_fragment); mSettingsButton = findViewById(R.id.setting_button); - mDeleteAccountButton = findViewById(R.id.delete_account_button); mAccountSpinner = findViewById(R.id.account_spinner); mProgressLayout = findViewById(R.id.progress_layout); } diff --git a/app_pojavlauncher/src/main/res/layout-land/activity_pojav_launcher.xml b/app_pojavlauncher/src/main/res/layout-land/activity_pojav_launcher.xml index a508abf219..4aea232798 100644 --- a/app_pojavlauncher/src/main/res/layout-land/activity_pojav_launcher.xml +++ b/app_pojavlauncher/src/main/res/layout-land/activity_pojav_launcher.xml @@ -28,17 +28,7 @@ app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" /> - - - - tools:text="HELLO THERE" - tools:drawableStart="@mipmap/ic_launcher" + + android:textSize="@dimen/_16ssp" + android:gravity="center_vertical" + android:drawablePadding="@dimen/_6sdp" + android:paddingStart="@dimen/_8sdp" + app:drawableStartSize="@dimen/_30sdp" + + tools:text="HELLO THERE" + tools:drawableStart="@mipmap/ic_launcher" + + + tools:ignore="RtlSymmetry" /> + + + + + From ed89b44d3b7d476011d8d3e97300f9e309bd9e07 Mon Sep 17 00:00:00 2001 From: Mathias-Boulay Date: Mon, 13 Jan 2025 01:09:54 +0100 Subject: [PATCH 8/8] tweak(ui): make the keyboard shift the input For some reason, when entering the settings fragment, it would have this behavior. --- app_pojavlauncher/src/main/AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app_pojavlauncher/src/main/AndroidManifest.xml b/app_pojavlauncher/src/main/AndroidManifest.xml index 740e44964b..ebaf69481c 100644 --- a/app_pojavlauncher/src/main/AndroidManifest.xml +++ b/app_pojavlauncher/src/main/AndroidManifest.xml @@ -55,7 +55,8 @@ + android:label="@string/app_short_name" + android:windowSoftInputMode="adjustResize"/>