Add compatibility for the new version of LSPosed

This commit is contained in:
世界
2026-04-19 15:45:46 +08:00
parent 509ef85646
commit 1b8f348fe1
8 changed files with 126 additions and 49 deletions

View File

@@ -0,0 +1,49 @@
package io.nekohasekai.sfa.xposed
import android.content.Context
import io.nekohasekai.sfa.xposed.hooks.HookIConnectivityManagerOnTransact
import io.nekohasekai.sfa.xposed.hooks.hidevpn.ConnectivityServiceHookHelper
import io.nekohasekai.sfa.xposed.hooks.hidevpn.HookNetworkCapabilitiesWriteToParcel
import io.nekohasekai.sfa.xposed.hooks.hidevpn.HookNetworkInterfaceGetName
import io.nekohasekai.sfa.xposed.hooks.hidevpnapp.HookPackageManagerGetInstalledPackages
object HookInstaller {
private const val TAG = "XposedInit"
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }
private val currentActivityThreadMethod by lazy { activityThreadClass.getMethod("currentActivityThread") }
private val getSystemContextMethod by lazy { activityThreadClass.getMethod("getSystemContext") }
fun install(classLoader: ClassLoader) {
val systemContext = resolveSystemContext()
HookErrorStore.i(TAG, "handleSystemServerLoaded")
val hooks = arrayOf(
ConnectivityServiceHookHelper(classLoader),
HookIConnectivityManagerOnTransact(classLoader, systemContext),
HookPackageManagerGetInstalledPackages(classLoader),
HookNetworkCapabilitiesWriteToParcel(),
HookNetworkInterfaceGetName(classLoader),
)
hooks.forEach { hook ->
try {
hook.injectHook()
} catch (e: Throwable) {
HookErrorStore.e(
TAG,
"Failed to inject ${hook.javaClass.simpleName}",
e,
)
}
}
}
private fun resolveSystemContext(): Context? = try {
val currentThread = currentActivityThreadMethod.invoke(null)
getSystemContextMethod.invoke(currentThread) as? Context
} catch (e: Throwable) {
HookErrorStore.e(TAG, "resolveSystemContext failed", e)
null
}
}

View File

@@ -1,54 +1,16 @@
package io.nekohasekai.sfa.xposed package io.nekohasekai.sfa.xposed
import android.content.Context
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
import io.nekohasekai.sfa.xposed.hooks.HookIConnectivityManagerOnTransact
import io.nekohasekai.sfa.xposed.hooks.hidevpn.ConnectivityServiceHookHelper
import io.nekohasekai.sfa.xposed.hooks.hidevpn.HookNetworkCapabilitiesWriteToParcel
import io.nekohasekai.sfa.xposed.hooks.hidevpn.HookNetworkInterfaceGetName
import io.nekohasekai.sfa.xposed.hooks.hidevpnapp.HookPackageManagerGetInstalledPackages
class XposedInit(base: XposedInterface, param: XposedModuleInterface.ModuleLoadedParam) : XposedModule(base, param) { class XposedInit(base: XposedInterface, param: XposedModuleInterface.ModuleLoadedParam) : XposedModule(base, param) {
private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") }
private val currentActivityThreadMethod by lazy { activityThreadClass.getMethod("currentActivityThread") }
private val getSystemContextMethod by lazy { activityThreadClass.getMethod("getSystemContext") }
override fun onSystemServerLoaded(param: XposedModuleInterface.SystemServerLoadedParam) { override fun onSystemServerLoaded(param: XposedModuleInterface.SystemServerLoadedParam) {
val systemContext = resolveSystemContext() HookInstaller.install(param.classLoader)
HookErrorStore.i("XposedInit", "handleSystemServerLoaded")
val hooks = arrayOf(
ConnectivityServiceHookHelper(param.classLoader),
HookIConnectivityManagerOnTransact(param.classLoader, systemContext),
HookPackageManagerGetInstalledPackages(param.classLoader),
HookNetworkCapabilitiesWriteToParcel(),
HookNetworkInterfaceGetName(param.classLoader),
)
hooks.forEach { hook ->
try {
hook.injectHook()
} catch (e: Throwable) {
HookErrorStore.e(
"XposedInit",
"Failed to inject ${hook.javaClass.simpleName}",
e,
)
}
}
} }
companion object { companion object {
const val TAG = "sing-box-lsposed" const val TAG = "sing-box-lsposed"
} }
private fun resolveSystemContext(): Context? = try {
val currentThread = currentActivityThreadMethod.invoke(null)
getSystemContextMethod.invoke(currentThread) as? Context
} catch (e: Throwable) {
HookErrorStore.e("XposedInit", "resolveSystemContext failed", e)
null
}
} }

View File

@@ -0,0 +1,11 @@
package io.nekohasekai.sfa.xposed
import io.github.libxposed.api.XposedModule
import io.github.libxposed.api.XposedModuleInterface
class XposedInit101 : XposedModule() {
override fun onSystemServerStarting(param: XposedModuleInterface.SystemServerStartingParam) {
HookInstaller.install(param.classLoader)
}
}

View File

@@ -1 +1,2 @@
io.nekohasekai.sfa.xposed.XposedInit io.nekohasekai.sfa.xposed.XposedInit
io.nekohasekai.sfa.xposed.XposedInit101

View File

@@ -1,3 +1,3 @@
minApiVersion=100 minApiVersion=100
targetApiVersion=100 targetApiVersion=101
staticScope=true staticScope=true

View File

@@ -21,9 +21,19 @@ import io.github.libxposed.api.utils.DexParser;
*/ */
public class XposedInterfaceWrapper implements XposedInterface { public class XposedInterfaceWrapper implements XposedInterface {
private final XposedInterface mBase; private volatile XposedInterface mBase;
XposedInterfaceWrapper(@NonNull XposedInterface base) { public XposedInterfaceWrapper() {
}
public XposedInterfaceWrapper(@NonNull XposedInterface base) {
mBase = base;
}
public final void attachFramework(@NonNull XposedInterface base) {
if (mBase != null) {
throw new IllegalStateException("Framework already attached");
}
mBase = base; mBase = base;
} }

View File

@@ -9,11 +9,16 @@ import androidx.annotation.NonNull;
@SuppressWarnings("unused") @SuppressWarnings("unused")
public abstract class XposedModule extends XposedInterfaceWrapper implements XposedModuleInterface { public abstract class XposedModule extends XposedInterfaceWrapper implements XposedModuleInterface {
/** /**
* Instantiates a new Xposed module.<br/> * No-arg constructor for API 101 contract: the framework instantiates the module via
* When the module is loaded into the target process, the constructor will be called. * {@code Class.getDeclaredConstructor()}, then calls {@link #attachFramework}.
* */
* @param base The implementation interface provided by the framework, should not be used by the module public XposedModule() {
* @param param Information about the process in which the module is loaded super();
}
/**
* Two-arg constructor for API 100 contract: the framework instantiates the module via
* {@code (XposedInterface, ModuleLoadedParam)} and attaches the framework base inline.
*/ */
public XposedModule(@NonNull XposedInterface base, @NonNull ModuleLoadedParam param) { public XposedModule(@NonNull XposedInterface base, @NonNull ModuleLoadedParam param) {
super(base); super(base);

View File

@@ -1,5 +1,6 @@
package io.github.libxposed.api; package io.github.libxposed.api;
import android.app.AppComponentFactory;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.os.Build; import android.os.Build;
@@ -32,7 +33,7 @@ public interface XposedModuleInterface {
} }
/** /**
* Wraps information about system server. * Wraps information about system server. API 100 flavor.
*/ */
interface SystemServerLoadedParam { interface SystemServerLoadedParam {
/** /**
@@ -44,6 +45,26 @@ public interface XposedModuleInterface {
ClassLoader getClassLoader(); ClassLoader getClassLoader();
} }
/**
* Wraps information about system server. API 101 flavor.
*/
interface SystemServerStartingParam {
@NonNull
ClassLoader getClassLoader();
}
/**
* Wraps information about a package whose classloader is ready. API 101.
*/
interface PackageReadyParam extends PackageLoadedParam {
@NonNull
ClassLoader getClassLoader();
@RequiresApi(Build.VERSION_CODES.P)
@NonNull
AppComponentFactory getAppComponentFactory();
}
/** /**
* Wraps information about the package being loaded. * Wraps information about the package being loaded.
*/ */
@@ -99,10 +120,28 @@ public interface XposedModuleInterface {
} }
/** /**
* Gets notified when the system server is loaded. * Gets notified when the system server is loaded. API 100.
* *
* @param param Information about system server * @param param Information about system server
*/ */
default void onSystemServerLoaded(@NonNull SystemServerLoadedParam param) { default void onSystemServerLoaded(@NonNull SystemServerLoadedParam param) {
} }
/**
* API 101: invoked once per process after the module instance is attached.
*/
default void onModuleLoaded(@NonNull ModuleLoadedParam param) {
}
/**
* API 101: invoked when a package's classloader is ready.
*/
default void onPackageReady(@NonNull PackageReadyParam param) {
}
/**
* API 101: replaces {@link #onSystemServerLoaded(SystemServerLoadedParam)}.
*/
default void onSystemServerStarting(@NonNull SystemServerStartingParam param) {
}
} }