diff --git a/docs/api/document.md b/docs/api/document.md index 2ec0c204..84b57eec 100644 --- a/docs/api/document.md +++ b/docs/api/document.md @@ -24,6 +24,10 @@ [filename](public/ModuleApplication.md ':include') +[filename](public/ModuleAppActivity.md ':include') + +[filename](public/ModuleAppCompatActivity.md ':include') + [filename](public/YukiModuleResources.md ':include') [filename](public/YukiResources.md ':include') diff --git a/docs/api/public/ModuleAppActivity.md b/docs/api/public/ModuleAppActivity.md new file mode 100644 index 00000000..8f2c48fa --- /dev/null +++ b/docs/api/public/ModuleAppActivity.md @@ -0,0 +1,17 @@ +## ModuleAppActivity [class] + +```kotlin +open class ModuleAppActivity : Activity() +``` + +**变更记录** + +`v1.0.93` `新增` + +**功能描述** + +> 代理 `Activity`。 + +继承于此类的 `Activity` 可以同时在宿主与模块中启动。 + +在 (Xposed) 宿主环境需要在宿主启动时调用 `Context.registerModuleAppActivities` 进行注册。 \ No newline at end of file diff --git a/docs/api/public/ModuleAppCompatActivity.md b/docs/api/public/ModuleAppCompatActivity.md new file mode 100644 index 00000000..fa90df2c --- /dev/null +++ b/docs/api/public/ModuleAppCompatActivity.md @@ -0,0 +1,33 @@ +## ModuleAppCompatActivity [class] + +```kotlin +open class ModuleAppCompatActivity : AppCompatActivity() +``` + +**变更记录** + +`v1.0.93` `新增` + +**功能描述** + +> 代理 `AppCompatActivity`。 + +继承于此类的 `Activity` 可以同时在宿主与模块中启动。 + +在 (Xposed) 宿主环境需要在宿主启动时调用 `Context.registerModuleAppActivities` 进行注册。 + +在 (Xposed) 宿主环境需要重写 `moduleTheme` 设置 AppCompat 主题,否则会无法启动。 + +### moduleTheme [field] + +```kotlin +open val moduleTheme: Int +``` + +**变更记录** + +`v1.0.93` `新增` + +**功能描述** + +> 设置当前代理的 `Activity` 主题。 \ No newline at end of file diff --git a/docs/api/public/YukiHookFactory.md b/docs/api/public/YukiHookFactory.md index b4f9a0f7..0f52eb0d 100644 --- a/docs/api/public/YukiHookFactory.md +++ b/docs/api/public/YukiHookFactory.md @@ -191,6 +191,150 @@ onAppLifecycle { } ``` +### registerModuleAppActivities [method] + +```kotlin +fun Context.registerModuleAppActivities(proxy: Any?) +``` + +**变更记录** + +`v1.0.93` `新增` + +**功能描述** + +> 向 Hook APP (宿主) 注册当前 Xposed 模块的 `Activity`。 + +注册成功后,你就可以直接使用 `Context.startActivity` 来启动未在宿主中注册的 `Activity`。 + +你要将需要在宿主启动的 `Activity` 继承于 `ModuleAppActivity` 或 `ModuleAppCompatActivity`。 + +为防止资源 ID 互相冲突,你需要在当前 Xposed 模块项目的 `build.gradle` 中修改资源 ID。 + +- Kotlin Gradle DSL + +```kotlin +androidResources.additionalParameters("--allow-reserved-package-id", "--package-id", "0x64") +``` + +- Groovy + +```groovy +aaptOptions.additionalParameters '--allow-reserved-package-id', '--package-id', '0x64' +``` + +!> 提供的示例资源 ID 值仅供参考,为了防止当前宿主存在多个 Xposed 模块,建议自定义你自己的资源 ID。 + +!> 只能在 (Xposed) 宿主环境使用此功能,其它环境下使用将不生效且会打印警告信息。 + +**功能示例** + +在 Hook 宿主之后,我们可以直接在 Hooker 中得到的 `Context` 注册当前模块的 `Activity` 代理。 + +> 示例如下 + +```kotlin +injectMember { + method { + name = "onCreate" + param(BundleClass) + } + afterHook { + instance().registerModuleAppActivities() + } +} +``` + +你还可以直接在 `AppLifecycle` 中注册当前模块的 `Activity` 代理。 + +> 示例如下 + +```kotlin +onAppLifecycle { + onCreate { + registerModuleAppActivities() + } +} +``` + +如果没有填写 `proxy` 参数,API 将会根据当前 `Context` 自动获取当前宿主的启动入口 `Activity` 进行代理。 + +通常情况下,它是有效的,但是以上情况在一些 APP 中会失效,例如一些 `Activity` 会在注册清单上加入启动参数,那么我们就需要使用另一种解决方案。 + +若未注册的 `Activity` 不能被正确启动,我们可以手动拿到宿主的 `AndroidManifest.xml` 进行分析,来得到一个注册过的 `Activity` 标签,获取其中的 `name`。 + +你需要选择一个当前宿主可能用不到的、不需要的 `Activity` 作为一个“傀儡”将其进行代理,通常是有效的。 + +比如我们已经找到了能够被代理的合适 `Activity`。 + +> 示例如下 + +```xml + +``` + +根据其中的 `name`,我们只需要在方法中加入这个参数进行注册即可。 + +> 示例如下 + +```kotlin +registerModuleAppActivities(proxy = "com.demo.test.activity.TestActivity") +``` + +另一种情况,如果你对宿主的类编写了一个 `stub`,那么你可以直接通过 `Class` 对象来进行注册。 + +> 示例如下 + +```kotlin +registerModuleAppActivities(TestActivity::class.java) +``` + +注册完成后,请将你需要使用宿主启动的模块中的 `Activity` 继承于 `ModuleAppActivity` 或 `ModuleAppCompatActivity`。 + +这些 `Activity` 现在无需注册即可无缝存活于宿主中。 + +> 示例如下 + +```kotlin +class HostTestActivity : ModuleAppActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 模块资源已被自动注入,可以直接使用 xml 装载布局 + setContentView(R.layout.activity_main) + } +} +``` + +若你需要继承于 `ModuleAppCompatActivity`,你需要手动设置 AppCompat 主题。 + +> 示例如下 + +```kotlin +class HostTestActivity : ModuleAppCompatActivity() { + + // 这里的主题名称仅供参考,请填写你模块中已有的主题名称 + override val moduleTheme get() = R.style.Theme_AppCompat + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + // 模块资源已被自动注入,可以直接使用 xml 装载布局 + setContentView(R.layout.activity_main) + } +} +``` + +以上步骤全部完成后,你就可以在 (Xposed) 宿主环境任意存在 `Context` 的地方愉快地调用 `startActivity` 了。 + +> 示例如下 + +```kotlin +val context: Context = ... // 假设这就是你的 Context +context.startActivity(context, HostTestActivity::class.java) +``` + ### ~~isSupportResourcesHook [field]~~ **变更记录** diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt index 822a3da9..0578a098 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/YukiHookFactory.kt @@ -29,6 +29,7 @@ package com.highcapable.yukihookapi.hook.factory +import android.app.Activity import android.content.Context import android.content.Intent import android.content.res.Resources @@ -41,6 +42,8 @@ import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.param.PackageParam import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppActivity +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppCompatActivity import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit import java.io.BufferedReader @@ -126,6 +129,20 @@ fun Context.injectModuleAppResources() = resources?.injectModuleAppResources() */ fun Resources.injectModuleAppResources() = AppParasitics.injectModuleAppResources(hostResources = this) +/** + * 向 Hook APP (宿主) 注册当前 Xposed 模块的 [Activity] + * + * 注册成功后 - 你就可以直接使用 [Context.startActivity] 来启动未在宿主中注册的 [Activity] + * + * - 你要将需要在宿主启动的 [Activity] 继承于 [ModuleAppActivity] 或 [ModuleAppCompatActivity] + * + * 详情请参考 [registerModuleAppActivities](https://fankes.github.io/YukiHookAPI/#/api/document?id=registermoduleappactivities-method) + * + * - ❗只能在 (Xposed) 宿主环境使用此功能 - 其它环境下使用将不生效且会打印警告信息 + * @param proxy 代理的 [Activity] - 必须存在于宿主的 AndroidMainifest 清单中 - 不填使用默认 [Activity] + */ +fun Context.registerModuleAppActivities(proxy: Any? = null) = AppParasitics.registerModuleAppActivities(context = this, proxy) + /** * 仅判断模块是否在太极、无极中激活 * diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/AppParasitics.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/AppParasitics.kt index c9182147..1b078433 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/AppParasitics.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/AppParasitics.kt @@ -24,11 +24,16 @@ * SOFTWARE. * * This file is Created by fankes on 2022/8/14. + * Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java */ +@file:Suppress("QueryPermissionsNeeded") + package com.highcapable.yukihookapi.hook.xposed.parasitic import android.app.Activity +import android.app.ActivityManager import android.app.Application +import android.app.Instrumentation import android.content.BroadcastReceiver import android.content.Context import android.content.Intent @@ -36,10 +41,9 @@ import android.content.IntentFilter import android.content.pm.PackageManager import android.content.res.Configuration import android.content.res.Resources +import android.os.Handler import com.highcapable.yukihookapi.YukiHookAPI -import com.highcapable.yukihookapi.hook.factory.classOf -import com.highcapable.yukihookapi.hook.factory.current -import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.factory.* import com.highcapable.yukihookapi.hook.log.yLoggerE import com.highcapable.yukihookapi.hook.log.yLoggerW import com.highcapable.yukihookapi.hook.param.wrapper.HookParamWrapper @@ -53,6 +57,10 @@ import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiModuleResources import com.highcapable.yukihookapi.hook.xposed.bridge.factory.YukiHookHelper import com.highcapable.yukihookapi.hook.xposed.bridge.factory.YukiMemberHook import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config.ActivityProxyConfig +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.HandlerDelegate +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.IActivityManagerProxy +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.InstrumentationDelegate /** * 这是一个管理 APP 寄生功能的控制类 @@ -69,6 +77,9 @@ internal object AppParasitics { /** [YukiHookDataChannel] 是否已经注册 */ private var isDataChannelRegister = false + /** [Activity] 代理是否已经注册 */ + private var isActivityProxyRegister = false + /** 已被注入到宿主 [Resources] 中的当前 Xposed 模块资源 HashCode 数组 */ private val injectedHostResourcesHashCodes = HashSet() @@ -237,6 +248,61 @@ internal object AppParasitics { dynamicModuleAppResources?.let { moduleAppResources = it } } + /** + * 向 Hook APP (宿主) 注册当前 Xposed 模块的 [Activity] + * @param context 当前 [Context] + * @param proxy 代理的 [Activity] + */ + internal fun registerModuleAppActivities(context: Context, proxy: Any?) { + if (isActivityProxyRegister) return + if (YukiHookBridge.hasXposedBridge.not()) return yLoggerW(msg = "You can only register Activity Proxy in Xposed Environment") + runCatching { + ActivityProxyConfig.apply { + proxyIntentName = "${YukiHookBridge.modulePackageName}.ACTIVITY_PROXY_INTENT" + proxyClassName = proxy?.let { + when (it) { + is String, is CharSequence -> it.toString() + is Class<*> -> it.name + else -> error("This proxy [$it] type is not allowed") + } + }?.takeIf { it.isNotBlank() } ?: context.packageManager?.runCatching { + queryIntentActivities(getLaunchIntentForPackage(context.packageName)!!, 0).first().activityInfo.name + }?.getOrNull() ?: "" + if ((proxyClassName.hasClass(context.classLoader) && classOf(proxyClassName, context.classLoader).hasMethod { + name = "setIntent"; param(IntentClass); superClass() + }).not() + ) (if (proxyClassName.isBlank()) error("Cound not got launch intent for package \"${context.packageName}\"") + else error("Could not found \"$proxyClassName\" or Class is not a type of Activity")) + } + /** Patched [Instrumentation] */ + ActivityThreadClass.field { name = "sCurrentActivityThread" }.ignored().get().any()?.current { + method { name = "getInstrumentation" } + .invoke() + ?.also { field { name = "mInstrumentation" }.set(InstrumentationDelegate.wrapper(it)) } + HandlerClass.field { name = "mCallback" }.get(field { name = "mH" }.any()).apply { + cast()?.apply { + if (current().name != classOf().name) set(HandlerDelegate.wrapper(baseInstance = this)) + } ?: set(HandlerDelegate.wrapper()) + } + } + /** Patched [ActivityManager] */ + runCatching { + runCatching { + ActivityManagerNativeClass.field { name = "gDefault" }.ignored().get().any() + }.getOrNull() ?: ActivityManagerClass.field { name = "IActivityManagerSingleton" }.ignored().get().any() + }.getOrNull()?.also { default -> + SingletonClass.field { name = "mInstance" }.ignored().result { + get(default).apply { any()?.also { set(IActivityManagerProxy.wrapper(IActivityManagerClass, it)) } } + ActivityTaskManagerClass.field { name = "IActivityTaskManagerSingleton" }.ignored().get().any().also { singleton -> + SingletonClass.method { name = "get" }.ignored().get(singleton).call() + get(singleton).apply { any()?.also { set(IActivityManagerProxy.wrapper(IActivityTaskManagerClass, it)) } } + } + } + } + isActivityProxyRegister = true + }.onFailure { yLoggerE(msg = "Activity Proxy initialization failed because got an Exception", e = it) } + } + /** * 当前 Hook APP (宿主) 的生命周期回调处理类 */ diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppActivity.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppActivity.kt new file mode 100644 index 00000000..d363c09d --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppActivity.kt @@ -0,0 +1,55 @@ +/* + * YukiHookAPI - An efficient Kotlin version of the Xposed Hook API. + * Copyright (C) 2019-2022 HighCapable + * https://github.com/fankes/YukiHookAPI + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2022/8/8. + * Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java + */ +package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import androidx.annotation.CallSuper +import com.highcapable.yukihookapi.hook.factory.classOf +import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.ModuleClassLoader + +/** + * 代理 [Activity] + * + * 继承于此类的 [Activity] 可以同时在宿主与模块中启动 + * + * - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册 + */ +open class ModuleAppActivity : Activity() { + + override fun getClassLoader(): ClassLoader? = ModuleClassLoader.instance() + + @CallSuper + override fun onRestoreInstanceState(savedInstanceState: Bundle) { + savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = classOf().classLoader + super.onRestoreInstanceState(savedInstanceState) + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppCompatActivity.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppCompatActivity.kt new file mode 100644 index 00000000..bc1afee3 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/base/ModuleAppCompatActivity.kt @@ -0,0 +1,71 @@ +/* + * YukiHookAPI - An efficient Kotlin version of the Xposed Hook API. + * Copyright (C) 2019-2022 HighCapable + * https://github.com/fankes/YukiHookAPI + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2022/8/8. + * Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java + */ +package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base + +import android.app.Activity +import android.content.Context +import android.os.Bundle +import androidx.annotation.CallSuper +import androidx.appcompat.app.AppCompatActivity +import com.highcapable.yukihookapi.hook.factory.classOf +import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.ModuleClassLoader + +/** + * 代理 [AppCompatActivity] + * + * 继承于此类的 [Activity] 可以同时在宿主与模块中启动 + * + * - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册 + * + * - 在 (Xposed) 宿主环境需要重写 [moduleTheme] 设置 AppCompat 主题 - 否则会无法启动 + */ +open class ModuleAppCompatActivity : AppCompatActivity() { + + /** + * 设置当前代理的 [Activity] 主题 + * @return [Int] + */ + open val moduleTheme get() = -1 + + override fun getClassLoader(): ClassLoader? = ModuleClassLoader.instance() + + @CallSuper + override fun onRestoreInstanceState(savedInstanceState: Bundle) { + savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = classOf().classLoader + super.onRestoreInstanceState(savedInstanceState) + } + + @CallSuper + override fun onCreate(savedInstanceState: Bundle?) { + if (YukiHookBridge.hasXposedBridge && moduleTheme != -1) setTheme(moduleTheme) + super.onCreate(savedInstanceState) + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/config/ActivityProxyConfig.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/config/ActivityProxyConfig.kt new file mode 100644 index 00000000..b9794f55 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/config/ActivityProxyConfig.kt @@ -0,0 +1,47 @@ +/* + * YukiHookAPI - An efficient Kotlin version of the Xposed Hook API. + * Copyright (C) 2019-2022 HighCapable + * https://github.com/fankes/YukiHookAPI + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2022/8/14. + */ +package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config + +import android.app.Activity +import android.content.Intent + +/** + * 当前代理的 [Activity] 参数配置类 + */ +internal object ActivityProxyConfig { + + /** + * 用于代理的 [Intent] 名称 + */ + internal var proxyIntentName = "" + + /** + * 需要代理的 [Activity] 类名 + */ + internal var proxyClassName = "" +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/HandlerDelegate.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/HandlerDelegate.kt new file mode 100644 index 00000000..5059545e --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/HandlerDelegate.kt @@ -0,0 +1,107 @@ +/* + * YukiHookAPI - An efficient Kotlin version of the Xposed Hook API. + * Copyright (C) 2019-2022 HighCapable + * https://github.com/fankes/YukiHookAPI + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2022/8/8. + * Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java + */ +package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate + +import android.app.Activity +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Message +import com.highcapable.yukihookapi.hook.factory.current +import com.highcapable.yukihookapi.hook.factory.field +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.log.yLoggerE +import com.highcapable.yukihookapi.hook.type.android.ActivityThreadClass +import com.highcapable.yukihookapi.hook.type.android.ClientTransactionClass +import com.highcapable.yukihookapi.hook.type.android.IBinderClass +import com.highcapable.yukihookapi.hook.type.android.IntentClass +import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config.ActivityProxyConfig + +/** + * 代理当前 [Handler.Callback] + * @param baseInstance 原始实例 + */ +internal class HandlerDelegate private constructor(private val baseInstance: Handler.Callback?) : Handler.Callback { + + internal companion object { + + /** 启动 [Activity] */ + private const val LAUNCH_ACTIVITY = 100 + + /** 执行事务处理 */ + private const val EXECUTE_TRANSACTION = 159 + + /** + * 从 [Handler.Callback] 创建 [HandlerDelegate] 实例 + * @param baseInstance [Handler.Callback] 实例 - 可空 + * @return [HandlerDelegate] + */ + internal fun wrapper(baseInstance: Handler.Callback? = null) = HandlerDelegate(baseInstance) + } + + override fun handleMessage(msg: Message): Boolean { + when (msg.what) { + LAUNCH_ACTIVITY -> runCatching { + msg.obj.current().field { name = "intent" }.apply { + cast()?.also { intent -> + IntentClass.field { name = "mExtras" }.ignored().get(intent).cast() + ?.classLoader = YukiHookAppHelper.currentApplication()?.classLoader + if (intent.hasExtra(ActivityProxyConfig.proxyIntentName)) + set(intent.getParcelableExtra(ActivityProxyConfig.proxyIntentName)) + } + } + }.onFailure { yLoggerE(msg = "Activity Proxy got an Exception in msg.what [$LAUNCH_ACTIVITY]", e = it) } + EXECUTE_TRANSACTION -> msg.obj?.runCatching client@{ + ClientTransactionClass.method { name = "getCallbacks" }.ignored().get(this).list().takeIf { it.isNotEmpty() } + ?.forEach { item -> + item?.current()?.takeIf { it.name.contains(other = "LaunchActivityItem") }?.field { name = "mIntent" }?.apply { + cast()?.also { intent -> + IntentClass.field { name = "mExtras" }.ignored().get(intent).cast() + ?.classLoader = YukiHookAppHelper.currentApplication()?.classLoader + if (intent.hasExtra(ActivityProxyConfig.proxyIntentName)) + intent.getParcelableExtra(ActivityProxyConfig.proxyIntentName).also { subIntent -> + if (Build.VERSION.SDK_INT >= 31) + ActivityThreadClass.method { name = "currentActivityThread" }.ignored().get().call() + ?.current()?.method { + name = "getLaunchingActivity" + param(IBinderClass) + }?.call(this@client.current().method { name = "getActivityToken" }.call()) + ?.current()?.field { name = "intent" }?.set(subIntent) + set(subIntent) + } + } + } + } + }?.onFailure { yLoggerE(msg = "Activity Proxy got an Exception in msg.what [$EXECUTE_TRANSACTION]", e = it) } + } + return baseInstance?.handleMessage(msg) ?: false + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/IActivityManagerProxy.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/IActivityManagerProxy.kt new file mode 100644 index 00000000..3c0e2f93 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/IActivityManagerProxy.kt @@ -0,0 +1,73 @@ +/* + * YukiHookAPI - An efficient Kotlin version of the Xposed Hook API. + * Copyright (C) 2019-2022 HighCapable + * https://github.com/fankes/YukiHookAPI + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2022/8/8. + * Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java + */ +package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate + +import android.app.ActivityManager +import android.content.Intent +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge +import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper +import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics +import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config.ActivityProxyConfig +import java.lang.reflect.InvocationHandler +import java.lang.reflect.Method +import java.lang.reflect.Proxy + +/** + * 代理当前 [ActivityManager] + * @param baseInstance 原始实例 + */ +internal class IActivityManagerProxy private constructor(private val baseInstance: Any) : InvocationHandler { + + internal companion object { + + /** + * 创建 [IActivityManagerProxy] 代理 + * @param clazz 代理的目标 [Class] + * @param instance 代理的目标实例 + * @return [Any] 代理包装后的实例 + */ + internal fun wrapper(clazz: Class<*>, instance: Any) = + Proxy.newProxyInstance(AppParasitics.baseClassLoader, arrayOf(clazz), IActivityManagerProxy(instance)) + } + + override fun invoke(proxy: Any?, method: Method?, args: Array?): Any? { + if (method?.name == "startActivity") args?.indexOfFirst { it is Intent }?.also { index -> + val argsInstance = (args[index] as? Intent) ?: return@also + val component = argsInstance.component + if (component != null && + component.packageName == YukiHookAppHelper.currentPackageName() && + component.className.startsWith(YukiHookBridge.modulePackageName) + ) args[index] = Intent().apply { + setClassName(component.packageName, ActivityProxyConfig.proxyClassName) + putExtra(ActivityProxyConfig.proxyIntentName, argsInstance) + } + } + return method?.invoke(baseInstance, *(args ?: emptyArray())) + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/InstrumentationDelegate.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/InstrumentationDelegate.kt new file mode 100644 index 00000000..daf91c84 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/InstrumentationDelegate.kt @@ -0,0 +1,327 @@ +/* + * YukiHookAPI - An efficient Kotlin version of the Xposed Hook API. + * Copyright (C) 2019-2022 HighCapable + * https://github.com/fankes/YukiHookAPI + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2022/8/8. + * Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java + */ +package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate + +import android.app.Activity +import android.app.Application +import android.app.Instrumentation +import android.app.UiAutomation +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.ActivityInfo +import android.os.* +import android.view.KeyEvent +import android.view.MotionEvent +import com.highcapable.yukihookapi.hook.factory.* +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge +import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics + +/** + * 代理当前 [Instrumentation] + * @param baseInstance 原始实例 + */ +internal class InstrumentationDelegate private constructor(private val baseInstance: Instrumentation) : Instrumentation() { + + internal companion object { + + /** + * 从 [Instrumentation] 创建 [InstrumentationDelegate] 实例 + * @param baseInstance [Instrumentation] 实例 + * @return [InstrumentationDelegate] + */ + internal fun wrapper(baseInstance: Instrumentation) = InstrumentationDelegate(baseInstance) + } + + /** + * 注入当前 [Activity] 生命周期 + * @param icicle [Bundle] + */ + private fun Activity.injectLifecycle(icicle: Bundle?) { + if (icicle != null && current().name.startsWith(YukiHookBridge.modulePackageName)) + icicle.classLoader = AppParasitics.baseClassLoader + if (current().name.startsWith(YukiHookBridge.modulePackageName)) injectModuleAppResources() + } + + override fun newActivity(cl: ClassLoader?, className: String?, intent: Intent?): Activity? = try { + baseInstance.newActivity(cl, className, intent) + } catch (e: Throwable) { + if (className?.startsWith(YukiHookBridge.modulePackageName) == true) + classOf(className).buildOf() ?: throw e + else throw e + } + + override fun onCreate(arguments: Bundle?) { + baseInstance.onCreate(arguments) + } + + override fun start() { + baseInstance.start() + } + + override fun onStart() { + baseInstance.onStart() + } + + override fun onException(obj: Any?, e: Throwable?) = baseInstance.onException(obj, e) + + override fun sendStatus(resultCode: Int, results: Bundle?) { + baseInstance.sendStatus(resultCode, results) + } + + override fun addResults(results: Bundle?) { + if (Build.VERSION.SDK_INT >= 26) baseInstance.addResults(results) + } + + override fun finish(resultCode: Int, results: Bundle?) { + baseInstance.finish(resultCode, results) + } + + override fun setAutomaticPerformanceSnapshots() { + baseInstance.setAutomaticPerformanceSnapshots() + } + + override fun startPerformanceSnapshot() { + baseInstance.startPerformanceSnapshot() + } + + override fun endPerformanceSnapshot() { + baseInstance.endPerformanceSnapshot() + } + + override fun onDestroy() { + baseInstance.onDestroy() + } + + override fun getContext(): Context? = baseInstance.context + + override fun getComponentName(): ComponentName? = baseInstance.componentName + + override fun getTargetContext(): Context? = baseInstance.targetContext + + override fun getProcessName(): String? = + if (Build.VERSION.SDK_INT >= 26) baseInstance.processName else AppParasitics.systemContext.processName + + override fun isProfiling() = baseInstance.isProfiling + + override fun startProfiling() { + baseInstance.startProfiling() + } + + override fun stopProfiling() { + baseInstance.stopProfiling() + } + + override fun setInTouchMode(inTouch: Boolean) { + baseInstance.setInTouchMode(inTouch) + } + + override fun waitForIdle(recipient: Runnable?) { + baseInstance.waitForIdle(recipient) + } + + override fun waitForIdleSync() { + baseInstance.waitForIdleSync() + } + + override fun runOnMainSync(runner: Runnable?) { + baseInstance.runOnMainSync(runner) + } + + override fun startActivitySync(intent: Intent?): Activity? = baseInstance.startActivitySync(intent) + + override fun startActivitySync(intent: Intent, options: Bundle?): Activity = + if (Build.VERSION.SDK_INT >= 28) baseInstance.startActivitySync(intent, options) else error("Operating system not supported") + + override fun addMonitor(monitor: ActivityMonitor?) { + baseInstance.addMonitor(monitor) + } + + override fun addMonitor(cls: String?, result: ActivityResult?, block: Boolean): ActivityMonitor? = + baseInstance.addMonitor(cls, result, block) + + override fun addMonitor(filter: IntentFilter?, result: ActivityResult?, block: Boolean): ActivityMonitor? = + baseInstance.addMonitor(filter, result, block) + + override fun checkMonitorHit(monitor: ActivityMonitor?, minHits: Int) = baseInstance.checkMonitorHit(monitor, minHits) + + override fun waitForMonitor(monitor: ActivityMonitor?): Activity? = baseInstance.waitForMonitor(monitor) + + override fun waitForMonitorWithTimeout(monitor: ActivityMonitor?, timeOut: Long): Activity? = + baseInstance.waitForMonitorWithTimeout(monitor, timeOut) + + override fun removeMonitor(monitor: ActivityMonitor?) { + baseInstance.removeMonitor(monitor) + } + + override fun invokeContextMenuAction(targetActivity: Activity?, id: Int, flag: Int) = + baseInstance.invokeContextMenuAction(targetActivity, id, flag) + + override fun invokeMenuActionSync(targetActivity: Activity?, id: Int, flag: Int) = + baseInstance.invokeMenuActionSync(targetActivity, id, flag) + + override fun sendCharacterSync(keyCode: Int) { + baseInstance.sendCharacterSync(keyCode) + } + + override fun sendKeyDownUpSync(key: Int) { + baseInstance.sendKeyDownUpSync(key) + } + + override fun sendKeySync(event: KeyEvent?) { + baseInstance.sendKeySync(event) + } + + override fun sendPointerSync(event: MotionEvent?) { + baseInstance.sendPointerSync(event) + } + + override fun sendStringSync(text: String?) { + baseInstance.sendStringSync(text) + } + + override fun sendTrackballEventSync(event: MotionEvent?) { + baseInstance.sendTrackballEventSync(event) + } + + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application? = + baseInstance.newApplication(cl, className, context) + + override fun callApplicationOnCreate(app: Application?) { + baseInstance.callApplicationOnCreate(app) + } + + override fun newActivity( + clazz: Class<*>?, context: Context?, + token: IBinder?, application: Application?, + intent: Intent?, info: ActivityInfo?, + title: CharSequence?, parent: Activity?, + id: String?, lastNonConfigurationInstance: Any? + ): Activity? = baseInstance.newActivity( + clazz, context, + token, application, + intent, info, title, + parent, id, lastNonConfigurationInstance + ) + + override fun callActivityOnCreate(activity: Activity, icicle: Bundle?, persistentState: PersistableBundle?) { + activity.injectLifecycle(icicle) + baseInstance.callActivityOnCreate(activity, icicle, persistentState) + } + + override fun callActivityOnCreate(activity: Activity, icicle: Bundle?) { + activity.injectLifecycle(icicle) + baseInstance.callActivityOnCreate(activity, icicle) + } + + override fun callActivityOnDestroy(activity: Activity?) { + baseInstance.callActivityOnDestroy(activity) + } + + override fun callActivityOnRestoreInstanceState(activity: Activity, savedInstanceState: Bundle) { + baseInstance.callActivityOnRestoreInstanceState(activity, savedInstanceState) + } + + override fun callActivityOnRestoreInstanceState(activity: Activity, savedInstanceState: Bundle?, persistentState: PersistableBundle?) { + baseInstance.callActivityOnRestoreInstanceState(activity, savedInstanceState, persistentState) + } + + override fun callActivityOnPostCreate(activity: Activity, savedInstanceState: Bundle?) { + baseInstance.callActivityOnPostCreate(activity, savedInstanceState) + } + + override fun callActivityOnPostCreate(activity: Activity, savedInstanceState: Bundle?, persistentState: PersistableBundle?) { + baseInstance.callActivityOnPostCreate(activity, savedInstanceState, persistentState) + } + + override fun callActivityOnNewIntent(activity: Activity?, intent: Intent?) { + baseInstance.callActivityOnNewIntent(activity, intent) + } + + override fun callActivityOnStart(activity: Activity?) { + baseInstance.callActivityOnStart(activity) + } + + override fun callActivityOnRestart(activity: Activity?) { + baseInstance.callActivityOnRestart(activity) + } + + override fun callActivityOnPause(activity: Activity?) { + baseInstance.callActivityOnPause(activity) + } + + override fun callActivityOnResume(activity: Activity?) { + baseInstance.callActivityOnResume(activity) + } + + override fun callActivityOnStop(activity: Activity?) { + baseInstance.callActivityOnStop(activity) + } + + override fun callActivityOnUserLeaving(activity: Activity?) { + baseInstance.callActivityOnUserLeaving(activity) + } + + override fun callActivityOnSaveInstanceState(activity: Activity, outState: Bundle) { + baseInstance.callActivityOnSaveInstanceState(activity, outState) + } + + override fun callActivityOnSaveInstanceState(activity: Activity, outState: Bundle, outPersistentState: PersistableBundle) { + baseInstance.callActivityOnSaveInstanceState(activity, outState, outPersistentState) + } + + override fun callActivityOnPictureInPictureRequested(activity: Activity) { + if (Build.VERSION.SDK_INT >= 30) baseInstance.callActivityOnPictureInPictureRequested(activity) + } + + @Deprecated("Deprecated in Java") + @Suppress("DEPRECATION") + override fun startAllocCounting() { + baseInstance.startAllocCounting() + } + + @Deprecated("Deprecated in Java") + @Suppress("DEPRECATION") + override fun stopAllocCounting() { + baseInstance.stopAllocCounting() + } + + override fun getAllocCounts(): Bundle? = baseInstance.allocCounts + + override fun getBinderCounts(): Bundle? = baseInstance.binderCounts + + override fun getUiAutomation(): UiAutomation? = baseInstance.uiAutomation + + override fun getUiAutomation(flags: Int): UiAutomation? = + if (Build.VERSION.SDK_INT >= 24) baseInstance.getUiAutomation(flags) else error("Operating system not supported") + + override fun acquireLooperManager(looper: Looper?): TestLooperManager? = + if (Build.VERSION.SDK_INT >= 26) baseInstance.acquireLooperManager(looper) else error("Operating system not supported") +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/ModuleClassLoader.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/ModuleClassLoader.kt new file mode 100644 index 00000000..dd3a91d0 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/parasitic/activity/delegate/ModuleClassLoader.kt @@ -0,0 +1,60 @@ +/* + * YukiHookAPI - An efficient Kotlin version of the Xposed Hook API. + * Copyright (C) 2019-2022 HighCapable + * https://github.com/fankes/YukiHookAPI + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * This file is Created by fankes on 2022/8/8. + * Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java + */ +package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate + +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge +import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper +import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics + +/** + * 自动处理 (Xposed) 宿主环境与模块环境的 [ClassLoader] + */ +internal class ModuleClassLoader private constructor() : ClassLoader(AppParasitics.baseClassLoader) { + + internal companion object { + + /** 当前 [ModuleClassLoader] 单例 */ + private var instance: ModuleClassLoader? = null + + /** + * 获取 [ModuleClassLoader] 单例 + * @return [ModuleClassLoader] + */ + internal fun instance() = instance ?: ModuleClassLoader().apply { instance = this } + } + + override fun loadClass(name: String, resolve: Boolean): Class<*> { + if (YukiHookBridge.hasXposedBridge.not()) return AppParasitics.baseClassLoader.loadClass(name) + return YukiHookAppHelper.currentApplication()?.classLoader?.let { loader -> + runCatching { return@let AppParasitics.baseClassLoader.loadClass(name) } + runCatching { if (name == "androidx.lifecycle.ReportFragment") return@let loader.loadClass(name) } + runCatching { AppParasitics.baseClassLoader.loadClass(name) }.getOrNull() ?: loader.loadClass(name) + } ?: super.loadClass(name, resolve) + } +} \ No newline at end of file