From e8e9105598d444e94f44cc4863ba3df58f59aea6 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Wed, 16 Aug 2023 09:46:22 +0800 Subject: [PATCH] Update API + Partial separate hook bridge --- .../de/robv/android/xposed/XC_MethodHook.java | 4 +- .../de/robv/android/xposed/XposedBridge.java | 165 ++-- .../lspd/hooker/LoadedApkGetCLHooker.java | 2 +- .../org/lsposed/lspd/impl/LSPosedBridge.java | 189 +++++ .../org/lsposed/lspd/impl/LSPosedContext.java | 782 ++---------------- .../lspd/impl/LSPosedHookCallback.java | 94 +++ .../lsposed/lspd/nativebridge/HookBridge.java | 2 +- core/src/main/jni/src/jni/hook_bridge.cpp | 71 +- 8 files changed, 431 insertions(+), 878 deletions(-) create mode 100644 core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java create mode 100644 core/src/main/java/org/lsposed/lspd/impl/LSPosedHookCallback.java diff --git a/core/src/main/java/de/robv/android/xposed/XC_MethodHook.java b/core/src/main/java/de/robv/android/xposed/XC_MethodHook.java index 798f47c9..12efd52a 100644 --- a/core/src/main/java/de/robv/android/xposed/XC_MethodHook.java +++ b/core/src/main/java/de/robv/android/xposed/XC_MethodHook.java @@ -119,8 +119,8 @@ public abstract class XC_MethodHook extends XCallback { */ public Object[] args; - private Object result = null; - private Throwable throwable = null; + public Object result = null; + public Throwable throwable = null; public boolean returnEarly = false; private final HashMap extras = new HashMap<>(); diff --git a/core/src/main/java/de/robv/android/xposed/XposedBridge.java b/core/src/main/java/de/robv/android/xposed/XposedBridge.java index 1c4cd75f..b7730431 100644 --- a/core/src/main/java/de/robv/android/xposed/XposedBridge.java +++ b/core/src/main/java/de/robv/android/xposed/XposedBridge.java @@ -25,6 +25,8 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.util.Log; +import org.lsposed.lspd.impl.LSPosedBridge; +import org.lsposed.lspd.impl.LSPosedHookCallback; import org.lsposed.lspd.nativebridge.HookBridge; import org.lsposed.lspd.nativebridge.ResourcesHook; @@ -35,10 +37,8 @@ import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; -import java.util.ArrayDeque; import java.util.Arrays; import java.util.HashSet; -import java.util.Queue; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; @@ -81,20 +81,6 @@ public final class XposedBridge { public static volatile ClassLoader dummyClassLoader = null; - private static final String castException = "Return value's type from hook callback does not match the hooked method"; - - private static final Method getCause; - - static { - Method tmp; - try { - tmp = InvocationTargetException.class.getMethod("getCause"); - } catch (Throwable e) { - tmp = null; - } - getCause = tmp; - } - public static void initXResources() { if (dummyClassLoader != null) { return; @@ -221,7 +207,7 @@ public final class XposedBridge { throw new IllegalArgumentException("callback should not be null!"); } - if (!HookBridge.hookMethod(false, (Executable) hookMethod, AdditionalHookInfo.class, callback.priority, callback)) { + if (!HookBridge.hookMethod(false, (Executable) hookMethod, LSPosedBridge.NativeHooker.class, callback.priority, callback)) { log("Failed to hook " + hookMethod); return null; } @@ -397,70 +383,25 @@ public final class XposedBridge { } } - public static class AdditionalHookInfo { - private final Object params; + public static class LegacyApiSupport { + private final XC_MethodHook.MethodHookParam param; + private final LSPosedHookCallback callback; + private final Object[] snapshot; - private AdditionalHookInfo(Executable method) { - var isStatic = Modifier.isStatic(method.getModifiers()); - Object returnType; - if (method instanceof Method) { - returnType = ((Method) method).getReturnType(); - } else { - returnType = null; - } - params = new Object[]{ - method, - returnType, - isStatic, - }; + private int beforeIdx; + + public LegacyApiSupport(LSPosedHookCallback callback, Object[] legacySnapshot) { + this.param = new XC_MethodHook.MethodHookParam<>(); + this.callback = callback; + this.snapshot = legacySnapshot; } - // This method is quite critical. We should try not to use system methods to avoid - // endless recursive - public Object callback(Object[] args) throws Throwable { - XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam<>(); - - var array = ((Object[]) params); - - var method = (T) array[0]; - var returnType = (Class) array[1]; - var isStatic = (Boolean) array[2]; - - param.method = method; - - if (isStatic) { - param.thisObject = null; - param.args = args; - } else { - param.thisObject = args[0]; - param.args = new Object[args.length - 1]; - //noinspection ManualArrayCopy - for (int i = 0; i < args.length - 1; ++i) { - param.args[i] = args[i + 1]; - } - } - - Object[] callbacksSnapshot = HookBridge.callbackSnapshot(HookerCallback.class, method); - if (callbacksSnapshot == null || callbacksSnapshot.length == 0) { + public void handleBefore() { + syncronizeApi(param, callback, true); + for (beforeIdx = 0; beforeIdx < snapshot.length; beforeIdx++) { try { - return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args); - } catch (InvocationTargetException ite) { - throw (Throwable) HookBridge.invokeOriginalMethod(getCause, ite); - } - } - Queue extras = new ArrayDeque<>(callbacksSnapshot.length); - - // call "before method" callbacks - int beforeIdx = 0; - do { - try { - var cb = callbacksSnapshot[beforeIdx]; - if (HookBridge.instanceOf(cb, XC_MethodHook.class)) { - ((XC_MethodHook) cb).beforeHookedMethod(param); - } else if (HookBridge.instanceOf(cb, HookerCallback.class)) { - var hooker = (HookerCallback) cb; - extras.add(hooker.beforeInvocation.invoke(null, method, param.thisObject, param.args)); - } + var cb = (XC_MethodHook) snapshot[beforeIdx]; + cb.beforeHookedMethod(param); } catch (Throwable t) { XposedBridge.log(t); @@ -475,62 +416,48 @@ public final class XposedBridge { beforeIdx++; break; } - } while (++beforeIdx < callbacksSnapshot.length); - - // call original method if not requested otherwise - if (!param.returnEarly) { - try { - param.setResult(HookBridge.invokeOriginalMethod(method, param.thisObject, param.args)); - } catch (InvocationTargetException e) { - param.setThrowable((Throwable) HookBridge.invokeOriginalMethod(getCause, e)); - } } + syncronizeApi(param, callback, false); + } - // call "after method" callbacks - int afterIdx = beforeIdx - 1; - do { + public void handleAfter() { + syncronizeApi(param, callback, true); + for (int afterIdx = beforeIdx - 1; afterIdx >= 0; afterIdx--) { Object lastResult = param.getResult(); Throwable lastThrowable = param.getThrowable(); - - var cb = callbacksSnapshot[afterIdx]; try { - if (HookBridge.instanceOf(cb, XC_MethodHook.class)) { - ((XC_MethodHook) cb).afterHookedMethod(param); - } else if (HookBridge.instanceOf(cb, HookerCallback.class)) { - var hooker = (HookerCallback) cb; - hooker.afterInvocation.invoke(null, extras.poll(), lastResult); - } + var cb = (XC_MethodHook) snapshot[afterIdx]; + cb.afterHookedMethod(param); } catch (Throwable t) { XposedBridge.log(t); // reset to last result (ignoring what the unexpectedly exiting callback did) - if (lastThrowable == null) + if (lastThrowable == null) { param.setResult(lastResult); - else + } else { param.setThrowable(lastThrowable); + } } - } while (--afterIdx >= 0); + } + syncronizeApi(param, callback, false); + } - // return - if (param.hasThrowable()) - throw param.getThrowable(); - else { - var result = param.getResult(); - if (returnType != null && !returnType.isPrimitive() && !HookBridge.instanceOf(result, returnType)) { - throw new ClassCastException(castException); - } - return result; + private void syncronizeApi(XC_MethodHook.MethodHookParam param, LSPosedHookCallback callback, boolean forward) { + if (forward) { + param.method = callback.method; + param.thisObject = callback.thisObject; + param.args = callback.args; + param.result = callback.result; + param.throwable = callback.throwable; + param.returnEarly = callback.isSkipped; + } else { + callback.method = param.method; + callback.thisObject = param.thisObject; + callback.args = param.args; + callback.result = param.result; + callback.throwable = param.throwable; + callback.isSkipped = param.returnEarly; } } } - - public static class HookerCallback { - Method beforeInvocation; - Method afterInvocation; - - public HookerCallback(Method beforeInvocation, Method afterInvocation) { - this.beforeInvocation = beforeInvocation; - this.afterInvocation = afterInvocation; - } - } } diff --git a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java index 40733165..8e7c476c 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkGetCLHooker.java @@ -135,7 +135,7 @@ public class LoadedApkGetCLHooker extends XC_MethodHook { @NonNull @Override - public ApplicationInfo getAppInfo() { + public ApplicationInfo getApplicationInfo() { return loadedApk.getApplicationInfo(); } diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java new file mode 100644 index 00000000..5917ceac --- /dev/null +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java @@ -0,0 +1,189 @@ +package org.lsposed.lspd.impl; + +import android.util.Log; + +import org.lsposed.lspd.nativebridge.HookBridge; + +import java.lang.reflect.Executable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +import de.robv.android.xposed.XposedBridge; + +public class LSPosedBridge { + + private static final String TAG = "LSPosed-Bridge"; + + private static final String castException = "Return value's type from hook callback does not match the hooked method"; + + private static final Method getCause; + + static { + Method tmp; + try { + tmp = InvocationTargetException.class.getMethod("getCause"); + } catch (Throwable e) { + tmp = null; + } + getCause = tmp; + } + + public static class HookerCallback { + Method beforeInvocation; + Method afterInvocation; + + public HookerCallback(Method beforeInvocation, Method afterInvocation) { + this.beforeInvocation = beforeInvocation; + this.afterInvocation = afterInvocation; + } + } + + public static void log(String text) { + Log.i(TAG, text); + } + + public static void log(Throwable t) { + String logStr = Log.getStackTraceString(t); + Log.e(TAG, logStr); + } + + public static class NativeHooker { + private final Object params; + + private NativeHooker(Executable method) { + var isStatic = Modifier.isStatic(method.getModifiers()); + Object returnType; + if (method instanceof Method) { + returnType = ((Method) method).getReturnType(); + } else { + returnType = null; + } + params = new Object[]{ + method, + returnType, + isStatic, + }; + } + + // This method is quite critical. We should try not to use system methods to avoid + // endless recursive + public Object callback(Object[] args) throws Throwable { + LSPosedHookCallback callback = new LSPosedHookCallback<>(); + + var array = ((Object[]) params); + + var method = (T) array[0]; + var returnType = (Class) array[1]; + var isStatic = (Boolean) array[2]; + + callback.method = method; + + if (isStatic) { + callback.thisObject = null; + callback.args = args; + } else { + callback.thisObject = args[0]; + callback.args = new Object[args.length - 1]; + //noinspection ManualArrayCopy + for (int i = 0; i < args.length - 1; ++i) { + callback.args[i] = args[i + 1]; + } + } + + Object[][] callbacksSnapshot = HookBridge.callbackSnapshot(HookerCallback.class, method); + Object[] modernSnapshot = callbacksSnapshot[0]; + Object[] legacySnapshot = callbacksSnapshot[1]; + + if (modernSnapshot.length == 0 && legacySnapshot.length == 0) { + try { + return HookBridge.invokeOriginalMethod(method, callback.thisObject, callback.args); + } catch (InvocationTargetException ite) { + throw (Throwable) HookBridge.invokeOriginalMethod(getCause, ite); + } + } + + Object[] ctxArray = new Object[modernSnapshot.length]; + XposedBridge.LegacyApiSupport legacy = null; + + // call "before method" callbacks + int beforeIdx; + for (beforeIdx = 0; beforeIdx < modernSnapshot.length; beforeIdx++) { + try { + var hooker = (HookerCallback) modernSnapshot[beforeIdx]; + ctxArray[beforeIdx] = hooker.beforeInvocation.invoke(null, callback); + } catch (Throwable t) { + LSPosedBridge.log(t); + + // reset result (ignoring what the unexpectedly exiting callback did) + callback.setResult(null); + callback.isSkipped = false; + continue; + } + + if (callback.isSkipped) { + // skip remaining "before" callbacks and corresponding "after" callbacks + beforeIdx++; + break; + } + } + + if (!callback.isSkipped && legacySnapshot.length != 0) { + // TODO: Separate classloader + legacy = new XposedBridge.LegacyApiSupport<>(callback, legacySnapshot); + legacy.handleBefore(); + } + + // call original method if not requested otherwise + if (!callback.isSkipped) { + try { + var result = HookBridge.invokeOriginalMethod(method, callback.thisObject, callback.args); + callback.setResult(result); + } catch (InvocationTargetException e) { + var throwable = (Throwable) HookBridge.invokeOriginalMethod(getCause, e); + callback.setThrowable(throwable); + } + } + + // call "after method" callbacks + for (int afterIdx = beforeIdx - 1; afterIdx >= 0; afterIdx--) { + Object lastResult = callback.getResult(); + Throwable lastThrowable = callback.getThrowable(); + var hooker = (HookerCallback) modernSnapshot[afterIdx]; + var context = ctxArray[afterIdx]; + try { + if (context == null) { + hooker.afterInvocation.invoke(null, callback); + } else { + hooker.afterInvocation.invoke(null, callback, context); + } + } catch (Throwable t) { + LSPosedBridge.log(t); + + // reset to last result (ignoring what the unexpectedly exiting callback did) + if (lastThrowable == null) { + callback.setResult(lastResult); + } else { + callback.setThrowable(lastThrowable); + } + } + } + + if (legacy != null) { + legacy.handleAfter(); + } + + // return + var t = callback.getThrowable(); + if (t != null) { + throw t; + } else { + var result = callback.getResult(); + if (returnType != null && !returnType.isPrimitive() && !HookBridge.instanceOf(result, returnType)) { + throw new ClassCastException(castException); + } + return result; + } + } + } +} diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java index 8e2e0f41..895cd454 100644 --- a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java @@ -2,38 +2,13 @@ package org.lsposed.lspd.impl; import android.annotation.SuppressLint; import android.app.ActivityThread; -import android.app.LoadedApk; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.ContextParams; -import android.content.ContextWrapper; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.IntentSender; -import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.content.res.AssetManager; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.content.res.XModuleResources; -import android.database.DatabaseErrorHandler; -import android.database.sqlite.SQLiteDatabase; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.net.Uri; import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; +import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; -import android.os.UserHandle; import android.util.Log; -import android.view.Display; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -47,15 +22,11 @@ import org.lsposed.lspd.service.ILSPInjectedModuleService; import org.lsposed.lspd.util.LspModuleClassLoader; import java.io.File; -import java.io.FileInputStream; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; @@ -65,9 +36,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import de.robv.android.xposed.XposedBridge; -import de.robv.android.xposed.XposedHelpers; -import io.github.libxposed.api.XposedContext; import io.github.libxposed.api.XposedInterface; import io.github.libxposed.api.XposedModule; import io.github.libxposed.api.XposedModuleInterface; @@ -75,14 +43,14 @@ import io.github.libxposed.api.annotations.AfterInvocation; import io.github.libxposed.api.annotations.BeforeInvocation; import io.github.libxposed.api.annotations.XposedHooker; import io.github.libxposed.api.errors.HookFailedError; +import io.github.libxposed.api.errors.XposedFrameworkError; import io.github.libxposed.api.utils.DexParser; @SuppressLint("NewApi") -public class LSPosedContext extends XposedContext { +public class LSPosedContext implements XposedInterface { private static final String TAG = "LSPosedContext"; - private static final String REMOTE_PREFIX = "remote://"; public static boolean isSystemServer; public static String appDir; @@ -90,18 +58,14 @@ public class LSPosedContext extends XposedContext { static final Set modules = ConcurrentHashMap.newKeySet(); - private final Object mSync = new Object(); - - private final Context mBase; private final String mPackageName; - private final String mApkPath; + private final ApplicationInfo mApplicationInfo; private final ILSPInjectedModuleService service; private final Map mRemotePrefs = new ConcurrentHashMap<>(); - LSPosedContext(Context base, String packageName, String apkPath, ILSPInjectedModuleService service) { - this.mBase = base; + LSPosedContext(String packageName, ApplicationInfo applicationInfo, ILSPInjectedModuleService service) { this.mPackageName = packageName; - this.mApkPath = apkPath; + this.mApplicationInfo = applicationInfo; this.service = service; } @@ -110,7 +74,7 @@ public class LSPosedContext extends XposedContext { try { module.onPackageLoaded(param); } catch (Throwable t) { - Log.e(TAG, "Error when calling onPackageLoaded of " + ((LSPosedContext) module.getBaseContext()).mPackageName, t); + Log.e(TAG, "Error when calling onPackageLoaded of " + module.getApplicationInfo().packageName, t); } } } @@ -120,7 +84,7 @@ public class LSPosedContext extends XposedContext { try { module.onSystemServerLoaded(param); } catch (Throwable t) { - Log.e(TAG, "Error when calling onSystemServerLoaded of " + ((LSPosedContext) module.getBaseContext()).mPackageName, t); + Log.e(TAG, "Error when calling onSystemServerLoaded of " + module.getApplicationInfo().packageName, t); } } } @@ -143,64 +107,16 @@ public class LSPosedContext extends XposedContext { Log.e(TAG, " This may cause strange issues and must be fixed by the module developer."); return false; } - module.applicationInfo.packageName = module.packageName; // Just in case - var loadedApk = at.getPackageInfoNoCheck(module.applicationInfo, null); - XposedHelpers.setObjectField(loadedApk, "mClassLoader", mcl); - XposedHelpers.setObjectField(loadedApk, "mDataDir", appDir); - var c = Class.forName("android.app.ContextImpl"); - var ctor = c.getDeclaredConstructors()[0]; - ctor.setAccessible(true); - var args = new Object[ctor.getParameterTypes().length]; - for (int i = 0; i < ctor.getParameterTypes().length; ++i) { - if (ctor.getParameterTypes()[i] == LoadedApk.class) { - args[i] = loadedApk; - continue; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (ctor.getParameterTypes()[i] == ContextParams.class) { - args[i] = new ContextParams.Builder().build(); - continue; - } - } - if (ctor.getParameterTypes()[i] == ActivityThread.class) { - args[i] = at; - continue; - } - if (ctor.getParameterTypes()[i] == int.class) { - args[i] = 0; - continue; - } - args[i] = null; - } - var ci = (Context) ctor.newInstance(args); - var ctx = new LSPosedContext(ci, module.packageName, module.apkPath, module.service); - var setOuterContext = c.getDeclaredMethod("setOuterContext", Context.class); - setOuterContext.setAccessible(true); - setOuterContext.invoke(ci, new ContextWrapper(ci) { - @Override - public Resources getResources() { - return ctx.getResources(); - } - - @Override - public Resources.Theme getTheme() { - return ctx.getTheme(); - } - - @Override - public void setTheme(int resid) { - ctx.setTheme(resid); - } - }); + var ctx = new LSPosedContext(module.packageName, module.applicationInfo, module.service); for (var entry : module.file.moduleClassNames) { - var moduleClass = ctx.getClassLoader().loadClass(entry); + var moduleClass = mcl.loadClass(entry); Log.d(TAG, " Loading class " + moduleClass); if (!XposedModule.class.isAssignableFrom(moduleClass)) { Log.e(TAG, " This class doesn't implement any sub-interface of XposedModule, skipping it"); continue; } try { - var moduleEntry = moduleClass.getConstructor(XposedContext.class, XposedModuleInterface.ModuleLoadedParam.class); + var moduleEntry = moduleClass.getConstructor(XposedInterface.class, XposedModuleInterface.ModuleLoadedParam.class); var moduleContext = (XposedModule) moduleEntry.newInstance(ctx, new XposedModuleInterface.ModuleLoadedParam() { @Override public boolean isSystemServer() { @@ -227,622 +143,6 @@ public class LSPosedContext extends XposedContext { return true; } - @Override - public AssetManager getAssets() { - return getResources().getAssets(); - } - - @Override - public Resources getResources() { - synchronized (mSync) { - var res = mBase.getResources(); - if (res == null) { - res = XModuleResources.createInstance(mApkPath, null); - XposedHelpers.setObjectField(mBase, "mResources", res); - } - return res; - } - } - - @Override - public PackageManager getPackageManager() { - return mBase.getPackageManager(); - } - - @Override - public ContentResolver getContentResolver() { - throw new AbstractMethodError(); - } - - @Override - public Looper getMainLooper() { - return mBase.getMainLooper(); - } - - @Override - public Context getApplicationContext() { - throw new AbstractMethodError(); - } - - @Override - public void setTheme(int resid) { - getResources(); - mBase.setTheme(resid); - } - - @Override - public Resources.Theme getTheme() { - getResources(); - return mBase.getTheme(); - } - - @Override - public ClassLoader getClassLoader() { - return mBase.getClassLoader(); - } - - @Override - public String getPackageName() { - return mBase.getPackageName(); - } - - @Override - public ApplicationInfo getApplicationInfo() { - return mBase.getApplicationInfo(); - } - - @Override - public String getPackageResourcePath() { - return mApkPath; - } - - @Override - public String getPackageCodePath() { - return mApkPath; - } - - @Override - public SharedPreferences getSharedPreferences(String name, int mode) { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - return mRemotePrefs.computeIfAbsent(name.substring(REMOTE_PREFIX.length()), n -> { - try { - return new LSPosedRemotePreferences(service, n); - } catch (Throwable e) { - log("Failed to get remote preferences", e); - return null; - } - }); - } else { - return mBase.getSharedPreferences(name, mode); - } - } - - @Override - public boolean moveSharedPreferencesFrom(Context sourceContext, String name) { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - throw new IllegalArgumentException("Moving remote preferences is not supported"); - } else { - return mBase.moveSharedPreferencesFrom(sourceContext, name); - } - } - - @Override - public boolean deleteSharedPreferences(String name) { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - throw new IllegalArgumentException("Read only implementation"); - } else { - return mBase.deleteSharedPreferences(name); - } - } - - @Override - public FileInputStream openFileInput(String name) throws FileNotFoundException { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - try { - return new FileInputStream(service.openRemoteFile(name.substring(REMOTE_PREFIX.length())).getFileDescriptor()); - } catch (RemoteException e) { - throw new FileNotFoundException(e.getMessage()); - } - } else { - return mBase.openFileInput(name); - } - } - - @Override - public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - throw new IllegalArgumentException("Read only implementation"); - } else { - return mBase.openFileOutput(name, mode); - } - } - - @Override - public boolean deleteFile(String name) { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - throw new IllegalArgumentException("Read only implementation"); - } else { - return mBase.deleteFile(name); - } - } - - @Override - public File getFileStreamPath(String name) { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - throw new IllegalArgumentException("Getting remote file path is not supported"); - } else { - return mBase.getFileStreamPath(name); - } - } - - @Override - public File getDataDir() { - return mBase.getDataDir(); - } - - @Override - public File getFilesDir() { - return mBase.getFilesDir(); - } - - @Override - public File getNoBackupFilesDir() { - return mBase.getNoBackupFilesDir(); - } - - @Nullable - @Override - public File getExternalFilesDir(@Nullable String type) { - throw new AbstractMethodError(); - } - - @Override - public File[] getExternalFilesDirs(String type) { - throw new AbstractMethodError(); - } - - @Override - public File getObbDir() { - throw new AbstractMethodError(); - } - - @Override - public File[] getObbDirs() { - throw new AbstractMethodError(); - } - - @Override - public File getCacheDir() { - return mBase.getCacheDir(); - } - - @Override - public File getCodeCacheDir() { - return mBase.getCodeCacheDir(); - } - - @Nullable - @Override - public File getExternalCacheDir() { - throw new AbstractMethodError(); - } - - @Override - public File[] getExternalCacheDirs() { - throw new AbstractMethodError(); - } - - @Override - public File[] getExternalMediaDirs() { - throw new AbstractMethodError(); - } - - @Override - public String[] fileList() { - String[] remoteFiles = new String[0]; - try { - remoteFiles = service.getRemoteFileList(); - } catch (RemoteException e) { - log("Failed to get remote file list", e); - } - var localFiles = mBase.fileList(); - var files = new String[remoteFiles.length + localFiles.length]; - for (int i = 0; i < remoteFiles.length; i++) { - files[i] = REMOTE_PREFIX + remoteFiles[i]; - } - System.arraycopy(localFiles, 0, files, remoteFiles.length, localFiles.length); - return files; - } - - @Override - public File getDir(String name, int mode) { - return mBase.getDir(name, mode); - } - - @Override - public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - return openOrCreateDatabase(name, mode, factory, null); - } else { - return mBase.openOrCreateDatabase(name, mode, factory); - } - } - - @Override - public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, @Nullable DatabaseErrorHandler errorHandler) { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - throw new IllegalArgumentException("Opening remote database is not supported"); - } else { - return mBase.openOrCreateDatabase(name, mode, factory, errorHandler); - } - } - - @Override - public boolean moveDatabaseFrom(Context sourceContext, String name) { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - throw new IllegalArgumentException("Moving remote database is not supported"); - } else { - return mBase.moveDatabaseFrom(sourceContext, name); - } - } - - @Override - public boolean deleteDatabase(String name) { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - throw new IllegalArgumentException("Read only implementation"); - } else { - return mBase.deleteDatabase(name); - } - } - - @Override - public File getDatabasePath(String name) { - if (name == null) throw new IllegalArgumentException("name must not be null"); - if (name.startsWith(REMOTE_PREFIX)) { - throw new IllegalArgumentException("Getting remote database path is not supported"); - } else { - return mBase.getDatabasePath(name); - } - } - - @Override - public String[] databaseList() { - return mBase.databaseList(); - } - - @Override - public Drawable getWallpaper() { - throw new AbstractMethodError(); - } - - @Override - public Drawable peekWallpaper() { - throw new AbstractMethodError(); - } - - @Override - public int getWallpaperDesiredMinimumWidth() { - throw new AbstractMethodError(); - } - - @Override - public int getWallpaperDesiredMinimumHeight() { - throw new AbstractMethodError(); - } - - @Override - public void setWallpaper(Bitmap bitmap) { - throw new AbstractMethodError(); - } - - @Override - public void setWallpaper(InputStream data) { - throw new AbstractMethodError(); - } - - @Override - public void clearWallpaper() { - throw new AbstractMethodError(); - } - - @Override - public void startActivity(Intent intent) { - throw new AbstractMethodError(); - } - - @Override - public void startActivity(Intent intent, @Nullable Bundle options) { - throw new AbstractMethodError(); - } - - @Override - public void startActivities(Intent[] intents) { - throw new AbstractMethodError(); - } - - @Override - public void startActivities(Intent[] intents, Bundle options) { - throw new AbstractMethodError(); - } - - @Override - public void startIntentSender(IntentSender intent, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) { - throw new AbstractMethodError(); - } - - @Override - public void startIntentSender(IntentSender intent, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, @Nullable Bundle options) { - throw new AbstractMethodError(); - } - - @Override - public void sendBroadcast(Intent intent) { - throw new AbstractMethodError(); - } - - @Override - public void sendBroadcast(Intent intent, @Nullable String receiverPermission) { - throw new AbstractMethodError(); - } - - @Override - public void sendOrderedBroadcast(Intent intent, @Nullable String receiverPermission) { - throw new AbstractMethodError(); - } - - @Override - public void sendOrderedBroadcast(@NonNull Intent intent, @Nullable String receiverPermission, @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) { - throw new AbstractMethodError(); - } - - @Override - public void sendBroadcastAsUser(Intent intent, UserHandle user) { - throw new AbstractMethodError(); - } - - @Override - public void sendBroadcastAsUser(Intent intent, UserHandle user, @Nullable String receiverPermission) { - throw new AbstractMethodError(); - } - - @Override - public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, @Nullable String receiverPermission, BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) { - throw new AbstractMethodError(); - } - - @Override - public void sendStickyBroadcast(Intent intent) { - throw new AbstractMethodError(); - } - - @Override - public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) { - throw new AbstractMethodError(); - } - - @Override - public void removeStickyBroadcast(Intent intent) { - throw new AbstractMethodError(); - } - - @Override - public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { - throw new AbstractMethodError(); - } - - @Override - public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) { - throw new AbstractMethodError(); - } - - @Override - public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) { - throw new AbstractMethodError(); - } - - @Nullable - @Override - public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) { - throw new AbstractMethodError(); - } - - @Nullable - @Override - public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter, int flags) { - throw new AbstractMethodError(); - } - - @Nullable - @Override - public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, @Nullable String broadcastPermission, @Nullable Handler scheduler) { - throw new AbstractMethodError(); - } - - @Nullable - @Override - public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, @Nullable String broadcastPermission, @Nullable Handler scheduler, int flags) { - throw new AbstractMethodError(); - } - - @Override - public void unregisterReceiver(BroadcastReceiver receiver) { - throw new AbstractMethodError(); - } - - @Nullable - @Override - public ComponentName startService(Intent service) { - throw new AbstractMethodError(); - } - - @Nullable - @Override - public ComponentName startForegroundService(Intent service) { - throw new AbstractMethodError(); - } - - @Override - public boolean stopService(Intent service) { - throw new AbstractMethodError(); - } - - @Override - public boolean bindService(Intent service, @NonNull ServiceConnection conn, int flags) { - throw new AbstractMethodError(); - } - - @Override - public void unbindService(@NonNull ServiceConnection conn) { - throw new AbstractMethodError(); - } - - @Override - public boolean startInstrumentation(@NonNull ComponentName className, @Nullable String profileFile, @Nullable Bundle arguments) { - throw new AbstractMethodError(); - } - - @Override - public Object getSystemService(@NonNull String name) { - return mBase.getSystemService(name); - } - - @Nullable - @Override - public String getSystemServiceName(@NonNull Class serviceClass) { - return mBase.getSystemServiceName(serviceClass); - } - - @Override - public int checkPermission(@NonNull String permission, int pid, int uid) { - throw new AbstractMethodError(); - } - - @Override - public int checkCallingPermission(@NonNull String permission) { - throw new AbstractMethodError(); - } - - @Override - public int checkCallingOrSelfPermission(@NonNull String permission) { - throw new AbstractMethodError(); - } - - @Override - public int checkSelfPermission(@NonNull String permission) { - throw new AbstractMethodError(); - } - - @Override - public void enforcePermission(@NonNull String permission, int pid, int uid, @Nullable String message) { - throw new AbstractMethodError(); - } - - @Override - public void enforceCallingPermission(@NonNull String permission, @Nullable String message) { - throw new AbstractMethodError(); - } - - @Override - public void enforceCallingOrSelfPermission(@NonNull String permission, @Nullable String message) { - throw new AbstractMethodError(); - } - - @Override - public void grantUriPermission(String toPackage, Uri uri, int modeFlags) { - throw new AbstractMethodError(); - } - - @Override - public void revokeUriPermission(Uri uri, int modeFlags) { - throw new AbstractMethodError(); - } - - @Override - public void revokeUriPermission(String toPackage, Uri uri, int modeFlags) { - throw new AbstractMethodError(); - } - - @Override - public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { - throw new AbstractMethodError(); - } - - @Override - public int checkCallingUriPermission(Uri uri, int modeFlags) { - throw new AbstractMethodError(); - } - - @Override - public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { - throw new AbstractMethodError(); - } - - @Override - public int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission, @Nullable String writePermission, int pid, int uid, int modeFlags) { - throw new AbstractMethodError(); - } - - @Override - public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) { - throw new AbstractMethodError(); - } - - @Override - public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) { - throw new AbstractMethodError(); - } - - @Override - public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) { - throw new AbstractMethodError(); - } - - @Override - public void enforceUriPermission(@Nullable Uri uri, @Nullable String readPermission, @Nullable String writePermission, int pid, int uid, int modeFlags, @Nullable String message) { - throw new AbstractMethodError(); - } - - @Override - public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException { - return mBase.createPackageContext(packageName, flags); - } - - @Override - public Context createContextForSplit(String splitName) { - throw new AbstractMethodError(); - } - - @Override - public Context createConfigurationContext(@NonNull Configuration overrideConfiguration) { - return new LSPosedContext(mBase.createConfigurationContext(overrideConfiguration), mPackageName, mApkPath, service); - } - - @Override - public Context createDisplayContext(@NonNull Display display) { - return new LSPosedContext(mBase.createDisplayContext(display), mPackageName, mApkPath, service); - } - - @Override - public Context createDeviceProtectedStorageContext() { - throw new AbstractMethodError(); - } - - @Override - public boolean isDeviceProtectedStorage() { - throw new AbstractMethodError(); - } - @NonNull @Override public String getFrameworkName() { @@ -891,8 +191,7 @@ public class LSPosedContext extends XposedContext { } boolean valid; valid = (method.getModifiers() & modifiers) == modifiers; - valid &= Arrays.equals(method.getParameterTypes(), new Class[]{Member.class, Object.class, Object[].class}); - valid &= method.getReturnType().equals(Hooker.class); + valid &= Arrays.equals(method.getParameterTypes(), new Class[]{BeforeHookCallback.class}); if (!valid) { throw new IllegalArgumentException("BeforeInvocation method format is invalid"); } @@ -904,7 +203,6 @@ public class LSPosedContext extends XposedContext { } boolean valid; valid = (method.getModifiers() & modifiers) == modifiers; - valid &= Arrays.equals(method.getParameterTypes(), new Class[]{Hooker.class, Object.class}); valid &= method.getReturnType().equals(void.class); if (!valid) { throw new IllegalArgumentException("AfterInvocation method format is invalid"); @@ -918,9 +216,19 @@ public class LSPosedContext extends XposedContext { if (afterInvocation == null) { throw new IllegalArgumentException("No method annotated with @AfterInvocation"); } + boolean valid; + var ctx = beforeInvocation.getReturnType(); + if (ctx == void.class) { + valid = Arrays.equals(afterInvocation.getParameterTypes(), new Class[]{AfterHookCallback.class}); + } else { + valid = Arrays.equals(afterInvocation.getParameterTypes(), new Class[]{AfterHookCallback.class, ctx}); + } + if (!valid) { + throw new IllegalArgumentException("AfterInvocation method format is invalid"); + } - var callback = new XposedBridge.HookerCallback(beforeInvocation, afterInvocation); - if (HookBridge.hookMethod(true, hookMethod, XposedBridge.AdditionalHookInfo.class, priority, callback)) { + var callback = new LSPosedBridge.HookerCallback(beforeInvocation, afterInvocation); + if (HookBridge.hookMethod(true, hookMethod, LSPosedBridge.NativeHooker.class, priority, callback)) { return new MethodUnhooker<>() { @NonNull @Override @@ -1063,4 +371,46 @@ public class LSPosedContext extends XposedContext { public DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException { return new LSPosedDexParser(dexData, includeAnnotations); } + + @NonNull + @Override + public ApplicationInfo getApplicationInfo() { + return mApplicationInfo; + } + + @NonNull + @Override + public SharedPreferences getRemotePreferences(String name) { + if (name == null) throw new IllegalArgumentException("name must not be null"); + return mRemotePrefs.computeIfAbsent(name, n -> { + try { + return new LSPosedRemotePreferences(service, n); + } catch (RemoteException e) { + log("Failed to get remote preferences", e); + throw new XposedFrameworkError(e); + } + }); + } + + @NonNull + @Override + public String[] listRemoteFiles() { + try { + return service.getRemoteFileList(); + } catch (RemoteException e) { + log("Failed to list remote files", e); + throw new XposedFrameworkError(e); + } + } + + @NonNull + @Override + public ParcelFileDescriptor openRemoteFile(String name) throws FileNotFoundException { + if (name == null) throw new IllegalArgumentException("name must not be null"); + try { + return service.openRemoteFile(name); + } catch (RemoteException e) { + throw new FileNotFoundException(e.getMessage()); + } + } } diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedHookCallback.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedHookCallback.java new file mode 100644 index 00000000..0caa22df --- /dev/null +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedHookCallback.java @@ -0,0 +1,94 @@ +package org.lsposed.lspd.impl; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.lang.reflect.Executable; +import java.lang.reflect.Member; + +import io.github.libxposed.api.XposedInterface; + +public class LSPosedHookCallback implements XposedInterface.BeforeHookCallback, XposedInterface.AfterHookCallback { + + public Member method; + + public Object thisObject; + + public Object[] args; + + public Object result; + + public Throwable throwable; + + public boolean isSkipped; + + public LSPosedHookCallback() { + } + + // Both before and after + + @NonNull + @Override + public Member getMember() { + return this.method; + } + + @Nullable + @Override + public Object getThisObject() { + return this.thisObject; + } + + @NonNull + @Override + public Object[] getArgs() { + return this.args; + } + + // Before + + @Override + public void returnAndSkip(@Nullable Object result) { + this.result = result; + this.throwable = null; + this.isSkipped = true; + } + + @Override + public void throwAndSkip(@Nullable Throwable throwable) { + this.result = null; + this.throwable = throwable; + this.isSkipped = true; + } + + // After + + @Nullable + @Override + public Object getResult() { + return this.result; + } + + @Nullable + @Override + public Throwable getThrowable() { + return this.throwable; + } + + @Override + public boolean isSkipped() { + return this.isSkipped; + } + + @Override + public void setResult(@Nullable Object result) { + this.result = result; + this.throwable = null; + } + + @Override + public void setThrowable(@Nullable Throwable throwable) { + this.result = null; + this.throwable = throwable; + } +} diff --git a/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java b/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java index db733ea2..e07caa0e 100644 --- a/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java +++ b/core/src/main/java/org/lsposed/lspd/nativebridge/HookBridge.java @@ -24,5 +24,5 @@ public class HookBridge { @FastNative public static native boolean setTrusted(Object cookie); - public static native Object[] callbackSnapshot(Class hooker_callback, Executable method); + public static native Object[][] callbackSnapshot(Class hooker_callback, Executable method); } diff --git a/core/src/main/jni/src/jni/hook_bridge.cpp b/core/src/main/jni/src/jni/hook_bridge.cpp index e2f5663c..467a6811 100644 --- a/core/src/main/jni/src/jni/hook_bridge.cpp +++ b/core/src/main/jni/src/jni/hook_bridge.cpp @@ -29,19 +29,14 @@ using namespace lsplant; namespace { -struct CallbackType { - bool use_modern_api; - union { - jobject callback_object; - struct { - jmethodID before_method; - jmethodID after_method; - }; - }; +struct ModuleCallback { + jmethodID before_method; + jmethodID after_method; }; struct HookItem { - std::multimap> callbacks; + std::multimap> legacy_callbacks; + std::multimap> modern_callbacks; private: std::atomic backup {nullptr}; static_assert(decltype(backup)::is_always_lock_free); @@ -130,18 +125,13 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, hookMethod, jboolean useModernApi, j } auto before_method = JNI_GetObjectField(env, callback, before_method_field); auto after_method = JNI_GetObjectField(env, callback, after_method_field); - auto callback_type = CallbackType { - .use_modern_api = true, + auto callback_type = ModuleCallback { .before_method = env->FromReflectedMethod(before_method), .after_method = env->FromReflectedMethod(after_method), }; - hook_item->callbacks.emplace(std::make_pair(priority, callback_type)); + hook_item->modern_callbacks.emplace(priority, callback_type); } else { - auto callback_type = CallbackType { - .use_modern_api = false, - .callback_object = env->NewGlobalRef(callback), - }; - hook_item->callbacks.emplace(std::make_pair(priority, callback_type)); + hook_item->legacy_callbacks.emplace(priority, env->NewGlobalRef(callback)); } return JNI_TRUE; } @@ -159,20 +149,19 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, unhookMethod, jboolean useModernApi, jobject backup = hook_item->GetBackup(); if (!backup) return JNI_FALSE; JNIMonitor monitor(env, backup); - jmethodID before = nullptr; if (useModernApi) { auto before_method = JNI_GetObjectField(env, callback, before_method_field); - before = env->FromReflectedMethod(before_method); - } - for (auto i = hook_item->callbacks.begin(); i != hook_item->callbacks.end(); ++i) { - if (useModernApi) { - if (i->second.use_modern_api && before == i->second.before_method) { - hook_item->callbacks.erase(i); + auto before = env->FromReflectedMethod(before_method); + for (auto i = hook_item->modern_callbacks.begin(); i != hook_item->modern_callbacks.end(); ++i) { + if (before == i->second.before_method) { + hook_item->modern_callbacks.erase(i); return JNI_TRUE; } - } else { - if (!i->second.use_modern_api && env->IsSameObject(i->second.callback_object, callback)) { - hook_item->callbacks.erase(i); + } + } else { + for (auto i = hook_item->legacy_callbacks.begin(); i != hook_item->legacy_callbacks.end(); ++i) { + if (env->IsSameObject(i->second, callback)) { + hook_item->legacy_callbacks.erase(i); return JNI_TRUE; } } @@ -328,17 +317,21 @@ LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jclass callbac jobject backup = hook_item->GetBackup(); if (!backup) return nullptr; JNIMonitor monitor(env, backup); - auto res = env->NewObjectArray((jsize) hook_item->callbacks.size(), env->FindClass("java/lang/Object"), nullptr); - for (jsize i = 0; auto callback: hook_item->callbacks) { - if (callback.second.use_modern_api) { - auto before_method = JNI_ToReflectedMethod(env, clazz, callback.second.before_method, JNI_TRUE); - auto after_method = JNI_ToReflectedMethod(env, clazz, callback.second.after_method, JNI_TRUE); - auto callback_object = JNI_NewObject(env, callback_class, callback_ctor, before_method, after_method); - env->SetObjectArrayElement(res, i++, env->NewLocalRef(callback_object)); - } else { - env->SetObjectArrayElement(res, i++, env->NewLocalRef(callback.second.callback_object)); - } + + auto res = env->NewObjectArray(2, env->FindClass("[Ljava/lang/Object;"), nullptr); + auto modern = env->NewObjectArray((jsize) hook_item->modern_callbacks.size(), env->FindClass("java/lang/Object"), nullptr); + auto legacy = env->NewObjectArray((jsize) hook_item->legacy_callbacks.size(), env->FindClass("java/lang/Object"), nullptr); + for (jsize i = 0; auto callback: hook_item->modern_callbacks) { + auto before_method = JNI_ToReflectedMethod(env, clazz, callback.second.before_method, JNI_TRUE); + auto after_method = JNI_ToReflectedMethod(env, clazz, callback.second.after_method, JNI_TRUE); + auto callback_object = JNI_NewObject(env, callback_class, callback_ctor, before_method, after_method); + env->SetObjectArrayElement(modern, i++, env->NewLocalRef(callback_object)); } + for (jsize i = 0; auto callback: hook_item->legacy_callbacks) { + env->SetObjectArrayElement(legacy, i++, env->NewLocalRef(callback.second)); + } + env->SetObjectArrayElement(res, 0, modern); + env->SetObjectArrayElement(res, 1, legacy); return res; } @@ -351,7 +344,7 @@ static JNINativeMethod gMethods[] = { LSP_NATIVE_METHOD(HookBridge, allocateObject, "(Ljava/lang/Class;)Ljava/lang/Object;"), LSP_NATIVE_METHOD(HookBridge, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"), LSP_NATIVE_METHOD(HookBridge, setTrusted, "(Ljava/lang/Object;)Z"), - LSP_NATIVE_METHOD(HookBridge, callbackSnapshot, "(Ljava/lang/Class;Ljava/lang/reflect/Executable;)[Ljava/lang/Object;"), + LSP_NATIVE_METHOD(HookBridge, callbackSnapshot, "(Ljava/lang/Class;Ljava/lang/reflect/Executable;)[[Ljava/lang/Object;"), }; void RegisterHookBridge(JNIEnv *env) {