diff --git a/yukihookapi-ksp-xposed/src/api/kotlin/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt b/yukihookapi-ksp-xposed/src/api/kotlin/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt index d38c7b80..0362d072 100644 --- a/yukihookapi-ksp-xposed/src/api/kotlin/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt +++ b/yukihookapi-ksp-xposed/src/api/kotlin/com/highcapable/yukihookapi_ksp_xposed/YukiHookXposedProcessor.kt @@ -234,7 +234,17 @@ class YukiHookXposedProcessor : SymbolProcessorProvider { packageName = packageName, fileName = xInitClassName ).apply { - write(CodeSourceFileTemplate.getXposedInitFileByteArray(packageName, fModulePackageName, entryClassName, xInitClassName)) + write(CodeSourceFileTemplate.getXposedInitFileByteArray(packageName, entryClassName, xInitClassName)) + flush() + close() + } + /** 插入 xposed_init_Impl 代码 */ + codeGenerator.createNewFile( + dependencies = Dependencies.ALL_FILES, + packageName = packageName, + fileName = "${entryClassName}_Impl" + ).apply { + write(CodeSourceFileTemplate.getXposedInitImplFileByteArray(packageName, fModulePackageName, entryClassName)) flush() close() } diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt index 6c2f2a52..a8dd57eb 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/YukiHookAPI.kt @@ -40,9 +40,10 @@ import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.factory.processName import com.highcapable.yukihookapi.hook.log.* import com.highcapable.yukihookapi.hook.param.PackageParam +import com.highcapable.yukihookapi.hook.param.type.HookEntryType import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper import com.highcapable.yukihookapi.hook.store.MemberCacheStore -import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookXposedBridge +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs import de.robv.android.xposed.XposedBridge import java.lang.reflect.Constructor @@ -77,13 +78,9 @@ object YukiHookAPI { * 获取当前 Hook 框架的名称 * * 从 [XposedBridge] 获取 TAG - * @return [String] 无法获取会返回 unknown - [hasXposedBridge] 不存在会返回 invalid + * @return [String] 无法获取会返回 unknown - [YukiHookBridge.hasXposedBridge] 不存在会返回 invalid */ - val executorName - get() = runCatching { - (XposedBridge::class.java.getDeclaredField("TAG").apply { isAccessible = true }.get(null) as? String?) - ?.replace(oldValue = "Bridge", newValue = "")?.replace(oldValue = "-", newValue = "")?.trim() ?: "unknown" - }.getOrNull() ?: "invalid" + val executorName get() = YukiHookBridge.executorName /** * 获取当前 Hook 框架的版本 @@ -91,7 +88,7 @@ object YukiHookAPI { * 获取 [XposedBridge.getXposedVersion] * @return [Int] 无法获取会返回 -1 */ - val executorVersion get() = runCatching { XposedBridge.getXposedVersion() }.getOrNull() ?: -1 + val executorVersion get() = YukiHookBridge.executorVersion /** * 配置 YukiHookAPI @@ -169,7 +166,7 @@ object YukiHookAPI { * @param wrapper 代理包装 [PackageParamWrapper] */ internal fun onXposedLoaded(wrapper: PackageParamWrapper) = - YukiHookXposedBridge.packageParamCallback?.invoke(PackageParam(wrapper).apply { printSplashLog() }) + YukiHookBridge.packageParamCallback?.invoke(PackageParam(wrapper).apply { printSplashLog() }) /** * 配置 [YukiHookAPI] 相关参数 @@ -189,8 +186,8 @@ object YukiHookAPI { */ fun encase(initiate: PackageParam.() -> Unit) { isLoadedFromBaseContext = false - if (hasXposedBridge) - YukiHookXposedBridge.packageParamCallback = initiate + if (YukiHookBridge.hasXposedBridge) + YukiHookBridge.packageParamCallback = initiate else printNoXposedEnvLog() } @@ -205,8 +202,8 @@ object YukiHookAPI { */ fun encase(vararg hooker: YukiBaseHooker) { isLoadedFromBaseContext = false - if (hasXposedBridge) - YukiHookXposedBridge.packageParamCallback = { + if (YukiHookBridge.hasXposedBridge) + YukiHookBridge.packageParamCallback = { if (hooker.isNotEmpty()) hooker.forEach { it.assignInstance(packageParam = this) } else yLoggerE(msg = "Failed to passing \"encase\" method because your hooker param is empty") @@ -230,7 +227,7 @@ object YukiHookAPI { fun encase(baseContext: Context?, initiate: PackageParam.() -> Unit) { isLoadedFromBaseContext = true when { - hasXposedBridge && baseContext != null -> initiate.invoke(baseContext.packageParam.apply { printSplashLog() }) + YukiHookBridge.hasXposedBridge && baseContext != null -> initiate.invoke(baseContext.packageParam.apply { printSplashLog() }) else -> printNoXposedEnvLog() } } @@ -251,7 +248,7 @@ object YukiHookAPI { */ fun encase(baseContext: Context?, vararg hooker: YukiBaseHooker) { isLoadedFromBaseContext = true - if (hasXposedBridge) + if (YukiHookBridge.hasXposedBridge) (if (baseContext != null) if (hooker.isNotEmpty()) { printSplashLog() @@ -262,7 +259,7 @@ object YukiHookAPI { /** 输出欢迎信息调试日志 */ private fun printSplashLog() { - if (Configs.isDebug.not() || isShowSplashLogOnceTime.not() || YukiHookXposedBridge.isModulePackageXposedEnv) return + if (Configs.isDebug.not() || isShowSplashLogOnceTime.not()) return isShowSplashLogOnceTime = false yLoggerI(msg = "Welcome to YukiHookAPI $API_VERSION_NAME($API_VERSION_CODE)! Using $executorName API $executorVersion") } @@ -274,11 +271,6 @@ object YukiHookAPI { * 通过 baseContext 创建 Hook 入口类 * @return [PackageParam] */ - private val Context.packageParam get() = PackageParam(PackageParamWrapper(packageName, processName, classLoader, applicationInfo)) - - /** - * 是否存在 [XposedBridge] - * @return [Boolean] - */ - internal val hasXposedBridge get() = executorVersion >= 0 + private val Context.packageParam + get() = PackageParam(PackageParamWrapper(HookEntryType.PACKAGE, packageName, processName, classLoader, applicationInfo)) } \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/annotation/xposed/InjectYukiHookWithXposed.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/annotation/xposed/InjectYukiHookWithXposed.kt index adb8bae5..86701af3 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/annotation/xposed/InjectYukiHookWithXposed.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/annotation/xposed/InjectYukiHookWithXposed.kt @@ -41,7 +41,7 @@ import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit * * 例子:com.example.module.hook.MainHook、com.example.module.hook.inject.MainInject、com.example.module.hook.custom.CustomClass * - * 你的 xposed_init 入口将被自动生成为 --> 你的模块APP包名/hook/...可允许子包名存在.../你的入口类_YukiHookXposedInit 或自定义 [entryClassName] + * 你的 xposed_init 入口将被自动生成为 --> 你的模块 APP 包名/hook/...可允许子包名存在.../你的入口类_YukiHookXposedInit 或自定义 [entryClassName] * * 例子:com.example.module.hook.MainHook_YukiHookXposedInit * diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/bean/CurrentClass.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/bean/CurrentClass.kt index d8903764..3fa59ec1 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/bean/CurrentClass.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/bean/CurrentClass.kt @@ -37,19 +37,47 @@ import com.highcapable.yukihookapi.hook.factory.method * @param instance 当前实例的 [Class] * @param self 当前实例本身 */ -class CurrentClass(private val instance: Class<*>, private val self: Any) { +class CurrentClass(@PublishedApi internal val instance: Class<*>, @PublishedApi internal val self: Any) { + + /** + * 调用父类实例 + * @return [SuperClass] + */ + fun superClass() = SuperClass() /** * 调用当前实例中的变量 * @param initiate 查找方法体 * @return [FieldFinder.Result.Instance] */ - fun field(initiate: FieldFinder.() -> Unit) = instance.field(initiate).get(self) + inline fun field(initiate: FieldFinder.() -> Unit) = instance.field(initiate).get(self) /** * 调用当前实例中的方法 * @param initiate 查找方法体 * @return [MethodFinder.Result.Instance] */ - fun method(initiate: MethodFinder.() -> Unit) = instance.method(initiate).get(self) + inline fun method(initiate: MethodFinder.() -> Unit) = instance.method(initiate).get(self) + + /** + * 当前类的父类实例的类操作对象 + * + * - ❗请使用 [superClass] 方法来获取 [SuperClass] + */ + inner class SuperClass { + + /** + * 调用父类实例中的变量 + * @param initiate 查找方法体 + * @return [FieldFinder.Result.Instance] + */ + inline fun field(initiate: FieldFinder.() -> Unit) = instance.superclass.field(initiate).get(self) + + /** + * 调用父类实例中的方法 + * @param initiate 查找方法体 + * @return [MethodFinder.Result.Instance] + */ + inline fun method(initiate: MethodFinder.() -> Unit) = instance.superclass.method(initiate).get(self) + } } \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/bean/HookResources.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/bean/HookResources.kt new file mode 100644 index 00000000..077b6651 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/bean/HookResources.kt @@ -0,0 +1,39 @@ +/* + * 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/5/1. + */ +package com.highcapable.yukihookapi.hook.bean + +import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiResources + +/** + * 创建一个当前 Hook 的 [YukiResources] 接管类 + * @param instance 实例 + */ +class HookResources(var instance: YukiResources? = null) { + + override fun toString() = "[instance] $instance" +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiHookCreater.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt similarity index 87% rename from yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiHookCreater.kt rename to yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt index 7e9acad6..a1cd6369 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiHookCreater.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt @@ -25,13 +25,12 @@ * * This file is Created by fankes on 2022/2/2. */ -@file:Suppress("MemberVisibilityCanBePrivate", "unused") +@file:Suppress("MemberVisibilityCanBePrivate", "unused", "PropertyName") package com.highcapable.yukihookapi.hook.core import android.os.SystemClock import com.highcapable.yukihookapi.YukiHookAPI -import com.highcapable.yukihookapi.annotation.YukiPrivateApi import com.highcapable.yukihookapi.hook.bean.HookClass import com.highcapable.yukihookapi.hook.core.finder.ConstructorFinder import com.highcapable.yukihookapi.hook.core.finder.FieldFinder @@ -40,21 +39,29 @@ import com.highcapable.yukihookapi.hook.log.yLoggerE import com.highcapable.yukihookapi.hook.log.yLoggerI import com.highcapable.yukihookapi.hook.param.HookParam import com.highcapable.yukihookapi.hook.param.PackageParam +import com.highcapable.yukihookapi.hook.param.type.HookEntryType import com.highcapable.yukihookapi.hook.param.wrapper.HookParamWrapper -import de.robv.android.xposed.XC_MethodHook -import de.robv.android.xposed.XC_MethodReplacement -import de.robv.android.xposed.XposedBridge +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge import java.lang.reflect.Field import java.lang.reflect.Member /** - * [YukiHookAPI] 核心 Hook 实现类 + * [YukiHookAPI] 的 [Member] 核心 Hook 实现类 * - * 这是一个 API 对接类 - 实现原生对接 [XposedBridge] + * 核心 API 对接 [YukiHookBridge.Hooker] 实现 * @param packageParam 需要传入 [PackageParam] 实现方法调用 * @param hookClass 要 Hook 的 [HookClass] 实例 */ -class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi internal val hookClass: HookClass) { +class YukiMemberHookCreater(private val packageParam: PackageParam, @PublishedApi internal val hookClass: HookClass) { + + /** 默认 Hook 回调优先级 */ + val PRIORITY_DEFAULT = 50 + + /** 延迟回调 Hook 方法结果 */ + val PRIORITY_LOWEST = -10000 + + /** 更快回调 Hook 方法结果 */ + val PRIORITY_HIGHEST = 10000 /** * Hook 模式定义 @@ -65,13 +72,13 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte /** [hookClass] 找不到时出现的错误回调 */ private var onHookClassNotFoundFailureCallback: ((Throwable) -> Unit)? = null - /** 是否对当前 [YukiHookCreater] 禁止执行 Hook 操作 */ + /** 是否对当前 [YukiMemberHookCreater] 禁止执行 Hook 操作 */ @PublishedApi internal var isDisableCreaterRunHook = false /** 设置要 Hook 的方法、构造类 */ @PublishedApi - internal var hookMembers = HashSet() + internal var preHookMembers = HashSet() /** * 得到当前被 Hook 的 [Class] @@ -85,25 +92,25 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte /** * 注入要 Hook 的方法、构造类 + * @param priority Hook 优先级 - 默认 [PRIORITY_DEFAULT] * @param tag 可设置标签 - 在发生错误时方便进行调试 * @param initiate 方法体 * @return [MemberHookCreater.Result] */ - inline fun injectMember(tag: String = "Default", initiate: MemberHookCreater.() -> Unit) = - MemberHookCreater(tag).apply(initiate).apply { hookMembers.add(this) }.build() + inline fun injectMember(priority: Int = PRIORITY_DEFAULT, tag: String = "Default", initiate: MemberHookCreater.() -> Unit) = + MemberHookCreater(priority, tag).apply(initiate).apply { preHookMembers.add(this) }.build() /** * Hook 执行入口 - * - * - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法 * @throws IllegalStateException 如果必要参数没有被设置 * @return [Result] */ @PublishedApi - @YukiPrivateApi internal fun hook(): Result { - if (YukiHookAPI.hasXposedBridge.not()) return Result() - return if (hookMembers.isEmpty()) error("Hook Members is empty, hook aborted") + if (YukiHookBridge.hasXposedBridge.not()) return Result() + /** 过滤 [HookEntryType.ZYGOTE] 与 [HookEntryType.PACKAGE] */ + if (packageParam.wrapper?.type == HookEntryType.RESOURCES) return Result() + return if (preHookMembers.isEmpty()) error("Hook Members is empty, hook aborted") else Result().also { Thread { /** 延迟使得方法取到返回值 */ @@ -111,7 +118,7 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte when { isDisableCreaterRunHook.not() && hookClass.instance != null -> { it.onPrepareHook?.invoke() - hookMembers.forEach { m -> m.hook() } + preHookMembers.forEach { m -> m.hook() } } isDisableCreaterRunHook.not() && hookClass.instance == null -> if (onHookClassNotFoundFailureCallback == null) @@ -126,9 +133,10 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte * Hook 核心功能实现类 * * 查找和处理需要 Hook 的方法、构造类 + * @param priority Hook 优先级 * @param tag 当前设置的标签 */ - inner class MemberHookCreater(var tag: String) { + inner class MemberHookCreater(private val priority: Int, internal val tag: String) { /** [beforeHook] 回调 */ private var beforeHookCallback: (HookParam.() -> Unit)? = null @@ -386,23 +394,15 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte /** * Hook 创建入口 - * - * - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法 * @return [Result] */ @PublishedApi - @YukiPrivateApi internal fun build() = Result() - /** - * Hook 执行入口 - * - * - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法 - */ + /** Hook 执行入口 */ @PublishedApi - @YukiPrivateApi internal fun hook() { - if (YukiHookAPI.hasXposedBridge.not() || isDisableMemberRunHook) return + if (YukiHookBridge.hasXposedBridge.not() || isDisableMemberRunHook) return if (hookClass.instance == null) { (hookClass.throwable ?: Throwable("HookClass [${hookClass.name}] not found")).also { onHookingFailureCallback?.invoke(it) @@ -411,11 +411,13 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte } return } + /** 定义替换 Hook 的 [HookParam] */ + val replaceHookParam = HookParam(createrInstance = this@YukiMemberHookCreater) + /** 定义替换 Hook 回调方法体 */ - val replaceMent = object : XC_MethodReplacement() { - override fun replaceHookedMethod(baseParam: MethodHookParam?): Any? { - if (baseParam == null) return null - return HookParam(createrInstance = this@YukiHookCreater, HookParamWrapper(baseParam)).let { param -> + val replaceMent = object : YukiHookBridge.Hooker.YukiMemberReplacement(priority) { + override fun replaceHookedMember(wrapper: HookParamWrapper): Any? { + return replaceHookParam.assign(wrapper).let { param -> try { if (replaceHookCallback != null || isReplaceHookOnlyResultMode) onHookLogMsg(msg = "Replace Hook Member [${member ?: "All Member $allMethodsName"}] done [$tag]") @@ -430,11 +432,16 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte } } + /** 定义前 Hook 的 [HookParam] */ + val beforeHookParam = HookParam(createrInstance = this@YukiMemberHookCreater) + + /** 定义后 Hook 的 [HookParam] */ + val afterHookParam = HookParam(createrInstance = this@YukiMemberHookCreater) + /** 定义前后 Hook 回调方法体 */ - val beforeAfterHook = object : XC_MethodHook() { - override fun beforeHookedMethod(baseParam: MethodHookParam?) { - if (baseParam == null) return - HookParam(createrInstance = this@YukiHookCreater, HookParamWrapper(baseParam)).also { param -> + val beforeAfterHook = object : YukiHookBridge.Hooker.YukiMemberHook(priority) { + override fun beforeHookedMember(wrapper: HookParamWrapper) { + beforeHookParam.assign(wrapper).also { param -> runCatching { beforeHookCallback?.invoke(param) if (beforeHookCallback != null) @@ -447,9 +454,8 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte } } - override fun afterHookedMethod(baseParam: MethodHookParam?) { - if (baseParam == null) return - HookParam(createrInstance = this@YukiHookCreater, HookParamWrapper(baseParam)).also { param -> + override fun afterHookedMember(wrapper: HookParamWrapper) { + afterHookParam.assign(wrapper).also { param -> runCatching { afterHookCallback?.invoke(param) if (afterHookCallback != null) @@ -467,9 +473,9 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte member.also { member -> runCatching { if (isReplaceHookMode) - XposedBridge.hookMethod(member, replaceMent)?.hookedMethod?.also { onHookedCallback?.invoke(it) } + YukiHookBridge.Hooker.hookMethod(member, replaceMent)?.also { onHookedCallback?.invoke(it) } ?: error("Hook Member [$member] failed") - else XposedBridge.hookMethod(member, beforeAfterHook)?.hookedMethod?.also { onHookedCallback?.invoke(it) } + else YukiHookBridge.Hooker.hookMethod(member, beforeAfterHook)?.also { onHookedCallback?.invoke(it) } ?: error("Hook Member [$member] failed") }.onFailure { onHookingFailureCallback?.invoke(it) @@ -493,23 +499,23 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte when (hookMemberMode) { HookMemberMode.HOOK_ALL_METHODS -> if (isReplaceHookMode) - XposedBridge.hookAllMethods(hookClass.instance, allMethodsName, replaceMent).also { + YukiHookBridge.Hooker.hookAllMethods(hookClass.instance, allMethodsName, replaceMent).also { if (it.isEmpty()) throw NoSuchMethodError("No Method name \"$allMethodsName\" matched") - else it.forEach { e -> onHookedCallback?.invoke(e.hookedMethod) } + else it.forEach { e -> onHookedCallback?.invoke(e) } } - else XposedBridge.hookAllMethods(hookClass.instance, allMethodsName, beforeAfterHook).also { + else YukiHookBridge.Hooker.hookAllMethods(hookClass.instance, allMethodsName, beforeAfterHook).also { if (it.isEmpty()) throw NoSuchMethodError("No Method name \"$allMethodsName\" matched") - else it.forEach { e -> onHookedCallback?.invoke(e.hookedMethod) } + else it.forEach { e -> onHookedCallback?.invoke(e) } } HookMemberMode.HOOK_ALL_CONSTRUCTORS -> if (isReplaceHookMode) - XposedBridge.hookAllConstructors(hookClass.instance, replaceMent).also { + YukiHookBridge.Hooker.hookAllConstructors(hookClass.instance, replaceMent).also { if (it.isEmpty()) throw NoSuchMethodError("No Constructor matched") - else it.forEach { e -> onHookedCallback?.invoke(e.hookedMethod) } + else it.forEach { e -> onHookedCallback?.invoke(e) } } - else XposedBridge.hookAllConstructors(hookClass.instance, beforeAfterHook).also { + else YukiHookBridge.Hooker.hookAllConstructors(hookClass.instance, beforeAfterHook).also { if (it.isEmpty()) throw NoSuchMethodError("No Constructor matched") - else it.forEach { e -> onHookedCallback?.invoke(e.hookedMethod) } + else it.forEach { e -> onHookedCallback?.invoke(e) } } else -> error("Hooked got a no error possible") } @@ -550,7 +556,7 @@ class YukiHookCreater(private val packageParam: PackageParam, @PublishedApi inte */ internal val isNotIgnoredNoSuchMemberFailure get() = onNoSuchMemberFailureCallback == null && isNotIgnoredHookingFailure - override fun toString() = "[tag] $tag [class] $hookClass [member] $member $allMethodsName [mode] $hookMemberMode" + override fun toString() = "[tag] $tag [priority] $priority [class] $hookClass [member] $member $allMethodsName [mode] $hookMemberMode" /** * 监听 Hook 结果实现类 diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiResourcesHookCreater.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiResourcesHookCreater.kt new file mode 100644 index 00000000..8645f83e --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiResourcesHookCreater.kt @@ -0,0 +1,368 @@ +/* + * 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/5/1. + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package com.highcapable.yukihookapi.hook.core + +import android.content.res.Resources +import com.highcapable.yukihookapi.YukiHookAPI +import com.highcapable.yukihookapi.hook.bean.HookResources +import com.highcapable.yukihookapi.hook.log.yLoggerE +import com.highcapable.yukihookapi.hook.log.yLoggerI +import com.highcapable.yukihookapi.hook.param.PackageParam +import com.highcapable.yukihookapi.hook.param.type.HookEntryType +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge +import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiResources + +/** + * [YukiHookAPI] 的 [Resources] 核心 Hook 实现类 + * + * @param packageParam 需要传入 [PackageParam] 实现方法调用 + * @param hookResources 要 Hook 的 [HookResources] 实例 + */ +class YukiResourcesHookCreater(private val packageParam: PackageParam, @PublishedApi internal val hookResources: HookResources) { + + /** 设置要 Hook 的 Resources */ + @PublishedApi + internal var preHookResources = HashSet() + + /** + * 注入要 Hook 的 Resources + * @param tag 可设置标签 - 在发生错误时方便进行调试 + * @param initiate 方法体 + * @return [ResourcesHookCreater.Result] + */ + inline fun injectResource(tag: String = "Default", initiate: ResourcesHookCreater.() -> Unit) = + ResourcesHookCreater(tag).apply(initiate).apply { preHookResources.add(this) }.build() + + /** + * Hook 执行入口 + * @throws IllegalStateException 如果必要参数没有被设置 + */ + @PublishedApi + internal fun hook() { + if (YukiHookBridge.hasXposedBridge.not()) return + /** 过滤 [HookEntryType.ZYGOTE] 与 [HookEntryType.RESOURCES] */ + if (packageParam.wrapper?.type == HookEntryType.PACKAGE) return + if (preHookResources.isEmpty()) error("Hook Resources is empty, hook aborted") + preHookResources.forEach { it.hook() } + } + + /** + * Hook 核心功能实现类 + * + * 查找和处理需要 Hook 的 Resources + * @param tag 当前设置的标签 + */ + inner class ResourcesHookCreater(private val tag: String) { + + /** + * 模块 APP Resources 替换实例 + * @param resId Resources Id + */ + private inner class ModuleResFwd(var resId: Int) + + /** 是否对当前 [ResourcesHookCreater] 禁止执行 Hook 操作 */ + @PublishedApi + internal var isDisableCreaterRunHook = false + + /** 当前的查找条件 */ + @PublishedApi + internal var conditions: ConditionFinder? = null + + /** Hook 出现错误回调 */ + private var onHookFailureCallback: ((Throwable) -> Unit)? = null + + /** 当前的替换值实例 */ + private var replaceInstance: Any? = null + + /** 当前的布局注入实例 */ + private var layoutInstance: (YukiResources.LayoutInflatedParam.() -> Unit)? = null + + /** 直接设置需要替换的 Resources Id */ + var resourceId = -1 + + /** + * 设置 Resources 查找条件 + * + * 若你设置了 [resourceId] 则此方法将不会被使用 + * @param initiate 条件方法体 + */ + inline fun conditions(initiate: ConditionFinder.() -> Unit) { + conditions = ConditionFinder().apply(initiate).build() + } + + /** + * 替换指定 Resources 为指定的值 + * @param any 可以是任何你想替换的类型 - 但要注意若当前类型不支持可能会报错 + */ + fun replaceTo(any: Any) { + replaceInstance = any + } + + /** + * 替换指定 Resources 为 true + * + * - ❗确保目标替换 Resources 的类型为 [Boolean] + */ + fun replaceToTrue() = replaceTo(any = true) + + /** + * 替换指定 Resources 为 false + * + * - ❗确保目标替换 Resources 的类型为 [Boolean] + */ + fun replaceToFalse() = replaceTo(any = false) + + /** + * 替换为当前 Xposed 模块的 Resources + * + * 你可以直接使用模块的 R.string.xxx、R.mipmap.xxx、R.drawable.xxx 替换 Hook APP 的 Resources + * @param resId 当前 Xposed 模块的 Resources Id + */ + fun replaceToModuleResource(resId: Int) { + replaceInstance = ModuleResFwd(resId) + } + + /** + * 作为装载的布局注入 + * @param initiate [YukiResources.LayoutInflatedParam] 方法体 + */ + fun injectAsLayout(initiate: YukiResources.LayoutInflatedParam.() -> Unit) { + layoutInstance = initiate + } + + /** + * 自动兼容当前替换的 Resources 类型 + * @param any 替换的任意类型 + * @return [Any] + */ + private fun compat(any: Any?) = if (any is ModuleResFwd) packageParam.moduleAppResources.fwd(any.resId) else any + + /** + * Hook 创建入口 + * @return [Result] + */ + @PublishedApi + internal fun build() = Result() + + /** Hook 执行入口 */ + @PublishedApi + internal fun hook() { + if (isDisableCreaterRunHook.not()) runCatching { + when { + conditions == null -> yLoggerE(msg = "You must set the conditions before hook a Resources [$tag]") + replaceInstance == null && layoutInstance == null -> yLoggerE(msg = "Resources Hook got null replaceInstance [$tag]") + packageParam.wrapper?.type == HookEntryType.RESOURCES && hookResources.instance != null -> + if (resourceId == -1) when { + layoutInstance != null -> + hookResources.instance?.hookLayout( + packageParam.packageName, conditions!!.type, + conditions!!.name, layoutInstance!! + ).run { onHookLogMsg(msg = "Hook Resources Layout $conditions done [$tag]") } + else -> hookResources.instance?.setReplacement( + packageParam.packageName, conditions!!.type, + conditions!!.name, compat(replaceInstance) + ).run { onHookLogMsg(msg = "Hook Resources Value $conditions done [$tag]") } + } else when { + layoutInstance != null -> hookResources.instance?.hookLayout(resourceId, layoutInstance!!) + .run { onHookLogMsg(msg = "Hook Resources Layout Id $resourceId done [$tag]") } + else -> hookResources.instance?.setReplacement(resourceId, compat(replaceInstance)) + .run { onHookLogMsg(msg = "Hook Resources Value Id $resourceId done [$tag]") } + } + packageParam.wrapper?.type == HookEntryType.ZYGOTE -> + if (resourceId == -1) when { + layoutInstance != null -> + YukiResources.hookSystemWideLayout( + packageParam.packageName, conditions!!.type, + conditions!!.name, layoutInstance!! + ).run { onHookLogMsg(msg = "Hook Wide Resources Layout $conditions done [$tag]") } + else -> YukiResources.setSystemWideReplacement( + packageParam.packageName, conditions!!.type, + conditions!!.name, compat(replaceInstance) + ).run { onHookLogMsg(msg = "Hook Wide Resources Value $conditions done [$tag]") } + } else when { + layoutInstance != null -> YukiResources.hookSystemWideLayout(resourceId, layoutInstance!!) + .run { onHookLogMsg(msg = "Hook Wide Resources Layout Id $resourceId done [$tag]") } + else -> YukiResources.setSystemWideReplacement(resourceId, compat(replaceInstance)) + .run { onHookLogMsg(msg = "Hook Wide Resources Value Id $resourceId done [$tag]") } + } + else -> yLoggerE(msg = "Resources Hook type is invalid [$tag]") + } + }.onFailure { + if (onHookFailureCallback == null) + yLoggerE(msg = "Resources Hook got an Exception [$tag]", e = it) + else onHookFailureCallback?.invoke(it) + } + } + + /** + * Hook 过程中开启了 [YukiHookAPI.Configs.isDebug] 输出调试信息 + * @param msg 调试日志内容 + */ + private fun onHookLogMsg(msg: String) { + if (YukiHookAPI.Configs.isDebug) yLoggerI(msg = msg) + } + + /** + * Resources 查找条件实现类 + */ + inner class ConditionFinder { + + /** Resources 类型 */ + internal var type = "" + + /** 设置 Resources 名称 */ + var name = "" + + /** 设置 Resources 类型为动画 */ + fun anim() { + type = "anim" + } + + /** 设置 Resources 类型为属性动画 */ + fun animator() { + type = "animator" + } + + /** 设置 Resources 类型为布朗(Boolean) */ + fun bool() { + type = "bool" + } + + /** 设置 Resources 类型为颜色(Color) */ + fun color() { + type = "color" + } + + /** 设置 Resources 类型为尺寸(Dimention) */ + fun dimen() { + type = "dimen" + } + + /** 设置 Resources 类型为 Drawable */ + fun drawable() { + type = "drawable" + } + + /** 设置 Resources 类型为整型(Integer) */ + fun integer() { + type = "integer" + } + + /** 设置 Resources 类型为布局(Layout) */ + fun layout() { + type = "layout" + } + + /** 设置 Resources 类型为 Plurals */ + fun plurals() { + type = "plurals" + } + + /** 设置 Resources 类型为字符串(String) */ + fun string() { + type = "string" + } + + /** 设置 Resources 类型为 Xml */ + fun xml() { + type = "xml" + } + + /** 设置 Resources 类型为位图(Mipmap) */ + fun mipmap() { + type = "mipmap" + } + + /** + * 创建查找对象实例 + * @return [ConditionFinder] + * @throws IllegalStateException 如果没有设置 [name] 或 [type] + */ + @PublishedApi + internal fun build(): ConditionFinder { + when { + name.isBlank() -> error("Resources Hook condition name cannot be empty [$tag]") + type.isBlank() -> error("Resources Hook condition type cannot be empty [$tag]") + } + return this + } + + override fun toString() = "[${if (packageParam.wrapper?.type == HookEntryType.ZYGOTE) "android." else ""}R.$type.$name]" + } + + /** + * 监听全部 Hook 结果实现类 + * + * 可在这里处理失败事件监听 + */ + inner class Result { + + /** + * 创建监听事件方法体 + * @param initiate 方法体 + * @return [Result] 可继续向下监听 + */ + inline fun result(initiate: Result.() -> Unit) = apply(initiate) + + /** + * 添加执行 Hook 需要满足的条件 + * + * 不满足条件将直接停止 Hook + * @param initiate 条件方法体 + * @return [Result] 可继续向下监听 + */ + inline fun by(initiate: () -> Boolean): Result { + isDisableCreaterRunHook = (runCatching { initiate() }.getOrNull() ?: false).not() + return this + } + + /** + * 监听 Hook 过程发生错误的回调方法 + * @param initiate 回调错误 + * @return [Result] 可继续向下监听 + */ + fun onHookingFailure(initiate: (Throwable) -> Unit): Result { + onHookFailureCallback = initiate + return this + } + + /** + * 忽略 Hook 过程出现的错误 + * @return [Result] 可继续向下监听 + */ + fun ignoredHookingFailure(): Result { + onHookingFailure {} + return this + } + } + + override fun toString() = "[tag] $tag [conditions] $conditions [replaceInstance] $replaceInstance [layoutInstance] $layoutInstance" + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/ConstructorFinder.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/ConstructorFinder.kt index 23f35f4d..2f5713ff 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/ConstructorFinder.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/ConstructorFinder.kt @@ -31,9 +31,10 @@ package com.highcapable.yukihookapi.hook.core.finder import com.highcapable.yukihookapi.annotation.YukiPrivateApi import com.highcapable.yukihookapi.hook.bean.VariousClass -import com.highcapable.yukihookapi.hook.core.YukiHookCreater +import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreater import com.highcapable.yukihookapi.hook.core.finder.base.BaseFinder import com.highcapable.yukihookapi.hook.core.finder.type.ModifierRules +import com.highcapable.yukihookapi.hook.factory.hasExtends import com.highcapable.yukihookapi.hook.log.yLoggerW import com.highcapable.yukihookapi.hook.type.defined.UndefinedType import com.highcapable.yukihookapi.hook.utils.ReflectionTool @@ -44,17 +45,23 @@ import java.lang.reflect.Constructor * [Constructor] 查找类 * * 可通过指定类型查找指定构造方法 - * @param hookInstance 当前 Hook 实例 - 填写后将自动设置 [YukiHookCreater.MemberHookCreater.member] + * @param hookInstance 当前 Hook 实例 - 填写后将自动设置 [YukiMemberHookCreater.MemberHookCreater.member] * @param classSet 当前需要查找的 [Class] 实例 */ class ConstructorFinder( @property:YukiPrivateApi - override val hookInstance: YukiHookCreater.MemberHookCreater? = null, + override val hookInstance: YukiMemberHookCreater.MemberHookCreater? = null, @property:YukiPrivateApi override val classSet: Class<*>? = null ) : BaseFinder(tag = "Constructor", hookInstance, classSet) { - /** 是否将结果设置到目标 [YukiHookCreater.MemberHookCreater] */ + /** 当前使用的 [classSet] */ + private var usedClassSet = classSet + + /** 是否在未找到后继续在当前 [classSet] 的父类中查找 */ + private var isFindInSuperClass = false + + /** 是否将结果设置到目标 [YukiMemberHookCreater.MemberHookCreater] */ private var isBindToHooker = false /** 当前重查找结果回调 */ @@ -136,16 +143,31 @@ class ConstructorFinder( return IndexTypeCondition(IndexConfigType.MATCH) } + /** + * 设置在 [classSet] 的所有父类中查找当前 [Constructor] + * + * - ❗若当前 [classSet] 的父类较多可能会耗时 - API 会自动循环到父类继承是 [Any] 前的最后一个类 + * @param isOnlySuperClass 是否仅在当前 [classSet] 的父类中查找 - 若父类是 [Any] 则不会生效 + */ + fun superClass(isOnlySuperClass: Boolean = false) { + isFindInSuperClass = true + if (isOnlySuperClass && classSet?.hasExtends == true) usedClassSet = classSet.superclass + } + /** * 得到构造方法 * @return [Constructor] * @throws NoSuchMethodError 如果找不到构造方法 */ - private val result get() = ReflectionTool.findConstructor(classSet, orderIndex, matchIndex, modifiers, paramCount, paramTypes) + private val result + get() = ReflectionTool.findConstructor( + usedClassSet, orderIndex, matchIndex, modifiers, + paramCount, paramTypes, isFindInSuperClass + ) /** * 设置实例 - * @param isBind 是否将结果设置到目标 [YukiHookCreater.MemberHookCreater] + * @param isBind 是否将结果设置到目标 [YukiMemberHookCreater.MemberHookCreater] * @param constructor 当前找到的 [Constructor] */ private fun setInstance(isBind: Boolean, constructor: Constructor<*>) { @@ -205,13 +227,8 @@ class ConstructorFinder( inline fun constructor(initiate: ConstructorFinder.() -> Unit) = Result().apply { remedyPlans.add(Pair(ConstructorFinder(hookInstance, classSet).apply(initiate), this)) } - /** - * 开始重查找 - * - * - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法 - */ + /** 开始重查找 */ @PublishedApi - @YukiPrivateApi internal fun build() { if (classSet == null) return if (remedyPlans.isNotEmpty()) run { diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/FieldFinder.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/FieldFinder.kt index 04c2ab6d..88aac584 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/FieldFinder.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/FieldFinder.kt @@ -32,9 +32,10 @@ package com.highcapable.yukihookapi.hook.core.finder import android.os.SystemClock import com.highcapable.yukihookapi.annotation.YukiPrivateApi import com.highcapable.yukihookapi.hook.bean.VariousClass -import com.highcapable.yukihookapi.hook.core.YukiHookCreater +import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreater import com.highcapable.yukihookapi.hook.core.finder.base.BaseFinder import com.highcapable.yukihookapi.hook.core.finder.type.ModifierRules +import com.highcapable.yukihookapi.hook.factory.hasExtends import com.highcapable.yukihookapi.hook.utils.ReflectionTool import com.highcapable.yukihookapi.hook.utils.runBlocking import java.lang.reflect.Field @@ -48,11 +49,17 @@ import java.lang.reflect.Field */ class FieldFinder( @property:YukiPrivateApi - override val hookInstance: YukiHookCreater.MemberHookCreater? = null, + override val hookInstance: YukiMemberHookCreater.MemberHookCreater? = null, @property:YukiPrivateApi override val classSet: Class<*>? = null ) : BaseFinder(tag = "Field", hookInstance, classSet) { + /** 当前使用的 [classSet] */ + private var usedClassSet = classSet + + /** 是否在未找到后继续在当前 [classSet] 的父类中查找 */ + private var isFindInSuperClass = false + /** [ModifierRules] 实例 */ @PublishedApi internal var modifiers: ModifierRules? = null @@ -121,11 +128,22 @@ class FieldFinder( return IndexTypeCondition(IndexConfigType.MATCH) } + /** + * 设置在 [classSet] 的所有父类中查找当前 [Field] + * + * - ❗若当前 [classSet] 的父类较多可能会耗时 - API 会自动循环到父类继承是 [Any] 前的最后一个类 + * @param isOnlySuperClass 是否仅在当前 [classSet] 的父类中查找 - 若父类是 [Any] 则不会生效 + */ + fun superClass(isOnlySuperClass: Boolean = false) { + isFindInSuperClass = true + if (isOnlySuperClass && classSet?.hasExtends == true) usedClassSet = classSet.superclass + } + /** * 得到变量处理结果 * * - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法 - * @param isBind 是否将结果设置到目标 [YukiHookCreater.MemberHookCreater] + * @param isBind 是否将结果设置到目标 [YukiMemberHookCreater.MemberHookCreater] * @return [Result] * @throws IllegalStateException 如果 [name] 没有被设置 */ @@ -133,7 +151,8 @@ class FieldFinder( override fun build(isBind: Boolean) = try { if (classSet != null) { runBlocking { - memberInstance = ReflectionTool.findField(classSet, orderIndex, matchIndex, name, modifiers, type.compat()) + memberInstance = + ReflectionTool.findField(usedClassSet, orderIndex, matchIndex, name, modifiers, type.compat(), isFindInSuperClass) }.result { onHookLogMsg(msg = "Find Field [${memberInstance}] takes ${it}ms [${hookTag}]") } Result() } else Result(isNoSuch = true, Throwable("classSet is null")) diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/MethodFinder.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/MethodFinder.kt index d1cc20f6..e22f841a 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/MethodFinder.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/MethodFinder.kt @@ -31,9 +31,10 @@ package com.highcapable.yukihookapi.hook.core.finder import com.highcapable.yukihookapi.annotation.YukiPrivateApi import com.highcapable.yukihookapi.hook.bean.VariousClass -import com.highcapable.yukihookapi.hook.core.YukiHookCreater +import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreater import com.highcapable.yukihookapi.hook.core.finder.base.BaseFinder import com.highcapable.yukihookapi.hook.core.finder.type.ModifierRules +import com.highcapable.yukihookapi.hook.factory.hasExtends import com.highcapable.yukihookapi.hook.log.yLoggerW import com.highcapable.yukihookapi.hook.type.defined.UndefinedType import com.highcapable.yukihookapi.hook.utils.ReflectionTool @@ -44,17 +45,23 @@ import java.lang.reflect.Method * [Method] 查找类 * * 可通过指定类型查找指定方法 - * @param hookInstance 当前 Hook 实例 - 填写后将自动设置 [YukiHookCreater.MemberHookCreater.member] + * @param hookInstance 当前 Hook 实例 - 填写后将自动设置 [YukiMemberHookCreater.MemberHookCreater.member] * @param classSet 当前需要查找的 [Class] 实例 */ class MethodFinder( @property:YukiPrivateApi - override val hookInstance: YukiHookCreater.MemberHookCreater? = null, + override val hookInstance: YukiMemberHookCreater.MemberHookCreater? = null, @property:YukiPrivateApi override val classSet: Class<*>? = null ) : BaseFinder(tag = "Method", hookInstance, classSet) { - /** 是否将结果设置到目标 [YukiHookCreater.MemberHookCreater] */ + /** 当前使用的 [classSet] */ + private var usedClassSet = classSet + + /** 是否在未找到后继续在当前 [classSet] 的父类中查找 */ + private var isFindInSuperClass = false + + /** 是否将结果设置到目标 [YukiMemberHookCreater.MemberHookCreater] */ private var isBindToHooker = false /** 当前重查找结果回调 */ @@ -182,17 +189,32 @@ class MethodFinder( return IndexTypeCondition(IndexConfigType.MATCH) } + /** + * 设置在 [classSet] 的所有父类中查找当前 [Method] + * + * - ❗若当前 [classSet] 的父类较多可能会耗时 - API 会自动循环到父类继承是 [Any] 前的最后一个类 + * @param isOnlySuperClass 是否仅在当前 [classSet] 的父类中查找 - 若父类是 [Any] 则不会生效 + */ + fun superClass(isOnlySuperClass: Boolean = false) { + isFindInSuperClass = true + if (isOnlySuperClass && classSet?.hasExtends == true) usedClassSet = classSet.superclass + } + /** * 得到方法 * @return [Method] * @throws NoSuchMethodError 如果找不到方法 */ private val result - get() = ReflectionTool.findMethod(classSet, orderIndex, matchIndex, name, modifiers, returnType.compat(), paramCount, paramTypes) + get() = ReflectionTool.findMethod( + usedClassSet, orderIndex, matchIndex, + name, modifiers, returnType.compat(), + paramCount, paramTypes, isFindInSuperClass + ) /** * 设置实例 - * @param isBind 是否将结果设置到目标 [YukiHookCreater.MemberHookCreater] + * @param isBind 是否将结果设置到目标 [YukiMemberHookCreater.MemberHookCreater] * @param method 当前找到的 [Method] */ private fun setInstance(isBind: Boolean, method: Method) { @@ -204,7 +226,7 @@ class MethodFinder( * 得到方法结果 * * - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法 - * @param isBind 是否将结果设置到目标 [YukiHookCreater.MemberHookCreater] + * @param isBind 是否将结果设置到目标 [YukiMemberHookCreater.MemberHookCreater] * @return [Result] */ @YukiPrivateApi @@ -254,13 +276,8 @@ class MethodFinder( inline fun method(initiate: MethodFinder.() -> Unit) = Result().apply { remedyPlans.add(Pair(MethodFinder(hookInstance, classSet).apply(initiate), this)) } - /** - * 开始重查找 - * - * - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法 - */ + /** 开始重查找 */ @PublishedApi - @YukiPrivateApi internal fun build() { if (classSet == null) return if (remedyPlans.isNotEmpty()) run { diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/base/BaseFinder.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/base/BaseFinder.kt index 77db71b2..3863f56b 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/base/BaseFinder.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/base/BaseFinder.kt @@ -31,11 +31,12 @@ import android.os.SystemClock import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.annotation.YukiPrivateApi import com.highcapable.yukihookapi.hook.bean.VariousClass -import com.highcapable.yukihookapi.hook.core.YukiHookCreater +import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreater import com.highcapable.yukihookapi.hook.factory.classOf import com.highcapable.yukihookapi.hook.log.yLoggerE import com.highcapable.yukihookapi.hook.log.yLoggerI import com.highcapable.yukihookapi.hook.type.defined.UndefinedType +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge import java.lang.reflect.Member import kotlin.math.abs @@ -47,7 +48,7 @@ import kotlin.math.abs */ abstract class BaseFinder( private val tag: String, - open val hookInstance: YukiHookCreater.MemberHookCreater? = null, + open val hookInstance: YukiMemberHookCreater.MemberHookCreater? = null, open val classSet: Class<*>? = null ) { @@ -170,14 +171,14 @@ abstract class BaseFinder( * @param msg 调试日志内容 */ internal fun onHookLogMsg(msg: String) { - if (YukiHookAPI.Configs.isDebug && YukiHookAPI.hasXposedBridge) yLoggerI(msg = msg) + if (YukiHookAPI.Configs.isDebug && YukiHookBridge.hasXposedBridge) yLoggerI(msg = msg) } /** * 得到结果 * * - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法 - * @param isBind 是否将结果设置到目标 [YukiHookCreater.MemberHookCreater] + * @param isBind 是否将结果设置到目标 [YukiMemberHookCreater.MemberHookCreater] * @return [Any] */ @YukiPrivateApi diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt index 96aa93cd..1375d42e 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt @@ -29,7 +29,6 @@ package com.highcapable.yukihookapi.hook.factory -import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.hook.bean.CurrentClass import com.highcapable.yukihookapi.hook.bean.HookClass import com.highcapable.yukihookapi.hook.core.finder.ConstructorFinder @@ -37,6 +36,7 @@ import com.highcapable.yukihookapi.hook.core.finder.FieldFinder import com.highcapable.yukihookapi.hook.core.finder.MethodFinder import com.highcapable.yukihookapi.hook.core.finder.type.ModifierRules import com.highcapable.yukihookapi.hook.store.MemberCacheStore +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge import de.robv.android.xposed.XposedHelpers import java.lang.reflect.Constructor import java.lang.reflect.Field @@ -63,6 +63,12 @@ val HookClass.normalClass get() = instance */ val String.hasClass get() = hasClass(loader = null) +/** + * 当前 [Class] 是否有继承关系 - 父类是 [Any] 将被认为没有继承关系 + * @return [Boolean] + */ +val Class<*>.hasExtends get() = superclass.name != "java.lang.Object" + /** * 通过字符串转换为实体类 * @param name [Class] 的完整包名+名称 @@ -74,7 +80,7 @@ fun classOf(name: String, loader: ClassLoader? = null): Class<*> { val hashCode = ("[$name][$loader]").hashCode() return MemberCacheStore.findClass(hashCode) ?: run { when { - YukiHookAPI.hasXposedBridge -> + YukiHookBridge.hasXposedBridge -> runCatching { XposedHelpers.findClassIfExists(name, loader) }.getOrNull() ?: when (loader) { null -> Class.forName(name) 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 7721eee2..224aa62e 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 @@ -46,13 +46,13 @@ import java.io.File import java.io.FileReader /** - * 在 [IYukiHookXposedInit] 中装载 [YukiHookAPI.Configs] - * @param initiate Hook 方法体 + * 在 [IYukiHookXposedInit] 中调用 [YukiHookAPI.configs] + * @param initiate 配置方法体 */ inline fun IYukiHookXposedInit.configs(initiate: YukiHookAPI.Configs.() -> Unit) = YukiHookAPI.configs(initiate) /** - * 在 [IYukiHookXposedInit] 中装载 [YukiHookAPI] + * 在 [IYukiHookXposedInit] 中调用 [YukiHookAPI.encase] * @param initiate Hook 方法体 */ fun IYukiHookXposedInit.encase(initiate: PackageParam.() -> Unit) = YukiHookAPI.encase(initiate) @@ -102,6 +102,12 @@ val Context.processName packageName ?: "" } +/** + * 判断当前 Hook Framework 是否支持资源钩子(Resources Hook) + * @return [Boolean] 是否支持 + */ +val Any?.isSupportResourcesHook get() = YukiHookModuleStatus.hasResourcesHook() + /** * 判断模块是否在 Xposed 或太极、无极中激活 * @return [Boolean] 是否激活 diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/log/LoggerFactory.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/log/LoggerFactory.kt index 8a6e8c5c..c33be2d3 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/log/LoggerFactory.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/log/LoggerFactory.kt @@ -35,8 +35,6 @@ import de.robv.android.xposed.XposedBridge /** * [YukiHookAPI] 向控制台和 [XposedBridge] 打印日志 - D - * - * - ❗此方法为私有功能性 API - 你不应该手动调用此方法 * @param msg 日志打印的内容 * @param isDisableLog 禁止打印日志 - 标识后将什么也不做 - 默认为 false */ @@ -46,8 +44,6 @@ internal fun yLoggerD(msg: String, isDisableLog: Boolean = false) { /** * [YukiHookAPI] 向控制台和 [XposedBridge] 打印日志 - I - * - * - ❗此方法为私有功能性 API - 你不应该手动调用此方法 * @param msg 日志打印的内容 * @param isDisableLog 禁止打印日志 - 标识后将什么也不做 - 默认为 false */ @@ -57,8 +53,6 @@ internal fun yLoggerI(msg: String, isDisableLog: Boolean = false) { /** * [YukiHookAPI] 向控制台和 [XposedBridge] 打印日志 - W - * - * - ❗此方法为私有功能性 API - 你不应该手动调用此方法 * @param msg 日志打印的内容 * @param isDisableLog 禁止打印日志 - 标识后将什么也不做 - 默认为 false */ @@ -68,8 +62,6 @@ internal fun yLoggerW(msg: String, isDisableLog: Boolean = false) { /** * [YukiHookAPI] 向控制台和 [XposedBridge] 打印日志 - E - * - * - ❗此方法为私有功能性 API - 你不应该手动调用此方法 * @param msg 日志打印的内容 * @param e 可填入异常堆栈信息 - 将自动完整打印到控制台 * @param isDisableLog 禁止打印日志 - 标识后将什么也不做 - 默认为 false diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/HookParam.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/HookParam.kt index 74b516fa..51057a52 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/HookParam.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/HookParam.kt @@ -29,7 +29,7 @@ package com.highcapable.yukihookapi.hook.param -import com.highcapable.yukihookapi.hook.core.YukiHookCreater +import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreater import com.highcapable.yukihookapi.hook.param.wrapper.HookParamWrapper import java.lang.reflect.Constructor import java.lang.reflect.Member @@ -37,16 +37,26 @@ import java.lang.reflect.Method /** * Hook 方法、构造类的目标对象实现类 - * @param createrInstance [YukiHookCreater] 的实例对象 + * @param createrInstance [YukiMemberHookCreater] 的实例对象 * @param wrapper [HookParam] 的参数包装类实例 */ -class HookParam(private val createrInstance: YukiHookCreater, private val wrapper: HookParamWrapper) { +class HookParam(private val createrInstance: YukiMemberHookCreater, private var wrapper: HookParamWrapper? = null) { + + /** + * 在回调中设置 [HookParam] 使用的 [HookParamWrapper] + * @param wrapper [HookParamWrapper] 实例 + * @return [HookParam] + */ + internal fun assign(wrapper: HookParamWrapper): HookParam { + this.wrapper = wrapper + return this + } /** * 获取当前 Hook 对象 [method] or [constructor] 的参数对象数组 * @return [Array] */ - val args get() = wrapper.args ?: arrayOf(0) + val args get() = wrapper?.args ?: arrayOf(0) /** * 获取当前 Hook 实例的对象 @@ -55,36 +65,36 @@ class HookParam(private val createrInstance: YukiHookCreater, private val wrappe * @return [Any] * @throws IllegalStateException 如果对象为空 */ - val instance get() = wrapper.instance ?: error("HookParam instance got null! Is this a static member?") + val instance get() = wrapper?.instance ?: error("HookParam instance got null! Is this a static member?") /** * 获取当前 Hook 实例的类对象 * @return [Class] */ - val instanceClass get() = wrapper.instance?.javaClass ?: createrInstance.instanceClass + val instanceClass get() = wrapper?.instance?.javaClass ?: createrInstance.instanceClass /** * 获取当前 Hook 对象的方法 * @return [Method] * @throws IllegalStateException 如果 [Method] 为空或方法类型不是 [Method] */ - val method get() = wrapper.member as? Method? ?: error("Current hook Method type is wrong or null") + val method get() = wrapper?.member as? Method? ?: error("Current hook Method type is wrong or null") /** * 获取当前 Hook 对象的构造方法 * @return [Constructor] * @throws IllegalStateException 如果 [Constructor] 为空或方法类型不是 [Constructor] */ - val constructor get() = wrapper.member as? Constructor<*>? ?: error("Current hook Constructor type is wrong or null") + val constructor get() = wrapper?.member as? Constructor<*>? ?: error("Current hook Constructor type is wrong or null") /** * 获取、设置当前 Hook 对象的 [method] or [constructor] 的返回值 * @return [Any] or null */ var result: Any? - get() = wrapper.result + get() = wrapper?.result set(value) { - wrapper.result = value + wrapper?.result = value } /** @@ -120,7 +130,7 @@ class HookParam(private val createrInstance: YukiHookCreater, private val wrappe * @param args 参数实例 * @return [T] */ - fun Member.invokeOriginal(vararg args: Any?) = wrapper.invokeOriginalMember(member = this, *args) as? T? + fun Member.invokeOriginal(vararg args: Any?) = wrapper?.invokeOriginalMember(member = this, *args) as? T? /** * 设置当前 Hook 对象方法的 [result] 返回值为 true @@ -286,7 +296,7 @@ class HookParam(private val createrInstance: YukiHookCreater, private val wrappe if (index < 0) error("HookParam Method args index must be >= 0") if (args.isEmpty()) error("HookParam Method args is empty, mabe not has args") if (index > args.lastIndex) error("HookParam Method args index out of bounds, max is ${args.lastIndex}") - wrapper.setArgs(index, any) + wrapper?.setArgs(index, any) } /** diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/PackageParam.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/PackageParam.kt index 52f31802..3bd26503 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/PackageParam.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/PackageParam.kt @@ -31,14 +31,21 @@ package com.highcapable.yukihookapi.hook.param import android.app.Application import android.content.pm.ApplicationInfo +import android.content.res.Resources import com.highcapable.yukihookapi.hook.bean.HookClass +import com.highcapable.yukihookapi.hook.bean.HookResources import com.highcapable.yukihookapi.hook.bean.VariousClass -import com.highcapable.yukihookapi.hook.core.YukiHookCreater +import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreater +import com.highcapable.yukihookapi.hook.core.YukiResourcesHookCreater import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.factory.classOf import com.highcapable.yukihookapi.hook.factory.hasClass import com.highcapable.yukihookapi.hook.factory.hookClass +import com.highcapable.yukihookapi.hook.param.type.HookEntryType import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge +import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiModuleResources +import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiResources import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs @@ -46,7 +53,7 @@ import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs * 装载 Hook 的目标 APP 入口对象实现类 * @param wrapper [PackageParam] 的参数包装类实例 - 默认是空的 */ -open class PackageParam(private var wrapper: PackageParamWrapper? = null) { +open class PackageParam(@PublishedApi internal var wrapper: PackageParamWrapper? = null) { /** * 获取当前 Hook APP 的 [ClassLoader] @@ -63,13 +70,6 @@ open class PackageParam(private var wrapper: PackageParamWrapper? = null) { */ val appInfo get() = wrapper?.appInfo ?: YukiHookAppHelper.currentApplicationInfo() ?: ApplicationInfo() - /** - * 获取当前 Hook APP 的 [Application] 实例 - * @return [Application] - * @throws IllegalStateException 如果 [Application] 是空的 - */ - val appContext get() = YukiHookAppHelper.currentApplication() ?: error("PackageParam got null appContext") - /** * 获取当前 Hook APP 的进程名称 * @@ -84,11 +84,29 @@ open class PackageParam(private var wrapper: PackageParamWrapper? = null) { */ val packageName get() = wrapper?.packageName ?: YukiHookAppHelper.currentPackageName() ?: "" + /** + * 获取当前 Hook APP 的 [Application] 实例 + * + * - ❗首次装载可能是空的 - 请延迟一段时间再获取 + * @return [Application] + * @throws IllegalStateException 如果 [Application] 是空的 + */ + val appContext get() = YukiHookAppHelper.currentApplication() ?: error("PackageParam got null appContext") + + /** + * 获取当前 Hook APP 的 Resources + * + * - ❗你只能在 [HookResources.hook] 方法体内或 [appContext] 装载完毕时进行调用 + * @return [Resources] + * @throws IllegalStateException 如果当前处于 [loadZygote] 或 [appContext] 尚未加载 + */ + val appResources get() = wrapper?.appResources ?: appContext.resources ?: error("You cannot call to appResources in this time") + /** * 获取当前 Hook APP 是否为第一个 [Application] * @return [Boolean] */ - val isFirstApplication get() = packageName == processName + val isFirstApplication get() = packageName.trim() == processName.trim() /** * 获取当前 Hook APP 的主进程名称 @@ -96,21 +114,50 @@ open class PackageParam(private var wrapper: PackageParamWrapper? = null) { * 其对应的就是 [packageName] * @return [String] */ - val mainProcessName get() = packageName + val mainProcessName get() = packageName.trim() + + /** + * 获取当前 Xposed 模块自身 APK 文件路径 + * + * - ❗作为 Hook API 装载时无法使用 - 会获取到空字符串 + * @return [String] + */ + val moduleAppFilePath get() = YukiHookBridge.moduleAppFilePath + + /** + * 获取当前 Xposed 模块自身 [Resources] + * + * - ❗作为 Hook API 或不支持的 Hook Framework 装载时无法使用 - 会抛出异常 + * @return [YukiModuleResources] + * @throws IllegalStateException 如果当前 Hook Framework 不支持此功能 + */ + val moduleAppResources get() = YukiHookBridge.moduleAppResources ?: error("Current Hook Framework not support moduleAppResources") /** * 获得当前使用的存取数据对象缓存实例 + * + * - ❗作为 Hook API 装载时无法使用 - 会抛出异常 * @return [YukiHookModulePrefs] */ val prefs by lazy { YukiHookModulePrefs() } /** * 获得当前使用的存取数据对象缓存实例 + * + * - ❗作为 Hook API 装载时无法使用 - 会抛出异常 * @param name 自定义 Sp 存储名称 * @return [YukiHookModulePrefs] */ fun prefs(name: String) = prefs.name(name) + /** + * 获得当前 Hook APP 的 [YukiResources] 对象 + * + * 请调用 [HookResources.hook] 方法开始 Hook + * @return [HookResources] + */ + fun resources() = HookResources(wrapper?.appResources) + /** * 赋值并克隆另一个 [PackageParam] * @param anotherParam 另一个 [PackageParam] @@ -120,21 +167,41 @@ open class PackageParam(private var wrapper: PackageParamWrapper? = null) { } /** - * 装载并 Hook 指定包名的 APP - * @param name 包名 + * 装载并 Hook 指定、全部包名的 APP + * + * 若要 Hook 系统框架 - 请使用 [loadZygote] + * @param name 包名 - 不填将过滤除了系统框架的全部 APP * @param initiate 方法体 */ - inline fun loadApp(name: String, initiate: PackageParam.() -> Unit) { - if (packageName == name) initiate(this) + inline fun loadApp(name: String = "", initiate: PackageParam.() -> Unit) { + if (wrapper?.type != HookEntryType.ZYGOTE && (packageName == name || name.isBlank())) initiate(this) } /** - * 装载并 Hook 指定包名的 APP - * @param name 包名 + * 装载并 Hook 指定、全部包名的 APP + * + * 若要 Hook 系统框架 - 请使用 [loadZygote] + * @param name 包名 - 不填将过滤除了系统框架的全部 APP * @param hooker Hook 子类 */ - fun loadApp(name: String, hooker: YukiBaseHooker) { - if (packageName == name) loadHooker(hooker) + fun loadApp(name: String = "", hooker: YukiBaseHooker) { + if (wrapper?.type != HookEntryType.ZYGOTE && (packageName == name || name.isBlank())) loadHooker(hooker) + } + + /** + * 装载并 Hook 系统框架 + * @param initiate 方法体 + */ + inline fun loadZygote(initiate: PackageParam.() -> Unit) { + if (wrapper?.type == HookEntryType.ZYGOTE) initiate(this) + } + + /** + * 装载并 Hook 系统框架 + * @param hooker Hook 子类 + */ + fun loadZygote(hooker: YukiBaseHooker) { + if (wrapper?.type == HookEntryType.ZYGOTE) loadHooker(hooker) } /** @@ -217,37 +284,44 @@ open class PackageParam(private var wrapper: PackageParamWrapper? = null) { * - ❗为防止任何字符串都被当做 [Class] 进行 Hook - 推荐优先使用 [findClass] * @param isUseAppClassLoader 是否使用 [appClassLoader] 重新绑定当前 [Class] - 默认启用 * @param initiate 方法体 - * @return [YukiHookCreater.Result] + * @return [YukiMemberHookCreater.Result] */ - inline fun String.hook(isUseAppClassLoader: Boolean = true, initiate: YukiHookCreater.() -> Unit) = + inline fun String.hook(isUseAppClassLoader: Boolean = true, initiate: YukiMemberHookCreater.() -> Unit) = findClass(name = this).hook(isUseAppClassLoader, initiate) /** * Hook 方法、构造类 * @param isUseAppClassLoader 是否使用 [appClassLoader] 重新绑定当前 [Class] - 默认启用 * @param initiate 方法体 - * @return [YukiHookCreater.Result] + * @return [YukiMemberHookCreater.Result] */ - inline fun Class<*>.hook(isUseAppClassLoader: Boolean = true, initiate: YukiHookCreater.() -> Unit) = + inline fun Class<*>.hook(isUseAppClassLoader: Boolean = true, initiate: YukiMemberHookCreater.() -> Unit) = hookClass.hook(isUseAppClassLoader, initiate) /** * Hook 方法、构造类 * @param isUseAppClassLoader 是否使用 [appClassLoader] 重新绑定当前 [Class] - 默认启用 * @param initiate 方法体 - * @return [YukiHookCreater.Result] + * @return [YukiMemberHookCreater.Result] */ - inline fun VariousClass.hook(isUseAppClassLoader: Boolean = true, initiate: YukiHookCreater.() -> Unit) = + inline fun VariousClass.hook(isUseAppClassLoader: Boolean = true, initiate: YukiMemberHookCreater.() -> Unit) = hookClass(if (isUseAppClassLoader) appClassLoader else null).hook(isUseAppClassLoader, initiate) /** * Hook 方法、构造类 * @param isUseAppClassLoader 是否使用 [appClassLoader] 重新绑定当前 [Class] - 默认启用 * @param initiate 方法体 - * @return [YukiHookCreater.Result] + * @return [YukiMemberHookCreater.Result] */ - inline fun HookClass.hook(isUseAppClassLoader: Boolean = true, initiate: YukiHookCreater.() -> Unit) = - YukiHookCreater(packageParam = this@PackageParam, hookClass = if (isUseAppClassLoader) bind() else this).apply(initiate).hook() + inline fun HookClass.hook(isUseAppClassLoader: Boolean = true, initiate: YukiMemberHookCreater.() -> Unit) = + YukiMemberHookCreater(packageParam = this@PackageParam, hookClass = if (isUseAppClassLoader) bind() else this).apply(initiate).hook() + + /** + * Hook APP 的 Resources + * @param initiate 方法体 + */ + inline fun HookResources.hook(initiate: YukiResourcesHookCreater.() -> Unit) = + YukiResourcesHookCreater(packageParam = this@PackageParam, hookResources = this).apply(initiate).hook() /** * [VariousClass] 转换为 [HookClass] 并绑定到 [appClassLoader] diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/type/HookEntryType.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/type/HookEntryType.kt new file mode 100644 index 00000000..75dda8bc --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/type/HookEntryType.kt @@ -0,0 +1,56 @@ +/* + * 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/4/26. + */ +package com.highcapable.yukihookapi.hook.param.type + +import com.highcapable.yukihookapi.annotation.YukiPrivateApi +import com.highcapable.yukihookapi.hook.param.type.HookEntryType.* +import de.robv.android.xposed.IXposedHookInitPackageResources +import de.robv.android.xposed.IXposedHookLoadPackage +import de.robv.android.xposed.IXposedHookZygoteInit + +/** + * 当前正在进行的 Hook 类型 + * + * [ZYGOTE] 为 [IXposedHookZygoteInit.initZygote] + * + * [PACKAGE] 为 [IXposedHookLoadPackage.handleLoadPackage] + * + * [RESOURCES] 为 [IXposedHookInitPackageResources.handleInitPackageResources] + */ +@YukiPrivateApi +enum class HookEntryType { + + /** initZygote */ + ZYGOTE, + + /** handleLoadPackage */ + PACKAGE, + + /** handleInitPackageResources */ + RESOURCES +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/wrapper/HookParamWrapper.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/wrapper/HookParamWrapper.kt index 4f19bfa8..be7f6047 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/wrapper/HookParamWrapper.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/wrapper/HookParamWrapper.kt @@ -40,34 +40,44 @@ import java.lang.reflect.Member * @param baseParam 对接 [XC_MethodHook.MethodHookParam] */ @YukiPrivateApi -class HookParamWrapper(private val baseParam: XC_MethodHook.MethodHookParam) { +class HookParamWrapper(private var baseParam: XC_MethodHook.MethodHookParam? = null) { + + /** + * 在回调中设置 [HookParamWrapper] 使用的 [XC_MethodHook.MethodHookParam] + * @param baseParam 对接 [XC_MethodHook.MethodHookParam] + * @return [HookParamWrapper] + */ + internal fun assign(baseParam: XC_MethodHook.MethodHookParam): HookParamWrapper { + this.baseParam = baseParam + return this + } /** * [Member] 实例 * @return [Member] or null */ - val member: Member? get() = baseParam.method + val member: Member? get() = baseParam?.method /** * 当前实例对象 * @return [Any] or null */ - val instance: Any? get() = baseParam.thisObject + val instance: Any? get() = baseParam?.thisObject /** * 方法、构造方法数组 * @return [Array] or null */ - val args: Array? get() = baseParam.args + val args: Array? get() = baseParam?.args /** * 方法、设置方法结果 * @return [Any] or null */ var result: Any? - get() = baseParam.result + get() = baseParam?.result set(value) { - baseParam.result = value + baseParam?.result = value } /** @@ -75,9 +85,7 @@ class HookParamWrapper(private val baseParam: XC_MethodHook.MethodHookParam) { * @param index 数组下标 * @param any 参数对象实例 */ - fun setArgs(index: Int, any: Any?) { - baseParam.args[index] = any - } + fun setArgs(index: Int, any: Any?) = baseParam?.args?.set(index, any) /** * 执行原始 [Member] @@ -87,8 +95,7 @@ class HookParamWrapper(private val baseParam: XC_MethodHook.MethodHookParam) { * @param args 参数实例 * @return [Any] or null */ - fun invokeOriginalMember(member: Member, vararg args: Any?): Any? = - XposedBridge.invokeOriginalMethod(member, instance, args) + fun invokeOriginalMember(member: Member, vararg args: Any?): Any? = XposedBridge.invokeOriginalMethod(member, instance, args) override fun toString() = "HookParamWrapper[$baseParam]" } \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/wrapper/PackageParamWrapper.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/wrapper/PackageParamWrapper.kt index 5fad189f..73433846 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/wrapper/PackageParamWrapper.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/wrapper/PackageParamWrapper.kt @@ -32,23 +32,30 @@ package com.highcapable.yukihookapi.hook.param.wrapper import android.content.pm.ApplicationInfo import com.highcapable.yukihookapi.annotation.YukiPrivateApi import com.highcapable.yukihookapi.hook.param.PackageParam +import com.highcapable.yukihookapi.hook.param.type.HookEntryType +import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiResources /** * 用于包装 [PackageParam] * * - ❗这是一个私有 API - 请不要在外部使用 + * @param type 当前正在进行的 Hook 类型 * @param packageName 包名 * @param processName 当前进程名 * @param appClassLoader APP [ClassLoader] * @param appInfo APP [ApplicationInfo] + * @param appResources APP [YukiResources] */ @YukiPrivateApi class PackageParamWrapper( + var type: HookEntryType, var packageName: String, var processName: String, var appClassLoader: ClassLoader, - var appInfo: ApplicationInfo + var appInfo: ApplicationInfo? = null, + var appResources: YukiResources? = null ) { - override fun toString() = "PackageParamWrapper [packageName] $packageName [processName] $processName [appInfo] $appInfo" + override fun toString() = + "PackageParamWrapper [type] $type [packageName] $packageName [processName] $processName [appInfo] $appInfo [appResources] $appResources" } \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionTool.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionTool.kt index 85344722..b679dcb0 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionTool.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/utils/ReflectionTool.kt @@ -28,6 +28,7 @@ package com.highcapable.yukihookapi.hook.utils import com.highcapable.yukihookapi.hook.core.finder.type.ModifierRules +import com.highcapable.yukihookapi.hook.factory.hasExtends import com.highcapable.yukihookapi.hook.store.MemberCacheStore import com.highcapable.yukihookapi.hook.type.defined.UndefinedType import java.lang.reflect.Constructor @@ -52,6 +53,7 @@ internal object ReflectionTool { * @param name 变量名称 * @param modifiers 变量描述 * @param type 变量类型 + * @param isFindInSuperClass 是否在未找到后继续在当前 [classSet] 的父类中查找 * @return [Field] * @throws IllegalStateException 如果 [classSet] 为 null 或未设置任何条件或 [type] 目标类不存在 * @throws NoSuchFieldError 如果找不到变量 @@ -62,7 +64,8 @@ internal object ReflectionTool { matchIndex: Pair?, name: String, modifiers: ModifierRules?, - type: Class<*>? + type: Class<*>?, + isFindInSuperClass: Boolean ): Field { if (type == UndefinedType) error("Field match type class is not found") if (orderIndex == null && matchIndex == null && name.isBlank() && modifiers == null && type == null) @@ -121,22 +124,25 @@ internal object ReflectionTool { } } ?: error("Can't find this Field [$name] because classSet is null") field?.also { MemberCacheStore.putField(hashCode, field) } - ?: throw NoSuchFieldError( + ?: if (isFindInSuperClass && classSet.hasExtends) + findField( + classSet.superclass, + orderIndex, matchIndex, + name, modifiers, + type, isFindInSuperClass = true + ) + else throw NoSuchFieldError( "Can't find this Field --> " + - "orderIndex:[${ - when { - orderIndex == null -> "unspecified" - orderIndex.second.not() -> "last" - else -> orderIndex.first - } - }] " + - "matchIndex:[${ - when { - matchIndex == null -> "unspecified" - matchIndex.second.not() -> "last" - else -> matchIndex.first - } - }] " + + when { + orderIndex == null -> "" + orderIndex.second.not() -> "orderIndex:[last] " + else -> "orderIndex:[${orderIndex.first}] " + } + + when { + matchIndex == null -> "" + matchIndex.second.not() -> "matchIndex:[last] " + else -> "matchIndex:[${matchIndex.first}] " + } + "name:[${name.takeIf { it.isNotBlank() } ?: "unspecified"}] " + "type:[${type ?: "unspecified"}] " + "modifiers:${modifiers ?: "[]"} " + @@ -156,6 +162,7 @@ internal object ReflectionTool { * @param returnType 方法返回值 * @param paramCount 方法参数个数 * @param paramTypes 方法参数类型 + * @param isFindInSuperClass 是否在未找到后继续在当前 [classSet] 的父类中查找 * @return [Method] * @throws IllegalStateException 如果 [classSet] 为 null 或未设置任何条件或 [paramTypes] 以及 [returnType] 目标类不存在 * @throws NoSuchMethodError 如果找不到方法 @@ -168,7 +175,8 @@ internal object ReflectionTool { modifiers: ModifierRules?, returnType: Class<*>?, paramCount: Int, - paramTypes: Array>? + paramTypes: Array>?, + isFindInSuperClass: Boolean ): Method { if (returnType == UndefinedType) error("Method match returnType class is not found") paramTypes?.takeIf { it.isNotEmpty() } @@ -257,22 +265,26 @@ internal object ReflectionTool { } } ?: error("Can't find this Method [$name] because classSet is null") method?.also { MemberCacheStore.putMethod(hashCode, method) } - ?: throw NoSuchMethodError( + ?: if (isFindInSuperClass && classSet.hasExtends) + findMethod( + classSet.superclass, + orderIndex, matchIndex, + name, modifiers, + returnType, paramCount, + paramTypes, isFindInSuperClass = true + ) + else throw NoSuchMethodError( "Can't find this Method --> " + - "orderIndex:[${ - when { - orderIndex == null -> "unspecified" - orderIndex.second.not() -> "last" - else -> orderIndex.first - } - }] " + - "matchIndex:[${ - when { - matchIndex == null -> "unspecified" - matchIndex.second.not() -> "last" - else -> matchIndex.first - } - }] " + + when { + orderIndex == null -> "" + orderIndex.second.not() -> "orderIndex:[last] " + else -> "orderIndex:[${orderIndex.first}] " + } + + when { + matchIndex == null -> "" + matchIndex.second.not() -> "matchIndex:[last] " + else -> "matchIndex:[${matchIndex.first}] " + } + "name:[${name.takeIf { it.isNotBlank() } ?: "unspecified"}] " + "paramCount:[${paramCount.takeIf { it >= 0 } ?: "unspecified"}] " + "paramTypes:[${paramTypes.typeOfString()}] " + @@ -292,6 +304,7 @@ internal object ReflectionTool { * @param modifiers 构造方法描述 * @param paramCount 构造方法参数个数 * @param paramTypes 构造方法参数类型 + * @param isFindInSuperClass 是否在未找到后继续在当前 [classSet] 的父类中查找 * @return [Constructor] * @throws IllegalStateException 如果 [classSet] 为 null 或未设置任何条件或 [paramTypes] 目标类不存在 * @throws NoSuchMethodError 如果找不到构造方法 @@ -302,7 +315,8 @@ internal object ReflectionTool { matchIndex: Pair?, modifiers: ModifierRules?, paramCount: Int, - paramTypes: Array>? + paramTypes: Array>?, + isFindInSuperClass: Boolean ): Constructor<*> { paramTypes?.takeIf { it.isNotEmpty() } ?.forEachIndexed { p, it -> if (it == UndefinedType) error("Constructor match paramType[$p] class is not found") } @@ -364,22 +378,25 @@ internal object ReflectionTool { } } ?: error("Can't find this Constructor because classSet is null") return constructor?.also { MemberCacheStore.putConstructor(hashCode, constructor) } - ?: throw NoSuchMethodError( + ?: if (isFindInSuperClass && classSet.hasExtends) + findConstructor( + classSet.superclass, + orderIndex, matchIndex, + modifiers, paramCount, + paramTypes, isFindInSuperClass = true + ) + else throw NoSuchMethodError( "Can't find this Constructor --> " + - "orderIndex:[${ - when { - orderIndex == null -> "unspecified" - orderIndex.second.not() -> "last" - else -> orderIndex.first - } - }] " + - "matchIndex:[${ - when { - matchIndex == null -> "unspecified" - matchIndex.second.not() -> "last" - else -> matchIndex.first - } - }] " + + when { + orderIndex == null -> "" + orderIndex.second.not() -> "orderIndex:[last] " + else -> "orderIndex:[${orderIndex.first}] " + } + + when { + matchIndex == null -> "" + matchIndex.second.not() -> "matchIndex:[last] " + else -> "matchIndex:[${matchIndex.first}] " + } + "paramCount:[${ paramCountR.takeIf { it >= 0 || it == -2 } ?.toString()?.replace(oldValue = "-2", newValue = "last") ?: "unspecified" diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/YukiHookModuleStatus.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/YukiHookModuleStatus.kt index b35eb3ee..7afd093f 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/YukiHookModuleStatus.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/YukiHookModuleStatus.kt @@ -29,8 +29,8 @@ package com.highcapable.yukihookapi.hook.xposed import android.app.Activity import androidx.annotation.Keep -import com.highcapable.yukihookapi.annotation.YukiPrivateApi import com.highcapable.yukihookapi.hook.factory.isModuleActive +import com.highcapable.yukihookapi.hook.factory.isSupportResourcesHook import com.highcapable.yukihookapi.hook.factory.isTaiChiModuleActive import com.highcapable.yukihookapi.hook.factory.isXposedModuleActive import com.highcapable.yukihookapi.hook.log.yLoggerD @@ -60,6 +60,9 @@ object YukiHookModuleStatus { /** 定义 Jvm 方法名 */ private const val IS_ACTIVE_METHOD_NAME = "__--" + /** 定义 Jvm 方法名 */ + private const val HAS_RESOURCES_HOOK_METHOD_NAME = "_--_" + /** 定义 Jvm 方法名 */ private const val GET_XPOSED_VERSION_METHOD_NAME = "--__" @@ -87,18 +90,28 @@ object YukiHookModuleStatus { * 此方法经过 Hook 后返回 true 即模块已激活 * * 请使用 [isModuleActive]、[isXposedModuleActive]、[isTaiChiModuleActive] 判断模块激活状态 - * - * - ❗此方法为私有功能性 API - 你不应该手动调用此方法 * @return [Boolean] */ @Keep - @YukiPrivateApi @JvmName(IS_ACTIVE_METHOD_NAME) internal fun isActive(): Boolean { yLoggerD(msg = IS_ACTIVE_METHOD_NAME, isDisableLog = true) return false } + /** + * 此方法经过 Hook 后返回 true 即当前 Hook Framework 支持资源钩子(Resources Hook) + * + * 请使用 [isSupportResourcesHook] 判断支持状态 + * @return [Boolean] + */ + @Keep + @JvmName(HAS_RESOURCES_HOOK_METHOD_NAME) + internal fun hasResourcesHook(): Boolean { + yLoggerD(msg = HAS_RESOURCES_HOOK_METHOD_NAME, isDisableLog = true) + return false + } + /** * 此方法经过 Hook 后返回 [XposedBridge.getXposedVersion] * @return [Int] diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/YukiHookBridge.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/YukiHookBridge.kt new file mode 100644 index 00000000..c1095373 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/YukiHookBridge.kt @@ -0,0 +1,373 @@ +/* + * 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/4/3. + */ +@file:Suppress("unused") + +package com.highcapable.yukihookapi.hook.xposed.bridge + +import android.content.pm.ApplicationInfo +import android.content.res.Resources +import com.highcapable.yukihookapi.YukiHookAPI +import com.highcapable.yukihookapi.annotation.YukiGenerateApi +import com.highcapable.yukihookapi.hook.factory.hasClass +import com.highcapable.yukihookapi.hook.param.PackageParam +import com.highcapable.yukihookapi.hook.param.type.HookEntryType +import com.highcapable.yukihookapi.hook.param.wrapper.HookParamWrapper +import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper +import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiModuleResources +import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiResources +import de.robv.android.xposed.* +import de.robv.android.xposed.callbacks.XC_InitPackageResources +import de.robv.android.xposed.callbacks.XC_LoadPackage +import java.lang.reflect.Member + +/** + * 这是一个对接 Xposed Hook 入口与 [XposedBridge] 的装载类实现桥 + * + * 实现与 [IXposedHookZygoteInit]、[IXposedHookLoadPackage]、[IXposedHookInitPackageResources] 接口通讯 + * + * - ❗装载代码将自动生成 - 请勿修改或移动以及重命名此类的任何方法与变量 + */ +@YukiGenerateApi +object YukiHookBridge { + + /** Android 系统框架名称 */ + private const val SYSTEM_FRAMEWORK_NAME = "android" + + /** Xposed 是否装载完成 */ + private var isXposedInitialized = false + + /** 已在 [PackageParam] 中被装载的 APP 包名 */ + private val loadedPackageNames = HashSet() + + /** 当前 [PackageParamWrapper] 实例数组 */ + private var packageParamWrappers = HashMap() + + /** 当前 [PackageParam] 方法体回调 */ + internal var packageParamCallback: (PackageParam.() -> Unit)? = null + + /** 当前 Xposed 模块自身 APK 路径 */ + internal var moduleAppFilePath = "" + + /** 当前 Xposed 模块自身 [Resources] */ + internal var moduleAppResources: YukiModuleResources? = null + + /** + * 模块是否装载了 Xposed 回调方法 + * + * - ❗装载代码将自动生成 - 请勿手动修改 - 会引发未知异常 + * @return [Boolean] + */ + @YukiGenerateApi + val isXposedCallbackSetUp + get() = isXposedInitialized.not() && packageParamCallback != null + + /** + * 预设的 Xposed 模块包名 + * + * - ❗装载代码将自动生成 - 请勿手动修改 - 会引发未知异常 + */ + @YukiGenerateApi + var modulePackageName = "" + + /** + * 获取当前 Hook 框架的名称 + * + * 从 [XposedBridge] 获取 TAG + * + * - ❗装载代码将自动生成 - 若要调用请使用 [YukiHookAPI.executorName] + * @return [String] 无法获取会返回 unknown - [hasXposedBridge] 不存在会返回 invalid + */ + @YukiGenerateApi + val executorName + get() = runCatching { + (XposedBridge::class.java.getDeclaredField("TAG").apply { isAccessible = true }.get(null) as? String?) + ?.replace(oldValue = "Bridge", newValue = "")?.replace(oldValue = "-", newValue = "")?.trim() ?: "unknown" + }.getOrNull() ?: "invalid" + + /** + * 获取当前 Hook 框架的版本 + * + * 获取 [XposedBridge.getXposedVersion] + * + * - ❗装载代码将自动生成 - 若要调用请使用 [YukiHookAPI.executorVersion] + * @return [Int] 无法获取会返回 -1 + */ + @YukiGenerateApi + val executorVersion + get() = runCatching { XposedBridge.getXposedVersion() }.getOrNull() ?: -1 + + /** + * 是否存在 [XposedBridge] + * @return [Boolean] + */ + internal val hasXposedBridge get() = executorVersion >= 0 + + /** + * 自动忽略 MIUI 系统可能出现的日志收集注入实例 + * @param packageName 当前包名 + * @return [Boolean] 是否存在 + */ + private fun isMiuiCatcherPatch(packageName: String?) = + (packageName == "com.miui.contentcatcher" || packageName == "com.miui.catcherpatch") && ("android.miui.R").hasClass + + /** + * 当前包名是否已在指定的 [HookEntryType] 被装载 + * @param packageName 包名 + * @param type 当前 Hook 类型 + * @return [Boolean] 是否已被装载 + */ + private fun isPackageLoaded(packageName: String, type: HookEntryType): Boolean { + if (loadedPackageNames.contains("$packageName:$type")) return true + loadedPackageNames.add("$packageName:$type") + return false + } + + /** + * 创建、修改 [PackageParamWrapper] + * @param type 当前正在进行的 Hook 类型 + * @param packageName 包名 + * @param processName 当前进程名 + * @param appClassLoader APP [ClassLoader] + * @param appInfo APP [ApplicationInfo] + * @param appResources APP [YukiResources] + * @return [PackageParamWrapper] or null + */ + private fun assignWrapper( + type: HookEntryType, + packageName: String, + processName: String = "", + appClassLoader: ClassLoader? = null, + appInfo: ApplicationInfo? = null, + appResources: YukiResources? = null + ) = run { + if (packageParamWrappers[packageName] == null) + PackageParamWrapper( + type = type, + packageName = packageName, + processName = processName, + appClassLoader = appClassLoader ?: XposedBridge.BOOTCLASSLOADER, + appInfo = appInfo, + appResources = appResources + ).also { packageParamWrappers[packageName] = it } + else packageParamWrappers[packageName]?.also { + it.type = type + if (packageName.isNotBlank()) it.packageName = packageName + if (processName.isNotBlank()) it.processName = processName + if (appClassLoader != null) it.appClassLoader = appClassLoader + if (appInfo != null) it.appInfo = appInfo + if (appResources != null) it.appResources = appResources + } + } + + /** + * 标识 Xposed API 装载完成 + * + * - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件 + */ + @YukiGenerateApi + fun callXposedInitialized() { + isXposedInitialized = true + } + + /** + * 装载 Xposed API Zygote 回调 + * + * - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件 + * @param sparam Xposed [IXposedHookZygoteInit.StartupParam] + */ + @YukiGenerateApi + fun callXposedZygoteLoaded(sparam: IXposedHookZygoteInit.StartupParam) { + moduleAppFilePath = sparam.modulePath + moduleAppResources = YukiModuleResources.createInstance(moduleAppFilePath) + } + + /** + * 装载 Xposed API 回调 + * + * 这里的入口会装载三次 + * + * - 在 [IXposedHookZygoteInit.initZygote] 时装载 + * + * - 在 [IXposedHookLoadPackage.handleLoadPackage] 时装载 + * + * - 在 [IXposedHookInitPackageResources.handleInitPackageResources] 时装载 + * + * - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed API 事件 + * @param isZygoteLoaded 是否为 Xposed [IXposedHookZygoteInit.initZygote] + * @param lpparam Xposed [XC_LoadPackage.LoadPackageParam] + * @param resparam Xposed [XC_InitPackageResources.InitPackageResourcesParam] + */ + @YukiGenerateApi + fun callXposedLoaded( + isZygoteLoaded: Boolean, + lpparam: XC_LoadPackage.LoadPackageParam? = null, + resparam: XC_InitPackageResources.InitPackageResourcesParam? = null + ) { + if (isMiuiCatcherPatch(packageName = lpparam?.packageName ?: resparam?.packageName).not()) when { + isZygoteLoaded -> { + if (isPackageLoaded(SYSTEM_FRAMEWORK_NAME, HookEntryType.ZYGOTE).not()) + assignWrapper(HookEntryType.ZYGOTE, SYSTEM_FRAMEWORK_NAME, SYSTEM_FRAMEWORK_NAME) + else null + } + lpparam != null -> { + if (lpparam.packageName != null && lpparam.processName != null && lpparam.appInfo != null && lpparam.classLoader != null && + isPackageLoaded(lpparam.packageName, HookEntryType.PACKAGE).not() + ) assignWrapper(HookEntryType.PACKAGE, lpparam.packageName, lpparam.processName, lpparam.classLoader, lpparam.appInfo) + else null + } + resparam != null -> { + if (isPackageLoaded(resparam.packageName, HookEntryType.RESOURCES).not()) + assignWrapper(HookEntryType.RESOURCES, resparam.packageName, appResources = YukiResources.createFromXResources(resparam.res)) + else null + } + else -> null + }?.let { YukiHookAPI.onXposedLoaded(it) } + } + + /** + * Hook 核心功能实现实例 + * + * 对接 [XposedBridge] 实现 Hook 功能 + */ + internal object Hooker { + + /** + * Hook 方法 + * + * 对接 [XposedBridge.hookMethod] + * @param hookMethod 需要 Hook 的方法、构造方法 + * @param callback 回调 + * @return [Member] or null + */ + internal fun hookMethod(hookMethod: Member?, callback: YukiHookCallback) = + XposedBridge.hookMethod(hookMethod, compatCallback(callback))?.hookedMethod + + /** + * Hook 当前 [hookClass] 所有 [methodName] 的方法 + * + * 对接 [XposedBridge.hookAllMethods] + * @param hookClass 当前 Hook 的 [Class] + * @param methodName 方法名 + * @param callback 回调 + * @return [HashSet] 成功 Hook 的方法数组 + */ + internal fun hookAllMethods(hookClass: Class<*>?, methodName: String, callback: YukiHookCallback) = HashSet().also { + XposedBridge.hookAllMethods(hookClass, methodName, compatCallback(callback)).takeIf { it.isNotEmpty() } + ?.forEach { e -> it.add(e.hookedMethod) } + } + + /** + * Hook 当前 [hookClass] 所有构造方法 + * + * 对接 [XposedBridge.hookAllConstructors] + * @param hookClass 当前 Hook 的 [Class] + * @param callback 回调 + * @return [HashSet] 成功 Hook 的构造方法数组 + */ + internal fun hookAllConstructors(hookClass: Class<*>?, callback: YukiHookCallback) = HashSet().also { + XposedBridge.hookAllConstructors(hookClass, compatCallback(callback)).takeIf { it.isNotEmpty() } + ?.forEach { e -> it.add(e.hookedMethod) } + } + + /** + * 兼容对接 Hook 回调接口 + * @param callback [YukiHookCallback] 接口 + * @return [XC_MethodHook] 原始接口 + */ + private fun compatCallback(callback: YukiHookCallback) = when (callback) { + is YukiMemberHook -> object : XC_MethodHook(callback.priority) { + + /** 创建 Hook 前 [HookParamWrapper] */ + val beforeHookWrapper = HookParamWrapper() + + /** 创建 Hook 后 [HookParamWrapper] */ + val afterHookWrapper = HookParamWrapper() + + override fun beforeHookedMethod(param: MethodHookParam?) { + if (param == null) return + callback.beforeHookedMember(beforeHookWrapper.assign(param)) + } + + override fun afterHookedMethod(param: MethodHookParam?) { + if (param == null) return + callback.afterHookedMember(afterHookWrapper.assign(param)) + } + } + is YukiMemberReplacement -> object : XC_MethodReplacement(callback.priority) { + + /** 创建替换 Hook [HookParamWrapper] */ + val replaceHookWrapper = HookParamWrapper() + + override fun replaceHookedMethod(param: MethodHookParam?): Any? { + if (param == null) return null + return callback.replaceHookedMember(replaceHookWrapper.assign(param)) + } + } + else -> error("Invalid YukiHookCallback type") + } + + /** + * Hook 方法回调接口 + * @param priority Hook 优先级 + */ + internal abstract class YukiMemberHook(override val priority: Int) : YukiHookCallback(priority) { + + /** + * 在方法执行之前注入 + * @param wrapper 包装实例 + */ + abstract fun beforeHookedMember(wrapper: HookParamWrapper) + + /** + * 在方法执行之后注入 + * @param wrapper 包装实例 + */ + abstract fun afterHookedMember(wrapper: HookParamWrapper) + } + + /** + * Hook 替换方法回调接口 + * @param priority Hook 优先级 + */ + internal abstract class YukiMemberReplacement(override val priority: Int) : YukiHookCallback(priority) { + + /** + * 拦截替换为指定结果 + * @param wrapper 包装实例 + * @return [Any] or null + */ + abstract fun replaceHookedMember(wrapper: HookParamWrapper): Any? + } + + /** + * Hook 回调接口父类 + * @param priority Hook 优先级 + */ + internal abstract class YukiHookCallback(open val priority: Int) + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/YukiHookXposedBridge.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/YukiHookXposedBridge.kt deleted file mode 100644 index a55df25e..00000000 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/YukiHookXposedBridge.kt +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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/4/3. - */ -@file:Suppress("unused") - -package com.highcapable.yukihookapi.hook.xposed.bridge - -import com.highcapable.yukihookapi.YukiHookAPI -import com.highcapable.yukihookapi.annotation.YukiGenerateApi -import com.highcapable.yukihookapi.hook.param.PackageParam -import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper -import de.robv.android.xposed.IXposedHookLoadPackage -import de.robv.android.xposed.callbacks.XC_LoadPackage - -/** - * 这是一个 Xposed 模块的入口装载类实现桥 - * - * 实现与 [IXposedHookLoadPackage] 接口通讯 - * - * - ❗装载代码将自动生成 - 请勿修改或移动以及重命名此类的任何方法与变量 - */ -@YukiGenerateApi -object YukiHookXposedBridge { - - /** Xposed 是否装载完成 */ - private var isXposedInitialized = false - - /** Xposed Hook API 方法体回调 */ - internal var packageParamCallback: (PackageParam.() -> Unit)? = null - - /** - * 模块是否装载了 Xposed 回调方法 - * - * - ❗装载代码将自动生成 - 请勿手动修改 - 会引发未知异常 - * @return [Boolean] - */ - @YukiGenerateApi - val isXposedCallbackSetUp - get() = isXposedInitialized.not() && packageParamCallback != null - - /** - * 当前 Hook 的对象是模块自身 - * - * - ❗装载代码将自动生成 - 请勿手动修改 - 会引发未知异常 - */ - @YukiGenerateApi - var isModulePackageXposedEnv = false - - /** - * 预设的 Xposed 模块包名 - * - * - ❗装载代码将自动生成 - 请勿手动修改 - 会引发未知异常 - */ - @YukiGenerateApi - var modulePackageName = "" - - /** - * 标识 Xposed API 装载完成 - * - * - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件 - */ - @YukiGenerateApi - fun callXposedInitialized() { - isXposedInitialized = true - } - - /** - * 装载 Xposed API 回调 - * - * - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件 - * @param lpparam Xposed [XC_LoadPackage.LoadPackageParam] - */ - @YukiGenerateApi - fun callXposedLoaded(lpparam: XC_LoadPackage.LoadPackageParam) { - /** 判断基础 API 可能为空的情况 */ - if (lpparam.packageName == null || lpparam.processName == null || lpparam.appInfo == null || lpparam.classLoader == null) return - /** 回调 Xposed API 装载 */ - YukiHookAPI.onXposedLoaded(PackageParamWrapper(lpparam.packageName, lpparam.processName, lpparam.classLoader, lpparam.appInfo)) - } -} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/dummy/YukiModuleResources.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/dummy/YukiModuleResources.kt new file mode 100644 index 00000000..cb78381d --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/dummy/YukiModuleResources.kt @@ -0,0 +1,65 @@ +/* + * 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/4/29. + */ +@file:Suppress("DEPRECATION") + +package com.highcapable.yukihookapi.hook.xposed.bridge.dummy + +import android.content.res.Resources +import android.content.res.XModuleResources +import android.content.res.XResForwarder + +/** + * 对接 [XModuleResources] 的中间层实例 + * @param baseInstance 原始实例 + */ +class YukiModuleResources(private val baseInstance: XModuleResources) : + Resources(baseInstance.assets, baseInstance.displayMetrics, baseInstance.configuration) { + + companion object { + + /** + * 对接 [XModuleResources.createInstance] 方法 + * + * 创建 [YukiModuleResources] 与 [XModuleResources] 实例 + * @param path Xposed 模块 APK 路径 + * @return [YukiModuleResources] + */ + fun createInstance(path: String) = YukiModuleResources(XModuleResources.createInstance(path, null)) + } + + /** + * 对接 [XModuleResources.fwd] 方法 + * + * 创建 [YukiResForwarder] 与 [XResForwarder] 实例 + * @param resId Resources Id + * @return [YukiResForwarder] + */ + fun fwd(resId: Int) = YukiResForwarder(baseInstance.fwd(resId)) + + override fun toString() = "YukiModuleResources by $baseInstance" +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/dummy/YukiResForwarder.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/dummy/YukiResForwarder.kt new file mode 100644 index 00000000..5d48c616 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/dummy/YukiResForwarder.kt @@ -0,0 +1,61 @@ +/* + * 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/4/29. + */ +@file:Suppress("unused") + +package com.highcapable.yukihookapi.hook.xposed.bridge.dummy + +import android.content.res.Resources +import android.content.res.XResForwarder + +/** + * 对接 [XResForwarder] 的中间层实例 + * @param baseInstance 原始实例 + */ +class YukiResForwarder(private val baseInstance: XResForwarder) { + + /** + * 获得 [XResForwarder] 实例 + * @return [XResForwarder] + */ + val instance get() = baseInstance + + /** + * 获得当前 Resources Id + * @return [Int] + */ + val id get() = baseInstance.id + + /** + * 获得当前 Resources + * @return [Resources] + * @throws IllegalStateException 如果 [XResForwarder] 出现问题 + */ + val resources get() = baseInstance.resources ?: error("XResForwarder is invalid") + + override fun toString() = "YukiResForwarder by $baseInstance" +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/dummy/YukiResources.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/dummy/YukiResources.kt new file mode 100644 index 00000000..22a0fa7c --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/dummy/YukiResources.kt @@ -0,0 +1,218 @@ +/* + * 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/4/29. + */ +@file:Suppress("DEPRECATION", "unused") + +package com.highcapable.yukihookapi.hook.xposed.bridge.dummy + +import android.content.res.Resources +import android.content.res.XResources +import android.graphics.drawable.Drawable +import android.view.View +import com.highcapable.yukihookapi.hook.xposed.bridge.dummy.YukiResources.LayoutInflatedParam +import de.robv.android.xposed.callbacks.XC_LayoutInflated + +/** + * 对接 [XResources] 的中间层实例 + * @param baseInstance 原始实例 + */ +class YukiResources(private val baseInstance: XResources) : + Resources(baseInstance.assets, baseInstance.displayMetrics, baseInstance.configuration) { + + companion object { + + /** + * 从 [XResources] 创建 [YukiResources] 实例 + * @param baseInstance [XResources] 实例 + * @return [YukiResources] + */ + internal fun createFromXResources(baseInstance: XResources) = YukiResources(baseInstance) + + /** + * 兼容对接替换 Resources + * @param replacement 替换 Resources + * @return [Any] 兼容后的替换 Resources + */ + private fun compat(replacement: Any?) = when (replacement) { + is YukiResForwarder -> replacement.instance + is Drawable -> object : XResources.DrawableLoader() { + override fun newDrawable(res: XResources?, id: Int): Drawable = replacement + } + else -> replacement + } + + /** + * 在 Zygote 中替换 Resources + * + * 对接 [XResources.setSystemWideReplacement] + * @param resId Resources Id + * @param replacement 替换 Resources + */ + internal fun setSystemWideReplacement(resId: Int, replacement: Any?) = XResources.setSystemWideReplacement(resId, compat(replacement)) + + /** + * 在 Zygote 中替换 Resources + * + * 对接 [XResources.setSystemWideReplacement] + * @param packageName 包名 + * @param type Resources 类型 + * @param name Resources 名称 + * @param replacement 替换 Resources + */ + internal fun setSystemWideReplacement(packageName: String, type: String, name: String, replacement: Any?) = + XResources.setSystemWideReplacement(packageName, type, name, compat(replacement)) + + /** + * 在 Zygote 中注入布局 Resources + * + * 对接 [XResources.hookSystemWideLayout] + * @param resId Resources Id + * @param initiate 注入方法体 + */ + internal fun hookSystemWideLayout(resId: Int, initiate: LayoutInflatedParam.() -> Unit) { + XResources.hookSystemWideLayout(resId, object : XC_LayoutInflated() { + override fun handleLayoutInflated(liparam: LayoutInflatedParam?) { + if (liparam == null) return + initiate(LayoutInflatedParam(liparam)) + } + }) + } + + /** + * 在 Zygote 中注入布局 Resources + * + * 对接 [XResources.hookSystemWideLayout] + * @param packageName 包名 + * @param type Resources 类型 + * @param name Resources 名称 + * @param initiate 注入方法体 + */ + internal fun hookSystemWideLayout(packageName: String, type: String, name: String, initiate: LayoutInflatedParam.() -> Unit) { + XResources.hookSystemWideLayout(packageName, type, name, object : XC_LayoutInflated() { + override fun handleLayoutInflated(liparam: LayoutInflatedParam?) { + if (liparam == null) return + initiate(LayoutInflatedParam(liparam)) + } + }) + } + } + + /** + * 执行替换 Resources + * + * 对接 [XResources.setReplacement] + * @param resId Resources Id + * @param replacement 替换 Resources + */ + internal fun setReplacement(resId: Int, replacement: Any?) = baseInstance.setReplacement(resId, compat(replacement)) + + /** + * 执行替换 Resources + * + * 对接 [XResources.setReplacement] + * @param packageName 包名 + * @param type Resources 类型 + * @param name Resources 名称 + * @param replacement 替换 Resources + */ + internal fun setReplacement(packageName: String, type: String, name: String, replacement: Any?) = + baseInstance.setReplacement(packageName, type, name, compat(replacement)) + + /** + * 执行注入布局 Resources + * + * 对接 [XResources.hookLayout] + * @param resId Resources Id + * @param initiate 注入方法体 + */ + internal fun hookLayout(resId: Int, initiate: LayoutInflatedParam.() -> Unit) { + baseInstance.hookLayout(resId, object : XC_LayoutInflated() { + override fun handleLayoutInflated(liparam: LayoutInflatedParam?) { + if (liparam == null) return + initiate(LayoutInflatedParam(liparam)) + } + }) + } + + /** + * 执行注入布局 Resources + * + * 对接 [XResources.hookLayout] + * @param packageName 包名 + * @param type Resources 类型 + * @param name Resources 名称 + * @param initiate 注入方法体 + */ + internal fun hookLayout(packageName: String, type: String, name: String, initiate: LayoutInflatedParam.() -> Unit) { + baseInstance.hookLayout(packageName, type, name, object : XC_LayoutInflated() { + override fun handleLayoutInflated(liparam: LayoutInflatedParam?) { + if (liparam == null) return + initiate(LayoutInflatedParam(liparam)) + } + }) + } + + /** + * 装载 Hook APP 的目标布局 Resources 实现类 + * @param baseParam 对接 [XC_LayoutInflated.LayoutInflatedParam] + */ + class LayoutInflatedParam(@PublishedApi internal val baseParam: XC_LayoutInflated.LayoutInflatedParam) { + + /** + * 获取当前被 Hook 的布局装载目录名称 + * + * 例如:layout、layout-land、layout-sw600dp + * @return [String] + */ + val variantName get() = baseParam.variant ?: "" + + /** + * 获取当前被 Hook 的布局实例 + * @return [View] + */ + val currentView get() = baseParam.view ?: error("LayoutInflatedParam View instance got null") + + /** + * 使用 Identifier 查找 Hook APP 指定 Id 的 [View] + * @param name Id 的 Identifier 名称 + * @return [T] or null + */ + inline fun View.findViewByIdentifier(name: String): T? = + findViewById(baseParam.res.getIdentifier(name, "id", baseParam.resNames.pkg)) + + /** + * 使用 Identifier 查找 Hook APP 当前装载布局中指定 Id 的 [View] + * @param name Id 的 Identifier 名称 + * @return [T] or null + */ + inline fun findViewByIdentifier(name: String) = currentView.findViewByIdentifier(name) + + override fun toString() = "LayoutInflatedParam by $baseParam" + } + + override fun toString() = "YukiResources by $baseInstance" +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/event/YukiXposedEvent.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/event/YukiXposedEvent.kt new file mode 100644 index 00000000..6d828d7e --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/bridge/event/YukiXposedEvent.kt @@ -0,0 +1,127 @@ +/* + * 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/4/30. + */ +@file:Suppress("unused") + +package com.highcapable.yukihookapi.hook.xposed.bridge.event + +import com.highcapable.yukihookapi.annotation.YukiGenerateApi +import de.robv.android.xposed.IXposedHookZygoteInit.StartupParam +import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam +import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam + +/** + * 实现对原生 Xposed API 的装载事件监听 + */ +object YukiXposedEvent { + + /** 监听 initZygote 开始的回调方法 */ + private var initZygoteCallback: ((StartupParam) -> Unit)? = null + + /** 监听 handleLoadPackage 开始的回调方法 */ + private var handleLoadPackageCallback: ((LoadPackageParam) -> Unit)? = null + + /** 监听 handleInitPackageResources 开始的回调方法 */ + private var handleInitPackageResourcesCallback: ((InitPackageResourcesParam) -> Unit)? = null + + /** + * 对 [YukiXposedEvent] 创建一个方法体 + * @param initiate 方法体 + */ + inline fun events(initiate: YukiXposedEvent.() -> Unit) { + YukiXposedEvent.apply(initiate) + } + + /** + * 设置 initZygote 事件监听 + * @param initiate 回调方法体 + */ + fun onInitZygote(initiate: (StartupParam) -> Unit) { + initZygoteCallback = initiate + } + + /** + * 设置 handleLoadPackage 事件监听 + * @param initiate 回调方法体 + */ + fun onHandleLoadPackage(initiate: (LoadPackageParam) -> Unit) { + handleLoadPackageCallback = initiate + } + + /** + * 设置 handleInitPackageResources 事件监听 + * @param initiate 回调方法体 + */ + fun onHandleInitPackageResources(initiate: (InitPackageResourcesParam) -> Unit) { + handleInitPackageResourcesCallback = initiate + } + + /** + * 回调监听事件处理类 + * + * - ❗装载代码将自动生成 - 请勿手动调用 + */ + @YukiGenerateApi + object EventHandler { + + /** + * 回调 initZygote 事件监听 + * + * - ❗装载代码将自动生成 - 请勿手动调用 + * @param sparam Xposed API 实例 + */ + @YukiGenerateApi + fun callInitZygote(sparam: StartupParam?) { + if (sparam == null) return + initZygoteCallback?.invoke(sparam) + } + + /** + * 回调 handleLoadPackage 事件监听 + * + * - ❗装载代码将自动生成 - 请勿手动调用 + * @param lpparam Xposed API 实例 + */ + @YukiGenerateApi + fun callHandleLoadPackage(lpparam: LoadPackageParam?) { + if (lpparam == null) return + handleLoadPackageCallback?.invoke(lpparam) + } + + /** + * 回调 handleInitPackageResources 事件监听 + * + * - ❗装载代码将自动生成 - 请勿手动调用 + * @param resparam Xposed API 实例 + */ + @YukiGenerateApi + fun callHandleInitPackageResources(resparam: InitPackageResourcesParam?) { + if (resparam == null) return + handleInitPackageResourcesCallback?.invoke(resparam) + } + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookModulePrefs.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookModulePrefs.kt index f4f3f8a8..69528bdc 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookModulePrefs.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookModulePrefs.kt @@ -38,7 +38,7 @@ import androidx.preference.PreferenceFragmentCompat import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.hook.log.loggerW import com.highcapable.yukihookapi.hook.log.yLoggerW -import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookXposedBridge +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData import com.highcapable.yukihookapi.hook.xposed.prefs.ui.ModulePreferenceFragment import de.robv.android.xposed.XSharedPreferences @@ -82,10 +82,10 @@ class YukiHookModulePrefs(private val context: Context? = null) { } /** 存储名称 - 默认包名 + _preferences */ - private var prefsName = "${YukiHookXposedBridge.modulePackageName.ifBlank { context?.packageName ?: "" }}_preferences" + private var prefsName = "${YukiHookBridge.modulePackageName.ifBlank { context?.packageName ?: "" }}_preferences" /** 是否为 Xposed 环境 */ - private val isXposedEnvironment = YukiHookAPI.hasXposedBridge + private val isXposedEnvironment = YukiHookBridge.hasXposedBridge /** [XSharedPreferences] 缓存的 [String] 键值数据 */ private var xPrefCacheKeyValueStrings = HashMap() @@ -114,7 +114,7 @@ class YukiHookModulePrefs(private val context: Context? = null) { /** 检查 API 装载状态 */ private fun checkApi() { if (YukiHookAPI.isLoadedFromBaseContext) error("YukiHookModulePrefs not allowed in Custom Hook API") - if (YukiHookAPI.hasXposedBridge && YukiHookXposedBridge.modulePackageName.isBlank()) + if (YukiHookBridge.hasXposedBridge && YukiHookBridge.modulePackageName.isBlank()) error("Xposed modulePackageName load failed, please reset and rebuild it") } @@ -123,7 +123,7 @@ class YukiHookModulePrefs(private val context: Context? = null) { * @return [XSharedPreferences] */ private val xPref - get() = XSharedPreferences(YukiHookXposedBridge.modulePackageName, prefsName).apply { + get() = XSharedPreferences(YukiHookBridge.modulePackageName, prefsName).apply { checkApi() makeWorldReadable() reload() diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/proxy/IYukiHookXposedInit.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/proxy/IYukiHookXposedInit.kt index 057d4d59..7c0f78f4 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/proxy/IYukiHookXposedInit.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/xposed/proxy/IYukiHookXposedInit.kt @@ -34,6 +34,7 @@ import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed import com.highcapable.yukihookapi.hook.factory.configs import com.highcapable.yukihookapi.hook.factory.encase +import com.highcapable.yukihookapi.hook.xposed.bridge.event.YukiXposedEvent /** * [YukiHookAPI] 的 Xposed 装载 API 调用接口 @@ -48,6 +49,8 @@ import com.highcapable.yukihookapi.hook.factory.encase * * 请在 [onHook] 中调用 [YukiHookAPI.encase] 或直接调用 [encase] * + * 你还可以实现监听原生 Xposed API 功能 - 重写 [onXposedEvent] 方法即可 + * * 详情请参考 [IYukiHookXposedInit 接口](https://fankes.github.io/YukiHookAPI/#/config/xposed-using?id=iyukihookxposedinit-%e6%8e%a5%e5%8f%a3) */ interface IYukiHookXposedInit { @@ -69,4 +72,23 @@ interface IYukiHookXposedInit { * 调用 [YukiHookAPI.encase] 或直接调用 [encase] 开始 Hook */ fun onHook() + + /** + * 监听 Xposed 原生装载事件 + * + * 若你的 Hook 事件中存在需要兼容的原生 Xposed 功能 - 可在这里实现 + * + * 请在这里使用 [YukiXposedEvent] 创建回调事件监听 + * + * 可监听的事件如下: + * + * [YukiXposedEvent.onInitZygote] + * + * [YukiXposedEvent.onHandleLoadPackage] + * + * [YukiXposedEvent.onHandleInitPackageResources] + * + * - ❗此接口仅供监听和实现原生 Xposed API 的功能 - 请不要在这里操作 [YukiHookAPI] + */ + fun onXposedEvent() {} } \ No newline at end of file