Use modern hook API for internal hookers
This commit is contained in:
parent
e8e9105598
commit
fc1adeac55
|
|
@ -34,34 +34,29 @@ import org.lsposed.lspd.hooker.HandleSystemServerProcessHooker;
|
||||||
import org.lsposed.lspd.hooker.LoadedApkCtorHooker;
|
import org.lsposed.lspd.hooker.LoadedApkCtorHooker;
|
||||||
import org.lsposed.lspd.hooker.OpenDexFileHooker;
|
import org.lsposed.lspd.hooker.OpenDexFileHooker;
|
||||||
import org.lsposed.lspd.impl.LSPosedContext;
|
import org.lsposed.lspd.impl.LSPosedContext;
|
||||||
|
import org.lsposed.lspd.impl.LSPosedHelper;
|
||||||
import org.lsposed.lspd.service.ILSPApplicationService;
|
import org.lsposed.lspd.service.ILSPApplicationService;
|
||||||
import org.lsposed.lspd.util.Utils;
|
import org.lsposed.lspd.util.Utils;
|
||||||
|
|
||||||
import dalvik.system.DexFile;
|
import dalvik.system.DexFile;
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
|
||||||
import de.robv.android.xposed.XposedInit;
|
import de.robv.android.xposed.XposedInit;
|
||||||
|
|
||||||
public class Startup {
|
public class Startup {
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private static void startBootstrapHook(boolean isSystem) {
|
private static void startBootstrapHook(boolean isSystem) {
|
||||||
Utils.logD("startBootstrapHook starts: isSystem = " + isSystem);
|
Utils.logD("startBootstrapHook starts: isSystem = " + isSystem);
|
||||||
XposedHelpers.findAndHookMethod(Thread.class, "dispatchUncaughtException",
|
LSPosedHelper.hookMethod(CrashDumpHooker.class, Thread.class, "dispatchUncaughtException", Throwable.class);
|
||||||
Throwable.class, new CrashDumpHooker());
|
|
||||||
if (isSystem) {
|
if (isSystem) {
|
||||||
XposedBridge.hookAllMethods(ZygoteInit.class,
|
LSPosedHelper.hookAllMethods(HandleSystemServerProcessHooker.class, ZygoteInit.class, "handleSystemServerProcess");
|
||||||
"handleSystemServerProcess", new HandleSystemServerProcessHooker());
|
|
||||||
} else {
|
} else {
|
||||||
var hooker = new OpenDexFileHooker();
|
LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openDexFile");
|
||||||
XposedBridge.hookAllMethods(DexFile.class, "openDexFile", hooker);
|
LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openInMemoryDexFile");
|
||||||
XposedBridge.hookAllMethods(DexFile.class, "openInMemoryDexFile", hooker);
|
LSPosedHelper.hookAllMethods(OpenDexFileHooker.class, DexFile.class, "openInMemoryDexFiles");
|
||||||
XposedBridge.hookAllMethods(DexFile.class, "openInMemoryDexFiles", hooker);
|
|
||||||
}
|
}
|
||||||
XposedHelpers.findAndHookConstructor(LoadedApk.class,
|
LSPosedHelper.hookConstructor(LoadedApkCtorHooker.class, LoadedApk.class,
|
||||||
ActivityThread.class, ApplicationInfo.class, CompatibilityInfo.class,
|
ActivityThread.class, ApplicationInfo.class, CompatibilityInfo.class,
|
||||||
ClassLoader.class, boolean.class, boolean.class, boolean.class,
|
ClassLoader.class, boolean.class, boolean.class, boolean.class);
|
||||||
new LoadedApkCtorHooker());
|
LSPosedHelper.hookAllMethods(AttachHooker.class, ActivityThread.class, "attach");
|
||||||
XposedBridge.hookAllMethods(ActivityThread.class, "attach", new AttachHooker());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void bootstrapXposed() {
|
public static void bootstrapXposed() {
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,17 @@
|
||||||
package org.lsposed.lspd.hooker;
|
package org.lsposed.lspd.hooker;
|
||||||
|
|
||||||
import static org.lsposed.lspd.core.ApplicationServiceClient.serviceClient;
|
|
||||||
|
|
||||||
import android.app.ActivityThread;
|
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 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 {
|
@XposedHooker
|
||||||
@Override
|
public class AttachHooker implements XposedInterface.Hooker {
|
||||||
protected void afterHookedMethod(MethodHookParam<?> param) throws Throwable {
|
|
||||||
XposedInit.loadModules((ActivityThread) param.thisObject);
|
@AfterInvocation
|
||||||
|
public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) {
|
||||||
|
XposedInit.loadModules((ActivityThread) callback.getThisObject());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,20 @@ package org.lsposed.lspd.hooker;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import de.robv.android.xposed.XC_MethodHook;
|
import org.lsposed.lspd.impl.LSPosedBridge;
|
||||||
import de.robv.android.xposed.XposedBridge;
|
|
||||||
|
|
||||||
public class CrashDumpHooker extends XC_MethodHook {
|
import io.github.libxposed.api.XposedInterface;
|
||||||
@Override
|
import io.github.libxposed.api.annotations.BeforeInvocation;
|
||||||
protected void beforeHookedMethod(MethodHookParam<?> param) {
|
import io.github.libxposed.api.annotations.XposedHooker;
|
||||||
|
|
||||||
|
@XposedHooker
|
||||||
|
public class CrashDumpHooker implements XposedInterface.Hooker {
|
||||||
|
|
||||||
|
@BeforeInvocation
|
||||||
|
public static void beforeHookedMethod(XposedInterface.BeforeHookCallback callback) {
|
||||||
try {
|
try {
|
||||||
var e = (Throwable) param.args[0];
|
var e = (Throwable) callback.getArgs()[0];
|
||||||
XposedBridge.log("Crash unexpectedly: " + Log.getStackTraceString(e));
|
LSPosedBridge.log("Crash unexpectedly: " + Log.getStackTraceString(e));
|
||||||
} catch (Throwable ignored) {
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,32 +20,35 @@
|
||||||
|
|
||||||
package org.lsposed.lspd.hooker;
|
package org.lsposed.lspd.hooker;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
|
||||||
import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
|
import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
|
||||||
|
import org.lsposed.lspd.impl.LSPosedHelper;
|
||||||
import org.lsposed.lspd.util.Hookers;
|
import org.lsposed.lspd.util.Hookers;
|
||||||
|
|
||||||
import de.robv.android.xposed.XC_MethodHook;
|
import io.github.libxposed.api.XposedInterface;
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import io.github.libxposed.api.annotations.AfterInvocation;
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import io.github.libxposed.api.annotations.XposedHooker;
|
||||||
|
|
||||||
// system_server initialization
|
// system_server initialization
|
||||||
public class HandleSystemServerProcessHooker extends XC_MethodHook {
|
@XposedHooker
|
||||||
|
public class HandleSystemServerProcessHooker implements XposedInterface.Hooker {
|
||||||
|
|
||||||
public static volatile ClassLoader systemServerCL;
|
public static volatile ClassLoader systemServerCL;
|
||||||
|
|
||||||
@Override
|
@SuppressLint("PrivateApi")
|
||||||
protected void afterHookedMethod(MethodHookParam<?> param) {
|
@AfterInvocation
|
||||||
|
public static void afterHookedMethod() {
|
||||||
Hookers.logD("ZygoteInit#handleSystemServerProcess() starts");
|
Hookers.logD("ZygoteInit#handleSystemServerProcess() starts");
|
||||||
try {
|
try {
|
||||||
// get system_server classLoader
|
// get system_server classLoader
|
||||||
systemServerCL = Thread.currentThread().getContextClassLoader();
|
systemServerCL = Thread.currentThread().getContextClassLoader();
|
||||||
// deopt methods in SYSTEMSERVERCLASSPATH
|
// deopt methods in SYSTEMSERVERCLASSPATH
|
||||||
PrebuiltMethodsDeopter.deoptSystemServerMethods(systemServerCL);
|
PrebuiltMethodsDeopter.deoptSystemServerMethods(systemServerCL);
|
||||||
XposedBridge.hookAllMethods(
|
var clazz = Class.forName("com.android.server.SystemServer", false, systemServerCL);
|
||||||
XposedHelpers.findClass("com.android.server.SystemServer", systemServerCL),
|
LSPosedHelper.hookAllMethods(StartBootstrapServicesHooker.class, clazz, "startBootstrapServices");
|
||||||
"startBootstrapServices", new StartBootstrapServicesHooker());
|
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Hookers.logE("error when hooking systemMain", t);
|
Hookers.logE("error when hooking systemMain", t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,19 +26,23 @@ import android.util.Log;
|
||||||
|
|
||||||
import org.lsposed.lspd.util.Hookers;
|
import org.lsposed.lspd.util.Hookers;
|
||||||
|
|
||||||
import de.robv.android.xposed.XC_MethodHook;
|
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import de.robv.android.xposed.XposedHelpers;
|
||||||
import de.robv.android.xposed.XposedInit;
|
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
|
// 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
|
@AfterInvocation
|
||||||
protected void afterHookedMethod(MethodHookParam<?> param) {
|
public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) {
|
||||||
Hookers.logD("LoadedApk#<init> starts");
|
Hookers.logD("LoadedApk#<init> starts");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
LoadedApk loadedApk = (LoadedApk) param.thisObject;
|
LoadedApk loadedApk = (LoadedApk) callback.getThisObject();
|
||||||
|
assert loadedApk != null;
|
||||||
String packageName = loadedApk.getPackageName();
|
String packageName = loadedApk.getPackageName();
|
||||||
Object mAppDir = XposedHelpers.getObjectField(loadedApk, "mAppDir");
|
Object mAppDir = XposedHelpers.getObjectField(loadedApk, "mAppDir");
|
||||||
Hookers.logD("LoadedApk#<init> ends: " + mAppDir);
|
Hookers.logD("LoadedApk#<init> ends: " + mAppDir);
|
||||||
|
|
|
||||||
|
|
@ -2,26 +2,30 @@ package org.lsposed.lspd.hooker;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
|
import org.lsposed.lspd.impl.LSPosedBridge;
|
||||||
import org.lsposed.lspd.nativebridge.HookBridge;
|
import org.lsposed.lspd.nativebridge.HookBridge;
|
||||||
|
|
||||||
import de.robv.android.xposed.XC_MethodHook;
|
import io.github.libxposed.api.XposedInterface;
|
||||||
import de.robv.android.xposed.XposedHelpers;
|
import io.github.libxposed.api.annotations.AfterInvocation;
|
||||||
|
import io.github.libxposed.api.annotations.XposedHooker;
|
||||||
|
|
||||||
public class OpenDexFileHooker extends XC_MethodHook {
|
@XposedHooker
|
||||||
@Override
|
public class OpenDexFileHooker implements XposedInterface.Hooker {
|
||||||
protected void afterHookedMethod(MethodHookParam<?> param) throws Throwable {
|
|
||||||
|
@AfterInvocation
|
||||||
|
public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) {
|
||||||
ClassLoader classLoader = null;
|
ClassLoader classLoader = null;
|
||||||
for (var arg : param.args) {
|
for (var arg : callback.getArgs()) {
|
||||||
if (arg instanceof ClassLoader) {
|
if (arg instanceof ClassLoader) {
|
||||||
classLoader = (ClassLoader) arg;
|
classLoader = (ClassLoader) arg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P && classLoader == null) {
|
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P && classLoader == null) {
|
||||||
classLoader = XposedHelpers.class.getClassLoader();
|
classLoader = LSPosedBridge.class.getClassLoader();
|
||||||
}
|
}
|
||||||
while (classLoader != null) {
|
while (classLoader != null) {
|
||||||
if (classLoader == XposedHelpers.class.getClassLoader()) {
|
if (classLoader == LSPosedBridge.class.getClassLoader()) {
|
||||||
HookBridge.setTrusted(param.getResult());
|
HookBridge.setTrusted(callback.getResult());
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
classLoader = classLoader.getParent();
|
classLoader = classLoader.getParent();
|
||||||
|
|
|
||||||
|
|
@ -27,16 +27,19 @@ import androidx.annotation.NonNull;
|
||||||
import org.lsposed.lspd.impl.LSPosedContext;
|
import org.lsposed.lspd.impl.LSPosedContext;
|
||||||
import org.lsposed.lspd.util.Hookers;
|
import org.lsposed.lspd.util.Hookers;
|
||||||
|
|
||||||
import de.robv.android.xposed.XC_MethodHook;
|
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
import de.robv.android.xposed.XposedInit;
|
import de.robv.android.xposed.XposedInit;
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
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.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
|
@BeforeInvocation
|
||||||
protected void beforeHookedMethod(MethodHookParam<?> param) {
|
public static void beforeHookedMethod() {
|
||||||
logD("SystemServer#startBootstrapServices() starts");
|
logD("SystemServer#startBootstrapServices() starts");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package org.lsposed.lspd.impl;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.lsposed.lspd.nativebridge.HookBridge;
|
import org.lsposed.lspd.nativebridge.HookBridge;
|
||||||
|
|
||||||
import java.lang.reflect.Executable;
|
import java.lang.reflect.Executable;
|
||||||
|
|
@ -10,6 +12,11 @@ import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedBridge;
|
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 {
|
public class LSPosedBridge {
|
||||||
|
|
||||||
|
|
@ -30,12 +37,19 @@ public class LSPosedBridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class HookerCallback {
|
public static class HookerCallback {
|
||||||
Method beforeInvocation;
|
@NonNull
|
||||||
Method afterInvocation;
|
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.beforeInvocation = beforeInvocation;
|
||||||
this.afterInvocation = afterInvocation;
|
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++) {
|
for (beforeIdx = 0; beforeIdx < modernSnapshot.length; beforeIdx++) {
|
||||||
try {
|
try {
|
||||||
var hooker = (HookerCallback) modernSnapshot[beforeIdx];
|
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) {
|
} catch (Throwable t) {
|
||||||
LSPosedBridge.log(t);
|
LSPosedBridge.log(t);
|
||||||
|
|
||||||
|
|
@ -150,12 +168,13 @@ public class LSPosedBridge {
|
||||||
Object lastResult = callback.getResult();
|
Object lastResult = callback.getResult();
|
||||||
Throwable lastThrowable = callback.getThrowable();
|
Throwable lastThrowable = callback.getThrowable();
|
||||||
var hooker = (HookerCallback) modernSnapshot[afterIdx];
|
var hooker = (HookerCallback) modernSnapshot[afterIdx];
|
||||||
var context = ctxArray[afterIdx];
|
|
||||||
try {
|
try {
|
||||||
if (context == null) {
|
if (hooker.afterParams == 0) {
|
||||||
|
hooker.afterInvocation.invoke(null);
|
||||||
|
} else if (hooker.afterParams == 1) {
|
||||||
hooker.afterInvocation.invoke(null, callback);
|
hooker.afterInvocation.invoke(null, callback);
|
||||||
} else {
|
} else {
|
||||||
hooker.afterInvocation.invoke(null, callback, context);
|
hooker.afterInvocation.invoke(null, callback, ctxArray[afterIdx]);
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
LSPosedBridge.log(t);
|
LSPosedBridge.log(t);
|
||||||
|
|
@ -186,4 +205,95 @@ public class LSPosedBridge {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void dummyCallback() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends Executable> XposedInterface.MethodUnhooker<T>
|
||||||
|
doHook(T hookMethod, int priority, Class<? extends XposedInterface.Hooker> 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,6 @@ import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.lang.reflect.Proxy;
|
import java.lang.reflect.Proxy;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
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.XposedInterface;
|
||||||
import io.github.libxposed.api.XposedModule;
|
import io.github.libxposed.api.XposedModule;
|
||||||
import io.github.libxposed.api.XposedModuleInterface;
|
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.errors.XposedFrameworkError;
|
||||||
import io.github.libxposed.api.utils.DexParser;
|
import io.github.libxposed.api.utils.DexParser;
|
||||||
|
|
||||||
|
|
@ -169,104 +164,28 @@ public class LSPosedContext implements XposedInterface {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends Executable> MethodUnhooker<T> doHook(T hookMethod, int priority, Class<? extends Hooker> 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
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public MethodUnhooker<Method> hook(@NonNull Method origin, @NonNull Class<? extends Hooker> hooker) {
|
public MethodUnhooker<Method> hook(@NonNull Method origin, @NonNull Class<? extends Hooker> hooker) {
|
||||||
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
return LSPosedBridge.doHook(origin, PRIORITY_DEFAULT, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public MethodUnhooker<Method> hook(@NonNull Method origin, int priority, @NonNull Class<? extends Hooker> hooker) {
|
public MethodUnhooker<Method> hook(@NonNull Method origin, int priority, @NonNull Class<? extends Hooker> hooker) {
|
||||||
return doHook(origin, priority, hooker);
|
return LSPosedBridge.doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull Class<? extends Hooker> hooker) {
|
public <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull Class<? extends Hooker> hooker) {
|
||||||
return doHook(origin, PRIORITY_DEFAULT, hooker);
|
return LSPosedBridge.doHook(origin, PRIORITY_DEFAULT, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NonNull
|
@NonNull
|
||||||
public <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) {
|
public <T> MethodUnhooker<Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull Class<? extends Hooker> hooker) {
|
||||||
return doHook(origin, priority, hooker);
|
return LSPosedBridge.doHook(origin, priority, hooker);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean doDeoptimize(@NonNull Executable method) {
|
private static boolean doDeoptimize(@NonNull Executable method) {
|
||||||
|
|
|
||||||
|
|
@ -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 <T> XposedInterface.MethodUnhooker<Method>
|
||||||
|
hookMethod(Class<? extends XposedInterface.Hooker> hooker, Class<T> 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 <T> Set<XposedInterface.MethodUnhooker<Method>>
|
||||||
|
hookAllMethods(Class<? extends XposedInterface.Hooker> hooker, Class<T> clazz, String methodName) {
|
||||||
|
var unhooks = new HashSet<XposedInterface.MethodUnhooker<Method>>();
|
||||||
|
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 <T> XposedInterface.MethodUnhooker<Constructor<T>>
|
||||||
|
hookConstructor(Class<? extends XposedInterface.Hooker> hooker, Class<T> clazz, Class<?>... parameterTypes) {
|
||||||
|
try {
|
||||||
|
var constructor = clazz.getDeclaredConstructor(parameterTypes);
|
||||||
|
return LSPosedBridge.doHook(constructor, XposedInterface.PRIORITY_DEFAULT, hooker);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
throw new HookFailedError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue