Implement hook APIs

This commit is contained in:
LoveSy 2023-01-03 21:47:55 +08:00 committed by LoveSy
parent 06c65a5a61
commit c77617c3e1
5 changed files with 174 additions and 32 deletions

View File

@ -20,10 +20,20 @@
package de.robv.android.xposed;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.lsposed.lspd.nativebridge.HookBridge;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.util.ConcurrentModificationException;
import java.util.HashMap;
import de.robv.android.xposed.callbacks.IXUnhook;
import de.robv.android.xposed.callbacks.XCallback;
import io.github.libxposed.XposedInterface;
/**
* Callback class for method hooks.
@ -93,7 +103,7 @@ public abstract class XC_MethodHook extends XCallback {
/**
* Wraps information about the method call and allows to influence it.
*/
public static final class MethodHookParam extends XCallback.Param {
public static final class MethodHookParam <T> extends XCallback.Param implements XposedInterface.BeforeHookCallback<T>, XposedInterface.AfterHookCallback<T> {
/**
* @hide
*/
@ -121,6 +131,8 @@ public abstract class XC_MethodHook extends XCallback {
private Throwable throwable = null;
public boolean returnEarly = false;
private final HashMap<String, Object> extras = new HashMap<>();
/**
* Returns the result of the method call.
*/
@ -146,6 +158,11 @@ public abstract class XC_MethodHook extends XCallback {
return throwable;
}
@Override
public boolean isSkipped() {
return returnEarly;
}
/**
* Returns true if an exception was thrown by the method.
*/
@ -172,6 +189,51 @@ public abstract class XC_MethodHook extends XCallback {
throw throwable;
return result;
}
@NonNull
@Override
public T getOrigin() {
return (T) method;
}
@Nullable
@Override
public Object getThis() {
return thisObject;
}
@NonNull
@Override
public Object[] getArgs() {
return args;
}
@Override
public void returnAndSkip(@Nullable Object returnValue) {
setResult(returnValue);
}
@Override
public void throwAndSkip(@Nullable Throwable throwable) {
setThrowable(throwable);
}
@Nullable
@Override
public Object invokeOrigin(@Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException {
return HookBridge.invokeOriginalMethod((Executable) method, thisObject, args);
}
@Nullable
@Override
public <U> U getExtra(@NonNull String key) {
return (U) extras.get(key);
}
@Override
public <U> void setExtra(@NonNull String key, @Nullable U value) throws ConcurrentModificationException {
extras.put(key, value);
}
}
/**

View File

@ -43,6 +43,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import io.github.libxposed.XposedInterface;
import io.github.libxposed.XposedModuleInterface;
/**
* This class contains most of Xposed's central logic, such as initialization and callbacks used by
@ -395,7 +396,7 @@ public final class XposedBridge {
}
}
public static class AdditionalHookInfo {
public static class AdditionalHookInfo<T> {
private final Object params;
private AdditionalHookInfo(Executable method) {
@ -416,7 +417,7 @@ public final class XposedBridge {
// 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();
XC_MethodHook.MethodHookParam<T> param = new XC_MethodHook.MethodHookParam<>();
var array = ((Object[]) params);
@ -451,7 +452,12 @@ public final class XposedBridge {
int beforeIdx = 0;
do {
try {
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
var cb = callbacksSnapshot[beforeIdx];
if (HookBridge.instanceOf(cb, XC_MethodHook.class)) {
((XC_MethodHook) cb).beforeHookedMethod(param);
} else if (HookBridge.instanceOf(cb, XposedInterface.BeforeMethodHooker.class)) {
((XposedInterface.BeforeMethodHooker<T>) cb).before(param);
}
} catch (Throwable t) {
XposedBridge.log(t);
@ -483,8 +489,13 @@ public final class XposedBridge {
Object lastResult = param.getResult();
Throwable lastThrowable = param.getThrowable();
var cb = callbacksSnapshot[afterIdx];
try {
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
if (HookBridge.instanceOf(cb, XC_MethodHook.class)) {
((XC_MethodHook) cb).afterHookedMethod(param);
} else if (HookBridge.instanceOf(cb, XposedInterface.AfterHookCallback.class)) {
((XposedInterface.AfterMethodHooker<T>) cb).after(param);
}
} catch (Throwable t) {
XposedBridge.log(t);

View File

@ -1,5 +1,7 @@
package org.lsposed.lspd.impl;
import static de.robv.android.xposed.callbacks.XCallback.PRIORITY_DEFAULT;
import android.app.ActivityThread;
import android.app.LoadedApk;
import android.content.BroadcastReceiver;
@ -49,6 +51,7 @@ import java.io.FileOutputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
@ -56,6 +59,8 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.XposedInit;
import io.github.libxposed.XposedContext;
@ -754,76 +759,102 @@ public class LSPosedContext extends XposedContext {
return BuildConfig.VERSION_CODE;
}
// TODO
private <T, U extends Executable> MethodUnhooker<T, U> doHook(U hookMethod, int priority, T callback) {
if (Modifier.isAbstract(hookMethod.getModifiers())) {
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod);
} else if (hookMethod.getDeclaringClass().getClassLoader() == XposedBridge.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");
}
if (callback == null) {
throw new IllegalArgumentException("callback should not be null!");
}
if (HookBridge.hookMethod(hookMethod, XposedBridge.AdditionalHookInfo.class, priority, callback)) {
return new MethodUnhooker<>() {
@NonNull
@Override
public U getOrigin() {
return hookMethod;
}
@NonNull
@Override
public T getHooker() {
return callback;
}
@Override
public void unhook() {
HookBridge.unhookMethod(hookMethod, callback);
}
};
}
log("Cannot hook " + hookMethod);
return null;
}
@Override
public MethodUnhooker<BeforeMethodHooker<Method>, Method> hookBefore(@NonNull Method origin, @NonNull BeforeMethodHooker<Method> hooker) {
return null;
return doHook(origin, PRIORITY_DEFAULT, hooker);
}
// TODO
@Override
public MethodUnhooker<AfterMethodHooker<Method>, Method> hookAfter(@NonNull Method origin, @NonNull AfterMethodHooker<Method> hooker) {
return null;
return doHook(origin, PRIORITY_DEFAULT, hooker);
}
// TODO
@Override
public MethodUnhooker<MethodHooker<Method>, Method> hook(@NonNull Method origin, @NonNull MethodHooker<Method> hooker) {
return null;
return doHook(origin, PRIORITY_DEFAULT, hooker);
}
// TODO
@Override
public MethodUnhooker<BeforeMethodHooker<Method>, Method> hookBefore(@NonNull Method origin, int priority, @NonNull BeforeMethodHooker<Method> hooker) {
return null;
return doHook(origin, priority, hooker);
}
// TODO
@Override
public MethodUnhooker<AfterMethodHooker<Method>, Method> hookAfter(@NonNull Method origin, int priority, @NonNull AfterMethodHooker<Method> hooker) {
return null;
return doHook(origin, priority, hooker);
}
// TODO
@Override
public MethodUnhooker<MethodHooker<Method>, Method> hook(@NonNull Method origin, int priority, @NonNull MethodHooker<Method> hooker) {
return null;
return doHook(origin, priority, hooker);
}
// TODO
@Override
public <T> MethodUnhooker<BeforeMethodHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, @NonNull BeforeMethodHooker<Constructor<T>> hooker) {
return null;
return doHook(origin, PRIORITY_DEFAULT, hooker);
}
// TODO
@Override
public <T> MethodUnhooker<AfterMethodHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, @NonNull AfterMethodHooker<Constructor<T>> hooker) {
return null;
return doHook(origin, PRIORITY_DEFAULT, hooker);
}
// TODO
@Override
public <T> MethodUnhooker<MethodHooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, @NonNull MethodHooker<Constructor<T>> hooker) {
return null;
return doHook(origin, PRIORITY_DEFAULT, hooker);
}
// TODO
@Override
public <T> MethodUnhooker<BeforeMethodHooker<Constructor<T>>, Constructor<T>> hookBefore(@NonNull Constructor<T> origin, int priority, @NonNull BeforeMethodHooker<Constructor<T>> hooker) {
return null;
return doHook(origin, priority, hooker);
}
// TODO
@Override
public <T> MethodUnhooker<AfterMethodHooker<Constructor<T>>, Constructor<T>> hookAfter(@NonNull Constructor<T> origin, int priority, @NonNull AfterMethodHooker<Constructor<T>> hooker) {
return null;
return doHook(origin, priority, hooker);
}
// TODO
@Override
public <T> MethodUnhooker<MethodHooker<Constructor<T>>, Constructor<T>> hook(@NonNull Constructor<T> origin, int priority, @NonNull MethodHooker<Constructor<T>> hooker) {
return null;
return doHook(origin, priority, hooker);
}
private static boolean deoptimize(@NonNull Executable method) {
@ -845,11 +876,10 @@ public class LSPosedContext extends XposedContext {
return deoptimize((Executable) constructor);
}
// TODO
@Nullable
@Override
public XposedUtils getUtils() {
return null;
return new LSPosedUtils(this);
}
@Override

View File

@ -0,0 +1,38 @@
package org.lsposed.lspd.impl;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.github.libxposed.XposedUtils;
public class LSPosedUtils implements XposedUtils {
private final LSPosedContext context;
LSPosedUtils(LSPosedContext context) {
this.context = context;
}
@NonNull
@Override
public <T> Class<T> classByName(@NonNull String className, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
throw new AbstractMethodError();
}
@Nullable
@Override
public <T> Class<T> classOrNullByName(@NonNull String className, @Nullable ClassLoader classLoader) {
throw new AbstractMethodError();
}
@NonNull
@Override
public <T> Class<T> classByBinaryName(@NonNull String binaryClassName, @Nullable ClassLoader classLoader) throws ClassNotFoundException {
throw new AbstractMethodError();
}
@Nullable
@Override
public <T> Class<T> classOrNullByBinaryName(@NonNull String binaryClassName, @Nullable ClassLoader classLoader) {
throw new AbstractMethodError();
}
}

View File

@ -4,6 +4,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ConcurrentModificationException;
@ -25,7 +26,7 @@ public interface XposedInterface {
void throwAndSkip(@Nullable Throwable throwable);
@Nullable
Object invokeOrigin(@Nullable Object thisObject, Object[] args);
Object invokeOrigin(@Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException;
<U> void setExtra(@NonNull String key, @Nullable U value) throws ConcurrentModificationException;
}
@ -53,7 +54,7 @@ public interface XposedInterface {
void setThrowable(@Nullable Throwable throwable);
@Nullable
Object invokeOrigin(@Nullable Object thisObject, Object[] args);
Object invokeOrigin(@Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalAccessException;
@Nullable
<U> U getExtra(@NonNull String key);