diff --git a/FCL/src/main/assets/app_runtime/lwjgl-boat/lwjgl.jar b/FCL/src/main/assets/app_runtime/lwjgl-boat/lwjgl.jar index 4d7ca811f2..410f8e59ea 100644 Binary files a/FCL/src/main/assets/app_runtime/lwjgl-boat/lwjgl.jar and b/FCL/src/main/assets/app_runtime/lwjgl-boat/lwjgl.jar differ diff --git a/FCL/src/main/assets/app_runtime/lwjgl-boat/version b/FCL/src/main/assets/app_runtime/lwjgl-boat/version index b6c40e6852..7ffaa345b5 100644 --- a/FCL/src/main/assets/app_runtime/lwjgl-boat/version +++ b/FCL/src/main/assets/app_runtime/lwjgl-boat/version @@ -1 +1 @@ -1736478001562 \ No newline at end of file +1738372810317 \ No newline at end of file diff --git a/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java b/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java index 2e90dbc235..4b5744df04 100644 --- a/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java +++ b/FCLauncher/src/main/java/com/tungsten/fclauncher/FCLauncher.java @@ -5,6 +5,7 @@ import android.content.Context; import android.os.Build; +import android.system.ErrnoException; import android.system.Os; import android.util.ArrayMap; @@ -163,7 +164,7 @@ private static String[] rebaseArgs(FCLConfig config) throws IOException { String[] args = new String[argList.size()]; for (int i = 0; i < argList.size(); i++) { String a = argList.get(i).replace("${natives_directory}", getLibraryPath(config.getContext(), config.getJavaPath(), config.getRenderer() == FCLConfig.Renderer.RENDERER_CUSTOM ? RendererPlugin.getSelected().getPath() : null)); - args[i] = config.getRenderer() == null ? a : a.replace("${gl_lib_name}", config.getRenderer() == FCLConfig.Renderer.RENDERER_CUSTOM ? RendererPlugin.getSelected().getGlName() : config.getRenderer().getGlLibName()); + args[i] = config.getRenderer() == null ? a : a.replace("${gl_lib_name}", config.getRenderer() == FCLConfig.Renderer.RENDERER_CUSTOM ? RendererPlugin.getSelected().getPath() + "/" +RendererPlugin.getSelected().getGlName() : config.getRenderer().getGlLibName()); } return args; } @@ -362,7 +363,13 @@ private static void setupGraphicAndSoundEngine(FCLConfig config, FCLBridge bridg } } }); - bridge.dlopen(RendererPlugin.getSelected().getPath() + "/" + RendererPlugin.getSelected().getGlName()); + long handle = bridge.dlopen(RendererPlugin.getSelected().getPath() + "/" + RendererPlugin.getSelected().getGlName()); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + try { + Os.setenv("RENDERER_HANDLE",handle+"",true); + } catch (ErrnoException ignore) { + } + } // bridge.dlopen(RendererPlugin.getSelected().getPath() + "/" + RendererPlugin.getSelected().getEglName()); } else { bridge.dlopen(nativeDir + "/" + config.getRenderer().getGlLibName()); diff --git a/FCLauncher/src/main/java/com/tungsten/fclauncher/bridge/FCLBridge.java b/FCLauncher/src/main/java/com/tungsten/fclauncher/bridge/FCLBridge.java index 1b2c311feb..6b454ae2bb 100644 --- a/FCLauncher/src/main/java/com/tungsten/fclauncher/bridge/FCLBridge.java +++ b/FCLauncher/src/main/java/com/tungsten/fclauncher/bridge/FCLBridge.java @@ -111,7 +111,7 @@ public FCLBridge() { public native void setenv(String key, String value); - public native int dlopen(String path); + public native long dlopen(String path); public native void setLdLibraryPath(String path); diff --git a/FCLauncher/src/main/jni/Android.mk b/FCLauncher/src/main/jni/Android.mk index 79ec41c0a0..7cb71eddac 100644 --- a/FCLauncher/src/main/jni/Android.mk +++ b/FCLauncher/src/main/jni/Android.mk @@ -6,7 +6,6 @@ LOCAL_SHARED_LIBRARIES := bytehook LOCAL_SRC_FILES := fcl/fcl_bridge.c \ fcl/fcl_event.c \ fcl/fcl_loader.c \ - fcl/jre_launcher.c \ fcl/utils.c LOCAL_C_INCLUDES := $(LOCAL_PATH)/fcl/include LOCAL_LDLIBS := -llog -ldl -landroid @@ -28,7 +27,8 @@ LOCAL_SRC_FILES := glfw/context.c \ glfw/osmesa_context.c \ glfw/platform.c \ glfw/posix_thread.c \ - glfw/posix_time.c + glfw/posix_time.c \ + glfw/lwjgl_dlopen_hook.c LOCAL_C_INCLUDES := $(LOCAL_PATH)/fcl/include \ $(LOCAL_PATH)/glfw/include LOCAL_CFLAGS := -Wall @@ -96,7 +96,9 @@ LOCAL_SRC_FILES := \ pojav/ctxbridges/swap_interval_no_egl.c \ pojav/environ/environ.c \ pojav/input_bridge_v3.c \ - pojav/virgl/virgl.c + pojav/virgl/virgl.c \ + pojav/jre_launcher.c \ + pojav/lwjgl_dlopen_hook.c LOCAL_C_INCLUDES := $(LOCAL_PATH)/pojav ifeq ($(TARGET_ARCH_ABI),arm64-v8a) LOCAL_CFLAGS += -DADRENO_POSSIBLE diff --git a/FCLauncher/src/main/jni/fcl/fcl_loader.c b/FCLauncher/src/main/jni/fcl/fcl_loader.c index 0977549372..d9eaaa435a 100644 --- a/FCLauncher/src/main/jni/fcl/fcl_loader.c +++ b/FCLauncher/src/main/jni/fcl/fcl_loader.c @@ -156,10 +156,9 @@ JNIEXPORT void JNICALL Java_com_tungsten_fclauncher_bridge_FCLBridge_setenv(JNIE (*env)->ReleaseStringUTFChars(env, str2, value); } -JNIEXPORT jint JNICALL Java_com_tungsten_fclauncher_bridge_FCLBridge_dlopen(JNIEnv* env, jobject jobject, jstring str) { +JNIEXPORT jlong JNICALL Java_com_tungsten_fclauncher_bridge_FCLBridge_dlopen(JNIEnv* env, jobject jobject, jstring str) { dlerror(); - int ret = 0; char const* lib_name = (*env)->GetStringUTFChars(env, str, 0); void* handle; @@ -169,12 +168,8 @@ JNIEXPORT jint JNICALL Java_com_tungsten_fclauncher_bridge_FCLBridge_dlopen(JNIE char * error = dlerror(); FCL_LOG("DLOPEN: loading %s (error = %s)", lib_name, error); - if (handle == NULL) { - ret = -1; - } - (*env)->ReleaseStringUTFChars(env, str, lib_name); - return ret; + return (jlong) handle; } JNIEXPORT void JNICALL Java_com_tungsten_fclauncher_bridge_FCLBridge_setLdLibraryPath(JNIEnv *env, jobject jobject, jstring ldLibraryPath) { diff --git a/FCLauncher/src/main/jni/glfw/lwjgl_dlopen_hook.c b/FCLauncher/src/main/jni/glfw/lwjgl_dlopen_hook.c new file mode 100644 index 0000000000..48bb61cd4c --- /dev/null +++ b/FCLauncher/src/main/jni/glfw/lwjgl_dlopen_hook.c @@ -0,0 +1,67 @@ +// +// Created by maks on 06.01.2025. +// + +#include +#include +#include + +#include +#include +#include +#include "fcl/include/fcl_internal.h" + +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) { + FCL_LOG("LWJGL linkerhook: replacing load for libvulkan.so with custom driver"); + return (jlong) maybe_load_vulkan(); + } + if (getenv("RENDERER_HANDLE") != NULL && strstr(filename,"plugin")) { + return (jlong) strtol(getenv("RENDERER_HANDLE"), NULL, 10); + } + + // 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; + jlong handle = (jlong) dlopen(filename, mode); + return handle; +} + +/** + * Install the LWJGL dlopen hook. This allows us to mitigate linker bugs and add custom library overrides. + */ +void installLwjglDlopenHook(JavaVM* vm) { + FCL_LOG("LwjglLinkerHook:%s", "Installing LWJGL dlopen() hook"); + JNIEnv *env = NULL; + (*vm)->GetEnv(vm, (void **) &env, JNI_VERSION_1_4); + jclass dynamicLinkLoader = (*env)->FindClass(env, "org/lwjgl/system/linux/DynamicLinkLoader"); + if(dynamicLinkLoader == NULL) { + FCL_LOG( "LwjglLinkerHook:%s", "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) { + FCL_LOG( "LwjglLinkerHook:%s", "Failed to register the hooked method"); + (*env)->ExceptionClear(env); + } +} \ No newline at end of file diff --git a/FCLauncher/src/main/jni/glfw/osmesa_context.c b/FCLauncher/src/main/jni/glfw/osmesa_context.c index a998524bcb..90ecfee082 100644 --- a/FCLauncher/src/main/jni/glfw/osmesa_context.c +++ b/FCLauncher/src/main/jni/glfw/osmesa_context.c @@ -10,6 +10,9 @@ #include #include #include +#include "fcl_internal.h" + +extern void installLwjglDlopenHook(JavaVM* vm); int (*vtest_main) (int argc, char** argv); void (*vtest_swap_buffers) (void); @@ -468,8 +471,23 @@ GLFWAPI OSMesaContext glfwGetOSMesaContext(GLFWwindow* handle) return window->context.osmesa.handle; } +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 (void*) strtoul(getenv("VULKAN_PTR"), NULL, 0x10); +} + JNIEXPORT jlong JNICALL Java_org_lwjgl_vulkan_VK_getVulkanDriverHandle(JNIEnv *env, jclass thiz) { if (getenv("VULKAN_PTR") == NULL) load_vulkan(); return strtoul(getenv("VULKAN_PTR"), NULL, 0x10); } + +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + if (fcl->android_jvm != vm) { + installLwjglDlopenHook(vm); + } + return JNI_VERSION_1_2; +} diff --git a/FCLauncher/src/main/jni/pojav/egl_bridge.c b/FCLauncher/src/main/jni/pojav/egl_bridge.c index 4689b896c8..18ca051771 100644 --- a/FCLauncher/src/main/jni/pojav/egl_bridge.c +++ b/FCLauncher/src/main/jni/pojav/egl_bridge.c @@ -208,14 +208,17 @@ 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/FCLauncher/src/main/jni/pojav/input_bridge_v3.c b/FCLauncher/src/main/jni/pojav/input_bridge_v3.c index d0f61807d2..5300d734fb 100644 --- a/FCLauncher/src/main/jni/pojav/input_bridge_v3.c +++ b/FCLauncher/src/main/jni/pojav/input_bridge_v3.c @@ -37,8 +37,9 @@ jint (*orig_ProcessImpl_forkAndExec)(JNIEnv *env, jobject process, jint mode, jb static void registerFunctions(JNIEnv *env); void hookExec(); -void installLinkerBugMitigation(); +extern void installLwjglDlopenHook(); void installEMUIIteratorMititgation(); + JNIEXPORT jstring JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeClipboard(JNIEnv* env, jclass clazz, jint action, jbyteArray copySrc); jint JNI_OnLoad(JavaVM* vm, __attribute__((unused)) void* reserved) { @@ -65,7 +66,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(); } @@ -260,47 +261,6 @@ void hookExec() { 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 - * 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; - FCL_LOG("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) { - FCL_LOG("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) { - FCL_LOG("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/FCLauncher/src/main/jni/fcl/jre_launcher.c b/FCLauncher/src/main/jni/pojav/jre_launcher.c similarity index 99% rename from FCLauncher/src/main/jni/fcl/jre_launcher.c rename to FCLauncher/src/main/jni/pojav/jre_launcher.c index 6076adae64..533304b135 100644 --- a/FCLauncher/src/main/jni/fcl/jre_launcher.c +++ b/FCLauncher/src/main/jni/pojav/jre_launcher.c @@ -33,7 +33,7 @@ // Boardwalk: missing include #include -#include "include/fcl_internal.h" +#include "fcl/include/fcl_internal.h" // Uncomment to try redirect signal handling to JVM diff --git a/FCLauncher/src/main/jni/pojav/lwjgl_dlopen_hook.c b/FCLauncher/src/main/jni/pojav/lwjgl_dlopen_hook.c new file mode 100644 index 0000000000..622976df8a --- /dev/null +++ b/FCLauncher/src/main/jni/pojav/lwjgl_dlopen_hook.c @@ -0,0 +1,70 @@ +// +// Created by maks on 06.01.2025. +// + +#include +#include +#include + +#include "environ/environ.h" + +#include +#include +#include +#include "fcl/include/fcl_internal.h" + +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) { + FCL_LOG("LWJGL linkerhook: replacing load for libvulkan.so with custom driver"); + return (jlong) maybe_load_vulkan(); + } + + if (getenv("RENDERER_HANDLE") != NULL && strstr(filename,"plugin")) { + return (jlong) strtol(getenv("RENDERER_HANDLE"), NULL, 10); + } + + // 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; + jlong handle = (jlong) dlopen(filename, mode); + return handle; +} + +/** + * Install the LWJGL dlopen hook. This allows us to mitigate linker bugs and add custom library overrides. + */ +void installLwjglDlopenHook() { + FCL_LOG("LwjglLinkerHook:%s", "Installing LWJGL dlopen() hook"); + JNIEnv* env = pojav_environ->runtimeJNIEnvPtr_JRE; + jclass dynamicLinkLoader = (*env)->FindClass(env, "org/lwjgl/system/linux/DynamicLinkLoader"); + if(dynamicLinkLoader == NULL) { + FCL_LOG( "LwjglLinkerHook:%s", "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) { + FCL_LOG( "LwjglLinkerHook:%s", "Failed to register the hooked method"); + (*env)->ExceptionClear(env); + } +} \ No newline at end of file diff --git a/LWJGL-Boat/src/main/java/org/lwjgl/glfw/GLFW.java b/LWJGL-Boat/src/main/java/org/lwjgl/glfw/GLFW.java index 2092429539..bce971cc0a 100644 --- a/LWJGL-Boat/src/main/java/org/lwjgl/glfw/GLFW.java +++ b/LWJGL-Boat/src/main/java/org/lwjgl/glfw/GLFW.java @@ -27,6 +27,14 @@ */ public class GLFW { + static { + try { + System.loadLibrary("glfw"); + } catch (UnsatisfiedLinkError e) { + e.printStackTrace(); + } + } + public static final SharedLibrary GLFW = Library.loadNative(GLFW.class, "org.lwjgl.glfw", Configuration.GLFW_LIBRARY_NAME.get(Platform.mapLibraryNameBundled("glfw")), true); /** Contains the function pointers loaded from the glfw {@link SharedLibrary}. */