From fc1adeac55a3499f77b2d649300c6d692459d566 Mon Sep 17 00:00:00 2001 From: Nullptr Date: Wed, 16 Aug 2023 11:56:41 +0800 Subject: [PATCH] Use modern hook API for internal hookers --- .../java/org/lsposed/lspd/core/Startup.java | 23 ++-- .../org/lsposed/lspd/hooker/AttachHooker.java | 20 ++- .../lsposed/lspd/hooker/CrashDumpHooker.java | 19 ++- .../HandleSystemServerProcessHooker.java | 23 ++-- .../lspd/hooker/LoadedApkCtorHooker.java | 14 +- .../lspd/hooker/OpenDexFileHooker.java | 22 ++-- .../hooker/StartBootstrapServicesHooker.java | 11 +- .../org/lsposed/lspd/impl/LSPosedBridge.java | 124 +++++++++++++++++- .../org/lsposed/lspd/impl/LSPosedContext.java | 89 +------------ .../org/lsposed/lspd/impl/LSPosedHelper.java | 46 +++++++ 10 files changed, 239 insertions(+), 152 deletions(-) create mode 100644 core/src/main/java/org/lsposed/lspd/impl/LSPosedHelper.java diff --git a/core/src/main/java/org/lsposed/lspd/core/Startup.java b/core/src/main/java/org/lsposed/lspd/core/Startup.java index 824b1b3c..d8621221 100644 --- a/core/src/main/java/org/lsposed/lspd/core/Startup.java +++ b/core/src/main/java/org/lsposed/lspd/core/Startup.java @@ -34,34 +34,29 @@ import org.lsposed.lspd.hooker.HandleSystemServerProcessHooker; import org.lsposed.lspd.hooker.LoadedApkCtorHooker; import org.lsposed.lspd.hooker.OpenDexFileHooker; import org.lsposed.lspd.impl.LSPosedContext; +import org.lsposed.lspd.impl.LSPosedHelper; import org.lsposed.lspd.service.ILSPApplicationService; import org.lsposed.lspd.util.Utils; import dalvik.system.DexFile; import de.robv.android.xposed.XposedBridge; -import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedInit; public class Startup { - @SuppressWarnings("deprecation") private static void startBootstrapHook(boolean isSystem) { Utils.logD("startBootstrapHook starts: isSystem = " + isSystem); - XposedHelpers.findAndHookMethod(Thread.class, "dispatchUncaughtException", - Throwable.class, new CrashDumpHooker()); + LSPosedHelper.hookMethod(CrashDumpHooker.class, Thread.class, "dispatchUncaughtException", Throwable.class); if (isSystem) { - XposedBridge.hookAllMethods(ZygoteInit.class, - "handleSystemServerProcess", new HandleSystemServerProcessHooker()); + LSPosedHelper.hookAllMethods(HandleSystemServerProcessHooker.class, ZygoteInit.class, "handleSystemServerProcess"); } else { - var hooker = new OpenDexFileHooker(); - XposedBridge.hookAllMethods(DexFile.class, "openDexFile", hooker); - XposedBridge.hookAllMethods(DexFile.class, "openInMemoryDexFile", hooker); - XposedBridge.hookAllMethods(DexFile.class, "openInMemoryDexFiles", hooker); + LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openDexFile"); + LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openInMemoryDexFile"); + LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openInMemoryDexFiles"); } - XposedHelpers.findAndHookConstructor(LoadedApk.class, + LSPosedHelper.hookConstructor(LoadedApkCtorHooker.class, LoadedApk.class, ActivityThread.class, ApplicationInfo.class, CompatibilityInfo.class, - ClassLoader.class, boolean.class, boolean.class, boolean.class, - new LoadedApkCtorHooker()); - XposedBridge.hookAllMethods(ActivityThread.class, "attach", new AttachHooker()); + ClassLoader.class, boolean.class, boolean.class, boolean.class); + LSPosedHelper.hookAllMethods(AttachHooker.class, ActivityThread.class, "attach"); } public static void bootstrapXposed() { diff --git a/core/src/main/java/org/lsposed/lspd/hooker/AttachHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/AttachHooker.java index e8115ab8..b003ac2f 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/AttachHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/AttachHooker.java @@ -1,19 +1,17 @@ package org.lsposed.lspd.hooker; -import static org.lsposed.lspd.core.ApplicationServiceClient.serviceClient; - import android.app.ActivityThread; -import org.lsposed.lspd.impl.LSPosedContext; - -import java.util.Optional; - -import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedInit; +import io.github.libxposed.api.XposedInterface; +import io.github.libxposed.api.annotations.AfterInvocation; +import io.github.libxposed.api.annotations.XposedHooker; -public class AttachHooker extends XC_MethodHook { - @Override - protected void afterHookedMethod(MethodHookParam param) throws Throwable { - XposedInit.loadModules((ActivityThread) param.thisObject); +@XposedHooker +public class AttachHooker implements XposedInterface.Hooker { + + @AfterInvocation + public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) { + XposedInit.loadModules((ActivityThread) callback.getThisObject()); } } diff --git a/core/src/main/java/org/lsposed/lspd/hooker/CrashDumpHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/CrashDumpHooker.java index 69729520..bb01539f 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/CrashDumpHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/CrashDumpHooker.java @@ -2,15 +2,20 @@ package org.lsposed.lspd.hooker; import android.util.Log; -import de.robv.android.xposed.XC_MethodHook; -import de.robv.android.xposed.XposedBridge; +import org.lsposed.lspd.impl.LSPosedBridge; -public class CrashDumpHooker extends XC_MethodHook { - @Override - protected void beforeHookedMethod(MethodHookParam param) { +import io.github.libxposed.api.XposedInterface; +import io.github.libxposed.api.annotations.BeforeInvocation; +import io.github.libxposed.api.annotations.XposedHooker; + +@XposedHooker +public class CrashDumpHooker implements XposedInterface.Hooker { + + @BeforeInvocation + public static void beforeHookedMethod(XposedInterface.BeforeHookCallback callback) { try { - var e = (Throwable) param.args[0]; - XposedBridge.log("Crash unexpectedly: " + Log.getStackTraceString(e)); + var e = (Throwable) callback.getArgs()[0]; + LSPosedBridge.log("Crash unexpectedly: " + Log.getStackTraceString(e)); } catch (Throwable ignored) { } } diff --git a/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java index 9ef9cda6..1aefebef 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java @@ -20,32 +20,35 @@ package org.lsposed.lspd.hooker; +import android.annotation.SuppressLint; + import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter; +import org.lsposed.lspd.impl.LSPosedHelper; import org.lsposed.lspd.util.Hookers; -import de.robv.android.xposed.XC_MethodHook; -import de.robv.android.xposed.XposedBridge; -import de.robv.android.xposed.XposedHelpers; +import io.github.libxposed.api.XposedInterface; +import io.github.libxposed.api.annotations.AfterInvocation; +import io.github.libxposed.api.annotations.XposedHooker; // system_server initialization -public class HandleSystemServerProcessHooker extends XC_MethodHook { +@XposedHooker +public class HandleSystemServerProcessHooker implements XposedInterface.Hooker { public static volatile ClassLoader systemServerCL; - @Override - protected void afterHookedMethod(MethodHookParam param) { + @SuppressLint("PrivateApi") + @AfterInvocation + public static void afterHookedMethod() { Hookers.logD("ZygoteInit#handleSystemServerProcess() starts"); try { // get system_server classLoader systemServerCL = Thread.currentThread().getContextClassLoader(); // deopt methods in SYSTEMSERVERCLASSPATH PrebuiltMethodsDeopter.deoptSystemServerMethods(systemServerCL); - XposedBridge.hookAllMethods( - XposedHelpers.findClass("com.android.server.SystemServer", systemServerCL), - "startBootstrapServices", new StartBootstrapServicesHooker()); + var clazz = Class.forName("com.android.server.SystemServer", false, systemServerCL); + LSPosedHelper.hookAllMethods(StartBootstrapServicesHooker.class, clazz, "startBootstrapServices"); } catch (Throwable t) { Hookers.logE("error when hooking systemMain", t); } } - } diff --git a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCtorHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCtorHooker.java index ab93fa48..0721d7b9 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCtorHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCtorHooker.java @@ -26,19 +26,23 @@ import android.util.Log; import org.lsposed.lspd.util.Hookers; -import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedInit; +import io.github.libxposed.api.XposedInterface; +import io.github.libxposed.api.annotations.AfterInvocation; +import io.github.libxposed.api.annotations.XposedHooker; // when a package is loaded for an existing process, trigger the callbacks as well -public class LoadedApkCtorHooker extends XC_MethodHook { +@XposedHooker +public class LoadedApkCtorHooker implements XposedInterface.Hooker { - @Override - protected void afterHookedMethod(MethodHookParam param) { + @AfterInvocation + public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) { Hookers.logD("LoadedApk# starts"); try { - LoadedApk loadedApk = (LoadedApk) param.thisObject; + LoadedApk loadedApk = (LoadedApk) callback.getThisObject(); + assert loadedApk != null; String packageName = loadedApk.getPackageName(); Object mAppDir = XposedHelpers.getObjectField(loadedApk, "mAppDir"); Hookers.logD("LoadedApk# ends: " + mAppDir); diff --git a/core/src/main/java/org/lsposed/lspd/hooker/OpenDexFileHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/OpenDexFileHooker.java index 501e7063..af6f70a2 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/OpenDexFileHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/OpenDexFileHooker.java @@ -2,26 +2,30 @@ package org.lsposed.lspd.hooker; import android.os.Build; +import org.lsposed.lspd.impl.LSPosedBridge; import org.lsposed.lspd.nativebridge.HookBridge; -import de.robv.android.xposed.XC_MethodHook; -import de.robv.android.xposed.XposedHelpers; +import io.github.libxposed.api.XposedInterface; +import io.github.libxposed.api.annotations.AfterInvocation; +import io.github.libxposed.api.annotations.XposedHooker; -public class OpenDexFileHooker extends XC_MethodHook { - @Override - protected void afterHookedMethod(MethodHookParam param) throws Throwable { +@XposedHooker +public class OpenDexFileHooker implements XposedInterface.Hooker { + + @AfterInvocation + public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) { ClassLoader classLoader = null; - for (var arg : param.args) { + for (var arg : callback.getArgs()) { if (arg instanceof ClassLoader) { classLoader = (ClassLoader) arg; } } if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P && classLoader == null) { - classLoader = XposedHelpers.class.getClassLoader(); + classLoader = LSPosedBridge.class.getClassLoader(); } while (classLoader != null) { - if (classLoader == XposedHelpers.class.getClassLoader()) { - HookBridge.setTrusted(param.getResult()); + if (classLoader == LSPosedBridge.class.getClassLoader()) { + HookBridge.setTrusted(callback.getResult()); return; } else { classLoader = classLoader.getParent(); diff --git a/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java index 2ee2eba8..30bf835d 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java @@ -27,16 +27,19 @@ import androidx.annotation.NonNull; import org.lsposed.lspd.impl.LSPosedContext; import org.lsposed.lspd.util.Hookers; -import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedInit; import de.robv.android.xposed.callbacks.XC_LoadPackage; +import io.github.libxposed.api.XposedInterface; import io.github.libxposed.api.XposedModuleInterface; +import io.github.libxposed.api.annotations.BeforeInvocation; +import io.github.libxposed.api.annotations.XposedHooker; -public class StartBootstrapServicesHooker extends XC_MethodHook { +@XposedHooker +public class StartBootstrapServicesHooker implements XposedInterface.Hooker { - @Override - protected void beforeHookedMethod(MethodHookParam param) { + @BeforeInvocation + public static void beforeHookedMethod() { logD("SystemServer#startBootstrapServices() starts"); try { diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java index 5917ceac..b47d274d 100644 --- a/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java @@ -2,6 +2,8 @@ package org.lsposed.lspd.impl; import android.util.Log; +import androidx.annotation.NonNull; + import org.lsposed.lspd.nativebridge.HookBridge; import java.lang.reflect.Executable; @@ -10,6 +12,11 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import de.robv.android.xposed.XposedBridge; +import io.github.libxposed.api.XposedInterface; +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; public class LSPosedBridge { @@ -30,12 +37,19 @@ public class LSPosedBridge { } public static class HookerCallback { - Method beforeInvocation; - Method afterInvocation; + @NonNull + final Method beforeInvocation; + @NonNull + final Method afterInvocation; - public HookerCallback(Method beforeInvocation, Method afterInvocation) { + final int beforeParams; + final int afterParams; + + public HookerCallback(@NonNull Method beforeInvocation, @NonNull Method afterInvocation) { this.beforeInvocation = beforeInvocation; this.afterInvocation = afterInvocation; + this.beforeParams = beforeInvocation.getParameterCount(); + this.afterParams = afterInvocation.getParameterCount(); } } @@ -111,7 +125,11 @@ public class LSPosedBridge { for (beforeIdx = 0; beforeIdx < modernSnapshot.length; beforeIdx++) { try { var hooker = (HookerCallback) modernSnapshot[beforeIdx]; - ctxArray[beforeIdx] = hooker.beforeInvocation.invoke(null, callback); + if (hooker.beforeParams == 0) { + ctxArray[beforeIdx] = hooker.beforeInvocation.invoke(null); + } else { + ctxArray[beforeIdx] = hooker.beforeInvocation.invoke(null, callback); + } } catch (Throwable t) { LSPosedBridge.log(t); @@ -150,12 +168,13 @@ public class LSPosedBridge { Object lastResult = callback.getResult(); Throwable lastThrowable = callback.getThrowable(); var hooker = (HookerCallback) modernSnapshot[afterIdx]; - var context = ctxArray[afterIdx]; try { - if (context == null) { + if (hooker.afterParams == 0) { + hooker.afterInvocation.invoke(null); + } else if (hooker.afterParams == 1) { hooker.afterInvocation.invoke(null, callback); } else { - hooker.afterInvocation.invoke(null, callback, context); + hooker.afterInvocation.invoke(null, callback, ctxArray[afterIdx]); } } catch (Throwable t) { LSPosedBridge.log(t); @@ -186,4 +205,95 @@ public class LSPosedBridge { } } } + + public static void dummyCallback() { + } + + public static XposedInterface.MethodUnhooker + doHook(T hookMethod, int priority, Class hooker) { + if (Modifier.isAbstract(hookMethod.getModifiers())) { + throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod); + } else if (hookMethod.getDeclaringClass().getClassLoader() == LSPosedContext.class.getClassLoader()) { + throw new IllegalArgumentException("Do not allow hooking inner methods"); + } else if (hookMethod.getDeclaringClass() == Method.class && hookMethod.getName().equals("invoke")) { + throw new IllegalArgumentException("Cannot hook Method.invoke"); + } else if (hooker == null) { + throw new IllegalArgumentException("hooker should not be null!"); + } else if (hooker.getAnnotation(XposedHooker.class) == null) { + throw new IllegalArgumentException("Hooker should be annotated with @XposedHooker"); + } + + Method beforeInvocation = null, afterInvocation = null; + var modifiers = Modifier.PUBLIC | Modifier.STATIC; + for (var method : hooker.getDeclaredMethods()) { + if (method.getAnnotation(BeforeInvocation.class) != null) { + if (beforeInvocation != null) { + throw new IllegalArgumentException("More than one method annotated with @BeforeInvocation"); + } + boolean valid = (method.getModifiers() & modifiers) == modifiers; + var params = method.getParameterTypes(); + if (params.length == 1) { + valid &= params[0].equals(XposedInterface.BeforeHookCallback.class); + } else if (params.length != 0) { + valid = false; + } + if (!valid) { + throw new IllegalArgumentException("BeforeInvocation method format is invalid"); + } + beforeInvocation = method; + } + if (method.getAnnotation(AfterInvocation.class) != null) { + if (afterInvocation != null) { + throw new IllegalArgumentException("More than one method annotated with @AfterInvocation"); + } + boolean valid = (method.getModifiers() & modifiers) == modifiers; + valid &= method.getReturnType().equals(void.class); + var params = method.getParameterTypes(); + if (params.length == 1 || params.length == 2) { + valid &= params[0].equals(XposedInterface.AfterHookCallback.class); + } else if (params.length != 0) { + valid = false; + } + if (!valid) { + throw new IllegalArgumentException("AfterInvocation method format is invalid"); + } + afterInvocation = method; + } + } + if (beforeInvocation == null && afterInvocation == null) { + throw new IllegalArgumentException("No method annotated with @BeforeInvocation or @AfterInvocation"); + } + try { + if (beforeInvocation == null) { + beforeInvocation = LSPosedBridge.class.getMethod("dummyCallback"); + } else if (afterInvocation == null) { + afterInvocation = LSPosedBridge.class.getMethod("dummyCallback"); + } else { + var ret = beforeInvocation.getReturnType(); + var params = afterInvocation.getParameterTypes(); + if (ret != void.class && params.length == 2 && !ret.equals(params[1])) { + throw new IllegalArgumentException("BeforeInvocation and AfterInvocation method format is invalid"); + } + } + } catch (NoSuchMethodException e) { + throw new HookFailedError(e); + } + + var callback = new LSPosedBridge.HookerCallback(beforeInvocation, afterInvocation); + if (HookBridge.hookMethod(true, hookMethod, LSPosedBridge.NativeHooker.class, priority, callback)) { + return new XposedInterface.MethodUnhooker<>() { + @NonNull + @Override + public T getOrigin() { + return hookMethod; + } + + @Override + public void unhook() { + HookBridge.unhookMethod(true, hookMethod, callback); + } + }; + } + throw new HookFailedError("Cannot hook " + hookMethod); + } } 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 895cd454..ba358899 100644 --- a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java @@ -31,7 +31,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.nio.ByteBuffer; -import java.util.Arrays; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -39,10 +38,6 @@ import java.util.concurrent.ConcurrentHashMap; import io.github.libxposed.api.XposedInterface; import io.github.libxposed.api.XposedModule; import io.github.libxposed.api.XposedModuleInterface; -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; @@ -169,104 +164,28 @@ public class LSPosedContext implements XposedInterface { } } - private MethodUnhooker doHook(T hookMethod, int priority, Class hooker) { - if (Modifier.isAbstract(hookMethod.getModifiers())) { - throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod); - } else if (hookMethod.getDeclaringClass().getClassLoader() == LSPosedContext.class.getClassLoader()) { - throw new IllegalArgumentException("Do not allow hooking inner methods"); - } else if (hookMethod.getDeclaringClass() == Method.class && hookMethod.getName().equals("invoke")) { - throw new IllegalArgumentException("Cannot hook Method.invoke"); - } else if (hooker == null) { - throw new IllegalArgumentException("hooker should not be null!"); - } else if (hooker.getAnnotation(XposedHooker.class) == null) { - throw new IllegalArgumentException("Hooker should be annotated with @XposedHooker"); - } - - Method beforeInvocation = null, afterInvocation = null; - var modifiers = Modifier.PUBLIC | Modifier.STATIC; - for (var method : hooker.getDeclaredMethods()) { - if (method.getAnnotation(BeforeInvocation.class) != null) { - if (beforeInvocation != null) { - throw new IllegalArgumentException("More than one method annotated with @BeforeInvocation"); - } - boolean valid; - valid = (method.getModifiers() & modifiers) == modifiers; - valid &= Arrays.equals(method.getParameterTypes(), new Class[]{BeforeHookCallback.class}); - if (!valid) { - throw new IllegalArgumentException("BeforeInvocation method format is invalid"); - } - beforeInvocation = method; - } - if (method.getAnnotation(AfterInvocation.class) != null) { - if (afterInvocation != null) { - throw new IllegalArgumentException("More than one method annotated with @AfterInvocation"); - } - boolean valid; - valid = (method.getModifiers() & modifiers) == modifiers; - valid &= method.getReturnType().equals(void.class); - if (!valid) { - throw new IllegalArgumentException("AfterInvocation method format is invalid"); - } - afterInvocation = method; - } - } - if (beforeInvocation == null) { - throw new IllegalArgumentException("No method annotated with @BeforeInvocation"); - } - 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 LSPosedBridge.HookerCallback(beforeInvocation, afterInvocation); - if (HookBridge.hookMethod(true, hookMethod, LSPosedBridge.NativeHooker.class, priority, callback)) { - return new MethodUnhooker<>() { - @NonNull - @Override - public T getOrigin() { - return hookMethod; - } - - @Override - public void unhook() { - HookBridge.unhookMethod(true, hookMethod, callback); - } - }; - } - throw new HookFailedError("Cannot hook " + hookMethod); - } - @Override @NonNull public MethodUnhooker hook(@NonNull Method origin, @NonNull Class hooker) { - return doHook(origin, PRIORITY_DEFAULT, hooker); + return LSPosedBridge.doHook(origin, PRIORITY_DEFAULT, hooker); } @Override @NonNull public MethodUnhooker hook(@NonNull Method origin, int priority, @NonNull Class hooker) { - return doHook(origin, priority, hooker); + return LSPosedBridge.doHook(origin, priority, hooker); } @Override @NonNull public MethodUnhooker> hook(@NonNull Constructor origin, @NonNull Class hooker) { - return doHook(origin, PRIORITY_DEFAULT, hooker); + return LSPosedBridge.doHook(origin, PRIORITY_DEFAULT, hooker); } @Override @NonNull public MethodUnhooker> hook(@NonNull Constructor origin, int priority, @NonNull Class hooker) { - return doHook(origin, priority, hooker); + return LSPosedBridge.doHook(origin, priority, hooker); } private static boolean doDeoptimize(@NonNull Executable method) { diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedHelper.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedHelper.java new file mode 100644 index 00000000..60f115e4 --- /dev/null +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedHelper.java @@ -0,0 +1,46 @@ +package org.lsposed.lspd.impl; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +import io.github.libxposed.api.XposedInterface; +import io.github.libxposed.api.errors.HookFailedError; + +public class LSPosedHelper { + + @SuppressWarnings("UnusedReturnValue") + public static XposedInterface.MethodUnhooker + hookMethod(Class hooker, Class clazz, String methodName, Class... parameterTypes) { + try { + var method = clazz.getDeclaredMethod(methodName, parameterTypes); + return LSPosedBridge.doHook(method, XposedInterface.PRIORITY_DEFAULT, hooker); + } catch (NoSuchMethodException e) { + throw new HookFailedError(e); + } + } + + @SuppressWarnings("UnusedReturnValue") + public static Set> + hookAllMethods(Class hooker, Class clazz, String methodName) { + var unhooks = new HashSet>(); + for (var method : clazz.getDeclaredMethods()) { + if (method.getName().equals(methodName)) { + unhooks.add(LSPosedBridge.doHook(method, XposedInterface.PRIORITY_DEFAULT, hooker)); + } + } + return unhooks; + } + + @SuppressWarnings("UnusedReturnValue") + public static XposedInterface.MethodUnhooker> + hookConstructor(Class hooker, Class clazz, Class... parameterTypes) { + try { + var constructor = clazz.getDeclaredConstructor(parameterTypes); + return LSPosedBridge.doHook(constructor, XposedInterface.PRIORITY_DEFAULT, hooker); + } catch (NoSuchMethodException e) { + throw new HookFailedError(e); + } + } +}