From 289ce5de91ccde48e1b60be5fa074b9a3bc698b9 Mon Sep 17 00:00:00 2001 From: amwatson Date: Thu, 18 Apr 2024 18:40:53 -0500 Subject: [PATCH] add JNIClassNames so that FindClass isn't used at runtime (this was a bug introduced by the upstream merge that seems to affect some quest users) --- src/android/app/build.gradle.kts | 2 +- src/android/app/src/main/jni/id_cache.cpp | 2 + .../main/jni/vr/layers/GameSurfaceLayer.cpp | 2 +- .../src/main/jni/vr/layers/RibbonLayer.cpp | 5 +- .../app/src/main/jni/vr/layers/UILayer.cpp | 12 ++- .../app/src/main/jni/vr/layers/UILayer.h | 10 ++- .../src/main/jni/vr/utils/JniClassNames.cpp | 50 +++++------ .../app/src/main/jni/vr/utils/JniClassNames.h | 7 +- .../app/src/main/jni/vr/utils/JniUtils.cpp | 89 +++++++++++++------ src/android/app/src/main/jni/vr/vr_main.cpp | 5 +- 10 files changed, 108 insertions(+), 76 deletions(-) diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index e8ba098bd..4fbfe82bf 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -149,7 +149,7 @@ android { productFlavors { create("canary") { dimension = "version" - applicationIdSuffix = ".canary" + applicationIdSuffix = ".playtest" } create("nightly") { diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index f1c16fc9f..075db6962 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -13,6 +13,7 @@ #include "jni/applets/swkbd.h" #include "jni/camera/still_image_camera.h" #include "jni/id_cache.h" +#include "vr/utils/JniClassNames.h" #include @@ -254,6 +255,7 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { SoftwareKeyboard::InitJNI(env); Camera::StillImage::InitJNI(env); AndroidStorage::InitJNI(env, s_native_library_class); + VR::JNI::InitJNI(env); return JNI_VERSION; } diff --git a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp index 5160f6106..3bbbbcefe 100644 --- a/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp +++ b/src/android/app/src/main/jni/vr/layers/GameSurfaceLayer.cpp @@ -425,7 +425,7 @@ void GameSurfaceLayer::SetTopPanelFromController(const XrVector3f& controllerPos // Set the initial distance of the window from the viewer. const float sphereRadius = XrMath::Vector3f::Length(mTopPanel.mPanelFromWorld.position - viewerPosition); - XrVector3f windowPosition = + const XrVector3f windowPosition = CalculatePanelPosition(viewerPosition, controllerPosition, sphereRadius); if (windowPosition.z >= -0.5f) { return; } if (XrMath::Vector3f::LengthSq(windowPosition - mLowerPanel.mPanelFromWorld.position) < diff --git a/src/android/app/src/main/jni/vr/layers/RibbonLayer.cpp b/src/android/app/src/main/jni/vr/layers/RibbonLayer.cpp index f89ec8ac5..fa22ccb0d 100644 --- a/src/android/app/src/main/jni/vr/layers/RibbonLayer.cpp +++ b/src/android/app/src/main/jni/vr/layers/RibbonLayer.cpp @@ -1,5 +1,6 @@ #include "RibbonLayer.h" +#include "../utils/JniClassNames.h" #include "../utils/LogUtils.h" #include "../utils/XrMath.h" @@ -38,8 +39,8 @@ XrQuaternionf CalculatePanelRotation(const XrVector3f& windowPosition, RibbonLayer::RibbonLayer(const XrVector3f&& position, const XrQuaternionf&& orientation, JNIEnv* jni, jobject activityObject, const XrSession& session) - : UILayer("org/citra/citra_emu/vr/ui/VrRibbonLayer", std::move(position), - std::move(orientation), jni, activityObject, session) + : UILayer(VR::JniGlobalRef::gVrRibbonLayerClass, std::move(position), std::move(orientation), + jni, activityObject, session) , mInitialPose({orientation, position}) { mIsMenuBackgroundSelectedMethodId = jni->GetMethodID(GetVrUILayerClass(), "isMenuBackgroundSelected", "()Z"); diff --git a/src/android/app/src/main/jni/vr/layers/UILayer.cpp b/src/android/app/src/main/jni/vr/layers/UILayer.cpp index cc4424c37..fd42af5b0 100644 --- a/src/android/app/src/main/jni/vr/layers/UILayer.cpp +++ b/src/android/app/src/main/jni/vr/layers/UILayer.cpp @@ -159,16 +159,15 @@ XrVector2f GetDensityScaleForSize(const int32_t texWidth, const int32_t texHeigh } // anonymous namespace -UILayer::UILayer(const std::string& className, const XrVector3f&& position, +UILayer::UILayer(const jclass classObject, const XrVector3f&& position, const XrQuaternionf&& orientation, JNIEnv* env, jobject activityObject, const XrSession& session) : mPanelFromWorld(XrPosef{orientation, position}) , mSession(session) , mEnv(env) { - const int32_t initializationStatus = Init(className, activityObject, position, session); + const int32_t initializationStatus = Init(classObject, activityObject, position, session); if (initializationStatus < 0) { - FAIL("Could not initialize UILayer(%s) -- error '%d'", className.c_str(), - initializationStatus); + FAIL("Could not initialize %s() -- error '%d'", __FUNCTION__, initializationStatus); } } @@ -215,10 +214,9 @@ bool UILayer::GetRayIntersectionWithPanel(const XrVector3f& start, } // Next error code: -8 -int32_t UILayer::Init(const std::string& className, const jobject activityObject, +int32_t UILayer::Init(const jclass classObject, const jobject activityObject, const XrVector3f& position, const XrSession& session) { - mVrUILayerClass = JniUtils::GetGlobalClassReference(mEnv, activityObject, className.c_str()); - BAIL_ON_COND(mVrUILayerClass == nullptr, "No java UI Layer class", -1); + mVrUILayerClass = classObject; jmethodID vrUILayerConstructor = mEnv->GetMethodID(mVrUILayerClass, "", "(Lorg/citra/citra_emu/vr/VrActivity;)V"); diff --git a/src/android/app/src/main/jni/vr/layers/UILayer.h b/src/android/app/src/main/jni/vr/layers/UILayer.h index 9bb507e6e..60de05f90 100644 --- a/src/android/app/src/main/jni/vr/layers/UILayer.h +++ b/src/android/app/src/main/jni/vr/layers/UILayer.h @@ -65,8 +65,10 @@ class UILayer { public: /** Constructor. - * @param className: the class name of the Java class representing the UI layer. + * @param classObject: the class object of the Java class representing the UI layer. * This class should subclass org.citra.citra_emu.ui.VrUILayer. + * We do it this way because after the v4 update with quest v64, FindClass() returns + * null on the JNI threads (even with the original classloader). * @param position: position of the layer * @param orientation: orientation of the layer * @param jni: the JNI environment. Should be attached to the current thread @@ -74,7 +76,7 @@ public: * the class information for UILayer * @param session a valid XrSession */ - UILayer(const std::string& className, const XrVector3f&& position, + UILayer(const jclass classObject, const XrVector3f&& position, const XrQuaternionf&& orientation, JNIEnv* jni, jobject activityObject, const XrSession& session); ~UILayer(); @@ -132,8 +134,8 @@ protected: XrPosef mPanelFromWorld; private: - int Init(const std::string& className, const jobject activityObject, const XrVector3f& position, - const XrSession& session); + int Init(const jclass classObject, const jobject activityObject, const XrVector3f& position, + const XrSession& session); void Shutdown(); /** Creates the swapchain. diff --git a/src/android/app/src/main/jni/vr/utils/JniClassNames.cpp b/src/android/app/src/main/jni/vr/utils/JniClassNames.cpp index db531cd1a..751587097 100644 --- a/src/android/app/src/main/jni/vr/utils/JniClassNames.cpp +++ b/src/android/app/src/main/jni/vr/utils/JniClassNames.cpp @@ -6,42 +6,36 @@ namespace VR { namespace JniGlobalRef { -jmethodID gFindClassMethodID = nullptr; -jobject gClassLoader = nullptr; +jclass gVrKeyboardLayerClass = nullptr; +jclass gVrErrorMessageLayerClass = nullptr; +jclass gVrRibbonLayerClass = nullptr; } // namespace JniGlobalRef } // namespace VR -void VR::JNI::InitJNI(JNIEnv* jni, jobject activityObject) { +void VR::JNI::InitJNI(JNIEnv* jni) { assert(jni != nullptr); - const jclass activityClass = jni->GetObjectClass(activityObject); - if (activityClass == nullptr) { FAIL("Failed to get activity class"); } - // Get the getClassLoader method ID - const jmethodID getClassLoaderMethod = - jni->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); - if (getClassLoaderMethod == nullptr) { FAIL("Failed to get getClassLoader method ID"); } + VR::JniGlobalRef::gVrKeyboardLayerClass = static_cast( + jni->NewGlobalRef(jni->FindClass("org/citra/citra_emu/vr/ui/VrKeyboardLayer"))); + if (VR::JniGlobalRef::gVrKeyboardLayerClass == nullptr) { + FAIL("Could not find VrKeyboardLayer class"); + } + VR::JniGlobalRef::gVrErrorMessageLayerClass = static_cast( + jni->NewGlobalRef(jni->FindClass("org/citra/citra_emu/vr/ui/VrErrorMessageLayer"))); + if (VR::JniGlobalRef::gVrErrorMessageLayerClass == nullptr) { + FAIL("Could not find VrErrorMessageLayer class"); + } - // Call getClassLoader of the activity object to obtain the class loader - const jobject classLoaderObject = jni->CallObjectMethod(activityObject, getClassLoaderMethod); - if (classLoaderObject == nullptr) { FAIL("Failed to get class loader object"); } - - JniGlobalRef::gClassLoader = jni->NewGlobalRef(classLoaderObject); - - // Step 3: Cache the findClass method ID - jclass classLoaderClass = jni->FindClass("java/lang/ClassLoader"); - if (classLoaderClass == nullptr) { FAIL("Failed to find class loader class"); } - JniGlobalRef::gFindClassMethodID = - jni->GetMethodID(classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); - if (JniGlobalRef::gFindClassMethodID == nullptr) { FAIL("Failed to get findClass method ID"); } - - // Cleanup local references - jni->DeleteLocalRef(activityClass); - jni->DeleteLocalRef(classLoaderClass); + VR::JniGlobalRef::gVrRibbonLayerClass = static_cast( + jni->NewGlobalRef(jni->FindClass("org/citra/citra_emu/vr/ui/VrRibbonLayer"))); + if (VR::JniGlobalRef::gVrRibbonLayerClass == nullptr) { + FAIL("Could not find VrRibbonLayer class"); + } } void VR::JNI::CleanupJNI(JNIEnv* jni) { assert(jni != nullptr); - if (JniGlobalRef::gClassLoader != nullptr) { jni->DeleteGlobalRef(JniGlobalRef::gClassLoader); } - JniGlobalRef::gClassLoader = nullptr; - JniGlobalRef::gFindClassMethodID = nullptr; + VR::JniGlobalRef::gVrKeyboardLayerClass = nullptr; + VR::JniGlobalRef::gVrErrorMessageLayerClass = nullptr; + VR::JniGlobalRef::gVrRibbonLayerClass = nullptr; } diff --git a/src/android/app/src/main/jni/vr/utils/JniClassNames.h b/src/android/app/src/main/jni/vr/utils/JniClassNames.h index 9756b7ad3..8374e779f 100644 --- a/src/android/app/src/main/jni/vr/utils/JniClassNames.h +++ b/src/android/app/src/main/jni/vr/utils/JniClassNames.h @@ -4,14 +4,15 @@ namespace VR { namespace JniGlobalRef { -extern jmethodID gFindClassMethodID; -extern jobject gClassLoader; +extern jclass gVrKeyboardLayerClass; +extern jclass gVrErrorMessageLayerClass; +extern jclass gVrRibbonLayerClass; } // namespace JniGlobalRef namespace JNI { // Called during JNI_OnLoad -void InitJNI(JNIEnv* env, jobject activityObject); +void InitJNI(JNIEnv* env); // Called during JNI_OnUnload void CleanupJNI(JNIEnv* env); } // namespace JNI diff --git a/src/android/app/src/main/jni/vr/utils/JniUtils.cpp b/src/android/app/src/main/jni/vr/utils/JniUtils.cpp index 540a1c4ad..6ac7db87e 100644 --- a/src/android/app/src/main/jni/vr/utils/JniUtils.cpp +++ b/src/android/app/src/main/jni/vr/utils/JniUtils.cpp @@ -12,39 +12,74 @@ License : Licensed under GPLv3 or any later version. #include "JniUtils.h" -#include "JniClassNames.h" #include "LogUtils.h" -jclass JniUtils::GetGlobalClassReference(JNIEnv* env, jobject activityObject, +jclass JniUtils::GetGlobalClassReference(JNIEnv* jni, jobject activityObject, const std::string& className) { - // Convert dot ('.') to slash ('/') in class name (Java uses dots, JNI uses slashes for class - // names) - std::string correctedClassName = className; - std::replace(correctedClassName.begin(), correctedClassName.end(), '.', '/'); - - // Convert std::string to jstring - jstring classNameJString = env->NewStringUTF(correctedClassName.c_str()); - - // Use the global class loader to find the class - jclass clazz = static_cast(env->CallObjectMethod( - VR::JniGlobalRef::gClassLoader, VR::JniGlobalRef::gFindClassMethodID, classNameJString)); - if (clazz == nullptr) { - // Class not found - ALOGE("Class not found: {}", correctedClassName.c_str()); + // First, get the class object of the activity to get its class loader + const jclass activityClass = jni->GetObjectClass(activityObject); + if (activityClass == nullptr) { + ALOGE("Failed to get activity class"); return nullptr; } - // Clean up the local reference to the class name jstring - env->DeleteLocalRef(classNameJString); - - // Check for exceptions and handle them. This is crucial to prevent crashes due to uncaught - // exceptions. - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - return nullptr; // Class not found or other issue + // Get the getClassLoader method ID + const jmethodID getClassLoaderMethod = + jni->GetMethodID(activityClass, "getClassLoader", "()Ljava/lang/ClassLoader;"); + if (getClassLoaderMethod == nullptr) { + ALOGE("Failed to get getClassLoader method ID"); + return nullptr; } - // Return a global reference to the class - return static_cast(env->NewGlobalRef(clazz)); + // Call getClassLoader of the activity object to obtain the class loader + const jobject classLoaderObject = jni->CallObjectMethod(activityObject, getClassLoaderMethod); + if (classLoaderObject == nullptr) { + ALOGE("Failed to get class loader object"); + return nullptr; + } + + // Get the class loader class + const jclass classLoaderClass = jni->FindClass("java/lang/ClassLoader"); + if (classLoaderClass == nullptr) { + ALOGE("Failed to get class loader class"); + return nullptr; + } + + // Get the findClass method ID from the class loader class + const jmethodID findClassMethod = + jni->GetMethodID(classLoaderClass, "findClass", "(Ljava/lang/String;)Ljava/lang/Class;"); + if (findClassMethod == nullptr) { + ALOGE("Failed to get findClass method ID"); + return nullptr; + } + + // Convert the class name string to a jstring + const jstring javaClassName = jni->NewStringUTF(className.c_str()); + if (javaClassName == nullptr) { + ALOGE("Failed to convert class name to jstring"); + return nullptr; + } + + // Call findClass on the class loader object with the class name + const jclass classToFind = static_cast( + jni->CallObjectMethod(classLoaderObject, findClassMethod, javaClassName)); + + // Clean up local references + jni->DeleteLocalRef(activityClass); + jni->DeleteLocalRef(classLoaderObject); + jni->DeleteLocalRef(classLoaderClass); + jni->DeleteLocalRef(javaClassName); + + if (classToFind == nullptr) { + // Handle error (Class not found) + return nullptr; + } + + // Create a global reference to the class + const jclass globalClassRef = reinterpret_cast(jni->NewGlobalRef(classToFind)); + + // Clean up the local reference of the class + jni->DeleteLocalRef(classToFind); + + return globalClassRef; } diff --git a/src/android/app/src/main/jni/vr/vr_main.cpp b/src/android/app/src/main/jni/vr/vr_main.cpp index 50fd0e3d7..5da443381 100644 --- a/src/android/app/src/main/jni/vr/vr_main.cpp +++ b/src/android/app/src/main/jni/vr/vr_main.cpp @@ -301,12 +301,12 @@ private: gOpenXr->mSession); mKeyboardLayer = std::make_unique( - "org/citra/citra_emu/vr/ui/VrKeyboardLayer", XrVector3f{0, -0.4f, -0.5f}, + VR::JniGlobalRef::gVrKeyboardLayerClass, XrVector3f{0, -0.4f, -0.5f}, XrMath::Quatf::FromEuler(-MATH_FLOAT_PI / 4.0f, 0.0f, 0.0f), jni, mActivityObject, gOpenXr->mSession); mErrorMessageLayer = std::make_unique( - "org/citra/citra_emu/vr/ui/VrErrorMessageLayer", XrVector3f{0, -0.1f, -1.0f}, + VR::JniGlobalRef::gVrErrorMessageLayerClass, XrVector3f{0, -0.1f, -1.0f}, XrQuaternionf{0, 0, 0, 1}, jni, mActivityObject, gOpenXr->mSession); // Create the cursor layer. @@ -1256,7 +1256,6 @@ Java_org_citra_citra_1emu_vr_VrActivity_nativeOnCreate(JNIEnv* env, jobject thiz // time to first frame. gOnCreateStartTime = std::chrono::steady_clock::now(); - VR::JNI::InitJNI(env, thiz); JavaVM* jvm; env->GetJavaVM(&jvm); auto ret = VRAppHandle(new VRAppThread(jvm, env, thiz)).l;