Update API + Partial separate hook bridge

This commit is contained in:
Nullptr 2023-08-16 09:46:22 +08:00 committed by Wang Han
parent 99f3217237
commit e8e9105598
8 changed files with 431 additions and 878 deletions

View File

@ -119,8 +119,8 @@ public abstract class XC_MethodHook extends XCallback {
*/ */
public Object[] args; public Object[] args;
private Object result = null; public Object result = null;
private Throwable throwable = null; public Throwable throwable = null;
public boolean returnEarly = false; public boolean returnEarly = false;
private final HashMap<String, Object> extras = new HashMap<>(); private final HashMap<String, Object> extras = new HashMap<>();

View File

@ -25,6 +25,8 @@ import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.util.Log; 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.HookBridge;
import org.lsposed.lspd.nativebridge.ResourcesHook; import org.lsposed.lspd.nativebridge.ResourcesHook;
@ -35,10 +37,8 @@ import java.lang.reflect.Member;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.ArrayDeque;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
@ -81,20 +81,6 @@ public final class XposedBridge {
public static volatile ClassLoader dummyClassLoader = null; 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() { public static void initXResources() {
if (dummyClassLoader != null) { if (dummyClassLoader != null) {
return; return;
@ -221,7 +207,7 @@ public final class XposedBridge {
throw new IllegalArgumentException("callback should not be null!"); 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); log("Failed to hook " + hookMethod);
return null; return null;
} }
@ -397,70 +383,25 @@ public final class XposedBridge {
} }
} }
public static class AdditionalHookInfo<T extends Executable> { public static class LegacyApiSupport<T extends Executable> {
private final Object params; private final XC_MethodHook.MethodHookParam<T> param;
private final LSPosedHookCallback<T> callback;
private final Object[] snapshot;
private AdditionalHookInfo(Executable method) { private int beforeIdx;
var isStatic = Modifier.isStatic(method.getModifiers());
Object returnType; public LegacyApiSupport(LSPosedHookCallback<T> callback, Object[] legacySnapshot) {
if (method instanceof Method) { this.param = new XC_MethodHook.MethodHookParam<>();
returnType = ((Method) method).getReturnType(); this.callback = callback;
} else { this.snapshot = legacySnapshot;
returnType = null;
}
params = new Object[]{
method,
returnType,
isStatic,
};
} }
// This method is quite critical. We should try not to use system methods to avoid public void handleBefore() {
// endless recursive syncronizeApi(param, callback, true);
public Object callback(Object[] args) throws Throwable { for (beforeIdx = 0; beforeIdx < snapshot.length; beforeIdx++) {
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) {
try { try {
return HookBridge.invokeOriginalMethod(method, param.thisObject, param.args); var cb = (XC_MethodHook) snapshot[beforeIdx];
} catch (InvocationTargetException ite) { cb.beforeHookedMethod(param);
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));
}
} catch (Throwable t) { } catch (Throwable t) {
XposedBridge.log(t); XposedBridge.log(t);
@ -475,62 +416,48 @@ public final class XposedBridge {
beforeIdx++; beforeIdx++;
break; 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 public void handleAfter() {
int afterIdx = beforeIdx - 1; syncronizeApi(param, callback, true);
do { for (int afterIdx = beforeIdx - 1; afterIdx >= 0; afterIdx--) {
Object lastResult = param.getResult(); Object lastResult = param.getResult();
Throwable lastThrowable = param.getThrowable(); Throwable lastThrowable = param.getThrowable();
var cb = callbacksSnapshot[afterIdx];
try { try {
if (HookBridge.instanceOf(cb, XC_MethodHook.class)) { var cb = (XC_MethodHook) snapshot[afterIdx];
((XC_MethodHook) cb).afterHookedMethod(param); cb.afterHookedMethod(param);
} else if (HookBridge.instanceOf(cb, HookerCallback.class)) {
var hooker = (HookerCallback) cb;
hooker.afterInvocation.invoke(null, extras.poll(), lastResult);
}
} catch (Throwable t) { } catch (Throwable t) {
XposedBridge.log(t); XposedBridge.log(t);
// reset to last result (ignoring what the unexpectedly exiting callback did) // reset to last result (ignoring what the unexpectedly exiting callback did)
if (lastThrowable == null) if (lastThrowable == null) {
param.setResult(lastResult); param.setResult(lastResult);
else } else {
param.setThrowable(lastThrowable); param.setThrowable(lastThrowable);
}
} }
} while (--afterIdx >= 0); }
syncronizeApi(param, callback, false);
}
// return private void syncronizeApi(XC_MethodHook.MethodHookParam<T> param, LSPosedHookCallback<T> callback, boolean forward) {
if (param.hasThrowable()) if (forward) {
throw param.getThrowable(); param.method = callback.method;
else { param.thisObject = callback.thisObject;
var result = param.getResult(); param.args = callback.args;
if (returnType != null && !returnType.isPrimitive() && !HookBridge.instanceOf(result, returnType)) { param.result = callback.result;
throw new ClassCastException(castException); param.throwable = callback.throwable;
} param.returnEarly = callback.isSkipped;
return result; } 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;
} }
} }
} }
public static class HookerCallback {
Method beforeInvocation;
Method afterInvocation;
public HookerCallback(Method beforeInvocation, Method afterInvocation) {
this.beforeInvocation = beforeInvocation;
this.afterInvocation = afterInvocation;
}
}
} }

View File

@ -135,7 +135,7 @@ public class LoadedApkGetCLHooker extends XC_MethodHook {
@NonNull @NonNull
@Override @Override
public ApplicationInfo getAppInfo() { public ApplicationInfo getApplicationInfo() {
return loadedApk.getApplicationInfo(); return loadedApk.getApplicationInfo();
} }

View File

@ -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;
}
}
}
}

View File

@ -2,38 +2,13 @@ package org.lsposed.lspd.impl;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ActivityThread; 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.SharedPreferences;
import android.content.pm.ApplicationInfo; 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.Build;
import android.os.Bundle; import android.os.ParcelFileDescriptor;
import android.os.Handler;
import android.os.Looper;
import android.os.Process; import android.os.Process;
import android.os.RemoteException; import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log; import android.util.Log;
import android.view.Display;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -47,15 +22,11 @@ import org.lsposed.lspd.service.ILSPInjectedModuleService;
import org.lsposed.lspd.util.LspModuleClassLoader; import org.lsposed.lspd.util.LspModuleClassLoader;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.Executable; import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
@ -65,9 +36,6 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; 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.XposedInterface;
import io.github.libxposed.api.XposedModule; import io.github.libxposed.api.XposedModule;
import io.github.libxposed.api.XposedModuleInterface; import io.github.libxposed.api.XposedModuleInterface;
@ -75,14 +43,14 @@ import io.github.libxposed.api.annotations.AfterInvocation;
import io.github.libxposed.api.annotations.BeforeInvocation; import io.github.libxposed.api.annotations.BeforeInvocation;
import io.github.libxposed.api.annotations.XposedHooker; import io.github.libxposed.api.annotations.XposedHooker;
import io.github.libxposed.api.errors.HookFailedError; import io.github.libxposed.api.errors.HookFailedError;
import io.github.libxposed.api.errors.XposedFrameworkError;
import io.github.libxposed.api.utils.DexParser; import io.github.libxposed.api.utils.DexParser;
@SuppressLint("NewApi") @SuppressLint("NewApi")
public class LSPosedContext extends XposedContext { public class LSPosedContext implements XposedInterface {
private static final String TAG = "LSPosedContext"; private static final String TAG = "LSPosedContext";
private static final String REMOTE_PREFIX = "remote://";
public static boolean isSystemServer; public static boolean isSystemServer;
public static String appDir; public static String appDir;
@ -90,18 +58,14 @@ public class LSPosedContext extends XposedContext {
static final Set<XposedModule> modules = ConcurrentHashMap.newKeySet(); static final Set<XposedModule> modules = ConcurrentHashMap.newKeySet();
private final Object mSync = new Object();
private final Context mBase;
private final String mPackageName; private final String mPackageName;
private final String mApkPath; private final ApplicationInfo mApplicationInfo;
private final ILSPInjectedModuleService service; private final ILSPInjectedModuleService service;
private final Map<String, SharedPreferences> mRemotePrefs = new ConcurrentHashMap<>(); private final Map<String, SharedPreferences> mRemotePrefs = new ConcurrentHashMap<>();
LSPosedContext(Context base, String packageName, String apkPath, ILSPInjectedModuleService service) { LSPosedContext(String packageName, ApplicationInfo applicationInfo, ILSPInjectedModuleService service) {
this.mBase = base;
this.mPackageName = packageName; this.mPackageName = packageName;
this.mApkPath = apkPath; this.mApplicationInfo = applicationInfo;
this.service = service; this.service = service;
} }
@ -110,7 +74,7 @@ public class LSPosedContext extends XposedContext {
try { try {
module.onPackageLoaded(param); module.onPackageLoaded(param);
} catch (Throwable t) { } 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 { try {
module.onSystemServerLoaded(param); module.onSystemServerLoaded(param);
} catch (Throwable t) { } 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."); Log.e(TAG, " This may cause strange issues and must be fixed by the module developer.");
return false; return false;
} }
module.applicationInfo.packageName = module.packageName; // Just in case var ctx = new LSPosedContext(module.packageName, module.applicationInfo, module.service);
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);
}
});
for (var entry : module.file.moduleClassNames) { for (var entry : module.file.moduleClassNames) {
var moduleClass = ctx.getClassLoader().loadClass(entry); var moduleClass = mcl.loadClass(entry);
Log.d(TAG, " Loading class " + moduleClass); Log.d(TAG, " Loading class " + moduleClass);
if (!XposedModule.class.isAssignableFrom(moduleClass)) { if (!XposedModule.class.isAssignableFrom(moduleClass)) {
Log.e(TAG, " This class doesn't implement any sub-interface of XposedModule, skipping it"); Log.e(TAG, " This class doesn't implement any sub-interface of XposedModule, skipping it");
continue; continue;
} }
try { 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() { var moduleContext = (XposedModule) moduleEntry.newInstance(ctx, new XposedModuleInterface.ModuleLoadedParam() {
@Override @Override
public boolean isSystemServer() { public boolean isSystemServer() {
@ -227,622 +143,6 @@ public class LSPosedContext extends XposedContext {
return true; 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 @NonNull
@Override @Override
public String getFrameworkName() { public String getFrameworkName() {
@ -891,8 +191,7 @@ public class LSPosedContext extends XposedContext {
} }
boolean valid; boolean valid;
valid = (method.getModifiers() & modifiers) == modifiers; valid = (method.getModifiers() & modifiers) == modifiers;
valid &= Arrays.equals(method.getParameterTypes(), new Class[]{Member.class, Object.class, Object[].class}); valid &= Arrays.equals(method.getParameterTypes(), new Class[]{BeforeHookCallback.class});
valid &= method.getReturnType().equals(Hooker.class);
if (!valid) { if (!valid) {
throw new IllegalArgumentException("BeforeInvocation method format is invalid"); throw new IllegalArgumentException("BeforeInvocation method format is invalid");
} }
@ -904,7 +203,6 @@ public class LSPosedContext extends XposedContext {
} }
boolean valid; boolean valid;
valid = (method.getModifiers() & modifiers) == modifiers; valid = (method.getModifiers() & modifiers) == modifiers;
valid &= Arrays.equals(method.getParameterTypes(), new Class[]{Hooker.class, Object.class});
valid &= method.getReturnType().equals(void.class); valid &= method.getReturnType().equals(void.class);
if (!valid) { if (!valid) {
throw new IllegalArgumentException("AfterInvocation method format is invalid"); throw new IllegalArgumentException("AfterInvocation method format is invalid");
@ -918,9 +216,19 @@ public class LSPosedContext extends XposedContext {
if (afterInvocation == null) { if (afterInvocation == null) {
throw new IllegalArgumentException("No method annotated with @AfterInvocation"); 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); var callback = new LSPosedBridge.HookerCallback(beforeInvocation, afterInvocation);
if (HookBridge.hookMethod(true, hookMethod, XposedBridge.AdditionalHookInfo.class, priority, callback)) { if (HookBridge.hookMethod(true, hookMethod, LSPosedBridge.NativeHooker.class, priority, callback)) {
return new MethodUnhooker<>() { return new MethodUnhooker<>() {
@NonNull @NonNull
@Override @Override
@ -1063,4 +371,46 @@ public class LSPosedContext extends XposedContext {
public DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException { public DexParser parseDex(@NonNull ByteBuffer dexData, boolean includeAnnotations) throws IOException {
return new LSPosedDexParser(dexData, includeAnnotations); 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());
}
}
} }

View File

@ -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;
}
}

View File

@ -24,5 +24,5 @@ public class HookBridge {
@FastNative @FastNative
public static native boolean setTrusted(Object cookie); 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);
} }

View File

@ -29,19 +29,14 @@
using namespace lsplant; using namespace lsplant;
namespace { namespace {
struct CallbackType { struct ModuleCallback {
bool use_modern_api; jmethodID before_method;
union { jmethodID after_method;
jobject callback_object;
struct {
jmethodID before_method;
jmethodID after_method;
};
};
}; };
struct HookItem { 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: private:
std::atomic<jobject> backup {nullptr}; std::atomic<jobject> backup {nullptr};
static_assert(decltype(backup)::is_always_lock_free); 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 before_method = JNI_GetObjectField(env, callback, before_method_field);
auto after_method = JNI_GetObjectField(env, callback, after_method_field); auto after_method = JNI_GetObjectField(env, callback, after_method_field);
auto callback_type = CallbackType { auto callback_type = ModuleCallback {
.use_modern_api = true,
.before_method = env->FromReflectedMethod(before_method), .before_method = env->FromReflectedMethod(before_method),
.after_method = env->FromReflectedMethod(after_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 { } else {
auto callback_type = CallbackType { hook_item->legacy_callbacks.emplace(priority, env->NewGlobalRef(callback));
.use_modern_api = false,
.callback_object = env->NewGlobalRef(callback),
};
hook_item->callbacks.emplace(std::make_pair(priority, callback_type));
} }
return JNI_TRUE; return JNI_TRUE;
} }
@ -159,20 +149,19 @@ LSP_DEF_NATIVE_METHOD(jboolean, HookBridge, unhookMethod, jboolean useModernApi,
jobject backup = hook_item->GetBackup(); jobject backup = hook_item->GetBackup();
if (!backup) return JNI_FALSE; if (!backup) return JNI_FALSE;
JNIMonitor monitor(env, backup); JNIMonitor monitor(env, backup);
jmethodID before = nullptr;
if (useModernApi) { if (useModernApi) {
auto before_method = JNI_GetObjectField(env, callback, before_method_field); auto before_method = JNI_GetObjectField(env, callback, before_method_field);
before = env->FromReflectedMethod(before_method); auto before = env->FromReflectedMethod(before_method);
} for (auto i = hook_item->modern_callbacks.begin(); i != hook_item->modern_callbacks.end(); ++i) {
for (auto i = hook_item->callbacks.begin(); i != hook_item->callbacks.end(); ++i) { if (before == i->second.before_method) {
if (useModernApi) { hook_item->modern_callbacks.erase(i);
if (i->second.use_modern_api && before == i->second.before_method) {
hook_item->callbacks.erase(i);
return JNI_TRUE; return JNI_TRUE;
} }
} else { }
if (!i->second.use_modern_api && env->IsSameObject(i->second.callback_object, callback)) { } else {
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; return JNI_TRUE;
} }
} }
@ -328,17 +317,21 @@ LSP_DEF_NATIVE_METHOD(jobjectArray, HookBridge, callbackSnapshot, jclass callbac
jobject backup = hook_item->GetBackup(); jobject backup = hook_item->GetBackup();
if (!backup) return nullptr; if (!backup) return nullptr;
JNIMonitor monitor(env, backup); 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) { auto res = env->NewObjectArray(2, env->FindClass("[Ljava/lang/Object;"), nullptr);
if (callback.second.use_modern_api) { auto modern = env->NewObjectArray((jsize) hook_item->modern_callbacks.size(), env->FindClass("java/lang/Object"), nullptr);
auto before_method = JNI_ToReflectedMethod(env, clazz, callback.second.before_method, JNI_TRUE); auto legacy = env->NewObjectArray((jsize) hook_item->legacy_callbacks.size(), env->FindClass("java/lang/Object"), nullptr);
auto after_method = JNI_ToReflectedMethod(env, clazz, callback.second.after_method, JNI_TRUE); for (jsize i = 0; auto callback: hook_item->modern_callbacks) {
auto callback_object = JNI_NewObject(env, callback_class, callback_ctor, before_method, after_method); auto before_method = JNI_ToReflectedMethod(env, clazz, callback.second.before_method, JNI_TRUE);
env->SetObjectArrayElement(res, i++, env->NewLocalRef(callback_object)); auto after_method = JNI_ToReflectedMethod(env, clazz, callback.second.after_method, JNI_TRUE);
} else { auto callback_object = JNI_NewObject(env, callback_class, callback_ctor, before_method, after_method);
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; return res;
} }
@ -351,7 +344,7 @@ static JNINativeMethod gMethods[] = {
LSP_NATIVE_METHOD(HookBridge, allocateObject, "(Ljava/lang/Class;)Ljava/lang/Object;"), 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, instanceOf, "(Ljava/lang/Object;Ljava/lang/Class;)Z"),
LSP_NATIVE_METHOD(HookBridge, setTrusted, "(Ljava/lang/Object;)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) { void RegisterHookBridge(JNIEnv *env) {