From d61aa69cfb0678a5761b749cf768d9be813601ee Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Fri, 13 Feb 2026 17:01:16 +0100 Subject: [PATCH 1/3] Implement constructor invocation APIs in LSPosedContext (#533) Unlike the existing `newInstance` variants which allocate and return a new object, these new APIs execute constructor logic on an existing, pre-allocated instance (`thisObject`). This separation of allocation and initialization allows for invoking original or super constructors within hook callbacks where the object reference is already established. The implementation leverages the existing JNI `HookBridge` methods, as `invokeOriginalMethod` and `invokeSpecialMethod` already support void-return signatures required for constructor execution. --- .../java/org/lsposed/lspd/impl/LSPosedContext.java | 13 ++++++++++++- xposed/libxposed | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java index 3fde28020..9551bbac7 100644 --- a/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedContext.java @@ -210,10 +210,16 @@ public boolean deoptimize(@NonNull Constructor constructor) { @Nullable @Override - public Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object[] args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + public Object invokeOrigin(@NonNull Method method, @Nullable Object thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { return HookBridge.invokeOriginalMethod(method, thisObject, args); } + @Override + public void invokeOrigin(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + // The bridge returns an Object (null for void/constructors), which we discard. + HookBridge.invokeOriginalMethod(constructor, thisObject, args); + } + private static char getTypeShorty(Class type) { if (type == int.class) { return 'I'; @@ -257,6 +263,11 @@ public Object invokeSpecial(@NonNull Method method, @NonNull Object thisObject, return HookBridge.invokeSpecialMethod(method, getExecutableShorty(method), method.getDeclaringClass(), thisObject, args); } + @Override + public void invokeSpecial(@NonNull Constructor constructor, @NonNull T thisObject, Object... args) throws InvocationTargetException, IllegalArgumentException, IllegalAccessException { + HookBridge.invokeSpecialMethod(constructor, getExecutableShorty(constructor), constructor.getDeclaringClass(), thisObject, args); + } + @NonNull @Override public T newInstanceOrigin(@NonNull Constructor constructor, Object... args) throws InvocationTargetException, IllegalAccessException, InstantiationException { diff --git a/xposed/libxposed b/xposed/libxposed index 545827303..55efdf9d1 160000 --- a/xposed/libxposed +++ b/xposed/libxposed @@ -1 +1 @@ -Subproject commit 54582730315ba4a3d7cfaf9baf9d23c419e07006 +Subproject commit 55efdf9d159195261d7326e9e125965a90025a12 From 40f09cf1c765b49ee1641101f471ded5297f5c30 Mon Sep 17 00:00:00 2001 From: JingMatrix Date: Fri, 13 Feb 2026 18:27:16 +0100 Subject: [PATCH 2/3] Adapt LSPosedBridge to convention-based hooker discovery (#534) We update `LSPosedBridge` to align with upstream API changes, which have replaced annotation-based hooker discovery with a naming convention. The `doHook` implementation has been refactored to: - Remove dependencies on the deleted `io.github.libxposed.api.annotations` package (`XposedHooker`, `BeforeInvocation`, `AfterInvocation`). - Scan for public static methods explicitly named `before` and `after` instead of relying on annotations. - Enforce validation on these named methods to ensure they match the required signatures. To adapt to this change, existing Hooker classes are refactored by removing the deprecated annotations and renaming their callback methods to `before` and `after` respectively. --- core/proguard-rules.pro | 9 +++++--- .../org/lsposed/lspd/hooker/AttachHooker.java | 6 +---- .../lsposed/lspd/hooker/CrashDumpHooker.java | 6 +---- .../HandleSystemServerProcessHooker.java | 6 +---- .../lspd/hooker/LoadedApkCreateCLHooker.java | 6 +---- .../lspd/hooker/LoadedApkCtorHooker.java | 6 +---- .../lspd/hooker/OpenDexFileHooker.java | 6 +---- .../hooker/StartBootstrapServicesHooker.java | 6 +---- .../org/lsposed/lspd/impl/LSPosedBridge.java | 22 +++++++------------ .../util/ParasiticManagerSystemHooker.java | 6 +---- xposed/libxposed | 2 +- 11 files changed, 23 insertions(+), 58 deletions(-) diff --git a/core/proguard-rules.pro b/core/proguard-rules.pro index 4638bb630..844704e3c 100644 --- a/core/proguard-rules.pro +++ b/core/proguard-rules.pro @@ -22,9 +22,12 @@ -keepclassmembers class org.lsposed.lspd.impl.LSPosedHookCallback { public ; } --keep,allowoptimization,allowobfuscation @io.github.libxposed.api.annotations.* class * { - @io.github.libxposed.api.annotations.BeforeInvocation ; - @io.github.libxposed.api.annotations.AfterInvocation ; +-keepclassmembers,allowoptimization class ** implements io.github.libxposed.api.XposedInterface$Hooker { + public static *** before(); + public static *** before(io.github.libxposed.api.XposedInterface$BeforeHookCallback); + public static void after(); + public static void after(io.github.libxposed.api.XposedInterface$AfterHookCallback); + public static void after(io.github.libxposed.api.XposedInterface$AfterHookCallback, ***); } -assumenosideeffects class android.util.Log { public static *** v(...); diff --git a/core/src/main/java/org/lsposed/lspd/hooker/AttachHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/AttachHooker.java index b003ac2f5..3e77ba156 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/AttachHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/AttachHooker.java @@ -4,14 +4,10 @@ import de.robv.android.xposed.XposedInit; import io.github.libxposed.api.XposedInterface; -import io.github.libxposed.api.annotations.AfterInvocation; -import io.github.libxposed.api.annotations.XposedHooker; -@XposedHooker public class AttachHooker implements XposedInterface.Hooker { - @AfterInvocation - public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) { + public static void after(XposedInterface.AfterHookCallback callback) { XposedInit.loadModules((ActivityThread) callback.getThisObject()); } } diff --git a/core/src/main/java/org/lsposed/lspd/hooker/CrashDumpHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/CrashDumpHooker.java index ea94b132c..53ab5c0c7 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/CrashDumpHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/CrashDumpHooker.java @@ -4,14 +4,10 @@ import org.lsposed.lspd.util.Utils.Log; import io.github.libxposed.api.XposedInterface; -import io.github.libxposed.api.annotations.BeforeInvocation; -import io.github.libxposed.api.annotations.XposedHooker; -@XposedHooker public class CrashDumpHooker implements XposedInterface.Hooker { - @BeforeInvocation - public static void beforeHookedMethod(XposedInterface.BeforeHookCallback callback) { + public static void before(XposedInterface.BeforeHookCallback callback) { try { var e = (Throwable) callback.getArgs()[0]; LSPosedBridge.log("Crash unexpectedly: " + Log.getStackTraceString(e)); diff --git a/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java index 4552153e3..0a67995cd 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/HandleSystemServerProcessHooker.java @@ -27,11 +27,8 @@ import org.lsposed.lspd.util.Hookers; import io.github.libxposed.api.XposedInterface; -import io.github.libxposed.api.annotations.AfterInvocation; -import io.github.libxposed.api.annotations.XposedHooker; // system_server initialization -@XposedHooker public class HandleSystemServerProcessHooker implements XposedInterface.Hooker { public interface Callback { @@ -42,8 +39,7 @@ public interface Callback { public static volatile Callback callback = null; @SuppressLint("PrivateApi") - @AfterInvocation - public static void afterHookedMethod() { + public static void after() { Hookers.logD("ZygoteInit#handleSystemServerProcess() starts"); try { // get system_server classLoader diff --git a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCreateCLHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCreateCLHooker.java index 161c730eb..b5c1bec08 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCreateCLHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCreateCLHooker.java @@ -51,11 +51,8 @@ import de.robv.android.xposed.callbacks.XC_LoadPackage; import io.github.libxposed.api.XposedInterface; import io.github.libxposed.api.XposedModuleInterface; -import io.github.libxposed.api.annotations.AfterInvocation; -import io.github.libxposed.api.annotations.XposedHooker; @SuppressLint("BlockedPrivateApi") -@XposedHooker public class LoadedApkCreateCLHooker implements XposedInterface.Hooker { private final static Field defaultClassLoaderField; @@ -77,8 +74,7 @@ static void addLoadedApk(LoadedApk loadedApk) { loadedApks.add(loadedApk); } - @AfterInvocation - public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) { + public static void after(XposedInterface.AfterHookCallback callback) { LoadedApk loadedApk = (LoadedApk) callback.getThisObject(); if (callback.getArgs()[0] != null || !loadedApks.contains(loadedApk)) { diff --git a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCtorHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCtorHooker.java index e84e3d1d6..e0a4af96e 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCtorHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/LoadedApkCtorHooker.java @@ -29,15 +29,11 @@ import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedInit; import io.github.libxposed.api.XposedInterface; -import io.github.libxposed.api.annotations.AfterInvocation; -import io.github.libxposed.api.annotations.XposedHooker; // when a package is loaded for an existing process, trigger the callbacks as well -@XposedHooker public class LoadedApkCtorHooker implements XposedInterface.Hooker { - @AfterInvocation - public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) { + public static void after(XposedInterface.AfterHookCallback callback) { Hookers.logD("LoadedApk# starts"); try { diff --git a/core/src/main/java/org/lsposed/lspd/hooker/OpenDexFileHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/OpenDexFileHooker.java index af6f70a2d..acbef26d0 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/OpenDexFileHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/OpenDexFileHooker.java @@ -6,14 +6,10 @@ import org.lsposed.lspd.nativebridge.HookBridge; import io.github.libxposed.api.XposedInterface; -import io.github.libxposed.api.annotations.AfterInvocation; -import io.github.libxposed.api.annotations.XposedHooker; -@XposedHooker public class OpenDexFileHooker implements XposedInterface.Hooker { - @AfterInvocation - public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) { + public static void after(XposedInterface.AfterHookCallback callback) { ClassLoader classLoader = null; for (var arg : callback.getArgs()) { if (arg instanceof ClassLoader) { diff --git a/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java b/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java index 30bf835d3..3594095fa 100644 --- a/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java +++ b/core/src/main/java/org/lsposed/lspd/hooker/StartBootstrapServicesHooker.java @@ -32,14 +32,10 @@ import de.robv.android.xposed.callbacks.XC_LoadPackage; import io.github.libxposed.api.XposedInterface; import io.github.libxposed.api.XposedModuleInterface; -import io.github.libxposed.api.annotations.BeforeInvocation; -import io.github.libxposed.api.annotations.XposedHooker; -@XposedHooker public class StartBootstrapServicesHooker implements XposedInterface.Hooker { - @BeforeInvocation - public static void beforeHookedMethod() { + public static void before() { logD("SystemServer#startBootstrapServices() starts"); try { diff --git a/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java b/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java index 27c7ce8ce..8dd201dd6 100644 --- a/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java +++ b/core/src/main/java/org/lsposed/lspd/impl/LSPosedBridge.java @@ -12,9 +12,6 @@ import de.robv.android.xposed.XposedBridge; import io.github.libxposed.api.XposedInterface; -import io.github.libxposed.api.annotations.AfterInvocation; -import io.github.libxposed.api.annotations.BeforeInvocation; -import io.github.libxposed.api.annotations.XposedHooker; import io.github.libxposed.api.errors.HookFailedError; public class LSPosedBridge { @@ -218,16 +215,14 @@ public static void dummyCallback() { throw new IllegalArgumentException("Cannot hook Method.invoke"); } else if (hooker == null) { throw new IllegalArgumentException("hooker should not be null!"); - } else if (hooker.getAnnotation(XposedHooker.class) == null) { - throw new IllegalArgumentException("Hooker should be annotated with @XposedHooker"); } Method beforeInvocation = null, afterInvocation = null; var modifiers = Modifier.PUBLIC | Modifier.STATIC; for (var method : hooker.getDeclaredMethods()) { - if (method.getAnnotation(BeforeInvocation.class) != null) { + if (method.getName().equals("before")) { if (beforeInvocation != null) { - throw new IllegalArgumentException("More than one method annotated with @BeforeInvocation"); + throw new IllegalArgumentException("More than one method named before"); } boolean valid = (method.getModifiers() & modifiers) == modifiers; var params = method.getParameterTypes(); @@ -237,13 +232,12 @@ public static void dummyCallback() { valid = false; } if (!valid) { - throw new IllegalArgumentException("BeforeInvocation method format is invalid"); + throw new IllegalArgumentException("before method format is invalid"); } beforeInvocation = method; - } - if (method.getAnnotation(AfterInvocation.class) != null) { + } else if (method.getName().equals("after")) { if (afterInvocation != null) { - throw new IllegalArgumentException("More than one method annotated with @AfterInvocation"); + throw new IllegalArgumentException("More than one method named after"); } boolean valid = (method.getModifiers() & modifiers) == modifiers; valid &= method.getReturnType().equals(void.class); @@ -254,13 +248,13 @@ public static void dummyCallback() { valid = false; } if (!valid) { - throw new IllegalArgumentException("AfterInvocation method format is invalid"); + throw new IllegalArgumentException("after method format is invalid"); } afterInvocation = method; } } if (beforeInvocation == null && afterInvocation == null) { - throw new IllegalArgumentException("No method annotated with @BeforeInvocation or @AfterInvocation"); + throw new IllegalArgumentException("No method named before or after found in " + hooker.getName()); } try { if (beforeInvocation == null) { @@ -271,7 +265,7 @@ public static void dummyCallback() { var ret = beforeInvocation.getReturnType(); var params = afterInvocation.getParameterTypes(); if (ret != void.class && params.length == 2 && !ret.equals(params[1])) { - throw new IllegalArgumentException("BeforeInvocation and AfterInvocation method format is invalid"); + throw new IllegalArgumentException("before and after method format is invalid"); } } } catch (NoSuchMethodException e) { diff --git a/magisk-loader/src/main/java/org/lsposed/lspd/util/ParasiticManagerSystemHooker.java b/magisk-loader/src/main/java/org/lsposed/lspd/util/ParasiticManagerSystemHooker.java index 578627a85..1a203916e 100644 --- a/magisk-loader/src/main/java/org/lsposed/lspd/util/ParasiticManagerSystemHooker.java +++ b/magisk-loader/src/main/java/org/lsposed/lspd/util/ParasiticManagerSystemHooker.java @@ -12,8 +12,6 @@ import org.lsposed.lspd.util.Utils; import io.github.libxposed.api.XposedInterface; -import io.github.libxposed.api.annotations.AfterInvocation; -import io.github.libxposed.api.annotations.XposedHooker; public class ParasiticManagerSystemHooker implements HandleSystemServerProcessHooker.Callback { @@ -33,10 +31,8 @@ public static void beforeHookedMethod(XposedInterface.BeforeHookCallback callbac } }*/ - @XposedHooker private static class Hooker implements XposedInterface.Hooker { - @AfterInvocation - public static void afterHookedMethod(XposedInterface.AfterHookCallback callback) throws Throwable { + public static void after(XposedInterface.AfterHookCallback callback) throws Throwable { var intent = (Intent) callback.getArgs()[0]; if (intent == null) return; if (!intent.hasCategory("org.lsposed.manager.LAUNCH_MANAGER")) return; diff --git a/xposed/libxposed b/xposed/libxposed index 55efdf9d1..64e29bd65 160000 --- a/xposed/libxposed +++ b/xposed/libxposed @@ -1 +1 @@ -Subproject commit 55efdf9d159195261d7326e9e125965a90025a12 +Subproject commit 64e29bd657ef4d2540b34402f5a988778f29e676 From ae0d4b9e3ab7ee04455e7b3a0b932e6652bbace1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 17:28:19 +0000 Subject: [PATCH 3/3] Bump the submodule group with 3 updates Bumps the submodule group with 3 updates: [external/fmt](https://github.com/fmtlib/fmt), [xposed/libxposed](https://github.com/libxposed/api) and [external/apache/commons-lang](https://github.com/apache/commons-lang). Updates `external/fmt` from `0e078f6` to `e55a02b` - [Release notes](https://github.com/fmtlib/fmt/releases) - [Commits](https://github.com/fmtlib/fmt/compare/0e078f6ed0624be8babc43bd145371d9f3a08aab...e55a02b39a6f51c8f79b6a0a6e1d1eba0e93fba4) Updates `xposed/libxposed` from `5458273` to `b896dbc` - [Commits](https://github.com/libxposed/api/compare/54582730315ba4a3d7cfaf9baf9d23c419e07006...b896dbcda3fa1550d04d43d962923318ed5a61a8) Updates `external/apache/commons-lang` from `675ab08` to `e1ffb6e` - [Commits](https://github.com/apache/commons-lang/compare/675ab08d0eb62b7d2edd43fe42512c896e84bcd6...e1ffb6ea0b9e21ea71c4540b0fca4db8e4c1dd93) --- updated-dependencies: - dependency-name: external/fmt dependency-version: e55a02b39a6f51c8f79b6a0a6e1d1eba0e93fba4 dependency-type: direct:production dependency-group: submodule - dependency-name: xposed/libxposed dependency-version: b896dbcda3fa1550d04d43d962923318ed5a61a8 dependency-type: direct:production dependency-group: submodule - dependency-name: external/apache/commons-lang dependency-version: e1ffb6ea0b9e21ea71c4540b0fca4db8e4c1dd93 dependency-type: direct:production dependency-group: submodule ... Signed-off-by: dependabot[bot] --- external/apache/commons-lang | 2 +- external/fmt | 2 +- xposed/libxposed | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/external/apache/commons-lang b/external/apache/commons-lang index 675ab08d0..1875db4f7 160000 --- a/external/apache/commons-lang +++ b/external/apache/commons-lang @@ -1 +1 @@ -Subproject commit 675ab08d0eb62b7d2edd43fe42512c896e84bcd6 +Subproject commit 1875db4f71db52f5a741368cc6a6a6a89883427b diff --git a/external/fmt b/external/fmt index 0e078f6ed..b35de87ad 160000 --- a/external/fmt +++ b/external/fmt @@ -1 +1 @@ -Subproject commit 0e078f6ed0624be8babc43bd145371d9f3a08aab +Subproject commit b35de87ad91951c8269fe533dca6aebc3e0a25ba diff --git a/xposed/libxposed b/xposed/libxposed index 64e29bd65..b896dbcda 160000 --- a/xposed/libxposed +++ b/xposed/libxposed @@ -1 +1 @@ -Subproject commit 64e29bd657ef4d2540b34402f5a988778f29e676 +Subproject commit b896dbcda3fa1550d04d43d962923318ed5a61a8