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