diff --git a/app/src/main/java/io/nekohasekai/sfa/xposed/HookInstaller.kt b/app/src/main/java/io/nekohasekai/sfa/xposed/HookInstaller.kt new file mode 100644 index 0000000..08d4c2b --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sfa/xposed/HookInstaller.kt @@ -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 + } +} diff --git a/app/src/main/java/io/nekohasekai/sfa/xposed/XposedInit.kt b/app/src/main/java/io/nekohasekai/sfa/xposed/XposedInit.kt index 10fe4a2..522da95 100644 --- a/app/src/main/java/io/nekohasekai/sfa/xposed/XposedInit.kt +++ b/app/src/main/java/io/nekohasekai/sfa/xposed/XposedInit.kt @@ -1,54 +1,16 @@ package io.nekohasekai.sfa.xposed -import android.content.Context import io.github.libxposed.api.XposedInterface import io.github.libxposed.api.XposedModule 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) { - 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) { - val systemContext = resolveSystemContext() - 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, - ) - } - } + HookInstaller.install(param.classLoader) } companion object { 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 - } } diff --git a/app/src/main/java/io/nekohasekai/sfa/xposed/XposedInit101.kt b/app/src/main/java/io/nekohasekai/sfa/xposed/XposedInit101.kt new file mode 100644 index 0000000..e5504a8 --- /dev/null +++ b/app/src/main/java/io/nekohasekai/sfa/xposed/XposedInit101.kt @@ -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) + } +} diff --git a/app/src/main/resources/META-INF/xposed/java_init.list b/app/src/main/resources/META-INF/xposed/java_init.list index 54a7373..06d0239 100644 --- a/app/src/main/resources/META-INF/xposed/java_init.list +++ b/app/src/main/resources/META-INF/xposed/java_init.list @@ -1 +1,2 @@ io.nekohasekai.sfa.xposed.XposedInit +io.nekohasekai.sfa.xposed.XposedInit101 diff --git a/app/src/main/resources/META-INF/xposed/module.prop b/app/src/main/resources/META-INF/xposed/module.prop index 8dc7ff3..ec34252 100644 --- a/app/src/main/resources/META-INF/xposed/module.prop +++ b/app/src/main/resources/META-INF/xposed/module.prop @@ -1,3 +1,3 @@ minApiVersion=100 -targetApiVersion=100 +targetApiVersion=101 staticScope=true diff --git a/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java b/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java index 425596f..71eba22 100644 --- a/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java +++ b/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedInterfaceWrapper.java @@ -21,9 +21,19 @@ import io.github.libxposed.api.utils.DexParser; */ 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; } diff --git a/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedModule.java b/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedModule.java index b2e1a03..0c8c755 100644 --- a/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedModule.java +++ b/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedModule.java @@ -9,11 +9,16 @@ import androidx.annotation.NonNull; @SuppressWarnings("unused") public abstract class XposedModule extends XposedInterfaceWrapper implements XposedModuleInterface { /** - * Instantiates a new Xposed module.
- * When the module is loaded into the target process, the constructor will be called. - * - * @param base The implementation interface provided by the framework, should not be used by the module - * @param param Information about the process in which the module is loaded + * No-arg constructor for API 101 contract: the framework instantiates the module via + * {@code Class.getDeclaredConstructor()}, then calls {@link #attachFramework}. + */ + public XposedModule() { + 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) { super(base); diff --git a/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java b/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java index 1cb548c..953edac 100644 --- a/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java +++ b/third_party/libxposed-api/src/main/java/io/github/libxposed/api/XposedModuleInterface.java @@ -1,5 +1,6 @@ package io.github.libxposed.api; +import android.app.AppComponentFactory; import android.content.pm.ApplicationInfo; 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 { /** @@ -44,6 +45,26 @@ public interface XposedModuleInterface { 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. */ @@ -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 */ 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) { + } }