diff --git a/README.md b/README.md index 1482d142c..a874f7383 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Android CI](https://github.com/PojavLauncherTeam/PojavLauncher/workflows/Android%20CI/badge.svg)](https://github.com/PojavLauncherTeam/PojavLauncher/actions) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/PojavLauncherTeam/PojavLauncher)](https://github.com/PojavLauncherTeam/PojavLauncher/actions) [![Crowdin](https://badges.crowdin.net/pojavlauncher/localized.svg)](https://crowdin.com/project/pojavlauncher) -[![Discord](https://img.shields.io/discord/724163890803638273.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.com/invite/pojavlauncher-724163890803638273) +[![Discord](https://img.shields.io/discord/724163890803638273.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.com/invite/aenk3EUvER) [![Twitter Follow](https://img.shields.io/twitter/follow/plaunchteam?color=blue&style=flat-square)](https://twitter.com/PLaunchTeam) * From [Boardwalk](https://github.com/zhuowei/Boardwalk)'s ashes here comes PojavLauncher! diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java index bdbab519b..50eb96e74 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/MainActivity.java @@ -442,38 +442,20 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe if(touchCharInput != null) touchCharInput.switchKeyboardState(); } - private static void setUri(Context context, String input, Intent intent) { - if(input.startsWith("file:")) { - int truncLength = 5; - if(input.startsWith("file://")) truncLength = 7; - input = input.substring(truncLength); - Log.i("MainActivity", input); - boolean isDirectory = new File(input).isDirectory(); - if(isDirectory) { - intent.setType(DocumentsContract.Document.MIME_TYPE_DIR); - }else{ - String type = null; - String extension = MimeTypeMap.getFileExtensionFromUrl(input); - if(extension != null) type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if(type == null) type = "*/*"; - intent.setType(type); - } - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.setData(DocumentsContract.buildDocumentUri( - context.getString(R.string.storageProviderAuthorities), input - )); - return; - } - intent.setDataAndType(Uri.parse(input), "*/*"); - } - public static void openLink(String link) { Context ctx = touchpad.getContext(); // no more better way to obtain a context statically ((Activity)ctx).runOnUiThread(() -> { try { - Intent intent = new Intent(Intent.ACTION_VIEW); - setUri(ctx, link, intent); - ctx.startActivity(intent); + if(link.startsWith("file:")) { + int truncLength = 5; + if(link.startsWith("file://")) truncLength = 7; + String path = link.substring(truncLength); + Tools.openPath(ctx, new File(path), false); + }else { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse(link), "*/*"); + ctx.startActivity(intent); + } } catch (Throwable th) { Tools.showError(ctx, th); } @@ -484,9 +466,7 @@ public class MainActivity extends BaseActivity implements ControlButtonMenuListe Context ctx = touchpad.getContext(); // no more better way to obtain a context statically ((Activity)ctx).runOnUiThread(() -> { try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(DocumentsContract.buildDocumentUri(ctx.getString(R.string.storageProviderAuthorities), path), "*/*"); - ctx.startActivity(intent); + Tools.openPath(ctx, new File(path), false); } catch (Throwable th) { Tools.showError(ctx, th); } diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java index d9acc1d0a..9bc091e47 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/Tools.java @@ -78,6 +78,7 @@ import org.apache.commons.codec.binary.Hex; import org.apache.commons.io.IOUtils; import org.lwjgl.glfw.CallbackBridge; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -88,6 +89,7 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Field; +import java.net.URLConnection; import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.ArrayList; @@ -1192,17 +1194,55 @@ public final class Tools { /** Triggers the share intent chooser, with the latestlog file attached to it */ public static void shareLog(Context context){ - Uri contentUri = DocumentsContract.buildDocumentUri(context.getString(R.string.storageProviderAuthorities), Tools.DIR_GAME_HOME + "/latestlog.txt"); + openPath(context, new File(Tools.DIR_GAME_HOME, "latestlog.txt"), true); + } - Intent shareIntent = new Intent(); - shareIntent.setAction(Intent.ACTION_SEND); - shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - shareIntent.setType("text/plain"); + /** + * Determine the MIME type of a File. + * @param file The file to determine the type of + * @return the type, or the default value *slash* if cannot be determined + */ + public static String getMimeType(File file) { + if(file.isDirectory()) return DocumentsContract.Document.MIME_TYPE_DIR; + String mimeType = null; + try (FileInputStream fileInputStream = new FileInputStream(file)){ + // Theoretically we don't even need the buffer since we don't care about the + // contents of the file after the guess, but mark-supported streams + // are a requirement of URLConnection.guessContentTypeFromStream() + try(BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream)) { + mimeType = URLConnection.guessContentTypeFromStream(bufferedInputStream); + } + }catch (IOException e) { + Log.w("FileMimeType", "Failed to determine MIME type by stream", e); + } + if(mimeType != null) return mimeType; + mimeType = URLConnection.guessContentTypeFromName(file.getName()); + if(mimeType != null) return mimeType; + return "*/*"; + } - Intent sendIntent = Intent.createChooser(shareIntent, "latestlog.txt"); - context.startActivity(sendIntent); + /** + * Open the path specified by a File in a file explorer or in a relevant application. + * @param context the current Context + * @param file the File to open + * @param share whether to open a "Share" or an "Open" dialog. + */ + public static void openPath(Context context, File file, boolean share) { + Uri contentUri = DocumentsContract.buildDocumentUri(context.getString(R.string.storageProviderAuthorities), file.getAbsolutePath()); + String mimeType = getMimeType(file); + Intent intent = new Intent(); + if(share) { + intent.setAction(Intent.ACTION_SEND); + intent.setType(getMimeType(file)); + intent.putExtra(Intent.EXTRA_STREAM, contentUri); + }else { + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(contentUri, mimeType); + } + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Intent chooserIntent = Intent.createChooser(intent, file.getName()); + context.startActivity(chooserIntent); } /** Mesure the textview height, given its current parameters */ diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java index 79a87bccd..c760ecfa8 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/MainMenuFragment.java @@ -1,5 +1,6 @@ package net.kdt.pojavlaunch.fragments; +import static net.kdt.pojavlaunch.Tools.openPath; import static net.kdt.pojavlaunch.Tools.shareLog; import android.content.Intent; @@ -20,7 +21,12 @@ import net.kdt.pojavlaunch.R; import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.extra.ExtraConstants; import net.kdt.pojavlaunch.extra.ExtraCore; +import net.kdt.pojavlaunch.prefs.LauncherPreferences; import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper; +import net.kdt.pojavlaunch.value.launcherprofiles.LauncherProfiles; +import net.kdt.pojavlaunch.value.launcherprofiles.MinecraftProfile; + +import java.io.File; public class MainMenuFragment extends Fragment { public static final String TAG = "MainMenuFragment"; @@ -38,6 +44,7 @@ public class MainMenuFragment extends Fragment { Button mCustomControlButton = view.findViewById(R.id.custom_control_button); Button mInstallJarButton = view.findViewById(R.id.install_jar_button); Button mShareLogsButton = view.findViewById(R.id.share_logs_button); + Button mOpenDirectoryButton = view.findViewById(R.id.open_files_button); ImageButton mEditProfileButton = view.findViewById(R.id.edit_profile_button); Button mPlayButton = view.findViewById(R.id.play_button); @@ -57,12 +64,24 @@ public class MainMenuFragment extends Fragment { mShareLogsButton.setOnClickListener((v) -> shareLog(requireContext())); + mOpenDirectoryButton.setOnClickListener((v)-> openPath(v.getContext(), getCurrentProfileDirectory(), false)); + + mNewsButton.setOnLongClickListener((v)->{ Tools.swapFragment(requireActivity(), GamepadMapperFragment.class, GamepadMapperFragment.TAG, null); return true; }); } + private File getCurrentProfileDirectory() { + String currentProfile = LauncherPreferences.DEFAULT_PREF.getString(LauncherPreferences.PREF_KEY_CURRENT_PROFILE, null); + if(!Tools.isValidString(currentProfile)) return new File(Tools.DIR_GAME_NEW); + LauncherProfiles.load(); + MinecraftProfile profileObject = LauncherProfiles.mainProfileJson.profiles.get(currentProfile); + if(profileObject == null) return new File(Tools.DIR_GAME_NEW); + return Tools.getGameDirPath(profileObject); + } + @Override public void onResume() { super.onResume(); diff --git a/app_pojavlauncher/src/main/jni/Android.mk b/app_pojavlauncher/src/main/jni/Android.mk index 3ac7a1f72..f0948a802 100644 --- a/app_pojavlauncher/src/main/jni/Android.mk +++ b/app_pojavlauncher/src/main/jni/Android.mk @@ -34,6 +34,7 @@ LOCAL_SHARED_LIBRARIES := bytehook LOCAL_SRC_FILES := \ bigcoreaffinity.c \ egl_bridge.c \ + ctxbridges/loader_dlopen.c \ ctxbridges/gl_bridge.c \ ctxbridges/osm_bridge.c \ ctxbridges/egl_loader.c \ diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/egl_loader.c b/app_pojavlauncher/src/main/jni/ctxbridges/egl_loader.c index ca53001e2..d64e400b0 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/egl_loader.c +++ b/app_pojavlauncher/src/main/jni/ctxbridges/egl_loader.c @@ -5,6 +5,7 @@ #include #include #include "egl_loader.h" +#include "loader_dlopen.h" EGLBoolean (*eglMakeCurrent_p) (EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); EGLBoolean (*eglDestroyContext_p) (EGLDisplay dpy, EGLContext ctx); @@ -24,28 +25,38 @@ EGLint (*eglGetError_p) (void); EGLContext (*eglCreateContext_p) (EGLDisplay dpy, EGLConfig config, EGLContext share_list, const EGLint *attrib_list); EGLBoolean (*eglSwapInterval_p) (EGLDisplay dpy, EGLint interval); EGLSurface (*eglGetCurrentSurface_p) (EGLint readdraw); +EGLBoolean (*eglQuerySurface_p)( EGLDisplay display, + EGLSurface surface, + EGLint attribute, + EGLint * value); +__eglMustCastToProperFunctionPointerType (*eglGetProcAddress_p) (const char *procname); -void dlsym_EGL() { - void* dl_handle = NULL; - if(getenv("POJAVEXEC_EGL")) dl_handle = dlopen(getenv("POJAVEXEC_EGL"), RTLD_LAZY); - if(dl_handle == NULL) dl_handle = dlopen("libEGL.so", RTLD_LAZY); - if(dl_handle == NULL) abort(); - eglBindAPI_p = dlsym(dl_handle,"eglBindAPI"); - eglChooseConfig_p = dlsym(dl_handle, "eglChooseConfig"); - eglCreateContext_p = dlsym(dl_handle, "eglCreateContext"); - eglCreatePbufferSurface_p = dlsym(dl_handle, "eglCreatePbufferSurface"); - eglCreateWindowSurface_p = dlsym(dl_handle, "eglCreateWindowSurface"); - eglDestroyContext_p = dlsym(dl_handle, "eglDestroyContext"); - eglDestroySurface_p = dlsym(dl_handle, "eglDestroySurface"); - eglGetConfigAttrib_p = dlsym(dl_handle, "eglGetConfigAttrib"); - eglGetCurrentContext_p = dlsym(dl_handle, "eglGetCurrentContext"); - eglGetDisplay_p = dlsym(dl_handle, "eglGetDisplay"); - eglGetError_p = dlsym(dl_handle, "eglGetError"); - eglInitialize_p = dlsym(dl_handle, "eglInitialize"); - eglMakeCurrent_p = dlsym(dl_handle, "eglMakeCurrent"); - eglSwapBuffers_p = dlsym(dl_handle, "eglSwapBuffers"); - eglReleaseThread_p = dlsym(dl_handle, "eglReleaseThread"); - eglSwapInterval_p = dlsym(dl_handle, "eglSwapInterval"); - eglTerminate_p = dlsym(dl_handle, "eglTerminate"); - eglGetCurrentSurface_p = dlsym(dl_handle,"eglGetCurrentSurface"); +bool dlsym_EGL() { + void* dl_handle = loader_dlopen(getenv("POJAVEXEC_EGL"),"libEGL.so", RTLD_LOCAL|RTLD_LAZY); + if(dl_handle == NULL) return false; + eglGetProcAddress_p = dlsym(dl_handle, "eglGetProcAddress"); + if(eglGetProcAddress_p == NULL) { + printf("%s\n", dlerror()); + return false; + } + eglBindAPI_p = (void*) eglGetProcAddress_p("eglBindAPI"); + eglChooseConfig_p = (void*) eglGetProcAddress_p("eglChooseConfig"); + eglCreateContext_p = (void*) eglGetProcAddress_p("eglCreateContext"); + eglCreatePbufferSurface_p = (void*) eglGetProcAddress_p("eglCreatePbufferSurface"); + eglCreateWindowSurface_p = (void*) eglGetProcAddress_p("eglCreateWindowSurface"); + eglDestroyContext_p = (void*) eglGetProcAddress_p("eglDestroyContext"); + eglDestroySurface_p = (void*) eglGetProcAddress_p("eglDestroySurface"); + eglGetConfigAttrib_p = (void*) eglGetProcAddress_p("eglGetConfigAttrib"); + eglGetCurrentContext_p = (void*) eglGetProcAddress_p("eglGetCurrentContext"); + eglGetDisplay_p = (void*) eglGetProcAddress_p("eglGetDisplay"); + eglGetError_p = (void*) eglGetProcAddress_p("eglGetError"); + eglInitialize_p = (void*) eglGetProcAddress_p("eglInitialize"); + eglMakeCurrent_p = (void*) eglGetProcAddress_p("eglMakeCurrent"); + eglSwapBuffers_p = (void*) eglGetProcAddress_p("eglSwapBuffers"); + eglReleaseThread_p = (void*) eglGetProcAddress_p("eglReleaseThread"); + eglSwapInterval_p = (void*) eglGetProcAddress_p("eglSwapInterval"); + eglTerminate_p = (void*) eglGetProcAddress_p("eglTerminate"); + eglGetCurrentSurface_p = (void*) eglGetProcAddress_p("eglGetCurrentSurface"); + eglQuerySurface_p = (void*) eglGetProcAddress_p("eglQuerySurface"); + return true; } diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/egl_loader.h b/app_pojavlauncher/src/main/jni/ctxbridges/egl_loader.h index 5c761d262..4e78408e8 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/egl_loader.h +++ b/app_pojavlauncher/src/main/jni/ctxbridges/egl_loader.h @@ -2,6 +2,7 @@ // Created by maks on 21.09.2022. // #include +#include #ifndef POJAVLAUNCHER_EGL_LOADER_H #define POJAVLAUNCHER_EGL_LOADER_H @@ -23,7 +24,12 @@ extern EGLint (*eglGetError_p) (void); extern EGLContext (*eglCreateContext_p) (EGLDisplay dpy, EGLConfig config, EGLContext share_list, const EGLint *attrib_list); extern EGLBoolean (*eglSwapInterval_p) (EGLDisplay dpy, EGLint interval); extern EGLSurface (*eglGetCurrentSurface_p) (EGLint readdraw); +extern EGLBoolean (*eglQuerySurface_p)( EGLDisplay display, + EGLSurface surface, + EGLint attribute, + EGLint * value); +extern __eglMustCastToProperFunctionPointerType (*eglGetProcAddress_p) (const char *procname); -void dlsym_EGL(); +bool dlsym_EGL(); #endif //POJAVLAUNCHER_EGL_LOADER_H diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c b/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c index a0efdd941..89cc44bc7 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c +++ b/app_pojavlauncher/src/main/jni/ctxbridges/gl_bridge.c @@ -20,7 +20,7 @@ static __thread gl_render_window_t* currentBundle; static EGLDisplay g_EglDisplay; bool gl_init() { - dlsym_EGL(); + if(!dlsym_EGL()) return false; g_EglDisplay = eglGetDisplay_p(EGL_DEFAULT_DISPLAY); if (g_EglDisplay == EGL_NO_DISPLAY) { __android_log_print(ANDROID_LOG_ERROR, g_LogTag, "%s", @@ -39,6 +39,41 @@ gl_render_window_t* gl_get_current() { return currentBundle; } +static void gl4esi_get_display_dimensions(int* width, int* height) { + if(currentBundle == NULL) goto zero; + EGLSurface surface = currentBundle->surface; + // Fetch dimensions from the EGL surface - the most reliable way + EGLBoolean result_width = eglQuerySurface_p(g_EglDisplay, surface, EGL_WIDTH, width); + EGLBoolean result_height = eglQuerySurface_p(g_EglDisplay, surface, EGL_HEIGHT, height); + if(!result_width || !result_height) goto zero; + return; + + zero: + // No idea what to do, but feeding gl4es incorrect or non-initialized dimensions may be + // a bad idea. Set to zero in case of errors. + *width = 0; + *height = 0; +} + +static bool already_initialized = false; +static void gl_init_gl4es_internals() { + if(already_initialized) return; + already_initialized = true; + void* gl4es = dlopen("libgl4es_114.so", RTLD_NOLOAD); + if(gl4es == NULL) return; + void (*set_getmainfbsize)(void (*new_getMainFBSize)(int* width, int* height)); + set_getmainfbsize = dlsym(gl4es, "set_getmainfbsize"); + if(set_getmainfbsize == NULL) goto warn; + set_getmainfbsize(gl4esi_get_display_dimensions); + goto cleanup; + + warn: + printf("gl4esinternals warning: gl4es was found but internals not initialized. expect rendering issues.\n"); + cleanup: + // dlclose just decreases a ref counter, so this is fine + dlclose(gl4es); +} + gl_render_window_t* gl_init_context(gl_render_window_t *share) { gl_render_window_t* bundle = malloc(sizeof(gl_render_window_t)); memset(bundle, 0, sizeof(gl_render_window_t)); @@ -110,6 +145,11 @@ void gl_swap_surface(gl_render_window_t* bundle) { } void gl_make_current(gl_render_window_t* bundle) { + // Perform initialization here as the renderer may not be loaded when gl_init or gl_init_context is called. + // Yes, even though it is dlopened on MC startup by Pojav, due to linker namespacing weirdness + // on API 29/MIUI it may not be loaded at the point of the gl_init call in the current namespace. + gl_init_gl4es_internals(); + if(bundle == NULL) { if(eglMakeCurrent_p(g_EglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { currentBundle = NULL; diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/loader_dlopen.c b/app_pojavlauncher/src/main/jni/ctxbridges/loader_dlopen.c new file mode 100644 index 000000000..cc8ff70da --- /dev/null +++ b/app_pojavlauncher/src/main/jni/ctxbridges/loader_dlopen.c @@ -0,0 +1,24 @@ +// +// Created by maks on 26.10.2024. +// +#include +#include +#include +#include +void* loader_dlopen(char* primaryName, char* secondaryName, int flags) { + void* dl_handle; + if(primaryName == NULL) goto secondary; + + dl_handle = dlopen(primaryName, flags); + if(dl_handle != NULL) return dl_handle; + if(secondaryName == NULL) goto dl_error; + + secondary: + dl_handle = dlopen(secondaryName, flags); + if(dl_handle == NULL) goto dl_error; + return dl_handle; + + dl_error: + printf("%s", dlerror()); + return NULL; +} \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/loader_dlopen.h b/app_pojavlauncher/src/main/jni/ctxbridges/loader_dlopen.h new file mode 100644 index 000000000..0e23e448a --- /dev/null +++ b/app_pojavlauncher/src/main/jni/ctxbridges/loader_dlopen.h @@ -0,0 +1,10 @@ +// +// Created by maks on 26.10.2024. +// + +#ifndef POJAVLAUNCHER_LOADER_DLOPEN_H +#define POJAVLAUNCHER_LOADER_DLOPEN_H + +void* loader_dlopen(char* primaryName, char* secondaryName, int flags); + +#endif //POJAVLAUNCHER_LOADER_DLOPEN_H diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c b/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c index 9f6e5c613..814f2e86d 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c +++ b/app_pojavlauncher/src/main/jni/ctxbridges/osm_bridge.c @@ -16,8 +16,8 @@ static char no_render_buffer[4]; void setNativeWindowSwapInterval(struct ANativeWindow* nativeWindow, int swapInterval); bool osm_init() { - dlsym_OSMesa(); - return true; // no more specific initialization required + if(!dlsym_OSMesa()) return false; + return true; } osm_render_window_t* osm_get_current() { diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/osmesa_loader.c b/app_pojavlauncher/src/main/jni/ctxbridges/osmesa_loader.c index 331e3c9a2..43cc3a579 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/osmesa_loader.c +++ b/app_pojavlauncher/src/main/jni/ctxbridges/osmesa_loader.c @@ -4,6 +4,7 @@ #include #include #include +#include "loader_dlopen.h" #include "osmesa_loader.h" GLboolean (*OSMesaMakeCurrent_p) (OSMesaContext ctx, void *buffer, GLenum type, @@ -17,26 +18,25 @@ void (*glFinish_p) (void); void (*glClearColor_p) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); void (*glClear_p) (GLbitfield mask); void (*glReadPixels_p) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * data); +void* (*OSMesaGetProcAddress_p)(const char* funcName); -void dlsym_OSMesa() { - char* main_path = NULL; - char* alt_path = NULL; - if(asprintf(&main_path, "%s/libOSMesa.so", getenv("POJAV_NATIVEDIR")) == -1 || - asprintf(&alt_path, "%s/libOSMesa.so.8", getenv("POJAV_NATIVEDIR")) == -1) { - abort(); +bool dlsym_OSMesa() { + void* dl_handle = loader_dlopen("libOSMesa.so.8", "libOSMesa.so", RTLD_LOCAL | RTLD_LAZY); + if(dl_handle == NULL) return false; + OSMesaGetProcAddress_p = dlsym(dl_handle, "OSMesaGetProcAddress"); + if(OSMesaGetProcAddress_p == NULL) { + printf("%s\n", dlerror()); + return false; } - void* dl_handle = NULL; - dl_handle = dlopen(alt_path, RTLD_GLOBAL); - if(dl_handle == NULL) dl_handle = dlopen(main_path, RTLD_GLOBAL); - if(dl_handle == NULL) abort(); - OSMesaMakeCurrent_p = dlsym(dl_handle, "OSMesaMakeCurrent"); - OSMesaGetCurrentContext_p = dlsym(dl_handle,"OSMesaGetCurrentContext"); - OSMesaCreateContext_p = dlsym(dl_handle, "OSMesaCreateContext"); - OSMesaDestroyContext_p = dlsym(dl_handle, "OSMesaDestroyContext"); - OSMesaPixelStore_p = dlsym(dl_handle,"OSMesaPixelStore"); - glGetString_p = dlsym(dl_handle,"glGetString"); - glClearColor_p = dlsym(dl_handle, "glClearColor"); - glClear_p = dlsym(dl_handle,"glClear"); - glFinish_p = dlsym(dl_handle,"glFinish"); - glReadPixels_p = dlsym(dl_handle,"glReadPixels"); + OSMesaMakeCurrent_p = OSMesaGetProcAddress_p("OSMesaMakeCurrent"); + OSMesaGetCurrentContext_p = OSMesaGetProcAddress_p("OSMesaGetCurrentContext"); + OSMesaCreateContext_p = OSMesaGetProcAddress_p("OSMesaCreateContext"); + OSMesaDestroyContext_p = OSMesaGetProcAddress_p("OSMesaDestroyContext"); + OSMesaPixelStore_p = OSMesaGetProcAddress_p("OSMesaPixelStore"); + glGetString_p = OSMesaGetProcAddress_p("glGetString"); + glClearColor_p = OSMesaGetProcAddress_p("glClearColor"); + glClear_p = OSMesaGetProcAddress_p("glClear"); + glFinish_p = OSMesaGetProcAddress_p("glFinish"); + glReadPixels_p = OSMesaGetProcAddress_p("glReadPixels"); + return true; } \ No newline at end of file diff --git a/app_pojavlauncher/src/main/jni/ctxbridges/osmesa_loader.h b/app_pojavlauncher/src/main/jni/ctxbridges/osmesa_loader.h index 6cb76d25c..9a876563b 100644 --- a/app_pojavlauncher/src/main/jni/ctxbridges/osmesa_loader.h +++ b/app_pojavlauncher/src/main/jni/ctxbridges/osmesa_loader.h @@ -6,6 +6,7 @@ #define POJAVLAUNCHER_OSMESA_LOADER_H #include +#include extern GLboolean (*OSMesaMakeCurrent_p) (OSMesaContext ctx, void *buffer, GLenum type, GLsizei width, GLsizei height); @@ -18,5 +19,7 @@ extern void (*glFinish_p) (void); extern void (*glClearColor_p) (GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha); extern void (*glClear_p) (GLbitfield mask); extern void (*glReadPixels_p) (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void * data); -void dlsym_OSMesa(); +extern void* (*OSMesaGetProcAddress_p)(const char* funcName); + +bool dlsym_OSMesa(); #endif //POJAVLAUNCHER_OSMESA_LOADER_H diff --git a/app_pojavlauncher/src/main/jni/input_bridge_v3.c b/app_pojavlauncher/src/main/jni/input_bridge_v3.c index 8a443577f..4bd94f6e1 100644 --- a/app_pojavlauncher/src/main/jni/input_bridge_v3.c +++ b/app_pojavlauncher/src/main/jni/input_bridge_v3.c @@ -546,17 +546,38 @@ JNIEXPORT void JNICALL Java_org_lwjgl_glfw_GLFW_nglfwSetShowingWindow(__attribut } JNIEXPORT void JNICALL Java_org_lwjgl_glfw_CallbackBridge_nativeSetWindowAttrib(__attribute__((unused)) JNIEnv* env, __attribute__((unused)) jclass clazz, jint attrib, jint value) { - if (!pojav_environ->showingWindow || !pojav_environ->isUseStackQueueCall) { + // Check for stack queue no longer necessary here as the JVM crash's origin is resolved + if (!pojav_environ->showingWindow) { // If the window is not shown, there is nothing to do yet. - // For Minecraft < 1.13, calling to JNI functions here crashes the JVM for some reason, therefore it is skipped for now. return; } - (*pojav_environ->runtimeJNIEnvPtr_JRE)->CallStaticVoidMethod( - pojav_environ->runtimeJNIEnvPtr_JRE, - pojav_environ->vmGlfwClass, pojav_environ->method_glftSetWindowAttrib, - (jlong) pojav_environ->showingWindow, attrib, value + // We cannot use pojav_environ->runtimeJNIEnvPtr_JRE here because that environment is attached + // on the thread that loaded pojavexec (which is the thread that first references the GLFW class) + // But this method is only called from the Android UI thread + + // Technically the better solution would be to have a permanently attached env pointer stored + // in environ for the Android UI thread but this is the only place that uses it + // (very rarely, only in lifecycle callbacks) so i dont care + + JavaVM* jvm = pojav_environ->runtimeJavaVMPtr; + JNIEnv *jvm_env = NULL; + jint env_result = (*jvm)->GetEnv(jvm, (void**)&jvm_env, JNI_VERSION_1_4); + if(env_result == JNI_EDETACHED) { + env_result = (*jvm)->AttachCurrentThread(jvm, &jvm_env, NULL); + } + if(env_result != JNI_OK) { + printf("input_bridge nativeSetWindowAttrib() JNI call failed: %i\n", env_result); + return; + } + + (*jvm_env)->CallStaticVoidMethod( + jvm_env, pojav_environ->vmGlfwClass, + pojav_environ->method_glftSetWindowAttrib, + (jlong) pojav_environ->showingWindow, attrib, value ); + + // Attaching every time is annoying, so stick the attachment to the Android GUI thread around } const static JNINativeMethod critical_fcns[] = { {"nativeSetUseInputStackQueue", "(Z)V", critical_set_stackqueue}, diff --git a/app_pojavlauncher/src/main/res/layout-land/fragment_launcher.xml b/app_pojavlauncher/src/main/res/layout-land/fragment_launcher.xml index 7881e8747..3cf2f0b6a 100644 --- a/app_pojavlauncher/src/main/res/layout-land/fragment_launcher.xml +++ b/app_pojavlauncher/src/main/res/layout-land/fragment_launcher.xml @@ -14,7 +14,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:orientation="vertical" - app:layout_constraintBottom_toTopOf="@id/mc_version_spinner" + app:layout_constraintBottom_toTopOf="@id/_background_display_view" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> @@ -30,11 +30,8 @@ app:layout_constraintGuide_percent="0.5"/> + app:layout_constraintTop_toBottomOf="@id/custom_control_button" + tools:layout_editor_absoluteX="0dp" /> + app:layout_constraintTop_toBottomOf="@id/install_jar_button" /> + + diff --git a/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml b/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml index e5c9d0301..ad6f7229d 100644 --- a/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml +++ b/app_pojavlauncher/src/main/res/layout/fragment_launcher.xml @@ -26,10 +26,8 @@ app:layout_constraintGuide_percent="0.5"/> + app:layout_constraintTop_toBottomOf="@id/news_button" /> + app:layout_constraintTop_toBottomOf="@id/custom_control_button" /> - app:layout_constraintTop_toBottomOf="@id/install_jar_button" - /> + diff --git a/app_pojavlauncher/src/main/res/values-land/styles.xml b/app_pojavlauncher/src/main/res/values-land/styles.xml index 82b6ade30..afdf92305 100644 --- a/app_pojavlauncher/src/main/res/values-land/styles.xml +++ b/app_pojavlauncher/src/main/res/values-land/styles.xml @@ -12,4 +12,11 @@ @dimen/_14ssp + + + diff --git a/app_pojavlauncher/src/main/res/values/strings.xml b/app_pojavlauncher/src/main/res/values/strings.xml index cda8a0947..cfc5affce 100644 --- a/app_pojavlauncher/src/main/res/values/strings.xml +++ b/app_pojavlauncher/src/main/res/values/strings.xml @@ -416,7 +416,8 @@ Change controller key bindings Allows you to modify the keyboard keys bound to each controller button Discord - https://discord.gg/pojavlauncher-724163890803638273 + Open game directory + https://discord.com/invite/aenk3EUvER Unsuitable username The username must be between 3–16 characters long, and must only contain latin letters, arabic numerals and underscores. Quick settings diff --git a/app_pojavlauncher/src/main/res/values/styles.xml b/app_pojavlauncher/src/main/res/values/styles.xml index ad48e8fd4..4da162a72 100644 --- a/app_pojavlauncher/src/main/res/values/styles.xml +++ b/app_pojavlauncher/src/main/res/values/styles.xml @@ -13,6 +13,19 @@ + + + + +