Commit 95189eff authored by swift_gan's avatar swift_gan Committed by swift_gan

init

parent 8eed3a00
include ':app', ':hooklib', ':hookers', ':annotation'
include ':app', ':hooklib', ':hookers', ':annotation', ':xposedcompat'
apply plugin: 'com.android.library'
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.linkedin.dexmaker:dexmaker:2.21.0'
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.swift.sandhook.xposedcompat" />
package com.swift.sandhook.xposedcompat;
public class XposedCompat {
}
package com.swift.sandhook.xposedcompat.methodgen;
import com.android.dx.Code;
import com.android.dx.Local;
import com.android.dx.TypeId;
import com.android.dx.rop.code.Insn;
import com.android.dx.rop.code.PlainInsn;
import com.android.dx.rop.code.RegisterSpec;
import com.android.dx.rop.code.RegisterSpecList;
import com.android.dx.rop.code.Rops;
import com.android.dx.rop.code.SourcePosition;
import com.android.dx.rop.type.Type;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class DexMakerUtils {
private static volatile Method addInstMethod, specMethod;
public static void autoBoxIfNecessary(Code code, Local<Object> target, Local source) {
String boxMethod = "valueOf";
TypeId<?> boxTypeId;
TypeId typeId = source.getType();
if (typeId.equals(TypeId.BOOLEAN)) {
boxTypeId = TypeId.get(Boolean.class);
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BOOLEAN), target, source);
} else if (typeId.equals(TypeId.BYTE)) {
boxTypeId = TypeId.get(Byte.class);
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BYTE), target, source);
} else if (typeId.equals(TypeId.CHAR)) {
boxTypeId = TypeId.get(Character.class);
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.CHAR), target, source);
} else if (typeId.equals(TypeId.DOUBLE)) {
boxTypeId = TypeId.get(Double.class);
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.DOUBLE), target, source);
} else if (typeId.equals(TypeId.FLOAT)) {
boxTypeId = TypeId.get(Float.class);
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.FLOAT), target, source);
} else if (typeId.equals(TypeId.INT)) {
boxTypeId = TypeId.get(Integer.class);
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.INT), target, source);
} else if (typeId.equals(TypeId.LONG)) {
boxTypeId = TypeId.get(Long.class);
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.LONG), target, source);
} else if (typeId.equals(TypeId.SHORT)) {
boxTypeId = TypeId.get(Short.class);
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.SHORT), target, source);
} else if (typeId.equals(TypeId.VOID)) {
code.loadConstant(target, null);
} else {
code.move(target, source);
}
}
public static void autoUnboxIfNecessary(Code code, Local target, Local source,
Map<TypeId, Local> tmpLocals, boolean castObj) {
String unboxMethod;
TypeId typeId = target.getType();
TypeId<?> boxTypeId;
if (typeId.equals(TypeId.BOOLEAN)) {
unboxMethod = "booleanValue";
boxTypeId = TypeId.get("Ljava/lang/Boolean;");
Local boxTypedLocal = tmpLocals.get(boxTypeId);
code.cast(boxTypedLocal, source);
code.invokeVirtual(boxTypeId.getMethod(TypeId.BOOLEAN, unboxMethod), target, boxTypedLocal);
} else if (typeId.equals(TypeId.BYTE)) {
unboxMethod = "byteValue";
boxTypeId = TypeId.get("Ljava/lang/Byte;");
Local boxTypedLocal = tmpLocals.get(boxTypeId);
code.cast(boxTypedLocal, source);
code.invokeVirtual(boxTypeId.getMethod(TypeId.BYTE, unboxMethod), target, boxTypedLocal);
} else if (typeId.equals(TypeId.CHAR)) {
unboxMethod = "charValue";
boxTypeId = TypeId.get("Ljava/lang/Character;");
Local boxTypedLocal = tmpLocals.get(boxTypeId);
code.cast(boxTypedLocal, source);
code.invokeVirtual(boxTypeId.getMethod(TypeId.CHAR, unboxMethod), target, boxTypedLocal);
} else if (typeId.equals(TypeId.DOUBLE)) {
unboxMethod = "doubleValue";
boxTypeId = TypeId.get("Ljava/lang/Double;");
Local boxTypedLocal = tmpLocals.get(boxTypeId);
code.cast(boxTypedLocal, source);
code.invokeVirtual(boxTypeId.getMethod(TypeId.DOUBLE, unboxMethod), target, boxTypedLocal);
} else if (typeId.equals(TypeId.FLOAT)) {
unboxMethod = "floatValue";
boxTypeId = TypeId.get("Ljava/lang/Float;");
Local boxTypedLocal = tmpLocals.get(boxTypeId);
code.cast(boxTypedLocal, source);
code.invokeVirtual(boxTypeId.getMethod(TypeId.FLOAT, unboxMethod), target, boxTypedLocal);
} else if (typeId.equals(TypeId.INT)) {
unboxMethod = "intValue";
boxTypeId = TypeId.get("Ljava/lang/Integer;");
Local boxTypedLocal = tmpLocals.get(boxTypeId);
code.cast(boxTypedLocal, source);
code.invokeVirtual(boxTypeId.getMethod(TypeId.INT, unboxMethod), target, boxTypedLocal);
} else if (typeId.equals(TypeId.LONG)) {
unboxMethod = "longValue";
boxTypeId = TypeId.get("Ljava/lang/Long;");
Local boxTypedLocal = tmpLocals.get(boxTypeId);
code.cast(boxTypedLocal, source);
code.invokeVirtual(boxTypeId.getMethod(TypeId.LONG, unboxMethod), target, boxTypedLocal);
} else if (typeId.equals(TypeId.SHORT)) {
unboxMethod = "shortValue";
boxTypeId = TypeId.get("Ljava/lang/Short;");
Local boxTypedLocal = tmpLocals.get(boxTypeId);
code.cast(boxTypedLocal, source);
code.invokeVirtual(boxTypeId.getMethod(TypeId.SHORT, unboxMethod), target, boxTypedLocal);
} else if (typeId.equals(TypeId.VOID)) {
code.loadConstant(target, null);
} else if (castObj) {
code.cast(target, source);
} else {
code.move(target, source);
}
}
public static Map<TypeId, Local> createResultLocals(Code code) {
HashMap<TypeId, Local> resultMap = new HashMap<>();
Local<Boolean> booleanLocal = code.newLocal(TypeId.BOOLEAN);
Local<Byte> byteLocal = code.newLocal(TypeId.BYTE);
Local<Character> charLocal = code.newLocal(TypeId.CHAR);
Local<Double> doubleLocal = code.newLocal(TypeId.DOUBLE);
Local<Float> floatLocal = code.newLocal(TypeId.FLOAT);
Local<Integer> intLocal = code.newLocal(TypeId.INT);
Local<Long> longLocal = code.newLocal(TypeId.LONG);
Local<Short> shortLocal = code.newLocal(TypeId.SHORT);
Local<Void> voidLocal = code.newLocal(TypeId.VOID);
Local<Object> objectLocal = code.newLocal(TypeId.OBJECT);
Local<Object> booleanObjLocal = code.newLocal(TypeId.get("Ljava/lang/Boolean;"));
Local<Object> byteObjLocal = code.newLocal(TypeId.get("Ljava/lang/Byte;"));
Local<Object> charObjLocal = code.newLocal(TypeId.get("Ljava/lang/Character;"));
Local<Object> doubleObjLocal = code.newLocal(TypeId.get("Ljava/lang/Double;"));
Local<Object> floatObjLocal = code.newLocal(TypeId.get("Ljava/lang/Float;"));
Local<Object> intObjLocal = code.newLocal(TypeId.get("Ljava/lang/Integer;"));
Local<Object> longObjLocal = code.newLocal(TypeId.get("Ljava/lang/Long;"));
Local<Object> shortObjLocal = code.newLocal(TypeId.get("Ljava/lang/Short;"));
Local<Object> voidObjLocal = code.newLocal(TypeId.get("Ljava/lang/Void;"));
// backup need initialized locals
code.loadConstant(booleanLocal, false);
code.loadConstant(byteLocal, (byte) 0);
code.loadConstant(charLocal, '\0');
code.loadConstant(doubleLocal,0.0);
code.loadConstant(floatLocal,0.0f);
code.loadConstant(intLocal, 0);
code.loadConstant(longLocal, 0L);
code.loadConstant(shortLocal, (short) 0);
code.loadConstant(voidLocal, null);
code.loadConstant(objectLocal, null);
// all to null
code.loadConstant(booleanObjLocal, null);
code.loadConstant(byteObjLocal, null);
code.loadConstant(charObjLocal, null);
code.loadConstant(doubleObjLocal, null);
code.loadConstant(floatObjLocal, null);
code.loadConstant(intObjLocal, null);
code.loadConstant(longObjLocal, null);
code.loadConstant(shortObjLocal, null);
code.loadConstant(voidObjLocal, null);
// package all
resultMap.put(TypeId.BOOLEAN, booleanLocal);
resultMap.put(TypeId.BYTE, byteLocal);
resultMap.put(TypeId.CHAR, charLocal);
resultMap.put(TypeId.DOUBLE, doubleLocal);
resultMap.put(TypeId.FLOAT, floatLocal);
resultMap.put(TypeId.INT, intLocal);
resultMap.put(TypeId.LONG, longLocal);
resultMap.put(TypeId.SHORT, shortLocal);
resultMap.put(TypeId.VOID, voidLocal);
resultMap.put(TypeId.OBJECT, objectLocal);
resultMap.put(TypeId.get("Ljava/lang/Boolean;"), booleanObjLocal);
resultMap.put(TypeId.get("Ljava/lang/Byte;"), byteObjLocal);
resultMap.put(TypeId.get("Ljava/lang/Character;"), charObjLocal);
resultMap.put(TypeId.get("Ljava/lang/Double;"), doubleObjLocal);
resultMap.put(TypeId.get("Ljava/lang/Float;"), floatObjLocal);
resultMap.put(TypeId.get("Ljava/lang/Integer;"), intObjLocal);
resultMap.put(TypeId.get("Ljava/lang/Long;"), longObjLocal);
resultMap.put(TypeId.get("Ljava/lang/Short;"), shortObjLocal);
resultMap.put(TypeId.get("Ljava/lang/Void;"), voidObjLocal);
return resultMap;
}
public static TypeId getObjTypeIdIfPrimitive(TypeId typeId) {
if (typeId.equals(TypeId.BOOLEAN)) {
return TypeId.get("Ljava/lang/Boolean;");
} else if (typeId.equals(TypeId.BYTE)) {
return TypeId.get("Ljava/lang/Byte;");
} else if (typeId.equals(TypeId.CHAR)) {
return TypeId.get("Ljava/lang/Character;");
} else if (typeId.equals(TypeId.DOUBLE)) {
return TypeId.get("Ljava/lang/Double;");
} else if (typeId.equals(TypeId.FLOAT)) {
return TypeId.get("Ljava/lang/Float;");
} else if (typeId.equals(TypeId.INT)) {
return TypeId.get("Ljava/lang/Integer;");
} else if (typeId.equals(TypeId.LONG)) {
return TypeId.get("Ljava/lang/Long;");
} else if (typeId.equals(TypeId.SHORT)) {
return TypeId.get("Ljava/lang/Short;");
} else if (typeId.equals(TypeId.VOID)) {
return TypeId.get("Ljava/lang/Void;");
} else {
return typeId;
}
}
public static void returnRightValue(Code code, Class<?> returnType, Map<Class, Local> resultLocals) {
String unboxMethod;
TypeId<?> boxTypeId;
code.returnValue(resultLocals.get(returnType));
}
public static void moveException(Code code, Local<?> result) {
addInstruction(code, new PlainInsn(Rops.opMoveException(Type.THROWABLE),
SourcePosition.NO_INFO, spec(result), RegisterSpecList.EMPTY));
}
public static void addInstruction(Code code, Insn insn) {
if (addInstMethod == null) {
try {
addInstMethod = Code.class.getDeclaredMethod("addInstruction", Insn.class);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
try {
addInstMethod.invoke(code, insn);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
public static RegisterSpec spec(Local<?> result) {
if (specMethod == null) {
try {
specMethod = Local.class.getDeclaredMethod("spec");
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
try {
return (RegisterSpec) specMethod.invoke(result);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
return null;
}
}
}
package com.swift.sandhook.xposedcompat.methodgen;
import android.text.TextUtils;
import com.android.dx.BinaryOp;
import com.android.dx.Code;
import com.android.dx.Comparison;
import com.android.dx.DexMaker;
import com.android.dx.FieldId;
import com.android.dx.Label;
import com.android.dx.Local;
import com.android.dx.MethodId;
import com.android.dx.TypeId;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import static com.swift.sandhook.xposedcompat.methodgen.DexMakerUtils.autoBoxIfNecessary;
import static com.swift.sandhook.xposedcompat.methodgen.DexMakerUtils.autoUnboxIfNecessary;
import static com.swift.sandhook.xposedcompat.methodgen.DexMakerUtils.createResultLocals;
import static com.swift.sandhook.xposedcompat.methodgen.DexMakerUtils.getObjTypeIdIfPrimitive;
import static com.swift.sandhook.xposedcompat.methodgen.DexMakerUtils.moveException;
public class HookerDexMaker {
public static final String METHOD_NAME_BACKUP = "backup";
public static final String METHOD_NAME_HOOK = "hook";
public static final String METHOD_NAME_CALL_BACKUP = "callBackup";
public static final String METHOD_NAME_SETUP = "setup";
public static final TypeId<Object[]> objArrayTypeId = TypeId.get(Object[].class);
private static final String CLASS_DESC_PREFIX = "L";
private static final String CLASS_NAME_PREFIX = "EdHooker";
private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo";
private static final String FIELD_NAME_METHOD = "method";
private static final String PARAMS_FIELD_NAME_METHOD = "method";
private static final String PARAMS_FIELD_NAME_THIS_OBJECT = "thisObject";
private static final String PARAMS_FIELD_NAME_ARGS = "args";
private static final String CALLBACK_METHOD_NAME_BEFORE = "callBeforeHookedMethod";
private static final String CALLBACK_METHOD_NAME_AFTER = "callAfterHookedMethod";
private static final String PARAMS_METHOD_NAME_IS_EARLY_RETURN = "isEarlyReturn";
private static final TypeId<Throwable> throwableTypeId = TypeId.get(Throwable.class);
private static final TypeId<Member> memberTypeId = TypeId.get(Member.class);
private static final TypeId<XC_MethodHook> callbackTypeId = TypeId.get(XC_MethodHook.class);
private static final TypeId<XposedBridge.AdditionalHookInfo> hookInfoTypeId
= TypeId.get(XposedBridge.AdditionalHookInfo.class);
private static final TypeId<XposedBridge.CopyOnWriteSortedSet> callbacksTypeId
= TypeId.get(XposedBridge.CopyOnWriteSortedSet.class);
private static final TypeId<XC_MethodHook.MethodHookParam> paramTypeId
= TypeId.get(XC_MethodHook.MethodHookParam.class);
private static final MethodId<XC_MethodHook.MethodHookParam, Void> setResultMethodId =
paramTypeId.getMethod(TypeId.VOID, "setResult", TypeId.OBJECT);
private static final MethodId<XC_MethodHook.MethodHookParam, Void> setThrowableMethodId =
paramTypeId.getMethod(TypeId.VOID, "setThrowable", throwableTypeId);
private static final MethodId<XC_MethodHook.MethodHookParam, Object> getResultMethodId =
paramTypeId.getMethod(TypeId.OBJECT, "getResult");
private static final MethodId<XC_MethodHook.MethodHookParam, Throwable> getThrowableMethodId =
paramTypeId.getMethod(throwableTypeId, "getThrowable");
private static final MethodId<XC_MethodHook.MethodHookParam, Boolean> hasThrowableMethodId =
paramTypeId.getMethod(TypeId.BOOLEAN, "hasThrowable");
private static final MethodId<XC_MethodHook, Void> callAfterCallbackMethodId =
callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_AFTER, paramTypeId);
private static final MethodId<XC_MethodHook, Void> callBeforeCallbackMethodId =
callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_BEFORE, paramTypeId);
private static final FieldId<XC_MethodHook.MethodHookParam, Boolean> returnEarlyFieldId =
paramTypeId.getField(TypeId.BOOLEAN, "returnEarly");
private static final TypeId<XposedBridge> xposedBridgeTypeId = TypeId.get(XposedBridge.class);
private static final MethodId<XposedBridge, Void> logThrowableMethodId =
xposedBridgeTypeId.getMethod(TypeId.VOID, "log", throwableTypeId);
private static final MethodId<XposedBridge, Void> logStrMethodId =
xposedBridgeTypeId.getMethod(TypeId.VOID, "log", TypeId.STRING);
private static AtomicLong sClassNameSuffix = new AtomicLong(1);
private FieldId<?, XposedBridge.AdditionalHookInfo> mHookInfoFieldId;
private FieldId<?, Member> mMethodFieldId;
private MethodId<?, ?> mBackupMethodId;
private MethodId<?, ?> mCallBackupMethodId;
private MethodId<?, ?> mHookMethodId;
private TypeId<?> mHookerTypeId;
private TypeId<?>[] mParameterTypeIds;
private Class<?>[] mActualParameterTypes;
private Class<?> mReturnType;
private TypeId<?> mReturnTypeId;
private boolean mIsStatic;
// TODO use this to generate methods
private boolean mHasThrowable;
private DexMaker mDexMaker;
private Member mMember;
private XposedBridge.AdditionalHookInfo mHookInfo;
private ClassLoader mAppClassLoader;
private Class<?> mHookClass;
private Method mHookMethod;
private Method mBackupMethod;
private Method mCallBackupMethod;
private String mDexDirPath;
private static TypeId<?>[] getParameterTypeIds(Class<?>[] parameterTypes, boolean isStatic) {
int parameterSize = parameterTypes.length;
int targetParameterSize = isStatic ? parameterSize : parameterSize + 1;
TypeId<?>[] parameterTypeIds = new TypeId<?>[targetParameterSize];
int offset = 0;
if (!isStatic) {
parameterTypeIds[0] = TypeId.OBJECT;
offset = 1;
}
for (int i = 0; i < parameterTypes.length; i++) {
parameterTypeIds[i + offset] = TypeId.get(parameterTypes[i]);
}
return parameterTypeIds;
}
private static Class<?>[] getParameterTypes(Class<?>[] parameterTypes, boolean isStatic) {
if (isStatic) {
return parameterTypes;
}
int parameterSize = parameterTypes.length;
int targetParameterSize = parameterSize + 1;
Class<?>[] newParameterTypes = new Class<?>[targetParameterSize];
int offset = 1;
newParameterTypes[0] = Object.class;
System.arraycopy(parameterTypes, 0, newParameterTypes, offset, parameterTypes.length);
return newParameterTypes;
}
public void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
ClassLoader appClassLoader, String dexDirPath) throws Exception {
if (member instanceof Method) {
Method method = (Method) member;
mIsStatic = Modifier.isStatic(method.getModifiers());
mReturnType = method.getReturnType();
if (mReturnType.equals(Void.class) || mReturnType.equals(void.class)
|| mReturnType.isPrimitive()) {
mReturnTypeId = TypeId.get(mReturnType);
} else {
// all others fallback to plain Object for convenience
mReturnType = Object.class;
mReturnTypeId = TypeId.OBJECT;
}
mParameterTypeIds = getParameterTypeIds(method.getParameterTypes(), mIsStatic);
mActualParameterTypes = getParameterTypes(method.getParameterTypes(), mIsStatic);
mHasThrowable = method.getExceptionTypes().length > 0;
} else if (member instanceof Constructor) {
Constructor constructor = (Constructor) member;
mIsStatic = false;
mReturnType = void.class;
mReturnTypeId = TypeId.VOID;
mParameterTypeIds = getParameterTypeIds(constructor.getParameterTypes(), mIsStatic);
mActualParameterTypes = getParameterTypes(constructor.getParameterTypes(), mIsStatic);
mHasThrowable = constructor.getExceptionTypes().length > 0;
} else if (member.getDeclaringClass().isInterface()) {
throw new IllegalArgumentException("Cannot hook interfaces: " + member.toString());
} else if (Modifier.isAbstract(member.getModifiers())) {
throw new IllegalArgumentException("Cannot hook abstract methods: " + member.toString());
} else {
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + member.toString());
}
mMember = member;
mHookInfo = hookInfo;
mDexDirPath = dexDirPath;
if (appClassLoader == null
|| appClassLoader.getClass().getName().equals("java.lang.BootClassLoader")) {
mAppClassLoader = this.getClass().getClassLoader();
} else {
mAppClassLoader = appClassLoader;
}
doMake();
}
private void doMake() throws Exception {
mDexMaker = new DexMaker();
// Generate a Hooker class.
String className = CLASS_NAME_PREFIX + sClassNameSuffix.getAndIncrement();
String classDesc = CLASS_DESC_PREFIX + className + ";";
mHookerTypeId = TypeId.get(classDesc);
mDexMaker.declare(mHookerTypeId, className + ".generated", Modifier.PUBLIC, TypeId.OBJECT);
generateFields();
generateSetupMethod();
generateBackupMethod();
generateHookMethod();
generateCallBackupMethod();
ClassLoader loader;
if (TextUtils.isEmpty(mDexDirPath)) {
throw new IllegalArgumentException("dexDirPath should not be empty!!!");
}
// Create the dex file and load it.
loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(mDexDirPath));
mHookClass = loader.loadClass(className);
// Execute our newly-generated code in-process.
mHookClass.getMethod(METHOD_NAME_SETUP, Member.class, XposedBridge.AdditionalHookInfo.class)
.invoke(null, mMember, mHookInfo);
mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes);
mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes);
mCallBackupMethod = mHookClass.getMethod(METHOD_NAME_CALL_BACKUP, mActualParameterTypes);
//HookMain.backupAndHook(mMember, mHookMethod, mBackupMethod);
}
public Method getHookMethod() {
return mHookMethod;
}
public Method getBackupMethod() {
return mBackupMethod;
}
public Method getCallBackupMethod() {
return mCallBackupMethod;
}
public Class getHookClass() {
return mHookClass;
}
private void generateFields() {
mHookInfoFieldId = mHookerTypeId.getField(hookInfoTypeId, FIELD_NAME_HOOK_INFO);
mMethodFieldId = mHookerTypeId.getField(memberTypeId, FIELD_NAME_METHOD);
mDexMaker.declare(mHookInfoFieldId, Modifier.STATIC, null);
mDexMaker.declare(mMethodFieldId, Modifier.STATIC, null);
}
private void generateSetupMethod() {
MethodId<?, Void> setupMethodId = mHookerTypeId.getMethod(
TypeId.VOID, METHOD_NAME_SETUP, memberTypeId, hookInfoTypeId);
Code code = mDexMaker.declare(setupMethodId, Modifier.PUBLIC | Modifier.STATIC);
// init logic
// get parameters
Local<Member> method = code.getParameter(0, memberTypeId);
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.getParameter(1, hookInfoTypeId);
// save params to static
code.sput(mMethodFieldId, method);
code.sput(mHookInfoFieldId, hookInfo);
code.returnVoid();
}
private void generateBackupMethod() {
mBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_BACKUP, mParameterTypeIds);
Code code = mDexMaker.declare(mBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
Map<TypeId, Local> resultLocals = createResultLocals(code);
// do nothing
if (mReturnTypeId.equals(TypeId.VOID)) {
code.returnVoid();
} else {
// we have limited the returnType to primitives or Object, so this should be safe
code.returnValue(resultLocals.get(mReturnTypeId));
}
}
private void generateCallBackupMethod() {
mCallBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_CALL_BACKUP, mParameterTypeIds);
Code code = mDexMaker.declare(mCallBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
// just call backup and return its result
Local[] allArgsLocals = createParameterLocals(code);
Map<TypeId, Local> resultLocals = createResultLocals(code);
if (mReturnTypeId.equals(TypeId.VOID)) {
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
code.returnVoid();
} else {
Local result = resultLocals.get(mReturnTypeId);
code.invokeStatic(mBackupMethodId, result, allArgsLocals);
code.returnValue(result);
}
}
private void generateHookMethod() {
mHookMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_HOOK, mParameterTypeIds);
Code code = mDexMaker.declare(mHookMethodId, Modifier.PUBLIC | Modifier.STATIC);
// code starts
// prepare common labels
Label noHookReturn = new Label();
Label incrementAndCheckBefore = new Label();
Label tryBeforeCatch = new Label();
Label noExceptionBefore = new Label();
Label checkAndCallBackup = new Label();
Label beginCallBefore = new Label();
Label beginCallAfter = new Label();
Label tryOrigCatch = new Label();
Label noExceptionOrig = new Label();
Label tryAfterCatch = new Label();
Label decrementAndCheckAfter = new Label();
Label noBackupThrowable = new Label();
Label throwThrowable = new Label();
// prepare locals
Local<Boolean> disableHooks = code.newLocal(TypeId.BOOLEAN);
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.newLocal(hookInfoTypeId);
Local<XposedBridge.CopyOnWriteSortedSet> callbacks = code.newLocal(callbacksTypeId);
Local<Object[]> snapshot = code.newLocal(objArrayTypeId);
Local<Integer> snapshotLen = code.newLocal(TypeId.INT);
Local<Object> callbackObj = code.newLocal(TypeId.OBJECT);
Local<XC_MethodHook> callback = code.newLocal(callbackTypeId);
Local<Object> resultObj = code.newLocal(TypeId.OBJECT); // as a temp Local
Local<Integer> one = code.newLocal(TypeId.INT);
Local<Object> nullObj = code.newLocal(TypeId.OBJECT);
Local<Throwable> throwable = code.newLocal(throwableTypeId);
Local<XC_MethodHook.MethodHookParam> param = code.newLocal(paramTypeId);
Local<Member> method = code.newLocal(memberTypeId);
Local<Object> thisObject = code.newLocal(TypeId.OBJECT);
Local<Object[]> args = code.newLocal(objArrayTypeId);
Local<Boolean> returnEarly = code.newLocal(TypeId.BOOLEAN);
Local<Integer> actualParamSize = code.newLocal(TypeId.INT);
Local<Integer> argIndex = code.newLocal(TypeId.INT);
Local<Integer> beforeIdx = code.newLocal(TypeId.INT);
Local<Object> lastResult = code.newLocal(TypeId.OBJECT);
Local<Throwable> lastThrowable = code.newLocal(throwableTypeId);
Local<Boolean> hasThrowable = code.newLocal(TypeId.BOOLEAN);
Local[] allArgsLocals = createParameterLocals(code);
Map<TypeId, Local> resultLocals = createResultLocals(code);
code.loadConstant(args, null);
code.loadConstant(argIndex, 0);
code.loadConstant(one, 1);
code.loadConstant(snapshotLen, 0);
code.loadConstant(nullObj, null);
// check XposedBridge.disableHooks flag
FieldId<XposedBridge, Boolean> disableHooksField =
xposedBridgeTypeId.getField(TypeId.BOOLEAN, "disableHooks");
code.sget(disableHooksField, disableHooks);
// disableHooks == true => no hooking
code.compareZ(Comparison.NE, noHookReturn, disableHooks);
// check callbacks length
code.sget(mHookInfoFieldId, hookInfo);
code.iget(hookInfoTypeId.getField(callbacksTypeId, "callbacks"), callbacks, hookInfo);
code.invokeVirtual(callbacksTypeId.getMethod(objArrayTypeId, "getSnapshot"), snapshot, callbacks);
code.arrayLength(snapshotLen, snapshot);
// snapshotLen == 0 => no hooking
code.compareZ(Comparison.EQ, noHookReturn, snapshotLen);
// start hooking
// prepare hooking locals
int paramsSize = mParameterTypeIds.length;
int offset = 0;
// thisObject
if (mIsStatic) {
// thisObject = null
code.loadConstant(thisObject, null);
} else {
// thisObject = args[0]
offset = 1;
code.move(thisObject, allArgsLocals[0]);
}
// actual args (exclude thisObject if this is not a static method)
code.loadConstant(actualParamSize, paramsSize - offset);
code.newArray(args, actualParamSize);
for (int i = offset; i < paramsSize; i++) {
Local parameter = allArgsLocals[i];
// save parameter to resultObj as Object
autoBoxIfNecessary(code, resultObj, parameter);
code.loadConstant(argIndex, i - offset);
// save Object to args
code.aput(args, argIndex, resultObj);
}
// create param
code.newInstance(param, paramTypeId.getConstructor());
// set method, thisObject, args
code.sget(mMethodFieldId, method);
code.iput(paramTypeId.getField(memberTypeId, "method"), param, method);
code.iput(paramTypeId.getField(TypeId.OBJECT, "thisObject"), param, thisObject);
code.iput(paramTypeId.getField(objArrayTypeId, "args"), param, args);
// call beforeCallbacks
code.loadConstant(beforeIdx, 0);
code.mark(beginCallBefore);
// start of try
code.addCatchClause(throwableTypeId, tryBeforeCatch);
code.aget(callbackObj, snapshot, beforeIdx);
code.cast(callback, callbackObj);
code.invokeVirtual(callBeforeCallbackMethodId, null, callback, param);
code.jump(noExceptionBefore);
// end of try
code.removeCatchClause(throwableTypeId);
// start of catch
code.mark(tryBeforeCatch);
moveException(code, throwable);
code.invokeStatic(logThrowableMethodId, null, throwable);
code.invokeVirtual(setResultMethodId, null, param, nullObj);
code.loadConstant(returnEarly, false);
code.iput(returnEarlyFieldId, param, returnEarly);
code.jump(incrementAndCheckBefore);
// no exception when calling beforeCallbacks
code.mark(noExceptionBefore);
code.iget(returnEarlyFieldId, returnEarly, param);
// if returnEarly == false, continue
code.compareZ(Comparison.EQ, incrementAndCheckBefore, returnEarly);
// returnEarly == true, break
code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one);
code.jump(checkAndCallBackup);
// increment and check to continue
code.mark(incrementAndCheckBefore);
code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one);
code.compare(Comparison.LT, beginCallBefore, beforeIdx, snapshotLen);
// check and call backup
code.mark(checkAndCallBackup);
code.iget(returnEarlyFieldId, returnEarly, param);
// if returnEarly == true, go to call afterCallbacks directly
code.compareZ(Comparison.NE, noExceptionOrig, returnEarly);
// try to call backup
// try start
code.addCatchClause(throwableTypeId, tryOrigCatch);
// we have to load args[] to paramLocals
// because args[] may be changed in beforeHookedMethod
// should consider first param is thisObj if hooked method is not static
offset = mIsStatic ? 0 : 1;
for (int i = offset; i < allArgsLocals.length; i++) {
code.loadConstant(argIndex, i - offset);
code.aget(resultObj, args, argIndex);
autoUnboxIfNecessary(code, allArgsLocals[i], resultObj, resultLocals, true);
}
// get pre-created Local with a matching typeId
if (mReturnTypeId.equals(TypeId.VOID)) {
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
// TODO maybe keep preset result to do some magic?
code.invokeVirtual(setResultMethodId, null, param, nullObj);
} else {
Local returnedResult = resultLocals.get(mReturnTypeId);
code.invokeStatic(mBackupMethodId, returnedResult, allArgsLocals);
// save returnedResult to resultObj as a Object
autoBoxIfNecessary(code, resultObj, returnedResult);
// save resultObj to param
code.invokeVirtual(setResultMethodId, null, param, resultObj);
}
// go to call afterCallbacks
code.jump(noExceptionOrig);
// try end
code.removeCatchClause(throwableTypeId);
// catch
code.mark(tryOrigCatch);
moveException(code, throwable);
// exception occurred when calling backup, save throwable to param
code.invokeVirtual(setThrowableMethodId, null, param, throwable);
code.mark(noExceptionOrig);
code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one);
// call afterCallbacks
code.mark(beginCallAfter);
// save results of backup calling
code.invokeVirtual(getResultMethodId, lastResult, param);
code.invokeVirtual(getThrowableMethodId, lastThrowable, param);
// try start
code.addCatchClause(throwableTypeId, tryAfterCatch);
code.aget(callbackObj, snapshot, beforeIdx);
code.cast(callback, callbackObj);
code.invokeVirtual(callAfterCallbackMethodId, null, callback, param);
// all good, just continue
code.jump(decrementAndCheckAfter);
// try end
code.removeCatchClause(throwableTypeId);
// catch
code.mark(tryAfterCatch);
moveException(code, throwable);
code.invokeStatic(logThrowableMethodId, null, throwable);
// if lastThrowable == null, go to recover lastResult
code.compareZ(Comparison.EQ, noBackupThrowable, lastThrowable);
// lastThrowable != null, recover lastThrowable
code.invokeVirtual(setThrowableMethodId, null, param, lastThrowable);
// continue
code.jump(decrementAndCheckAfter);
code.mark(noBackupThrowable);
// recover lastResult and continue
code.invokeVirtual(setResultMethodId, null, param, lastResult);
// decrement and check continue
code.mark(decrementAndCheckAfter);
code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one);
code.compareZ(Comparison.GE, beginCallAfter, beforeIdx);
// callbacks end
// return
code.invokeVirtual(hasThrowableMethodId, hasThrowable, param);
// if hasThrowable, throw the throwable and return
code.compareZ(Comparison.NE, throwThrowable, hasThrowable);
// return getResult
if (mReturnTypeId.equals(TypeId.VOID)) {
code.returnVoid();
} else {
// getResult always return an Object, so save to resultObj
code.invokeVirtual(getResultMethodId, resultObj, param);
// have to unbox it if returnType is primitive
// casting Object
TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId);
Local matchObjLocal = resultLocals.get(objTypeId);
code.cast(matchObjLocal, resultObj);
// have to use matching typed Object(Integer, Double ...) to do unboxing
Local toReturn = resultLocals.get(mReturnTypeId);
autoUnboxIfNecessary(code, toReturn, matchObjLocal, resultLocals, true);
// return
code.returnValue(toReturn);
}
// throw throwable
code.mark(throwThrowable);
code.invokeVirtual(getThrowableMethodId, throwable, param);
code.throwValue(throwable);
// call backup and return
code.mark(noHookReturn);
if (mReturnTypeId.equals(TypeId.VOID)) {
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
code.returnVoid();
} else {
Local result = resultLocals.get(mReturnTypeId);
code.invokeStatic(mBackupMethodId, result, allArgsLocals);
code.returnValue(result);
}
}
private Local[] createParameterLocals(Code code) {
Local[] paramLocals = new Local[mParameterTypeIds.length];
for (int i = 0; i < mParameterTypeIds.length; i++) {
paramLocals[i] = code.getParameter(i, mParameterTypeIds[i]);
}
return paramLocals;
}
}
package de.robv.android.xposed;
import android.os.Environment;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Adler32;
import static de.robv.android.xposed.XposedHelpers.inputStreamToByteArray;
/**
* Helper class which can create a very simple .dex file, containing only a class definition
* with a super class (no methods, fields, ...).
*/
/*package*/ class DexCreator {
public static File DALVIK_CACHE = new File(Environment.getDataDirectory(), "dalvik-cache");
/** Returns the default dex file name for the class. */
public static File getDefaultFile(String childClz) {
return new File(DALVIK_CACHE, "xposed_" + childClz.substring(childClz.lastIndexOf('.') + 1) + ".dex");
}
/**
* Creates (or returns) the path to a dex file which defines the superclass of {@clz} as extending
* {@code realSuperClz}, which by itself must extend {@code topClz}.
*/
public static File ensure(String clz, Class<?> realSuperClz, Class<?> topClz) throws IOException {
if (!topClz.isAssignableFrom(realSuperClz)) {
throw new ClassCastException("Cannot initialize " + clz + " because " + realSuperClz + " does not extend " + topClz);
}
try {
return ensure("xposed.dummy." + clz + "SuperClass", realSuperClz);
} catch (IOException e) {
throw new IOException("Failed to create a superclass for " + clz, e);
}
}
/** Like {@link #ensure(File, String, String)}, just for the default dex file name. */
public static File ensure(String childClz, Class<?> superClz) throws IOException {
return ensure(getDefaultFile(childClz), childClz, superClz.getName());
}
/**
* Makes sure that the given file is a simple dex file containing the given classes.
* Creates the file if that's not the case.
*/
public static File ensure(File file, String childClz, String superClz) throws IOException {
// First check if a valid file exists.
try {
byte[] dex = inputStreamToByteArray(new FileInputStream(file));
if (matches(dex, childClz, superClz)) {
return file;
} else {
file.delete();
}
} catch (IOException e) {
file.delete();
}
// If not, create a new dex file.
byte[] dex = create(childClz, superClz);
FileOutputStream fos = new FileOutputStream(file);
fos.write(dex);
fos.close();
return file;
}
/**
* Checks whether the Dex file fits to the class names.
* Assumes that the file has been created with this class.
*/
public static boolean matches(byte[] dex, String childClz, String superClz) throws IOException {
boolean childFirst = childClz.compareTo(superClz) < 0;
byte[] childBytes = stringToBytes("L" + childClz.replace('.', '/') + ";");
byte[] superBytes = stringToBytes("L" + superClz.replace('.', '/') + ";");
int pos = 0xa0;
if (pos + childBytes.length + superBytes.length >= dex.length) {
return false;
}
for (byte b : childFirst ? childBytes : superBytes) {
if (dex[pos++] != b) {
return false;
}
}
for (byte b : childFirst ? superBytes: childBytes) {
if (dex[pos++] != b) {
return false;
}
}
return true;
}
/** Creates the byte array for the dex file. */
public static byte[] create(String childClz, String superClz) throws IOException {
boolean childFirst = childClz.compareTo(superClz) < 0;
byte[] childBytes = stringToBytes("L" + childClz.replace('.', '/') + ";");
byte[] superBytes = stringToBytes("L" + superClz.replace('.', '/') + ";");
int stringsSize = childBytes.length + superBytes.length;
int padding = -stringsSize & 3;
stringsSize += padding;
ByteArrayOutputStream out = new ByteArrayOutputStream();
// header
out.write("dex\n035\0".getBytes()); // magic
out.write(new byte[24]); // placeholder for checksum and signature
writeInt(out, 0xfc + stringsSize); // file size
writeInt(out, 0x70); // header size
writeInt(out, 0x12345678); // endian constant
writeInt(out, 0); // link size
writeInt(out, 0); // link offset
writeInt(out, 0xa4 + stringsSize); // map offset
writeInt(out, 2); // strings count
writeInt(out, 0x70); // strings offset
writeInt(out, 2); // types count
writeInt(out, 0x78); // types offset
writeInt(out, 0); // prototypes count
writeInt(out, 0); // prototypes offset
writeInt(out, 0); // fields count
writeInt(out, 0); // fields offset
writeInt(out, 0); // methods count
writeInt(out, 0); // methods offset
writeInt(out, 1); // classes count
writeInt(out, 0x80); // classes offset
writeInt(out, 0x5c + stringsSize); // data size
writeInt(out, 0xa0); // data offset
// string map
writeInt(out, 0xa0);
writeInt(out, 0xa0 + (childFirst ? childBytes.length : superBytes.length));
// types
writeInt(out, 0); // first type = first string
writeInt(out, 1); // second type = second string
// class definitions
writeInt(out, childFirst ? 0 : 1); // class to define = child type
writeInt(out, 1); // access flags = public
writeInt(out, childFirst ? 1 : 0); // super class = super type
writeInt(out, 0); // no interface
writeInt(out, -1); // no source file
writeInt(out, 0); // no annotations
writeInt(out, 0); // no class data
writeInt(out, 0); // no static values
// string data
out.write(childFirst ? childBytes : superBytes);
out.write(childFirst ? superBytes : childBytes);
out.write(new byte[padding]);
// annotations
writeInt(out, 0); // no items
// map
writeInt(out, 7); // items count
writeMapItem(out, 0, 1, 0); // header
writeMapItem(out, 1, 2, 0x70); // strings
writeMapItem(out, 2, 2, 0x78); // types
writeMapItem(out, 6, 1, 0x80); // classes
writeMapItem(out, 0x2002, 2, 0xa0); // string data
writeMapItem(out, 0x1003, 1, 0xa0 + stringsSize); // annotations
writeMapItem(out, 0x1000, 1, 0xa4 + stringsSize); // map list
byte[] buf = out.toByteArray();
updateSignature(buf);
updateChecksum(buf);
return buf;
}
private static void updateSignature(byte[] dex) {
// Update SHA-1 signature
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(dex, 32, dex.length - 32);
md.digest(dex, 12, 20);
} catch (NoSuchAlgorithmException | DigestException e) {
throw new RuntimeException(e);
}
}
private static void updateChecksum(byte[] dex) {
// Update Adler32 checksum
Adler32 a32 = new Adler32();
a32.update(dex, 12, dex.length - 12);
int chksum = (int) a32.getValue();
dex[8] = (byte) (chksum & 0xff);
dex[9] = (byte) (chksum >> 8 & 0xff);
dex[10] = (byte) (chksum >> 16 & 0xff);
dex[11] = (byte) (chksum >> 24 & 0xff);
}
private static void writeUleb128(OutputStream out, int value) throws IOException {
while (value > 0x7f) {
out.write((value & 0x7f) | 0x80);
value >>>= 7;
}
out.write(value);
}
private static void writeInt(OutputStream out, int value) throws IOException {
out.write(value);
out.write(value >> 8);
out.write(value >> 16);
out.write(value >> 24);
}
private static void writeMapItem(OutputStream out, int type, int count, int offset) throws IOException {
writeInt(out, type);
writeInt(out, count);
writeInt(out, offset);
}
private static byte[] stringToBytes(String s) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
writeUleb128(bytes, s.length());
// This isn't MUTF-8, but should be OK.
bytes.write(s.getBytes("UTF-8"));
bytes.write(0);
return bytes.toByteArray();
}
private DexCreator() {}
}
package de.robv.android.xposed;
public class GeneClass_Template {
public static java.lang.reflect.Member method;
public static de.robv.android.xposed.XposedBridge.AdditionalHookInfo tAdditionalInfoObj;
public static boolean backup(Object obj, int i) {
return false;
}
public static boolean hook(Object obj, int i) throws Throwable {
Throwable th;
if (!de.robv.android.xposed.XposedBridge.disableHooks) {
Object[] snapshot = tAdditionalInfoObj.callbacks.getSnapshot();
int length = snapshot.length;
if (length != 0) {
de.robv.android.xposed.XC_MethodHook.MethodHookParam methodHookParam = new de.robv.android.xposed.XC_MethodHook.MethodHookParam();
methodHookParam.method = method;
Object[] objArr = new Object[1];
methodHookParam.args = objArr;
methodHookParam.thisObject = obj;
objArr[0] = Integer.valueOf(i);
int i2 = 0;
do {
try {
((de.robv.android.xposed.XC_MethodHook) snapshot[i2]).callBeforeHookedMethod(methodHookParam);
if (methodHookParam.returnEarly) {
i2++;
break;
}
} catch (Throwable th2) {
de.robv.android.xposed.XposedBridge.log(th2);
methodHookParam.setResult(null);
methodHookParam.returnEarly = false;
}
i2++;
} while (i2 < length);
if (!methodHookParam.returnEarly) {
try {
methodHookParam.setResult(Boolean.valueOf(backup(obj, i)));
} catch (Throwable th3) {
methodHookParam.setThrowable(th3);
}
}
i2--;
do {
Object result = methodHookParam.getResult();
Throwable th2 = methodHookParam.getThrowable();
try {
((de.robv.android.xposed.XC_MethodHook) snapshot[i2]).callAfterHookedMethod(methodHookParam);
} catch (Throwable th4) {
de.robv.android.xposed.XposedBridge.log(th4);
if (th2 == null) {
methodHookParam.setResult(result);
} else {
methodHookParam.setThrowable(th2);
}
}
i2--;
} while (i2 >= 0);
if (!methodHookParam.hasThrowable()) {
return ((Boolean) methodHookParam.getResult()).booleanValue();
}
throw methodHookParam.getThrowable();
}
}
return backup(obj, i);
}
public static void setup(java.lang.reflect.Member member, de.robv.android.xposed.XposedBridge.AdditionalHookInfo additionalHookInfo) {
method = member;
tAdditionalInfoObj = additionalHookInfo;
}
}
\ No newline at end of file
package de.robv.android.xposed;
/**
* Hook the initialization of Java-based command-line tools (like pm).
*
* @hide Xposed no longer hooks command-line tools, therefore this interface shouldn't be
* implemented anymore.
*/
public interface IXposedHookCmdInit extends IXposedMod {
/**
* Called very early during startup of a command-line tool.
* @param startupParam Details about the module itself and the started process.
* @throws Throwable Everything is caught, but it will prevent further initialization of the module.
*/
void initCmdApp(StartupParam startupParam) throws Throwable;
/** Data holder for {@link #initCmdApp}. */
final class StartupParam {
/*package*/ StartupParam() {}
/** The path to the module's APK. */
public String modulePath;
/** The class name of the tools that the hook was invoked for. */
public String startClassName;
}
}
package de.robv.android.xposed;
import android.content.res.XResources;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam;
/**
* Get notified when the resources for an app are initialized.
* In {@link #handleInitPackageResources}, resource replacements can be created.
*
* <p>This interface should be implemented by the module's main class. Xposed will take care of
* registering it as a callback automatically.
*/
public interface IXposedHookInitPackageResources extends IXposedMod {
/**
* This method is called when resources for an app are being initialized.
* Modules can call special methods of the {@link XResources} class in order to replace resources.
*
* @param resparam Information about the resources.
* @throws Throwable Everything the callback throws is caught and logged.
*/
void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable;
/** @hide */
final class Wrapper extends XC_InitPackageResources {
private final IXposedHookInitPackageResources instance;
public Wrapper(IXposedHookInitPackageResources instance) {
this.instance = instance;
}
@Override
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
instance.handleInitPackageResources(resparam);
}
}
}
package de.robv.android.xposed;
import android.app.Application;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;
/**
* Get notified when an app ("Android package") is loaded.
* This is especially useful to hook some app-specific methods.
*
* <p>This interface should be implemented by the module's main class. Xposed will take care of
* registering it as a callback automatically.
*/
public interface IXposedHookLoadPackage extends IXposedMod {
/**
* This method is called when an app is loaded. It's called very early, even before
* {@link Application#onCreate} is called.
* Modules can set up their app-specific hooks here.
*
* @param lpparam Information about the app.
* @throws Throwable Everything the callback throws is caught and logged.
*/
void handleLoadPackage(LoadPackageParam lpparam) throws Throwable;
/** @hide */
final class Wrapper extends XC_LoadPackage {
private final IXposedHookLoadPackage instance;
public Wrapper(IXposedHookLoadPackage instance) {
this.instance = instance;
}
@Override
public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
instance.handleLoadPackage(lpparam);
}
}
}
package de.robv.android.xposed;
/**
* Hook the initialization of Zygote process(es), from which all the apps are forked.
*
* <p>Implement this interface in your module's main class in order to be notified when Android is
* starting up. In {@link IXposedHookZygoteInit}, you can modify objects and place hooks that should
* be applied for every app. Only the Android framework/system classes are available at that point
* in time. Use {@code null} as class loader for {@link XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)}
* and its variants.
*
* <p>If you want to hook one/multiple specific apps, use {@link IXposedHookLoadPackage} instead.
*/
public interface IXposedHookZygoteInit extends IXposedMod {
/**
* Called very early during startup of Zygote.
* @param startupParam Details about the module itself and the started process.
* @throws Throwable everything is caught, but will prevent further initialization of the module.
*/
void initZygote(StartupParam startupParam) throws Throwable;
/** Data holder for {@link #initZygote}. */
final class StartupParam {
/*package*/ StartupParam() {}
/** The path to the module's APK. */
public String modulePath;
/**
* Always {@code true} on 32-bit ROMs. On 64-bit, it's only {@code true} for the primary
* process that starts the system_server.
*/
public boolean startsSystemServer;
}
}
package de.robv.android.xposed;
/** Marker interface for Xposed modules. Cannot be implemented directly. */
/* package */ interface IXposedMod {}
package de.robv.android.xposed;
import android.os.SELinux;
import de.robv.android.xposed.services.BaseService;
import de.robv.android.xposed.services.DirectAccessService;
/**
* A helper to work with (or without) SELinux, abstracting much of its big complexity.
*/
public final class SELinuxHelper {
private SELinuxHelper() {}
/**
* Determines whether SELinux is disabled or enabled.
*
* @return A boolean indicating whether SELinux is enabled.
*/
public static boolean isSELinuxEnabled() {
return sIsSELinuxEnabled;
}
/**
* Determines whether SELinux is permissive or enforcing.
*
* @return A boolean indicating whether SELinux is enforcing.
*/
public static boolean isSELinuxEnforced() {
return sIsSELinuxEnabled && SELinux.isSELinuxEnforced();
}
/**
* Gets the security context of the current process.
*
* @return A String representing the security context of the current process.
*/
public static String getContext() {
return sIsSELinuxEnabled ? SELinux.getContext() : null;
}
/**
* Retrieve the service to be used when accessing files in {@code /data/data/*}.
*
* <p class="caution"><strong>IMPORTANT:</strong> If you call this from the Zygote process,
* don't re-use the result in different process!
*
* @return An instance of the service.
*/
public static BaseService getAppDataFileService() {
if (sServiceAppDataFile != null)
return sServiceAppDataFile;
throw new UnsupportedOperationException();
}
// ----------------------------------------------------------------------------
private static boolean sIsSELinuxEnabled = false;
private static BaseService sServiceAppDataFile = new DirectAccessService(); // ed: initialized directly
/*package*/ static void initOnce() {
// ed: we assume all selinux policies have been added lively using magiskpolicy
// try {
// sIsSELinuxEnabled = SELinux.isSELinuxEnabled();
// } catch (NoClassDefFoundError ignored) {}
}
/*package*/ static void initForProcess(String packageName) {
// ed: sServiceAppDataFile has been initialized with default value
// if (sIsSELinuxEnabled) {
// if (packageName == null) { // Zygote
// sServiceAppDataFile = new ZygoteService();
// } else if (packageName.equals("android")) { //system_server
// sServiceAppDataFile = BinderService.getService(BinderService.TARGET_APP);
// } else { // app
// sServiceAppDataFile = new DirectAccessService();
// }
// } else {
// sServiceAppDataFile = new DirectAccessService();
// }
}
}
package de.robv.android.xposed;
import java.lang.reflect.Member;
import de.robv.android.xposed.callbacks.IXUnhook;
import de.robv.android.xposed.callbacks.XCallback;
/**
* Callback class for method hooks.
*
* <p>Usually, anonymous subclasses of this class are created which override
* {@link #beforeHookedMethod} and/or {@link #afterHookedMethod}.
*/
public abstract class XC_MethodHook extends XCallback {
/**
* Creates a new callback with default priority.
*/
@SuppressWarnings("deprecation")
public XC_MethodHook() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* <p class="note">Note that {@link #afterHookedMethod} will be called in reversed order, i.e.
* the callback with the highest priority will be called last. This way, the callback has the
* final control over the return value. {@link #beforeHookedMethod} is called as usual, i.e.
* highest priority first.
*
* @param priority See {@link XCallback#priority}.
*/
public XC_MethodHook(int priority) {
super(priority);
}
/**
* Called before the invocation of the method.
*
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
* to prevent the original method from being called.
*
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
*
* @param param Information about the method call.
* @throws Throwable Everything the callback throws is caught and logged.
*/
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {}
public void callBeforeHookedMethod(MethodHookParam param) throws Throwable {
beforeHookedMethod(param);
}
/**
* Called after the invocation of the method.
*
* <p>You can use {@link MethodHookParam#setResult} and {@link MethodHookParam#setThrowable}
* to modify the return value of the original method.
*
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
*
* @param param Information about the method call.
* @throws Throwable Everything the callback throws is caught and logged.
*/
protected void afterHookedMethod(MethodHookParam param) throws Throwable {}
public void callAfterHookedMethod(MethodHookParam param) throws Throwable {
afterHookedMethod(param);
}
/**
* Wraps information about the method call and allows to influence it.
*/
public static final class MethodHookParam extends XCallback.Param {
/** @hide */
@SuppressWarnings("deprecation")
public MethodHookParam() {
super();
}
/** The hooked method/constructor. */
public Member method;
/** The {@code this} reference for an instance method, or {@code null} for static methods. */
public Object thisObject;
/** Arguments to the method call. */
public Object[] args;
private Object result = null;
private Throwable throwable = null;
public boolean returnEarly = false;
/** Returns the result of the method call. */
public Object getResult() {
return result;
}
/**
* Modify the result of the method call.
*
* <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
*/
public void setResult(Object result) {
this.result = result;
this.throwable = null;
this.returnEarly = true;
}
/** Returns the {@link Throwable} thrown by the method, or {@code null}. */
public Throwable getThrowable() {
return throwable;
}
/** Returns true if an exception was thrown by the method. */
public boolean hasThrowable() {
return throwable != null;
}
/**
* Modify the exception thrown of the method call.
*
* <p>If called from {@link #beforeHookedMethod}, it prevents the call to the original method.
*/
public void setThrowable(Throwable throwable) {
this.throwable = throwable;
this.result = null;
this.returnEarly = true;
}
/** Returns the result of the method call, or throws the Throwable caused by it. */
public Object getResultOrThrowable() throws Throwable {
if (throwable != null)
throw throwable;
return result;
}
}
/**
* An object with which the method/constructor can be unhooked.
*/
public class Unhook implements IXUnhook<XC_MethodHook> {
private final Member hookMethod;
/*package*/ Unhook(Member hookMethod) {
this.hookMethod = hookMethod;
}
/**
* Returns the method/constructor that has been hooked.
*/
public Member getHookedMethod() {
return hookMethod;
}
@Override
public XC_MethodHook getCallback() {
return XC_MethodHook.this;
}
@SuppressWarnings("deprecation")
@Override
public void unhook() {
XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this);
}
}
}
package de.robv.android.xposed;
import de.robv.android.xposed.callbacks.XCallback;
/**
* A special case of {@link XC_MethodHook} which completely replaces the original method.
*/
public abstract class XC_MethodReplacement extends XC_MethodHook {
/**
* Creates a new callback with default priority.
*/
public XC_MethodReplacement() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link XCallback#priority}.
*/
public XC_MethodReplacement(int priority) {
super(priority);
}
/** @hide */
@Override
protected final void beforeHookedMethod(MethodHookParam param) throws Throwable {
try {
Object result = replaceHookedMethod(param);
param.setResult(result);
} catch (Throwable t) {
param.setThrowable(t);
}
}
/** @hide */
@Override
@SuppressWarnings("EmptyMethod")
protected final void afterHookedMethod(MethodHookParam param) throws Throwable {}
/**
* Shortcut for replacing a method completely. Whatever is returned/thrown here is taken
* instead of the result of the original method (which will not be called).
*
* <p>Note that implementations shouldn't call {@code super(param)}, it's not necessary.
*
* @param param Information about the method call.
* @throws Throwable Anything that is thrown by the callback will be passed on to the original caller.
*/
@SuppressWarnings("UnusedParameters")
protected abstract Object replaceHookedMethod(MethodHookParam param) throws Throwable;
/**
* Predefined callback that skips the method without replacements.
*/
public static final XC_MethodReplacement DO_NOTHING = new XC_MethodReplacement(PRIORITY_HIGHEST*2) {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return null;
}
};
/**
* Creates a callback which always returns a specific value.
*
* @param result The value that should be returned to callers of the hooked method.
*/
public static XC_MethodReplacement returnConstant(final Object result) {
return returnConstant(PRIORITY_DEFAULT, result);
}
/**
* Like {@link #returnConstant(Object)}, but allows to specify a priority for the callback.
*
* @param priority See {@link XCallback#priority}.
* @param result The value that should be returned to callers of the hooked method.
*/
public static XC_MethodReplacement returnConstant(int priority, final Object result) {
return new XC_MethodReplacement(priority) {
@Override
protected Object replaceHookedMethod(MethodHookParam param) throws Throwable {
return result;
}
};
}
}
package de.robv.android.xposed;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Environment;
import android.preference.PreferenceManager;
import android.util.Log;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParserException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import de.robv.android.xposed.services.FileResult;
/**
* This class is basically the same as SharedPreferencesImpl from AOSP, but
* read-only and without listeners support. Instead, it is made to be
* compatible with all ROMs.
*/
public final class XSharedPreferences implements SharedPreferences {
private static final String TAG = "XSharedPreferences";
private final File mFile;
private final String mFilename;
private Map<String, Object> mMap;
private boolean mLoaded = false;
private long mLastModified;
private long mFileSize;
/**
* Read settings from the specified file.
* @param prefFile The file to read the preferences from.
*/
public XSharedPreferences(File prefFile) {
mFile = prefFile;
mFilename = mFile.getAbsolutePath();
startLoadFromDisk();
}
/**
* Read settings from the default preferences for a package.
* These preferences are returned by {@link PreferenceManager#getDefaultSharedPreferences}.
* @param packageName The package name.
*/
public XSharedPreferences(String packageName) {
this(packageName, packageName + "_preferences");
}
/**
* Read settings from a custom preferences file for a package.
* These preferences are returned by {@link Context#getSharedPreferences(String, int)}.
* @param packageName The package name.
* @param prefFileName The file name without ".xml".
*/
public XSharedPreferences(String packageName, String prefFileName) {
mFile = new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/" + prefFileName + ".xml");
mFilename = mFile.getAbsolutePath();
startLoadFromDisk();
}
/**
* Tries to make the preferences file world-readable.
*
* <p><strong>Warning:</strong> This is only meant to work around permission "fix" functions that are part
* of some recoveries. It doesn't replace the need to open preferences with {@code MODE_WORLD_READABLE}
* in the module's UI code. Otherwise, Android will set stricter permissions again during the next save.
*
* <p>This will only work if executed as root (e.g. {@code initZygote()}) and only if SELinux is disabled.
*
* @return {@code true} in case the file could be made world-readable.
*/
@SuppressLint("SetWorldReadable")
public boolean makeWorldReadable() {
if (!SELinuxHelper.getAppDataFileService().hasDirectFileAccess())
return false; // It doesn't make much sense to make the file readable if we wouldn't be able to access it anyway.
if (!mFile.exists()) // Just in case - the file should never be created if it doesn't exist.
return false;
return mFile.setReadable(true, false);
}
/**
* Returns the file that is backing these preferences.
*
* <p><strong>Warning:</strong> The file might not be accessible directly.
*/
public File getFile() {
return mFile;
}
private void startLoadFromDisk() {
synchronized (this) {
mLoaded = false;
}
new Thread("XSharedPreferences-load") {
@Override
public void run() {
synchronized (XSharedPreferences.this) {
loadFromDiskLocked();
}
}
}.start();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void loadFromDiskLocked() {
if (mLoaded) {
return;
}
Map map = null;
FileResult result = null;
try {
result = SELinuxHelper.getAppDataFileService().getFileInputStream(mFilename, mFileSize, mLastModified);
if (result.stream != null) {
map = XmlUtils.readMapXml(result.stream);
result.stream.close();
} else {
// The file is unchanged, keep the current values
map = mMap;
}
} catch (XmlPullParserException e) {
Log.w(TAG, "getSharedPreferences", e);
} catch (FileNotFoundException ignored) {
// SharedPreferencesImpl has a canRead() check, so it doesn't log anything in case the file doesn't exist
} catch (IOException e) {
Log.w(TAG, "getSharedPreferences", e);
} finally {
if (result != null && result.stream != null) {
try {
result.stream.close();
} catch (RuntimeException rethrown) {
throw rethrown;
} catch (Exception ignored) {
}
}
}
mLoaded = true;
if (map != null) {
mMap = map;
mLastModified = result.mtime;
mFileSize = result.size;
} else {
mMap = new HashMap<>();
}
notifyAll();
}
/**
* Reload the settings from file if they have changed.
*
* <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive.
*/
public synchronized void reload() {
if (hasFileChanged())
startLoadFromDisk();
}
/**
* Check whether the file has changed since the last time it has been loaded.
*
* <p><strong>Warning:</strong> With enforcing SELinux, this call might be quite expensive.
*/
public synchronized boolean hasFileChanged() {
try {
FileResult result = SELinuxHelper.getAppDataFileService().statFile(mFilename);
return mLastModified != result.mtime || mFileSize != result.size;
} catch (FileNotFoundException ignored) {
// SharedPreferencesImpl doesn't log anything in case the file doesn't exist
return true;
} catch (IOException e) {
Log.w(TAG, "hasFileChanged", e);
return true;
}
}
private void awaitLoadedLocked() {
while (!mLoaded) {
try {
wait();
} catch (InterruptedException unused) {
}
}
}
/** @hide */
@Override
public Map<String, ?> getAll() {
synchronized (this) {
awaitLoadedLocked();
return new HashMap<>(mMap);
}
}
/** @hide */
@Override
public String getString(String key, String defValue) {
synchronized (this) {
awaitLoadedLocked();
String v = (String)mMap.get(key);
return v != null ? v : defValue;
}
}
/** @hide */
@Override
@SuppressWarnings("unchecked")
public Set<String> getStringSet(String key, Set<String> defValues) {
synchronized (this) {
awaitLoadedLocked();
Set<String> v = (Set<String>) mMap.get(key);
return v != null ? v : defValues;
}
}
/** @hide */
@Override
public int getInt(String key, int defValue) {
synchronized (this) {
awaitLoadedLocked();
Integer v = (Integer)mMap.get(key);
return v != null ? v : defValue;
}
}
/** @hide */
@Override
public long getLong(String key, long defValue) {
synchronized (this) {
awaitLoadedLocked();
Long v = (Long)mMap.get(key);
return v != null ? v : defValue;
}
}
/** @hide */
@Override
public float getFloat(String key, float defValue) {
synchronized (this) {
awaitLoadedLocked();
Float v = (Float)mMap.get(key);
return v != null ? v : defValue;
}
}
/** @hide */
@Override
public boolean getBoolean(String key, boolean defValue) {
synchronized (this) {
awaitLoadedLocked();
Boolean v = (Boolean)mMap.get(key);
return v != null ? v : defValue;
}
}
/** @hide */
@Override
public boolean contains(String key) {
synchronized (this) {
awaitLoadedLocked();
return mMap.containsKey(key);
}
}
/** @deprecated Not supported by this implementation. */
@Deprecated
@Override
public Editor edit() {
throw new UnsupportedOperationException("read-only implementation");
}
/** @deprecated Not supported by this implementation. */
@Deprecated
@Override
public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException("listeners are not supported in this implementation");
}
/** @deprecated Not supported by this implementation. */
@Deprecated
@Override
public void unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
throw new UnsupportedOperationException("listeners are not supported in this implementation");
}
}
package de.robv.android.xposed;
import android.annotation.SuppressLint;
import android.util.Log;
import com.elderdrivers.riru.xposed.core.HookMain;
import com.elderdrivers.riru.xposed.dexmaker.DynamicBridge;
import com.elderdrivers.riru.xposed.dexmaker.MethodInfo;
import com.elderdrivers.riru.xposed.util.MethodHookUtils;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
import static de.robv.android.xposed.XposedHelpers.getIntField;
/**
* This class contains most of Xposed's central logic, such as initialization and callbacks used by
* the native side. It also includes methods to add new hooks.
*/
@SuppressWarnings("JniMissingFunction")
public final class XposedBridge {
/**
* The system class loader which can be used to locate Android framework classes.
* Application classes cannot be retrieved from it.
*
* @see ClassLoader#getSystemClassLoader
*/
public static final ClassLoader BOOTCLASSLOADER = XposedBridge.class.getClassLoader();
/** @hide */
public static final String TAG = "EdXposed-Bridge";
/** @deprecated Use {@link #getXposedVersion()} instead. */
@Deprecated
public static int XPOSED_BRIDGE_VERSION;
/*package*/ static boolean isZygote = true; // ed: RuntimeInit.main() tool process not supported yet
private static int runtime = 2; // ed: only support art
private static final int RUNTIME_DALVIK = 1;
private static final int RUNTIME_ART = 2;
public static boolean disableHooks = false;
// This field is set "magically" on MIUI.
/*package*/ static long BOOT_START_TIME;
private static final Object[] EMPTY_ARRAY = new Object[0];
// built-in handlers
private static final Map<Member, CopyOnWriteSortedSet<XC_MethodHook>> sHookedMethodCallbacks = new HashMap<>();
public static final CopyOnWriteSortedSet<XC_LoadPackage> sLoadedPackageCallbacks = new CopyOnWriteSortedSet<>();
/*package*/ static final CopyOnWriteSortedSet<XC_InitPackageResources> sInitPackageResourcesCallbacks = new CopyOnWriteSortedSet<>();
private XposedBridge() {}
/**
* Called when native methods and other things are initialized, but before preloading classes etc.
* @hide
*/
@SuppressWarnings("deprecation")
public static void main(String[] args) {
// ed: moved
}
/** @hide */
// protected static final class ToolEntryPoint {
// protected static void main(String[] args) {
// isZygote = false;
// XposedBridge.main(args);
// }
// }
private static void initXResources() throws IOException {
// ed: no support for now
}
@SuppressLint("SetWorldReadable")
private static File ensureSuperDexFile(String clz, Class<?> realSuperClz, Class<?> topClz) throws IOException {
XposedBridge.removeFinalFlagNative(realSuperClz);
File dexFile = DexCreator.ensure(clz, realSuperClz, topClz);
dexFile.setReadable(true, false);
return dexFile;
}
// private static boolean hadInitErrors() {
// // ed: assuming never had errors
// return false;
// }
// private static native int getRuntime();
// /*package*/ static native boolean startsSystemServer();
// /*package*/ static native String getStartClassName();
// /*package*/ native static boolean initXResourcesNative();
/**
* Returns the currently installed version of the Xposed framework.
*/
public static int getXposedVersion() {
// ed: fixed value for now
return 90;
}
/**
* Writes a message to the Xposed error log.
*
* <p class="warning"><b>DON'T FLOOD THE LOG!!!</b> This is only meant for error logging.
* If you want to write information/debug messages, use logcat.
*
* @param text The log message.
*/
public synchronized static void log(String text) {
Log.i(TAG, text);
}
/**
* Logs a stack trace to the Xposed error log.
*
* <p class="warning"><b>DON'T FLOOD THE LOG!!!</b> This is only meant for error logging.
* If you want to write information/debug messages, use logcat.
*
* @param t The Throwable object for the stack trace.
*/
public synchronized static void log(Throwable t) {
Log.e(TAG, Log.getStackTraceString(t));
}
/**
* Hook any method (or constructor) with the specified callback. See below for some wrappers
* that make it easier to find a method/constructor in one step.
*
* @param hookMethod The method to be hooked.
* @param callback The callback to be executed when the hooked method is called.
* @return An object that can be used to remove the hook.
*
* @see XposedHelpers#findAndHookMethod(String, ClassLoader, String, Object...)
* @see XposedHelpers#findAndHookMethod(Class, String, Object...)
* @see #hookAllMethods
* @see XposedHelpers#findAndHookConstructor(String, ClassLoader, Object...)
* @see XposedHelpers#findAndHookConstructor(Class, Object...)
* @see #hookAllConstructors
*/
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
hookMethod = MethodHookUtils.preCheck(hookMethod);
if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) {
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
} else if (hookMethod.getDeclaringClass().isInterface()) {
throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
} else if (Modifier.isAbstract(hookMethod.getModifiers())) {
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
}
if (callback == null) {
throw new IllegalArgumentException("callback should not be null!");
}
boolean newMethod = false;
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
synchronized (sHookedMethodCallbacks) {
callbacks = sHookedMethodCallbacks.get(hookMethod);
if (callbacks == null) {
callbacks = new CopyOnWriteSortedSet<>();
sHookedMethodCallbacks.put(hookMethod, callbacks);
newMethod = true;
}
}
callbacks.add(callback);
if (newMethod) {
Class<?> declaringClass = hookMethod.getDeclaringClass();
int slot;
Class<?>[] parameterTypes;
Class<?> returnType;
if (runtime == RUNTIME_ART) {
slot = 0;
parameterTypes = null;
returnType = null;
} else if (hookMethod instanceof Method) {
slot = getIntField(hookMethod, "slot");
parameterTypes = ((Method) hookMethod).getParameterTypes();
returnType = ((Method) hookMethod).getReturnType();
} else {
slot = getIntField(hookMethod, "slot");
parameterTypes = ((Constructor<?>) hookMethod).getParameterTypes();
returnType = null;
}
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks, parameterTypes, returnType);
MethodInfo methodInfo = new MethodInfo(hookMethod);
declaringClass = methodInfo.getClassForSure();
Member reflectMethod = (Member) HookMain.findMethod(
declaringClass, methodInfo.methodName, methodInfo.methodSig);
if (reflectMethod == null) {
Log.e(TAG, "method not found: name="
+ methodInfo.methodName + ", sig=" + methodInfo.methodSig);
reflectMethod = hookMethod;
}
hookMethodNative(reflectMethod, declaringClass, slot, additionalInfo);
}
return callback.new Unhook(hookMethod);
}
/**
* Removes the callback for a hooked method/constructor.
*
* @deprecated Use {@link XC_MethodHook.Unhook#unhook} instead. An instance of the {@code Unhook}
* class is returned when you hook the method.
*
* @param hookMethod The method for which the callback should be removed.
* @param callback The reference to the callback as specified in {@link #hookMethod}.
*/
@Deprecated
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
synchronized (sHookedMethodCallbacks) {
callbacks = sHookedMethodCallbacks.get(hookMethod);
if (callbacks == null)
return;
}
callbacks.remove(callback);
}
/**
* Hooks all methods with a certain name that were declared in the specified class. Inherited
* methods and constructors are not considered. For constructors, use
* {@link #hookAllConstructors} instead.
*
* @param hookClass The class to check for declared methods.
* @param methodName The name of the method(s) to hook.
* @param callback The callback to be executed when the hooked methods are called.
* @return A set containing one object for each found method which can be used to unhook it.
*/
@SuppressWarnings("UnusedReturnValue")
public static Set<XC_MethodHook.Unhook> hookAllMethods(Class<?> hookClass, String methodName, XC_MethodHook callback) {
Set<XC_MethodHook.Unhook> unhooks = new HashSet<>();
for (Member method : hookClass.getDeclaredMethods())
if (method.getName().equals(methodName))
unhooks.add(hookMethod(method, callback));
return unhooks;
}
/**
* Hook all constructors of the specified class.
*
* @param hookClass The class to check for constructors.
* @param callback The callback to be executed when the hooked constructors are called.
* @return A set containing one object for each found constructor which can be used to unhook it.
*/
@SuppressWarnings("UnusedReturnValue")
public static Set<XC_MethodHook.Unhook> hookAllConstructors(Class<?> hookClass, XC_MethodHook callback) {
Set<XC_MethodHook.Unhook> unhooks = new HashSet<>();
for (Member constructor : hookClass.getDeclaredConstructors())
unhooks.add(hookMethod(constructor, callback));
return unhooks;
}
/**
* This method is called as a replacement for hooked methods.
*/
private static Object handleHookedMethod(Member method, int originalMethodId, Object additionalInfoObj,
Object thisObject, Object[] args) throws Throwable {
AdditionalHookInfo additionalInfo = (AdditionalHookInfo) additionalInfoObj;
if (disableHooks) {
try {
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
additionalInfo.returnType, thisObject, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
Object[] callbacksSnapshot = additionalInfo.callbacks.getSnapshot();
final int callbacksLength = callbacksSnapshot.length;
if (callbacksLength == 0) {
try {
return invokeOriginalMethodNative(method, originalMethodId, additionalInfo.parameterTypes,
additionalInfo.returnType, thisObject, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
}
MethodHookParam param = new MethodHookParam();
param.method = method;
param.thisObject = thisObject;
param.args = args;
// call "before method" callbacks
int beforeIdx = 0;
do {
try {
((XC_MethodHook) callbacksSnapshot[beforeIdx]).beforeHookedMethod(param);
} catch (Throwable t) {
XposedBridge.log(t);
// reset result (ignoring what the unexpectedly exiting callback did)
param.setResult(null);
param.returnEarly = false;
continue;
}
if (param.returnEarly) {
// skip remaining "before" callbacks and corresponding "after" callbacks
beforeIdx++;
break;
}
} while (++beforeIdx < callbacksLength);
// call original method if not requested otherwise
if (!param.returnEarly) {
try {
param.setResult(invokeOriginalMethodNative(method, originalMethodId,
additionalInfo.parameterTypes, additionalInfo.returnType, param.thisObject, param.args));
} catch (InvocationTargetException e) {
param.setThrowable(e.getCause());
}
}
// call "after method" callbacks
int afterIdx = beforeIdx - 1;
do {
Object lastResult = param.getResult();
Throwable lastThrowable = param.getThrowable();
try {
((XC_MethodHook) callbacksSnapshot[afterIdx]).afterHookedMethod(param);
} catch (Throwable t) {
XposedBridge.log(t);
// reset to last result (ignoring what the unexpectedly exiting callback did)
if (lastThrowable == null)
param.setResult(lastResult);
else
param.setThrowable(lastThrowable);
}
} while (--afterIdx >= 0);
// return
if (param.hasThrowable())
throw param.getThrowable();
else
return param.getResult();
}
/**
* Adds a callback to be executed when an app ("Android package") is loaded.
*
* <p class="note">You probably don't need to call this. Simply implement {@link IXposedHookLoadPackage}
* in your module class and Xposed will take care of registering it as a callback.
*
* @param callback The callback to be executed.
* @hide
*/
public static void hookLoadPackage(XC_LoadPackage callback) {
synchronized (sLoadedPackageCallbacks) {
sLoadedPackageCallbacks.add(callback);
}
}
/**
* Adds a callback to be executed when the resources for an app are initialized.
*
* <p class="note">You probably don't need to call this. Simply implement {@link IXposedHookInitPackageResources}
* in your module class and Xposed will take care of registering it as a callback.
*
* @param callback The callback to be executed.
* @hide
*/
public static void hookInitPackageResources(XC_InitPackageResources callback) {
// TODO not supported yet
// synchronized (sInitPackageResourcesCallbacks) {
// sInitPackageResourcesCallbacks.add(callback);
// }
}
/**
* Intercept every call to the specified method and call a handler function instead.
* @param method The method to intercept
*/
private synchronized static void hookMethodNative(final Member method, Class<?> declaringClass,
int slot, final Object additionalInfoObj) {
DynamicBridge.hookMethod(method, (AdditionalHookInfo) additionalInfoObj);
}
private static Object invokeOriginalMethodNative(Member method, int methodId,
Class<?>[] parameterTypes,
Class<?> returnType,
Object thisObject, Object[] args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
return DynamicBridge.invokeOriginalMethod(method, thisObject, args);
}
/**
* Basically the same as {@link Method#invoke}, but calls the original method
* as it was before the interception by Xposed. Also, access permissions are not checked.
*
* <p class="caution">There are very few cases where this method is needed. A common mistake is
* to replace a method and then invoke the original one based on dynamic conditions. This
* creates overhead and skips further hooks by other modules. Instead, just hook (don't replace)
* the method and call {@code param.setResult(null)} in {@link XC_MethodHook#beforeHookedMethod}
* if the original method should be skipped.
*
* @param method The method to be called.
* @param thisObject For non-static calls, the "this" pointer, otherwise {@code null}.
* @param args Arguments for the method call as Object[] array.
* @return The result returned from the invoked method.
* @throws NullPointerException
* if {@code receiver == null} for a non-static method
* @throws IllegalAccessException
* if this method is not accessible (see {@link AccessibleObject})
* @throws IllegalArgumentException
* if the number of arguments doesn't match the number of parameters, the receiver
* is incompatible with the declaring class, or an argument could not be unboxed
* or converted by a widening conversion to the corresponding parameter type
* @throws InvocationTargetException
* if an exception was thrown by the invoked method
*/
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
throws NullPointerException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
if (args == null) {
args = EMPTY_ARRAY;
}
Class<?>[] parameterTypes;
Class<?> returnType;
if (runtime == RUNTIME_ART && (method instanceof Method || method instanceof Constructor)) {
parameterTypes = null;
returnType = null;
} else if (method instanceof Method) {
parameterTypes = ((Method) method).getParameterTypes();
returnType = ((Method) method).getReturnType();
} else if (method instanceof Constructor) {
parameterTypes = ((Constructor<?>) method).getParameterTypes();
returnType = null;
} else {
throw new IllegalArgumentException("method must be of type Method or Constructor");
}
return invokeOriginalMethodNative(method, 0, parameterTypes, returnType, thisObject, args);
}
/*package*/ static void setObjectClass(Object obj, Class<?> clazz) {
if (clazz.isAssignableFrom(obj.getClass())) {
throw new IllegalArgumentException("Cannot transfer object from " + obj.getClass() + " to " + clazz);
}
setObjectClassNative(obj, clazz);
}
private static native void setObjectClassNative(Object obj, Class<?> clazz);
/*package*/ static native void dumpObjectNative(Object obj);
/*package*/ static Object cloneToSubclass(Object obj, Class<?> targetClazz) {
if (obj == null)
return null;
if (!obj.getClass().isAssignableFrom(targetClazz))
throw new ClassCastException(targetClazz + " doesn't extend " + obj.getClass());
return cloneToSubclassNative(obj, targetClazz);
}
private static native Object cloneToSubclassNative(Object obj, Class<?> targetClazz);
private static native void removeFinalFlagNative(Class<?> clazz);
// /*package*/ static native void closeFilesBeforeForkNative();
// /*package*/ static native void reopenFilesAfterForkNative();
//
// /*package*/ static native void invalidateCallersNative(Member[] methods);
/** @hide */
public static final class CopyOnWriteSortedSet<E> {
private transient volatile Object[] elements = EMPTY_ARRAY;
@SuppressWarnings("UnusedReturnValue")
public synchronized boolean add(E e) {
int index = indexOf(e);
if (index >= 0)
return false;
Object[] newElements = new Object[elements.length + 1];
System.arraycopy(elements, 0, newElements, 0, elements.length);
newElements[elements.length] = e;
Arrays.sort(newElements);
elements = newElements;
return true;
}
@SuppressWarnings("UnusedReturnValue")
public synchronized boolean remove(E e) {
int index = indexOf(e);
if (index == -1)
return false;
Object[] newElements = new Object[elements.length - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index, elements.length - index - 1);
elements = newElements;
return true;
}
private int indexOf(Object o) {
for (int i = 0; i < elements.length; i++) {
if (o.equals(elements[i]))
return i;
}
return -1;
}
public Object[] getSnapshot() {
return elements;
}
}
public static class AdditionalHookInfo {
public final CopyOnWriteSortedSet<XC_MethodHook> callbacks;
public final Class<?>[] parameterTypes;
public final Class<?> returnType;
private AdditionalHookInfo(CopyOnWriteSortedSet<XC_MethodHook> callbacks, Class<?>[] parameterTypes, Class<?> returnType) {
this.callbacks = callbacks;
this.parameterTypes = parameterTypes;
this.returnType = returnType;
}
}
}
package de.robv.android.xposed;
import android.content.res.AssetManager;
import android.content.res.Resources;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.zip.ZipFile;
import dalvik.system.DexFile;
import external.org.apache.commons.lang3.ClassUtils;
import external.org.apache.commons.lang3.reflect.MemberUtils;
/**
* Helpers that simplify hooking and calling methods/constructors, getting and settings fields, ...
*/
public final class XposedHelpers {
private XposedHelpers() {}
private static final HashMap<String, Field> fieldCache = new HashMap<>();
private static final HashMap<String, Method> methodCache = new HashMap<>();
private static final HashMap<String, Constructor<?>> constructorCache = new HashMap<>();
private static final WeakHashMap<Object, HashMap<String, Object>> additionalFields = new WeakHashMap<>();
private static final HashMap<String, ThreadLocal<AtomicInteger>> sMethodDepth = new HashMap<>();
/**
* Look up a class with the specified class loader.
*
* <p>There are various allowed syntaxes for the class name, but it's recommended to use one of
* these:
* <ul>
* <li>{@code java.lang.String}
* <li>{@code java.lang.String[]} (array)
* <li>{@code android.app.ActivityThread.ResourcesKey}
* <li>{@code android.app.ActivityThread$ResourcesKey}
* </ul>
*
* @param className The class name in one of the formats mentioned above.
* @param classLoader The class loader, or {@code null} for the boot class loader.
* @return A reference to the class.
* @throws ClassNotFoundError In case the class was not found.
*/
public static Class<?> findClass(String className, ClassLoader classLoader) {
if (classLoader == null)
classLoader = XposedBridge.BOOTCLASSLOADER;
try {
return ClassUtils.getClass(classLoader, className, false);
} catch (ClassNotFoundException e) {
throw new ClassNotFoundError(e);
}
}
/**
* Look up and return a class if it exists.
* Like {@link #findClass}, but doesn't throw an exception if the class doesn't exist.
*
* @param className The class name.
* @param classLoader The class loader, or {@code null} for the boot class loader.
* @return A reference to the class, or {@code null} if it doesn't exist.
*/
public static Class<?> findClassIfExists(String className, ClassLoader classLoader) {
try {
return findClass(className, classLoader);
} catch (ClassNotFoundError e) {
return null;
}
}
/**
* Look up a field in a class and set it to accessible.
*
* @param clazz The class which either declares or inherits the field.
* @param fieldName The field name.
* @return A reference to the field.
* @throws NoSuchFieldError In case the field was not found.
*/
public static Field findField(Class<?> clazz, String fieldName) {
String fullFieldName = clazz.getName() + '#' + fieldName;
if (fieldCache.containsKey(fullFieldName)) {
Field field = fieldCache.get(fullFieldName);
if (field == null)
throw new NoSuchFieldError(fullFieldName);
return field;
}
try {
Field field = findFieldRecursiveImpl(clazz, fieldName);
field.setAccessible(true);
fieldCache.put(fullFieldName, field);
return field;
} catch (NoSuchFieldException e) {
fieldCache.put(fullFieldName, null);
throw new NoSuchFieldError(fullFieldName);
}
}
/**
* Look up and return a field if it exists.
* Like {@link #findField}, but doesn't throw an exception if the field doesn't exist.
*
* @param clazz The class which either declares or inherits the field.
* @param fieldName The field name.
* @return A reference to the field, or {@code null} if it doesn't exist.
*/
public static Field findFieldIfExists(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName);
} catch (NoSuchFieldError e) {
return null;
}
}
private static Field findFieldRecursiveImpl(Class<?> clazz, String fieldName) throws NoSuchFieldException {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
while (true) {
clazz = clazz.getSuperclass();
if (clazz == null || clazz.equals(Object.class))
break;
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException ignored) {}
}
throw e;
}
}
/**
* Returns the first field of the given type in a class.
* Might be useful for Proguard'ed classes to identify fields with unique types.
*
* @param clazz The class which either declares or inherits the field.
* @param type The type of the field.
* @return A reference to the first field of the given type.
* @throws NoSuchFieldError In case no matching field was not found.
*/
public static Field findFirstFieldByExactType(Class<?> clazz, Class<?> type) {
Class<?> clz = clazz;
do {
for (Field field : clz.getDeclaredFields()) {
if (field.getType() == type) {
field.setAccessible(true);
return field;
}
}
} while ((clz = clz.getSuperclass()) != null);
throw new NoSuchFieldError("Field of type " + type.getName() + " in class " + clazz.getName());
}
/**
* Look up a method and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
* for details.
*/
public static XC_MethodHook.Unhook findAndHookMethod(Class<?> clazz, String methodName, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
throw new IllegalArgumentException("no callback defined");
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
Method m = findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
return XposedBridge.hookMethod(m, callback);
}
/**
* Look up a method and hook it. The last argument must be the callback for the hook.
*
* <p>This combines calls to {@link #findMethodExact(Class, String, Object...)} and
* {@link XposedBridge#hookMethod}.
*
* <p class="warning">The method must be declared or overridden in the given class, inherited
* methods are not considered! That's because each method implementation exists only once in
* the memory, and when classes inherit it, they just get another reference to the implementation.
* Hooking a method therefore applies to all classes inheriting the same implementation. You
* have to expect that the hook applies to subclasses (unless they override the method), but you
* shouldn't have to worry about hooks applying to superclasses, hence this "limitation".
* There could be undesired or even dangerous hooks otherwise, e.g. if you hook
* {@code SomeClass.equals()} and that class doesn't override the {@code equals()} on some ROMs,
* making you hook {@code Object.equals()} instead.
*
* <p>There are two ways to specify the parameter types. If you already have a reference to the
* {@link Class}, use that. For Android framework classes, you can often use something like
* {@code String.class}. If you don't have the class reference, you can simply use the
* full class name as a string, e.g. {@code java.lang.String} or {@code com.example.MyClass}.
* It will be passed to {@link #findClass} with the same class loader that is used for the target
* method, see its documentation for the allowed notations.
*
* <p>Primitive types, such as {@code int}, can be specified using {@code int.class} (recommended)
* or {@code Integer.TYPE}. Note that {@code Integer.class} doesn't refer to {@code int} but to
* {@code Integer}, which is a normal class (boxed primitive). Therefore it must not be used when
* the method expects an {@code int} parameter - it has to be used for {@code Integer} parameters
* though, so check the method signature in detail.
*
* <p>As last argument to this method (after the list of target method parameters), you need
* to specify the callback that should be executed when the method is invoked. It's usually
* an anonymous subclass of {@link XC_MethodHook} or {@link XC_MethodReplacement}.
*
* <p><b>Example</b>
* <pre class="prettyprint">
* // In order to hook this method ...
* package com.example;
* public class SomeClass {
* public int doSomething(String s, int i, MyClass m) {
* ...
* }
* }
*
* // ... you can use this call:
* findAndHookMethod("com.example.SomeClass", lpparam.classLoader, String.class, int.class, "com.example.MyClass", new XC_MethodHook() {
* &#64;Override
* protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
* String oldText = (String) param.args[0];
* Log.d("MyModule", oldText);
*
* param.args[0] = "test";
* param.args[1] = 42; // auto-boxing is working here
* setBooleanField(param.args[2], "great", true);
*
* // This would not work (as MyClass can't be resolved at compile time):
* // MyClass myClass = (MyClass) param.args[2];
* // myClass.great = true;
* }
* });
* </pre>
*
* @param className The name of the class which implements the method.
* @param classLoader The class loader for resolving the target and parameter classes.
* @param methodName The target method name.
* @param parameterTypesAndCallback The parameter types of the target method, plus the callback.
* @throws NoSuchMethodError In case the method was not found.
* @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
* @return An object which can be used to remove the callback again.
*/
public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) {
return findAndHookMethod(findClass(className, classLoader), methodName, parameterTypesAndCallback);
}
/**
* Look up a method in a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Method findMethodExact(Class<?> clazz, String methodName, Object... parameterTypes) {
return findMethodExact(clazz, methodName, getParameterClasses(clazz.getClassLoader(), parameterTypes));
}
/**
* Look up and return a method if it exists.
* See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
*/
public static Method findMethodExactIfExists(Class<?> clazz, String methodName, Object... parameterTypes) {
try {
return findMethodExact(clazz, methodName, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a method in a class and set it to accessible.
* The method must be declared or overridden in the given class.
*
* <p>See {@link #findAndHookMethod(String, ClassLoader, String, Object...)} for details about
* the method and parameter type resolution.
*
* @param className The name of the class which implements the method.
* @param classLoader The class loader for resolving the target and parameter classes.
* @param methodName The target method name.
* @param parameterTypes The parameter types of the target method.
* @throws NoSuchMethodError In case the method was not found.
* @throws ClassNotFoundError In case the target class or one of the parameter types couldn't be resolved.
* @return A reference to the method.
*/
public static Method findMethodExact(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) {
return findMethodExact(findClass(className, classLoader), methodName, getParameterClasses(classLoader, parameterTypes));
}
/**
* Look up and return a method if it exists.
* Like {@link #findMethodExact(String, ClassLoader, String, Object...)}, but doesn't throw an
* exception if the method doesn't exist.
*
* @param className The name of the class which implements the method.
* @param classLoader The class loader for resolving the target and parameter classes.
* @param methodName The target method name.
* @param parameterTypes The parameter types of the target method.
* @return A reference to the method, or {@code null} if it doesn't exist.
*/
public static Method findMethodExactIfExists(String className, ClassLoader classLoader, String methodName, Object... parameterTypes) {
try {
return findMethodExact(className, classLoader, methodName, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a method in a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*
* <p>This variant requires that you already have reference to all the parameter types.
*/
public static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#exact";
if (methodCache.containsKey(fullMethodName)) {
Method method = methodCache.get(fullMethodName);
if (method == null)
throw new NoSuchMethodError(fullMethodName);
return method;
}
try {
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
methodCache.put(fullMethodName, method);
return method;
} catch (NoSuchMethodException e) {
methodCache.put(fullMethodName, null);
throw new NoSuchMethodError(fullMethodName);
}
}
/**
* Returns an array of all methods declared/overridden in a class with the specified parameter types.
*
* <p>The return type is optional, it will not be compared if it is {@code null}.
* Use {@code void.class} if you want to search for methods returning nothing.
*
* @param clazz The class to look in.
* @param returnType The return type, or {@code null} (see above).
* @param parameterTypes The parameter types.
* @return An array with matching methods, all set to accessible already.
*/
public static Method[] findMethodsByExactParameters(Class<?> clazz, Class<?> returnType, Class<?>... parameterTypes) {
List<Method> result = new LinkedList<>();
for (Method method : clazz.getDeclaredMethods()) {
if (returnType != null && returnType != method.getReturnType())
continue;
Class<?>[] methodParameterTypes = method.getParameterTypes();
if (parameterTypes.length != methodParameterTypes.length)
continue;
boolean match = true;
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] != methodParameterTypes[i]) {
match = false;
break;
}
}
if (!match)
continue;
method.setAccessible(true);
result.add(method);
}
return result.toArray(new Method[result.size()]);
}
/**
* Look up a method in a class and set it to accessible.
*
* <p>This does'nt only look for exact matches, but for the best match. All considered candidates
* must be compatible with the given parameter types, i.e. the parameters must be assignable
* to the method's formal parameters. Inherited methods are considered here.
*
* @param clazz The class which declares, inherits or overrides the method.
* @param methodName The method name.
* @param parameterTypes The types of the method's parameters.
* @return A reference to the best-matching method.
* @throws NoSuchMethodError In case no suitable method was found.
*/
public static Method findMethodBestMatch(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#bestmatch";
if (methodCache.containsKey(fullMethodName)) {
Method method = methodCache.get(fullMethodName);
if (method == null)
throw new NoSuchMethodError(fullMethodName);
return method;
}
try {
Method method = findMethodExact(clazz, methodName, parameterTypes);
methodCache.put(fullMethodName, method);
return method;
} catch (NoSuchMethodError ignored) {}
Method bestMatch = null;
Class<?> clz = clazz;
boolean considerPrivateMethods = true;
do {
for (Method method : clz.getDeclaredMethods()) {
// don't consider private methods of superclasses
if (!considerPrivateMethods && Modifier.isPrivate(method.getModifiers()))
continue;
// compare name and parameters
if (method.getName().equals(methodName) && ClassUtils.isAssignable(parameterTypes, method.getParameterTypes(), true)) {
// get accessible version of method
if (bestMatch == null || MemberUtils.compareParameterTypes(
method.getParameterTypes(),
bestMatch.getParameterTypes(),
parameterTypes) < 0) {
bestMatch = method;
}
}
}
considerPrivateMethods = false;
} while ((clz = clz.getSuperclass()) != null);
if (bestMatch != null) {
bestMatch.setAccessible(true);
methodCache.put(fullMethodName, bestMatch);
return bestMatch;
} else {
NoSuchMethodError e = new NoSuchMethodError(fullMethodName);
methodCache.put(fullMethodName, null);
throw e;
}
}
/**
* Look up a method in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects.
*/
public static Method findMethodBestMatch(Class<?> clazz, String methodName, Object... args) {
return findMethodBestMatch(clazz, methodName, getParameterTypes(args));
}
/**
* Look up a method in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects. For any item that is
* {@code null}, the type is taken from {@code parameterTypes} instead.
*/
public static Method findMethodBestMatch(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object[] args) {
Class<?>[] argsClasses = null;
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] != null)
continue;
if (argsClasses == null)
argsClasses = getParameterTypes(args);
parameterTypes[i] = argsClasses[i];
}
return findMethodBestMatch(clazz, methodName, parameterTypes);
}
/**
* Returns an array with the classes of the given objects.
*/
public static Class<?>[] getParameterTypes(Object... args) {
Class<?>[] clazzes = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
clazzes[i] = (args[i] != null) ? args[i].getClass() : null;
}
return clazzes;
}
/**
* Retrieve classes from an array, where each element might either be a Class
* already, or a String with the full class name.
*/
private static Class<?>[] getParameterClasses(ClassLoader classLoader, Object[] parameterTypesAndCallback) {
Class<?>[] parameterClasses = null;
for (int i = parameterTypesAndCallback.length - 1; i >= 0; i--) {
Object type = parameterTypesAndCallback[i];
if (type == null)
throw new ClassNotFoundError("parameter type must not be null", null);
// ignore trailing callback
if (type instanceof XC_MethodHook)
continue;
if (parameterClasses == null)
parameterClasses = new Class<?>[i+1];
if (type instanceof Class)
parameterClasses[i] = (Class<?>) type;
else if (type instanceof String)
parameterClasses[i] = findClass((String) type, classLoader);
else
throw new ClassNotFoundError("parameter type must either be specified as Class or String", null);
}
// if there are no arguments for the method
if (parameterClasses == null)
parameterClasses = new Class<?>[0];
return parameterClasses;
}
/**
* Returns an array of the given classes.
*/
public static Class<?>[] getClassesAsArray(Class<?>... clazzes) {
return clazzes;
}
private static String getParametersString(Class<?>... clazzes) {
StringBuilder sb = new StringBuilder("(");
boolean first = true;
for (Class<?> clazz : clazzes) {
if (first)
first = false;
else
sb.append(",");
if (clazz != null)
sb.append(clazz.getCanonicalName());
else
sb.append("null");
}
sb.append(")");
return sb.toString();
}
/**
* Look up a constructor of a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExact(Class<?> clazz, Object... parameterTypes) {
return findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypes));
}
/**
* Look up and return a constructor if it exists.
* See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExactIfExists(Class<?> clazz, Object... parameterTypes) {
try {
return findConstructorExact(clazz, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a constructor of a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExact(String className, ClassLoader classLoader, Object... parameterTypes) {
return findConstructorExact(findClass(className, classLoader), getParameterClasses(classLoader, parameterTypes));
}
/**
* Look up and return a constructor if it exists.
* See {@link #findMethodExactIfExists(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExactIfExists(String className, ClassLoader classLoader, Object... parameterTypes) {
try {
return findConstructorExact(className, classLoader, parameterTypes);
} catch (ClassNotFoundError | NoSuchMethodError e) {
return null;
}
}
/**
* Look up a constructor of a class and set it to accessible.
* See {@link #findMethodExact(String, ClassLoader, String, Object...)} for details.
*/
public static Constructor<?> findConstructorExact(Class<?> clazz, Class<?>... parameterTypes) {
String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#exact";
if (constructorCache.containsKey(fullConstructorName)) {
Constructor<?> constructor = constructorCache.get(fullConstructorName);
if (constructor == null)
throw new NoSuchMethodError(fullConstructorName);
return constructor;
}
try {
Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
constructorCache.put(fullConstructorName, constructor);
return constructor;
} catch (NoSuchMethodException e) {
constructorCache.put(fullConstructorName, null);
throw new NoSuchMethodError(fullConstructorName);
}
}
/**
* Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
* for details.
*/
public static XC_MethodHook.Unhook findAndHookConstructor(Class<?> clazz, Object... parameterTypesAndCallback) {
if (parameterTypesAndCallback.length == 0 || !(parameterTypesAndCallback[parameterTypesAndCallback.length-1] instanceof XC_MethodHook))
throw new IllegalArgumentException("no callback defined");
XC_MethodHook callback = (XC_MethodHook) parameterTypesAndCallback[parameterTypesAndCallback.length-1];
Constructor<?> m = findConstructorExact(clazz, getParameterClasses(clazz.getClassLoader(), parameterTypesAndCallback));
return XposedBridge.hookMethod(m, callback);
}
/**
* Look up a constructor and hook it. See {@link #findAndHookMethod(String, ClassLoader, String, Object...)}
* for details.
*/
public static XC_MethodHook.Unhook findAndHookConstructor(String className, ClassLoader classLoader, Object... parameterTypesAndCallback) {
return findAndHookConstructor(findClass(className, classLoader), parameterTypesAndCallback);
}
/**
* Look up a constructor in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details.
*/
public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Class<?>... parameterTypes) {
String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#bestmatch";
if (constructorCache.containsKey(fullConstructorName)) {
Constructor<?> constructor = constructorCache.get(fullConstructorName);
if (constructor == null)
throw new NoSuchMethodError(fullConstructorName);
return constructor;
}
try {
Constructor<?> constructor = findConstructorExact(clazz, parameterTypes);
constructorCache.put(fullConstructorName, constructor);
return constructor;
} catch (NoSuchMethodError ignored) {}
Constructor<?> bestMatch = null;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
// compare name and parameters
if (ClassUtils.isAssignable(parameterTypes, constructor.getParameterTypes(), true)) {
// get accessible version of method
if (bestMatch == null || MemberUtils.compareParameterTypes(
constructor.getParameterTypes(),
bestMatch.getParameterTypes(),
parameterTypes) < 0) {
bestMatch = constructor;
}
}
}
if (bestMatch != null) {
bestMatch.setAccessible(true);
constructorCache.put(fullConstructorName, bestMatch);
return bestMatch;
} else {
NoSuchMethodError e = new NoSuchMethodError(fullConstructorName);
constructorCache.put(fullConstructorName, null);
throw e;
}
}
/**
* Look up a constructor in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects.
*/
public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Object... args) {
return findConstructorBestMatch(clazz, getParameterTypes(args));
}
/**
* Look up a constructor in a class and set it to accessible.
*
* <p>See {@link #findMethodBestMatch(Class, String, Class...)} for details. This variant
* determines the parameter types from the classes of the given objects. For any item that is
* {@code null}, the type is taken from {@code parameterTypes} instead.
*/
public static Constructor<?> findConstructorBestMatch(Class<?> clazz, Class<?>[] parameterTypes, Object[] args) {
Class<?>[] argsClasses = null;
for (int i = 0; i < parameterTypes.length; i++) {
if (parameterTypes[i] != null)
continue;
if (argsClasses == null)
argsClasses = getParameterTypes(args);
parameterTypes[i] = argsClasses[i];
}
return findConstructorBestMatch(clazz, parameterTypes);
}
/**
* Thrown when a class loader is unable to find a class. Unlike {@link ClassNotFoundException},
* callers are not forced to explicitly catch this. If uncaught, the error will be passed to the
* next caller in the stack.
*/
public static final class ClassNotFoundError extends Error {
private static final long serialVersionUID = -1070936889459514628L;
/** @hide */
public ClassNotFoundError(Throwable cause) {
super(cause);
}
/** @hide */
public ClassNotFoundError(String detailMessage, Throwable cause) {
super(detailMessage, cause);
}
}
/**
* Returns the index of the first parameter declared with the given type.
*
* @throws NoSuchFieldError if there is no parameter with that type.
* @hide
*/
public static int getFirstParameterIndexByType(Member method, Class<?> type) {
Class<?>[] classes = (method instanceof Method) ?
((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes();
for (int i = 0 ; i < classes.length; i++) {
if (classes[i] == type) {
return i;
}
}
throw new NoSuchFieldError("No parameter of type " + type + " found in " + method);
}
/**
* Returns the index of the parameter declared with the given type, ensuring that there is exactly one such parameter.
*
* @throws NoSuchFieldError if there is no or more than one parameter with that type.
* @hide
*/
public static int getParameterIndexByType(Member method, Class<?> type) {
Class<?>[] classes = (method instanceof Method) ?
((Method) method).getParameterTypes() : ((Constructor) method).getParameterTypes();
int idx = -1;
for (int i = 0 ; i < classes.length; i++) {
if (classes[i] == type) {
if (idx == -1) {
idx = i;
} else {
throw new NoSuchFieldError("More than one parameter of type " + type + " found in " + method);
}
}
}
if (idx != -1) {
return idx;
} else {
throw new NoSuchFieldError("No parameter of type " + type + " found in " + method);
}
}
//#################################################################################################
/** Sets the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setObjectField(Object obj, String fieldName, Object value) {
try {
findField(obj.getClass(), fieldName).set(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setBooleanField(Object obj, String fieldName, boolean value) {
try {
findField(obj.getClass(), fieldName).setBoolean(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setByteField(Object obj, String fieldName, byte value) {
try {
findField(obj.getClass(), fieldName).setByte(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setCharField(Object obj, String fieldName, char value) {
try {
findField(obj.getClass(), fieldName).setChar(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setDoubleField(Object obj, String fieldName, double value) {
try {
findField(obj.getClass(), fieldName).setDouble(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setFloatField(Object obj, String fieldName, float value) {
try {
findField(obj.getClass(), fieldName).setFloat(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setIntField(Object obj, String fieldName, int value) {
try {
findField(obj.getClass(), fieldName).setInt(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setLongField(Object obj, String fieldName, long value) {
try {
findField(obj.getClass(), fieldName).setLong(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static void setShortField(Object obj, String fieldName, short value) {
try {
findField(obj.getClass(), fieldName).setShort(obj, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/** Returns the value of an object field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static Object getObjectField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).get(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** For inner classes, returns the surrounding instance, i.e. the {@code this} reference of the surrounding class. */
public static Object getSurroundingThis(Object obj) {
return getObjectField(obj, "this$0");
}
/** Returns the value of a {@code boolean} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean getBooleanField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getBoolean(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code byte} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static byte getByteField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getByte(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code char} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static char getCharField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getChar(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code double} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static double getDoubleField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getDouble(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code float} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static float getFloatField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getFloat(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of an {@code int} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static int getIntField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getInt(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code long} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static long getLongField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getLong(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a {@code short} field in the given object instance. A class reference is not sufficient! See also {@link #findField}. */
public static short getShortField(Object obj, String fieldName) {
try {
return findField(obj.getClass(), fieldName).getShort(obj);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/** Sets the value of a static object field in the given class. See also {@link #findField}. */
public static void setStaticObjectField(Class<?> clazz, String fieldName, Object value) {
try {
findField(clazz, fieldName).set(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code boolean} field in the given class. See also {@link #findField}. */
public static void setStaticBooleanField(Class<?> clazz, String fieldName, boolean value) {
try {
findField(clazz, fieldName).setBoolean(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code byte} field in the given class. See also {@link #findField}. */
public static void setStaticByteField(Class<?> clazz, String fieldName, byte value) {
try {
findField(clazz, fieldName).setByte(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code char} field in the given class. See also {@link #findField}. */
public static void setStaticCharField(Class<?> clazz, String fieldName, char value) {
try {
findField(clazz, fieldName).setChar(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code double} field in the given class. See also {@link #findField}. */
public static void setStaticDoubleField(Class<?> clazz, String fieldName, double value) {
try {
findField(clazz, fieldName).setDouble(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code float} field in the given class. See also {@link #findField}. */
public static void setStaticFloatField(Class<?> clazz, String fieldName, float value) {
try {
findField(clazz, fieldName).setFloat(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code int} field in the given class. See also {@link #findField}. */
public static void setStaticIntField(Class<?> clazz, String fieldName, int value) {
try {
findField(clazz, fieldName).setInt(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code long} field in the given class. See also {@link #findField}. */
public static void setStaticLongField(Class<?> clazz, String fieldName, long value) {
try {
findField(clazz, fieldName).setLong(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code short} field in the given class. See also {@link #findField}. */
public static void setStaticShortField(Class<?> clazz, String fieldName, short value) {
try {
findField(clazz, fieldName).setShort(null, value);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/** Returns the value of a static object field in the given class. See also {@link #findField}. */
public static Object getStaticObjectField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).get(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Returns the value of a static {@code boolean} field in the given class. See also {@link #findField}. */
public static boolean getStaticBooleanField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getBoolean(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code byte} field in the given class. See also {@link #findField}. */
public static byte getStaticByteField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getByte(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code char} field in the given class. See also {@link #findField}. */
public static char getStaticCharField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getChar(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code double} field in the given class. See also {@link #findField}. */
public static double getStaticDoubleField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getDouble(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code float} field in the given class. See also {@link #findField}. */
public static float getStaticFloatField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getFloat(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code int} field in the given class. See also {@link #findField}. */
public static int getStaticIntField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getInt(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code long} field in the given class. See also {@link #findField}. */
public static long getStaticLongField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getLong(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
/** Sets the value of a static {@code short} field in the given class. See also {@link #findField}. */
public static short getStaticShortField(Class<?> clazz, String fieldName) {
try {
return findField(clazz, fieldName).getShort(null);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
}
}
//#################################################################################################
/**
* Calls an instance or static method of the given object.
* The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}.
*
* @param obj The object instance. A class reference is not sufficient!
* @param methodName The method name.
* @param args The arguments for the method call.
* @throws NoSuchMethodError In case no suitable method was found.
* @throws InvocationTargetError In case an exception was thrown by the invoked method.
*/
public static Object callMethod(Object obj, String methodName, Object... args) {
try {
return findMethodBestMatch(obj.getClass(), methodName, args).invoke(obj, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* Calls an instance or static method of the given object.
* See {@link #callMethod(Object, String, Object...)}.
*
* <p>This variant allows you to specify parameter types, which can help in case there are multiple
* methods with the same name, especially if you call it with {@code null} parameters.
*/
public static Object callMethod(Object obj, String methodName, Class<?>[] parameterTypes, Object... args) {
try {
return findMethodBestMatch(obj.getClass(), methodName, parameterTypes, args).invoke(obj, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* Calls a static method of the given class.
* The method is resolved using {@link #findMethodBestMatch(Class, String, Object...)}.
*
* @param clazz The class reference.
* @param methodName The method name.
* @param args The arguments for the method call.
* @throws NoSuchMethodError In case no suitable method was found.
* @throws InvocationTargetError In case an exception was thrown by the invoked method.
*/
public static Object callStaticMethod(Class<?> clazz, String methodName, Object... args) {
try {
return findMethodBestMatch(clazz, methodName, args).invoke(null, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* Calls a static method of the given class.
* See {@link #callStaticMethod(Class, String, Object...)}.
*
* <p>This variant allows you to specify parameter types, which can help in case there are multiple
* methods with the same name, especially if you call it with {@code null} parameters.
*/
public static Object callStaticMethod(Class<?> clazz, String methodName, Class<?>[] parameterTypes, Object... args) {
try {
return findMethodBestMatch(clazz, methodName, parameterTypes, args).invoke(null, args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
}
}
/**
* This class provides a wrapper for an exception thrown by a method invocation.
*
* @see #callMethod(Object, String, Object...)
* @see #callStaticMethod(Class, String, Object...)
* @see #newInstance(Class, Object...)
*/
public static final class InvocationTargetError extends Error {
private static final long serialVersionUID = -1070936889459514628L;
/** @hide */
public InvocationTargetError(Throwable cause) {
super(cause);
}
}
//#################################################################################################
/**
* Creates a new instance of the given class.
* The constructor is resolved using {@link #findConstructorBestMatch(Class, Object...)}.
*
* @param clazz The class reference.
* @param args The arguments for the constructor call.
* @throws NoSuchMethodError In case no suitable constructor was found.
* @throws InvocationTargetError In case an exception was thrown by the invoked method.
* @throws InstantiationError In case the class cannot be instantiated.
*/
public static Object newInstance(Class<?> clazz, Object... args) {
try {
return findConstructorBestMatch(clazz, args).newInstance(args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
} catch (InstantiationException e) {
throw new InstantiationError(e.getMessage());
}
}
/**
* Creates a new instance of the given class.
* See {@link #newInstance(Class, Object...)}.
*
* <p>This variant allows you to specify parameter types, which can help in case there are multiple
* constructors with the same name, especially if you call it with {@code null} parameters.
*/
public static Object newInstance(Class<?> clazz, Class<?>[] parameterTypes, Object... args) {
try {
return findConstructorBestMatch(clazz, parameterTypes, args).newInstance(args);
} catch (IllegalAccessException e) {
// should not happen
XposedBridge.log(e);
throw new IllegalAccessError(e.getMessage());
} catch (IllegalArgumentException e) {
throw e;
} catch (InvocationTargetException e) {
throw new InvocationTargetError(e.getCause());
} catch (InstantiationException e) {
throw new InstantiationError(e.getMessage());
}
}
//#################################################################################################
/**
* Attaches any value to an object instance. This simulates adding an instance field.
* The value can be retrieved again with {@link #getAdditionalInstanceField}.
*
* @param obj The object instance for which the value should be stored.
* @param key The key in the value map for this object instance.
* @param value The value to store.
* @return The previously stored value for this instance/key combination, or {@code null} if there was none.
*/
public static Object setAdditionalInstanceField(Object obj, String key, Object value) {
if (obj == null)
throw new NullPointerException("object must not be null");
if (key == null)
throw new NullPointerException("key must not be null");
HashMap<String, Object> objectFields;
synchronized (additionalFields) {
objectFields = additionalFields.get(obj);
if (objectFields == null) {
objectFields = new HashMap<>();
additionalFields.put(obj, objectFields);
}
}
synchronized (objectFields) {
return objectFields.put(key, value);
}
}
/**
* Returns a value which was stored with {@link #setAdditionalInstanceField}.
*
* @param obj The object instance for which the value has been stored.
* @param key The key in the value map for this object instance.
* @return The stored value for this instance/key combination, or {@code null} if there is none.
*/
public static Object getAdditionalInstanceField(Object obj, String key) {
if (obj == null)
throw new NullPointerException("object must not be null");
if (key == null)
throw new NullPointerException("key must not be null");
HashMap<String, Object> objectFields;
synchronized (additionalFields) {
objectFields = additionalFields.get(obj);
if (objectFields == null)
return null;
}
synchronized (objectFields) {
return objectFields.get(key);
}
}
/**
* Removes and returns a value which was stored with {@link #setAdditionalInstanceField}.
*
* @param obj The object instance for which the value has been stored.
* @param key The key in the value map for this object instance.
* @return The previously stored value for this instance/key combination, or {@code null} if there was none.
*/
public static Object removeAdditionalInstanceField(Object obj, String key) {
if (obj == null)
throw new NullPointerException("object must not be null");
if (key == null)
throw new NullPointerException("key must not be null");
HashMap<String, Object> objectFields;
synchronized (additionalFields) {
objectFields = additionalFields.get(obj);
if (objectFields == null)
return null;
}
synchronized (objectFields) {
return objectFields.remove(key);
}
}
/** Like {@link #setAdditionalInstanceField}, but the value is stored for the class of {@code obj}. */
public static Object setAdditionalStaticField(Object obj, String key, Object value) {
return setAdditionalInstanceField(obj.getClass(), key, value);
}
/** Like {@link #getAdditionalInstanceField}, but the value is returned for the class of {@code obj}. */
public static Object getAdditionalStaticField(Object obj, String key) {
return getAdditionalInstanceField(obj.getClass(), key);
}
/** Like {@link #removeAdditionalInstanceField}, but the value is removed and returned for the class of {@code obj}. */
public static Object removeAdditionalStaticField(Object obj, String key) {
return removeAdditionalInstanceField(obj.getClass(), key);
}
/** Like {@link #setAdditionalInstanceField}, but the value is stored for {@code clazz}. */
public static Object setAdditionalStaticField(Class<?> clazz, String key, Object value) {
return setAdditionalInstanceField(clazz, key, value);
}
/** Like {@link #setAdditionalInstanceField}, but the value is returned for {@code clazz}. */
public static Object getAdditionalStaticField(Class<?> clazz, String key) {
return getAdditionalInstanceField(clazz, key);
}
/** Like {@link #setAdditionalInstanceField}, but the value is removed and returned for {@code clazz}. */
public static Object removeAdditionalStaticField(Class<?> clazz, String key) {
return removeAdditionalInstanceField(clazz, key);
}
//#################################################################################################
/**
* Loads an asset from a resource object and returns the content as {@code byte} array.
*
* @param res The resources from which the asset should be loaded.
* @param path The path to the asset, as in {@link AssetManager#open}.
* @return The content of the asset.
*/
public static byte[] assetAsByteArray(Resources res, String path) throws IOException {
return inputStreamToByteArray(res.getAssets().open(path));
}
/*package*/ static byte[] inputStreamToByteArray(InputStream is) throws IOException {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
byte[] temp = new byte[1024];
int read;
while ((read = is.read(temp)) > 0) {
buf.write(temp, 0, read);
}
is.close();
return buf.toByteArray();
}
/**
* Invokes the {@link Closeable#close()} method, ignoring IOExceptions.
*/
/*package*/ static void closeSilently(Closeable c) {
if (c != null) {
try {
c.close();
} catch (IOException ignored) {}
}
}
/**
* Invokes the {@link DexFile#close()} method, ignoring IOExceptions.
*/
/*package*/ static void closeSilently(DexFile dexFile) {
if (dexFile != null) {
try {
dexFile.close();
} catch (IOException ignored) {}
}
}
/**
* Invokes the {@link ZipFile#close()} method, ignoring IOExceptions.
*/
/*package*/ static void closeSilently(ZipFile zipFile) {
if (zipFile != null) {
try {
zipFile.close();
} catch (IOException ignored) {}
}
}
/**
* Returns the lowercase hex string representation of a file's MD5 hash sum.
*/
public static String getMD5Sum(String file) throws IOException {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
InputStream is = new FileInputStream(file);
byte[] buffer = new byte[8192];
int read;
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
is.close();
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
return bigInt.toString(16);
} catch (NoSuchAlgorithmException e) {
return "";
}
}
//#################################################################################################
/**
* Increments the depth counter for the given method.
*
* <p>The intention of the method depth counter is to keep track of the call depth for recursive
* methods, e.g. to override parameters only for the outer call. The Xposed framework uses this
* to load drawable replacements only once per call, even when multiple
* {@link Resources#getDrawable} variants call each other.
*
* @param method The method name. Should be prefixed with a unique, module-specific string.
* @return The updated depth.
*/
public static int incrementMethodDepth(String method) {
return getMethodDepthCounter(method).get().incrementAndGet();
}
/**
* Decrements the depth counter for the given method.
* See {@link #incrementMethodDepth} for details.
*
* @param method The method name. Should be prefixed with a unique, module-specific string.
* @return The updated depth.
*/
public static int decrementMethodDepth(String method) {
return getMethodDepthCounter(method).get().decrementAndGet();
}
/**
* Returns the current depth counter for the given method.
* See {@link #incrementMethodDepth} for details.
*
* @param method The method name. Should be prefixed with a unique, module-specific string.
* @return The updated depth.
*/
public static int getMethodDepth(String method) {
return getMethodDepthCounter(method).get().get();
}
private static ThreadLocal<AtomicInteger> getMethodDepthCounter(String method) {
synchronized (sMethodDepth) {
ThreadLocal<AtomicInteger> counter = sMethodDepth.get(method);
if (counter == null) {
counter = new ThreadLocal<AtomicInteger>() {
@Override
protected AtomicInteger initialValue() {
return new AtomicInteger();
}
};
sMethodDepth.put(method, counter);
}
return counter;
}
}
/*package*/ static boolean fileContains(File file, String str) throws IOException {
// There are certainly more efficient algorithms (e.g. Boyer-Moore used in grep),
// but the naive approach should be sufficient here.
BufferedReader in = null;
try {
in = new BufferedReader(new FileReader(file));
String line;
while ((line = in.readLine()) != null) {
if (line.contains(str)) {
return true;
}
}
return false;
} finally {
closeSilently(in);
}
}
//#################################################################################################
/**
* Returns the method that is overridden by the given method.
* It returns {@code null} if the method doesn't override another method or if that method is
* abstract, i.e. if this is the first implementation in the hierarchy.
*/
/*package*/ static Method getOverriddenMethod(Method method) {
int modifiers = method.getModifiers();
if (Modifier.isStatic(modifiers) || Modifier.isPrivate(modifiers)) {
return null;
}
String name = method.getName();
Class<?>[] parameters = method.getParameterTypes();
Class<?> clazz = method.getDeclaringClass().getSuperclass();
while (clazz != null) {
try {
Method superMethod = clazz.getDeclaredMethod(name, parameters);
modifiers = superMethod.getModifiers();
if (!Modifier.isPrivate(modifiers) && !Modifier.isAbstract(modifiers)) {
return superMethod;
} else {
return null;
}
} catch (NoSuchMethodException ignored) {
clazz = clazz.getSuperclass();
}
}
return null;
}
/**
* Returns all methods which this class overrides.
*/
/*package*/ static Set<Method> getOverriddenMethods(Class<?> clazz) {
Set<Method> methods = new HashSet<>();
for (Method method : clazz.getDeclaredMethods()) {
Method overridden = getOverriddenMethod(method);
if (overridden != null) {
methods.add(overridden);
}
}
return methods;
}
//#################################################################################################
// TODO helpers for view traversing
/*To make it easier, I will try and implement some more helpers:
- add view before/after existing view (I already mentioned that I think)
- get index of view in its parent
- get next/previous sibling (maybe with an optional argument "type", that might be ImageView.class and gives you the next sibling that is an ImageView)?
- get next/previous element (similar to the above, but would also work if the next element has a different parent, it would just go up the hierarchy and then down again until it finds a matching element)
- find the first child that is an instance of a specified class
- find all (direct or indirect) children of a specified class
*/
}
package de.robv.android.xposed;
import android.annotation.SuppressLint;
import android.app.AndroidAppHelper;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.os.ZygoteInit;
import com.elderdrivers.riru.xposed.BuildConfig;
import com.elderdrivers.riru.xposed.entry.Router;
import com.elderdrivers.riru.xposed.util.Utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;
import de.robv.android.xposed.services.BaseService;
import static com.elderdrivers.riru.xposed.entry.hooker.XposedBlackListHooker.BLACK_LIST_PACKAGE_NAME;
import static de.robv.android.xposed.XposedHelpers.closeSilently;
import static de.robv.android.xposed.XposedHelpers.findClass;
import static de.robv.android.xposed.XposedHelpers.findFieldIfExists;
import static de.robv.android.xposed.XposedHelpers.setStaticBooleanField;
import static de.robv.android.xposed.XposedHelpers.setStaticLongField;
public final class XposedInit {
private static final String TAG = XposedBridge.TAG;
private static boolean startsSystemServer = false;
private static final String startClassName = ""; // ed: no support for tool process anymore
public static final String INSTALLER_PACKAGE_NAME = "com.solohsu.android.edxp.manager";
public static final String INSTALLER_LEGACY_PACKAGE_NAME = "de.robv.android.xposed.installer";
@SuppressLint("SdCardPath")
public static final String INSTALLER_DATA_BASE_DIR = Build.VERSION.SDK_INT >= 24
? "/data/user_de/0/" + INSTALLER_PACKAGE_NAME + "/"
: "/data/data/" + INSTALLER_PACKAGE_NAME + "/";
private static final String INSTANT_RUN_CLASS = "com.android.tools.fd.runtime.BootstrapApplication";
// TODO not supported yet
private static boolean disableResources = true;
private static final String[] XRESOURCES_CONFLICTING_PACKAGES = {"com.sygic.aura"};
private XposedInit() {
}
private static volatile AtomicBoolean bootstrapHooked = new AtomicBoolean(false);
/**
* Hook some methods which we want to create an easier interface for developers.
*/
/*package*/
public static void initForZygote(boolean isSystem) throws Throwable {
if (!bootstrapHooked.compareAndSet(false, true)) {
return;
}
startsSystemServer = isSystem;
Router.startBootstrapHook(isSystem);
// MIUI
if (findFieldIfExists(ZygoteInit.class, "BOOT_START_TIME") != null) {
setStaticLongField(ZygoteInit.class, "BOOT_START_TIME", XposedBridge.BOOT_START_TIME);
}
// Samsung
if (Build.VERSION.SDK_INT >= 24) {
Class<?> zygote = findClass("com.android.internal.os.Zygote", null);
try {
setStaticBooleanField(zygote, "isEnhancedZygoteASLREnabled", false);
} catch (NoSuchFieldError ignored) {
}
}
}
/*package*/
static void hookResources() throws Throwable {
// ed: not for now
}
private static boolean needsToCloseFilesForFork() {
// ed: we always start to do our work after forking finishes
return false;
}
/**
* Try to load all modules defined in <code>INSTALLER_DATA_BASE_DIR/conf/modules.list</code>
*/
private static volatile AtomicBoolean modulesLoaded = new AtomicBoolean(false);
public static void loadModules() throws IOException {
if (!modulesLoaded.compareAndSet(false, true)) {
return;
}
final String filename = INSTALLER_DATA_BASE_DIR + "conf/modules.list";
BaseService service = SELinuxHelper.getAppDataFileService();
if (!service.checkFileExists(filename)) {
Log.e(TAG, "Cannot load any modules because " + filename + " was not found");
return;
}
ClassLoader topClassLoader = XposedBridge.BOOTCLASSLOADER;
ClassLoader parent;
while ((parent = topClassLoader.getParent()) != null) {
topClassLoader = parent;
}
InputStream stream = service.getFileInputStream(filename);
BufferedReader apks = new BufferedReader(new InputStreamReader(stream));
String apk;
while ((apk = apks.readLine()) != null) {
loadModule(apk, topClassLoader);
}
apks.close();
}
/**
* Load a module from an APK by calling the init(String) method for all classes defined
* in <code>assets/xposed_init</code>.
*/
private static void loadModule(String apk, ClassLoader topClassLoader) {
if (BuildConfig.DEBUG)
Log.i(TAG, "Loading modules from " + apk);
if (!TextUtils.isEmpty(apk) && apk.contains(BLACK_LIST_PACKAGE_NAME)) {
if (BuildConfig.DEBUG)
Log.i(TAG, "We are going to take over black list's job...");
return;
}
if (!new File(apk).exists()) {
Log.e(TAG, " File does not exist");
return;
}
DexFile dexFile;
try {
dexFile = new DexFile(apk);
} catch (IOException e) {
Log.e(TAG, " Cannot load module", e);
return;
}
if (dexFile.loadClass(INSTANT_RUN_CLASS, topClassLoader) != null) {
Log.e(TAG, " Cannot load module, please disable \"Instant Run\" in Android Studio.");
closeSilently(dexFile);
return;
}
if (dexFile.loadClass(XposedBridge.class.getName(), topClassLoader) != null) {
Log.e(TAG, " Cannot load module:");
Log.e(TAG, " The Xposed API classes are compiled into the module's APK.");
Log.e(TAG, " This may cause strange issues and must be fixed by the module developer.");
Log.e(TAG, " For details, see: http://api.xposed.info/using.html");
closeSilently(dexFile);
return;
}
closeSilently(dexFile);
ZipFile zipFile = null;
InputStream is;
try {
zipFile = new ZipFile(apk);
ZipEntry zipEntry = zipFile.getEntry("assets/xposed_init");
if (zipEntry == null) {
Log.e(TAG, " assets/xposed_init not found in the APK");
closeSilently(zipFile);
return;
}
is = zipFile.getInputStream(zipEntry);
} catch (IOException e) {
Log.e(TAG, " Cannot read assets/xposed_init in the APK", e);
closeSilently(zipFile);
return;
}
ClassLoader mcl = new PathClassLoader(apk, XposedInit.class.getClassLoader());
BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is));
try {
String moduleClassName;
while ((moduleClassName = moduleClassesReader.readLine()) != null) {
moduleClassName = moduleClassName.trim();
if (moduleClassName.isEmpty() || moduleClassName.startsWith("#"))
continue;
try {
if (BuildConfig.DEBUG)
Log.i(TAG, " Loading class " + moduleClassName);
Class<?> moduleClass = mcl.loadClass(moduleClassName);
if (!IXposedMod.class.isAssignableFrom(moduleClass)) {
Log.e(TAG, " This class doesn't implement any sub-interface of IXposedMod, skipping it");
continue;
} else if (disableResources && IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) {
Log.e(TAG, " This class requires resource-related hooks (which are disabled), skipping it.");
continue;
}
final Object moduleInstance = moduleClass.newInstance();
if (XposedBridge.isZygote) {
if (moduleInstance instanceof IXposedHookZygoteInit) {
IXposedHookZygoteInit.StartupParam param = new IXposedHookZygoteInit.StartupParam();
param.modulePath = apk;
param.startsSystemServer = startsSystemServer;
((IXposedHookZygoteInit) moduleInstance).initZygote(param);
}
if (moduleInstance instanceof IXposedHookLoadPackage)
XposedBridge.hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance));
if (moduleInstance instanceof IXposedHookInitPackageResources)
XposedBridge.hookInitPackageResources(new IXposedHookInitPackageResources.Wrapper((IXposedHookInitPackageResources) moduleInstance));
} else {
if (moduleInstance instanceof IXposedHookCmdInit) {
IXposedHookCmdInit.StartupParam param = new IXposedHookCmdInit.StartupParam();
param.modulePath = apk;
param.startClassName = startClassName;
((IXposedHookCmdInit) moduleInstance).initCmdApp(param);
}
}
} catch (Throwable t) {
Log.e(TAG, " Failed to load class " + moduleClassName, t);
}
}
} catch (IOException e) {
Log.e(TAG, " Failed to load module from " + apk, e);
} finally {
closeSilently(is);
closeSilently(zipFile);
}
}
public final static HashSet<String> loadedPackagesInProcess = new HashSet<>(1);
public static void logD(String prefix) {
Utils.logD(String.format("%s: pkg=%s, prc=%s", prefix, AndroidAppHelper.currentPackageName(),
AndroidAppHelper.currentProcessName()));
}
public static void logE(String prefix, Throwable throwable) {
Utils.logE(String.format("%s: pkg=%s, prc=%s", prefix, AndroidAppHelper.currentPackageName(),
AndroidAppHelper.currentProcessName()), throwable);
}
}
package de.robv.android.xposed.callbacks;
import de.robv.android.xposed.IXposedHookZygoteInit;
/**
* Interface for objects that can be used to remove callbacks.
*
* <p class="warning">Just like hooking methods etc., unhooking applies only to the current process.
* In other process (or when the app is removed from memory and then restarted), the hook will still
* be active. The Zygote process (see {@link IXposedHookZygoteInit}) is an exception, the hook won't
* be inherited by any future processes forked from it in the future.
*
* @param <T> The class of the callback.
*/
public interface IXUnhook<T> {
/**
* Returns the callback that has been registered.
*/
T getCallback();
/**
* Removes the callback.
*/
void unhook();
}
package de.robv.android.xposed.callbacks;
import android.content.res.XResources;
import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
/**
* This class is only used for internal purposes, except for the {@link InitPackageResourcesParam}
* subclass.
*/
public abstract class XC_InitPackageResources extends XCallback implements IXposedHookInitPackageResources {
/**
* Creates a new callback with default priority.
* @hide
*/
@SuppressWarnings("deprecation")
public XC_InitPackageResources() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link XCallback#priority}.
* @hide
*/
public XC_InitPackageResources(int priority) {
super(priority);
}
/**
* Wraps information about the resources being initialized.
*/
public static final class InitPackageResourcesParam extends XCallback.Param {
/** @hide */
public InitPackageResourcesParam(CopyOnWriteSortedSet<XC_InitPackageResources> callbacks) {
super(callbacks);
}
/** The name of the package for which resources are being loaded. */
public String packageName;
/**
* Reference to the resources that can be used for calls to
* {@link XResources#setReplacement(String, String, String, Object)}.
*/
public XResources res;
}
/** @hide */
@Override
protected void call(Param param) throws Throwable {
if (param instanceof InitPackageResourcesParam)
handleInitPackageResources((InitPackageResourcesParam) param);
}
}
package de.robv.android.xposed.callbacks;
import android.content.res.XResources;
import android.content.res.XResources.ResourceNames;
import android.view.View;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
/**
* Callback for hooking layouts. Such callbacks can be passed to {@link XResources#hookLayout}
* and its variants.
*/
public abstract class XC_LayoutInflated extends XCallback {
/**
* Creates a new callback with default priority.
*/
@SuppressWarnings("deprecation")
public XC_LayoutInflated() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link XCallback#priority}.
*/
public XC_LayoutInflated(int priority) {
super(priority);
}
/**
* Wraps information about the inflated layout.
*/
public static final class LayoutInflatedParam extends XCallback.Param {
/** @hide */
public LayoutInflatedParam(CopyOnWriteSortedSet<XC_LayoutInflated> callbacks) {
super(callbacks);
}
/** The view that has been created from the layout. */
public View view;
/** Container with the ID and name of the underlying resource. */
public ResourceNames resNames;
/** Directory from which the layout was actually loaded (e.g. "layout-sw600dp"). */
public String variant;
/** Resources containing the layout. */
public XResources res;
}
/** @hide */
@Override
protected void call(Param param) throws Throwable {
if (param instanceof LayoutInflatedParam)
handleLayoutInflated((LayoutInflatedParam) param);
}
/**
* This method is called when the hooked layout has been inflated.
*
* @param liparam Information about the layout and the inflated view.
* @throws Throwable Everything the callback throws is caught and logged.
*/
public abstract void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable;
/**
* An object with which the callback can be removed.
*/
public class Unhook implements IXUnhook<XC_LayoutInflated> {
private final String resDir;
private final int id;
/** @hide */
public Unhook(String resDir, int id) {
this.resDir = resDir;
this.id = id;
}
/**
* Returns the resource ID of the hooked layout.
*/
public int getId() {
return id;
}
@Override
public XC_LayoutInflated getCallback() {
return XC_LayoutInflated.this;
}
@Override
public void unhook() {
XResources.unhookLayout(resDir, id, XC_LayoutInflated.this);
}
}
}
package de.robv.android.xposed.callbacks;
import android.content.pm.ApplicationInfo;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
/**
* This class is only used for internal purposes, except for the {@link LoadPackageParam}
* subclass.
*/
public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoadPackage {
/**
* Creates a new callback with default priority.
* @hide
*/
@SuppressWarnings("deprecation")
public XC_LoadPackage() {
super();
}
/**
* Creates a new callback with a specific priority.
*
* @param priority See {@link XCallback#priority}.
* @hide
*/
public XC_LoadPackage(int priority) {
super(priority);
}
/**
* Wraps information about the app being loaded.
*/
public static final class LoadPackageParam extends XCallback.Param {
/** @hide */
public LoadPackageParam(CopyOnWriteSortedSet<XC_LoadPackage> callbacks) {
super(callbacks);
}
/** The name of the package being loaded. */
public String packageName;
/** The process in which the package is executed. */
public String processName;
/** The ClassLoader used for this package. */
public ClassLoader classLoader;
/** More information about the application being loaded. */
public ApplicationInfo appInfo;
/** Set to {@code true} if this is the first (and main) application for this process. */
public boolean isFirstApplication;
}
/** @hide */
@Override
protected void call(Param param) throws Throwable {
if (param instanceof LoadPackageParam)
handleLoadPackage((LoadPackageParam) param);
}
}
package de.robv.android.xposed.callbacks;
import android.os.Bundle;
import java.io.Serializable;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
/**
* Base class for Xposed callbacks.
*
* This class only keeps a priority for ordering multiple callbacks.
* The actual (abstract) callback methods are added by subclasses.
*/
public abstract class XCallback implements Comparable<XCallback> {
/**
* Callback priority, higher number means earlier execution.
*
* <p>This is usually set to {@link #PRIORITY_DEFAULT}. However, in case a certain callback should
* be executed earlier or later a value between {@link #PRIORITY_HIGHEST} and {@link #PRIORITY_LOWEST}
* can be set instead. The values are just for orientation though, Xposed doesn't enforce any
* boundaries on the priority values.
*/
public final int priority;
/** @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it! */
@Deprecated
public XCallback() {
this.priority = PRIORITY_DEFAULT;
}
/** @hide */
public XCallback(int priority) {
this.priority = priority;
}
/**
* Base class for Xposed callback parameters.
*/
public static abstract class Param {
/** @hide */
public final Object[] callbacks;
private Bundle extra;
/** @deprecated This constructor can't be hidden for technical reasons. Nevertheless, don't use it! */
@Deprecated
protected Param() {
callbacks = null;
}
/** @hide */
protected Param(CopyOnWriteSortedSet<? extends XCallback> callbacks) {
this.callbacks = callbacks.getSnapshot();
}
/**
* This can be used to store any data for the scope of the callback.
*
* <p>Use this instead of instance variables, as it has a clear reference to e.g. each
* separate call to a method, even when the same method is called recursively.
*
* @see #setObjectExtra
* @see #getObjectExtra
*/
public synchronized Bundle getExtra() {
if (extra == null)
extra = new Bundle();
return extra;
}
/**
* Returns an object stored with {@link #setObjectExtra}.
*/
public Object getObjectExtra(String key) {
Serializable o = getExtra().getSerializable(key);
if (o instanceof SerializeWrapper)
return ((SerializeWrapper) o).object;
return null;
}
/**
* Stores any object for the scope of the callback. For data types that support it, use
* the {@link Bundle} returned by {@link #getExtra} instead.
*/
public void setObjectExtra(String key, Object o) {
getExtra().putSerializable(key, new SerializeWrapper(o));
}
private static class SerializeWrapper implements Serializable {
private static final long serialVersionUID = 1L;
private final Object object;
public SerializeWrapper(Object o) {
object = o;
}
}
}
/** @hide */
public static void callAll(Param param) {
if (param.callbacks == null)
throw new IllegalStateException("This object was not created for use with callAll");
for (int i = 0; i < param.callbacks.length; i++) {
try {
((XCallback) param.callbacks[i]).call(param);
} catch (Throwable t) { XposedBridge.log(t); }
}
}
/** @hide */
protected void call(Param param) throws Throwable {}
/** @hide */
@Override
public int compareTo(XCallback other) {
if (this == other)
return 0;
// order descending by priority
if (other.priority != this.priority)
return other.priority - this.priority;
// then randomly
else if (System.identityHashCode(this) < System.identityHashCode(other))
return -1;
else
return 1;
}
/** The default priority, see {@link #priority}. */
public static final int PRIORITY_DEFAULT = 50;
/** Execute this callback late, see {@link #priority}. */
public static final int PRIORITY_LOWEST = -10000;
/** Execute this callback early, see {@link #priority}. */
public static final int PRIORITY_HIGHEST = 10000;
}
/**
* Contains the base classes for callbacks.
*
* <p>For historical reasons, {@link de.robv.android.xposed.XC_MethodHook} and
* {@link de.robv.android.xposed.XC_MethodReplacement} are directly in the
* {@code de.robv.android.xposed} package.
*/
package de.robv.android.xposed.callbacks;
package de.robv.android.xposed.services;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import de.robv.android.xposed.SELinuxHelper;
/**
* General definition of a file access service provided by the Xposed framework.
*
* <p>References to a concrete subclass should generally be retrieved from {@link SELinuxHelper}.
*/
public abstract class BaseService {
/** Flag for {@link #checkFileAccess}: Read access. */
public static final int R_OK = 4;
/** Flag for {@link #checkFileAccess}: Write access. */
public static final int W_OK = 2;
/** Flag for {@link #checkFileAccess}: Executable access. */
public static final int X_OK = 1;
/** Flag for {@link #checkFileAccess}: File/directory exists. */
public static final int F_OK = 0;
/**
* Checks whether the services accesses files directly (instead of using IPC).
*
* @return {@code true} in case direct access is possible.
*/
public boolean hasDirectFileAccess() {
return false;
}
/**
* Check whether a file is accessible. SELinux might enforce stricter checks.
*
* @param filename The absolute path of the file to check.
* @param mode The mode for POSIX's {@code access()} function.
* @return The result of the {@code access()} function.
*/
public abstract boolean checkFileAccess(String filename, int mode);
/**
* Check whether a file exists.
*
* @param filename The absolute path of the file to check.
* @return The result of the {@code access()} function.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public boolean checkFileExists(String filename) {
return checkFileAccess(filename, F_OK);
}
/**
* Determine the size and modification time of a file.
*
* @param filename The absolute path of the file to check.
* @return A {@link FileResult} object holding the result.
* @throws IOException In case an error occurred while retrieving the information.
*/
public abstract FileResult statFile(String filename) throws IOException;
/**
* Determine the size time of a file.
*
* @param filename The absolute path of the file to check.
* @return The file size.
* @throws IOException In case an error occurred while retrieving the information.
*/
public long getFileSize(String filename) throws IOException {
return statFile(filename).size;
}
/**
* Determine the size time of a file.
*
* @param filename The absolute path of the file to check.
* @return The file modification time.
* @throws IOException In case an error occurred while retrieving the information.
*/
public long getFileModificationTime(String filename) throws IOException {
return statFile(filename).mtime;
}
/**
* Read a file into memory.
*
* @param filename The absolute path of the file to read.
* @return A {@code byte} array with the file content.
* @throws IOException In case an error occurred while reading the file.
*/
public abstract byte[] readFile(String filename) throws IOException;
/**
* Read a file into memory, but only if it has changed since the last time.
*
* @param filename The absolute path of the file to read.
* @param previousSize File size of last read.
* @param previousTime File modification time of last read.
* @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#content} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file.
*/
public abstract FileResult readFile(String filename, long previousSize, long previousTime) throws IOException;
/**
* Read a file into memory, optionally only if it has changed since the last time.
*
* @param filename The absolute path of the file to read.
* @param offset Number of bytes to skip at the beginning of the file.
* @param length Number of bytes to read (0 means read to end of file).
* @param previousSize Optional: File size of last read.
* @param previousTime Optional: File modification time of last read.
* @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#content} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file.
*/
public abstract FileResult readFile(String filename, int offset, int length,
long previousSize, long previousTime) throws IOException;
/**
* Get a stream to the file content.
* Depending on the service, it may or may not be read completely into memory.
*
* @param filename The absolute path of the file to read.
* @return An {@link InputStream} to the file content.
* @throws IOException In case an error occurred while reading the file.
*/
public InputStream getFileInputStream(String filename) throws IOException {
return new ByteArrayInputStream(readFile(filename));
}
/**
* Get a stream to the file content, but only if it has changed since the last time.
* Depending on the service, it may or may not be read completely into memory.
*
* @param filename The absolute path of the file to read.
* @param previousSize Optional: File size of last read.
* @param previousTime Optional: File modification time of last read.
* @return A {@link FileResult} object holding the result.
* <p>The {@link FileResult#stream} field might be {@code null} if the file
* is unmodified ({@code previousSize} and {@code previousTime} are still valid).
* @throws IOException In case an error occurred while reading the file.
*/
public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
FileResult result = readFile(filename, previousSize, previousTime);
if (result.content == null)
return result;
return new FileResult(new ByteArrayInputStream(result.content), result.size, result.mtime);
}
// ----------------------------------------------------------------------------
/*package*/ BaseService() {}
/*package*/ static void ensureAbsolutePath(String filename) {
if (!filename.startsWith("/")) {
throw new IllegalArgumentException("Only absolute filenames are allowed: " + filename);
}
}
/*package*/ static void throwCommonIOException(int errno, String errorMsg, String filename, String defaultText) throws IOException {
switch (errno) {
case 1: // EPERM
case 13: // EACCES
throw new FileNotFoundException(errorMsg != null ? errorMsg : "Permission denied: " + filename);
case 2: // ENOENT
throw new FileNotFoundException(errorMsg != null ? errorMsg : "No such file or directory: " + filename);
case 12: // ENOMEM
throw new OutOfMemoryError(errorMsg);
case 21: // EISDIR
throw new FileNotFoundException(errorMsg != null ? errorMsg : "Is a directory: " + filename);
default:
throw new IOException(errorMsg != null ? errorMsg : "Error " + errno + defaultText + filename);
}
}
}
package de.robv.android.xposed.services;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import java.io.IOException;
/** @hide */
public final class BinderService extends BaseService {
public static final int TARGET_APP = 0;
public static final int TARGET_SYSTEM = 1;
/**
* Retrieve the binder service running in the specified context.
* @param target Either {@link #TARGET_APP} or {@link #TARGET_SYSTEM}.
* @return A reference to the service.
* @throws IllegalStateException In case the service doesn't exist (should never happen).
*/
public static BinderService getService(int target) {
if (target < 0 || target > sServices.length) {
throw new IllegalArgumentException("Invalid service target " + target);
}
synchronized (sServices) {
if (sServices[target] == null) {
sServices[target] = new BinderService(target);
}
return sServices[target];
}
}
@Override
public boolean checkFileAccess(String filename, int mode) {
ensureAbsolutePath(filename);
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(INTERFACE_TOKEN);
data.writeString(filename);
data.writeInt(mode);
try {
mRemote.transact(ACCESS_FILE_TRANSACTION, data, reply, 0);
} catch (RemoteException e) {
data.recycle();
reply.recycle();
return false;
}
reply.readException();
int result = reply.readInt();
reply.recycle();
data.recycle();
return result == 0;
}
@Override
public FileResult statFile(String filename) throws IOException {
ensureAbsolutePath(filename);
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(INTERFACE_TOKEN);
data.writeString(filename);
try {
mRemote.transact(STAT_FILE_TRANSACTION, data, reply, 0);
} catch (RemoteException e) {
data.recycle();
reply.recycle();
throw new IOException(e);
}
reply.readException();
int errno = reply.readInt();
if (errno != 0)
throwCommonIOException(errno, null, filename, " while retrieving attributes for ");
long size = reply.readLong();
long time = reply.readLong();
reply.recycle();
data.recycle();
return new FileResult(size, time);
}
@Override
public byte[] readFile(String filename) throws IOException {
return readFile(filename, 0, 0, 0, 0).content;
}
@Override
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
return readFile(filename, 0, 0, previousSize, previousTime);
}
@Override
public FileResult readFile(String filename, int offset, int length,
long previousSize, long previousTime) throws IOException {
ensureAbsolutePath(filename);
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(INTERFACE_TOKEN);
data.writeString(filename);
data.writeInt(offset);
data.writeInt(length);
data.writeLong(previousSize);
data.writeLong(previousTime);
try {
mRemote.transact(READ_FILE_TRANSACTION, data, reply, 0);
} catch (RemoteException e) {
data.recycle();
reply.recycle();
throw new IOException(e);
}
reply.readException();
int errno = reply.readInt();
String errorMsg = reply.readString();
long size = reply.readLong();
long time = reply.readLong();
byte[] content = reply.createByteArray();
reply.recycle();
data.recycle();
switch (errno) {
case 0:
return new FileResult(content, size, time);
case 22: // EINVAL
if (errorMsg != null) {
IllegalArgumentException iae = new IllegalArgumentException(errorMsg);
if (offset == 0 && length == 0)
throw new IOException(iae);
else
throw iae;
} else {
throw new IllegalArgumentException("Offset " + offset + " / Length " + length
+ " is out of range for " + filename + " with size " + size);
}
default:
throwCommonIOException(errno, errorMsg, filename, " while reading ");
throw new IllegalStateException(); // not reached
}
}
// ----------------------------------------------------------------------------
private static final String INTERFACE_TOKEN = "de.robv.android.xposed.IXposedService";
private static final int ACCESS_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 2;
private static final int STAT_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 3;
private static final int READ_FILE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION + 4;
private static final String[] SERVICE_NAMES = { "user.xposed.app", "user.xposed.system" };
private static final BinderService[] sServices = new BinderService[2];
private final IBinder mRemote;
private BinderService(int target) {
IBinder binder = ServiceManager.getService(SERVICE_NAMES[target]);
if (binder == null)
throw new IllegalStateException("Service " + SERVICE_NAMES[target] + " does not exist");
this.mRemote = binder;
}
}
package de.robv.android.xposed.services;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/** @hide */
public final class DirectAccessService extends BaseService {
@Override
public boolean hasDirectFileAccess() {
return true;
}
@SuppressWarnings("RedundantIfStatement")
@Override
public boolean checkFileAccess(String filename, int mode) {
File file = new File(filename);
if (mode == F_OK && !file.exists()) return false;
if ((mode & R_OK) != 0 && !file.canRead()) return false;
if ((mode & W_OK) != 0 && !file.canWrite()) return false;
if ((mode & X_OK) != 0 && !file.canExecute()) return false;
return true;
}
@Override
public boolean checkFileExists(String filename) {
return new File(filename).exists();
}
@Override
public FileResult statFile(String filename) throws IOException {
File file = new File(filename);
return new FileResult(file.length(), file.lastModified());
}
@Override
public byte[] readFile(String filename) throws IOException {
File file = new File(filename);
byte content[] = new byte[(int)file.length()];
FileInputStream fis = new FileInputStream(file);
fis.read(content);
fis.close();
return content;
}
@Override
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
File file = new File(filename);
long size = file.length();
long time = file.lastModified();
if (previousSize == size && previousTime == time)
return new FileResult(size, time);
return new FileResult(readFile(filename), size, time);
}
@Override
public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
File file = new File(filename);
long size = file.length();
long time = file.lastModified();
if (previousSize == size && previousTime == time)
return new FileResult(size, time);
// Shortcut for the simple case
if (offset <= 0 && length <= 0)
return new FileResult(readFile(filename), size, time);
// Check range
if (offset > 0 && offset >= size) {
throw new IllegalArgumentException("Offset " + offset + " is out of range for " + filename);
} else if (offset < 0) {
offset = 0;
}
if (length > 0 && (offset + length) > size) {
throw new IllegalArgumentException("Length " + length + " is out of range for " + filename);
} else if (length <= 0) {
length = (int) (size - offset);
}
byte content[] = new byte[length];
FileInputStream fis = new FileInputStream(file);
fis.skip(offset);
fis.read(content);
fis.close();
return new FileResult(content, size, time);
}
/**
* {@inheritDoc}
* <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
*/
@Override
public InputStream getFileInputStream(String filename) throws IOException {
return new BufferedInputStream(new FileInputStream(filename), 16*1024);
}
/**
* {@inheritDoc}
* <p>This implementation returns a BufferedInputStream instead of loading the file into memory.
*/
@Override
public FileResult getFileInputStream(String filename, long previousSize, long previousTime) throws IOException {
File file = new File(filename);
long size = file.length();
long time = file.lastModified();
if (previousSize == size && previousTime == time)
return new FileResult(size, time);
return new FileResult(new BufferedInputStream(new FileInputStream(filename), 16*1024), size, time);
}
}
package de.robv.android.xposed.services;
import java.io.InputStream;
/**
* Holder for the result of a {@link BaseService#readFile} or {@link BaseService#statFile} call.
*/
public final class FileResult {
/** File content, might be {@code null} if the file wasn't read. */
public final byte[] content;
/** File input stream, might be {@code null} if the file wasn't read. */
public final InputStream stream;
/** File size. */
public final long size;
/** File last modification time. */
public final long mtime;
/*package*/ FileResult(long size, long mtime) {
this.content = null;
this.stream = null;
this.size = size;
this.mtime = mtime;
}
/*package*/ FileResult(byte[] content, long size, long mtime) {
this.content = content;
this.stream = null;
this.size = size;
this.mtime = mtime;
}
/*package*/ FileResult(InputStream stream, long size, long mtime) {
this.content = null;
this.stream = stream;
this.size = size;
this.mtime = mtime;
}
/** @hide */
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
if (content != null) {
sb.append("content.length: ");
sb.append(content.length);
sb.append(", ");
}
if (stream != null) {
sb.append("stream: ");
sb.append(stream.toString());
sb.append(", ");
}
sb.append("size: ");
sb.append(size);
sb.append(", mtime: ");
sb.append(mtime);
sb.append("}");
return sb.toString();
}
}
package de.robv.android.xposed.services;
import java.io.IOException;
import java.util.Arrays;
/** @hide */
@SuppressWarnings("JniMissingFunction")
public final class ZygoteService extends BaseService {
@Override
public native boolean checkFileAccess(String filename, int mode);
@Override
public native FileResult statFile(String filename) throws IOException;
@Override
public native byte[] readFile(String filename) throws IOException;
@Override
// Just for completeness, we don't expect this to be called often in Zygote.
public FileResult readFile(String filename, long previousSize, long previousTime) throws IOException {
FileResult stat = statFile(filename);
if (previousSize == stat.size && previousTime == stat.mtime)
return stat;
return new FileResult(readFile(filename), stat.size, stat.mtime);
}
@Override
// Just for completeness, we don't expect this to be called often in Zygote.
public FileResult readFile(String filename, int offset, int length, long previousSize, long previousTime) throws IOException {
FileResult stat = statFile(filename);
if (previousSize == stat.size && previousTime == stat.mtime)
return stat;
// Shortcut for the simple case
if (offset <= 0 && length <= 0)
return new FileResult(readFile(filename), stat.size, stat.mtime);
// Check range
if (offset > 0 && offset >= stat.size) {
throw new IllegalArgumentException("offset " + offset + " >= size " + stat.size + " for " + filename);
} else if (offset < 0) {
offset = 0;
}
if (length > 0 && (offset + length) > stat.size) {
throw new IllegalArgumentException("offset " + offset + " + length " + length + " > size " + stat.size + " for " + filename);
} else if (length <= 0) {
length = (int) (stat.size - offset);
}
byte[] content = readFile(filename);
return new FileResult(Arrays.copyOfRange(content, offset, offset + length), stat.size, stat.mtime);
}
}
/**
* Contains file access services provided by the Xposed framework.
*/
package de.robv.android.xposed.services;
<resources>
<string name="app_name">xposedcompat</string>
</resources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment