diff --git a/docs/api/public/YukiMemberHookCreater.md b/docs/api/public/YukiMemberHookCreater.md index 5c4933f1..50a4362d 100644 --- a/docs/api/public/YukiMemberHookCreater.md +++ b/docs/api/public/YukiMemberHookCreater.md @@ -518,6 +518,22 @@ fun intercept() !> 这将会禁止此方法执行并返回 `null`。 +#### removeSelf [method] + +```kotlin +fun removeSelf(result: (Boolean) -> Unit) +``` + +**变更记录** + +`v1.0.93` `新增` + +**功能描述** + +> 移除当前注入的 Hook 方法、构造方法 (解除 Hook)。 + +!> 你只能在 Hook 回调方法中使用此功能。 + #### Result [class] ```kotlin @@ -736,6 +752,22 @@ fun ignoredAllFailure(): Result > 忽略全部 Hook 过程发生的错误。 +##### remove [method] + +```kotlin +fun remove(result: (Boolean) -> Unit) +``` + +**变更记录** + +`v1.0.93` `新增` + +**功能描述** + +> 移除当前注入的 Hook 方法、构造方法 (解除 Hook)。 + +!> 你只能在 Hook 成功后才能解除 Hook,可监听 `onHooked` 事件。 + ### Result [class] ```kotlin diff --git a/docs/guide/example.md b/docs/guide/example.md index 511e4c38..88280c94 100644 --- a/docs/guide/example.md +++ b/docs/guide/example.md @@ -302,6 +302,48 @@ loadZygote { 更多功能请参考 [ResourcesHookCreater](api/document?id=resourceshookcreater-class)。 +### 解除 Hook + +原生的 Xposed 为我们提供了一个 `XC_MethodHook.Unhook` 功能,可以从 Hook 队列中将当前 Hook 移除,`YukiHookAPI` 同样可以实现此功能。 + +第一种方法,保存当前注入对象的 `Result` 实例,在适当的时候和地方调用 `remove` 即可解除该注入对象。 + +> 示例如下 + +```kotlin +// 设置一个变量保存当前实例 +val hookResult = injectMember { + method { + name = "test" + returnType = UnitType + } + afterHook { + // ... + } +} +// 在适当的时候调用如下方法即可 +hookResult.remove() +``` + +第二种方法,在 Hook 回调方法中调用 `removeSelf` 移除自身。 + +> 示例如下 + +```kotlin +injectMember { + method { + name = "test" + returnType = UnitType + } + afterHook { + // 直接调用如下方法即可 + removeSelf() + } +} +``` + +更多功能请参考 [MemberHookCreater](api/document?id=memberhookcreater-class)。 + ## 异常处理 > `YukiHookAPI` 重新设计了对异常的监听,任何异常都不会在 Hook 过程中抛出,避免打断下一个 Hook 流程导致 Hook 进程“死掉”。 diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt index ebd04feb..4e28b907 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/YukiMemberHookCreater.kt @@ -139,11 +139,12 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara * @param packageName 当前 Hook 的 APP 包名 */ inner class MemberHookCreater @PublishedApi internal constructor( - private val priority: Int, - internal val tag: String, - internal val packageName: String + private val priority: Int, internal val tag: String, internal val packageName: String ) { + /** Hook 结果实例 */ + private var result: Result? = null + /** 是否已经执行 Hook */ private var isHooked = false @@ -206,6 +207,9 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara /** 全部方法的名称 */ private var allMethodsName = "" + /** 当前被 Hook 的方法、构造方法实例 */ + private var hookedMember: YukiHookBridge.Hooker.YukiHookedMember? = null + /** * 手动指定要 Hook 的方法、构造方法 * @@ -421,12 +425,20 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara replaceHookResult = null } + /** + * 移除当前注入的 Hook 方法、构造方法 (解除 Hook) + * + * - ❗你只能在 Hook 回调方法中使用此功能 + * @param result 回调是否成功 + */ + fun removeSelf(result: (Boolean) -> Unit = {}) = this.result?.remove(result) ?: result(false) + /** * Hook 创建入口 * @return [Result] */ @PublishedApi - internal fun build() = Result() + internal fun build() = Result().apply { result = this } /** Hook 执行入口 */ @PublishedApi @@ -508,9 +520,12 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara (if (isReplaceHookMode) YukiHookBridge.Hooker.hookMethod(member, replaceMent) else YukiHookBridge.Hooker.hookMethod(member, beforeAfterHook)).also { when { - it.first == null -> error("Hook Member [$member] failed") - it.second -> onAlreadyHookedCallback?.invoke(it.first!!) - else -> onHookedCallback?.invoke(it.first!!) + it.first.member == null -> error("Hook Member [$member] failed") + it.second -> onAlreadyHookedCallback?.invoke(it.first.member!!) + else -> { + hookedMember = it.first + onHookedCallback?.invoke(it.first.member!!) + } } } }.onFailure { @@ -537,8 +552,11 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara else YukiHookBridge.Hooker.hookAllMethods(hookClass.instance, allMethodsName, beforeAfterHook)).also { when { it.first.isEmpty() -> throw NoSuchMethodError("No Method name \"$allMethodsName\" matched") - it.second -> it.first.forEach { e -> onAlreadyHookedCallback?.invoke(e) } - else -> it.first.forEach { e -> onHookedCallback?.invoke(e) } + it.second -> it.first.forEach { e -> if (e.member != null) onAlreadyHookedCallback?.invoke(e.member!!) } + else -> { + hookedMember = it.first.first() + it.first.forEach { e -> if (e.member != null) onHookedCallback?.invoke(e.member!!) } + } } } HookMemberMode.HOOK_ALL_CONSTRUCTORS -> @@ -546,8 +564,11 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara else YukiHookBridge.Hooker.hookAllConstructors(hookClass.instance, beforeAfterHook)).also { when { it.first.isEmpty() -> throw NoSuchMethodError("No Constructor matched") - it.second -> it.first.forEach { e -> onAlreadyHookedCallback?.invoke(e) } - else -> it.first.forEach { e -> onHookedCallback?.invoke(e) } + it.second -> it.first.forEach { e -> if (e.member != null) onAlreadyHookedCallback?.invoke(e.member!!) } + else -> { + hookedMember = it.first.first() + it.first.forEach { e -> if (e.member != null) onHookedCallback?.invoke(e.member!!) } + } } } else -> error("Hooked got a no error possible") @@ -707,6 +728,19 @@ class YukiMemberHookCreater(@PublishedApi internal val packageParam: PackagePara * @return [Result] 可继续向下监听 */ fun ignoredAllFailure() = onAllFailure {} + + /** + * 移除当前注入的 Hook 方法、构造方法 (解除 Hook) + * + * - ❗你只能在 Hook 成功后才能解除 Hook - 可监听 [onHooked] 事件 + * @param result 回调是否成功 + */ + fun remove(result: (Boolean) -> Unit = {}) { + hookedMember?.unhook() + runCatching { preHookMembers.remove(this@MemberHookCreater.toString()) } + result(hookedMember != null) + hookedMember = null + } } } 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 index 5a2c088f..aad325ff 100644 --- 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 @@ -39,8 +39,6 @@ import android.content.res.Configuration import android.content.res.Resources import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.annotation.YukiGenerateApi -import com.highcapable.yukihookapi.hook.factory.allConstructors -import com.highcapable.yukihookapi.hook.factory.allMethods import com.highcapable.yukihookapi.hook.factory.hasClass import com.highcapable.yukihookapi.hook.param.PackageParam import com.highcapable.yukihookapi.hook.param.type.HookEntryType @@ -441,13 +439,13 @@ object YukiHookBridge { internal object Hooker { /** 已经 Hook 的 [Member] 数组 */ - private val hookedMembers = HashSet() + private val hookedMembers = HashSet() /** 已经 Hook 的全部 [Method] 数组 */ - private val hookedAllMethods = HashSet() + private val hookedAllMethods = HashMap>() /** 已经 Hook 的全部 [Constructor] 数组 */ - private val hookedAllConstructors = HashSet() + private val hookedAllConstructors = HashMap>() /** 默认 Hook 回调优先级 */ internal const val PRIORITY_DEFAULT = 50 @@ -494,12 +492,18 @@ object YukiHookBridge { * 对接 [XposedBridge.hookMethod] * @param hookMethod 需要 Hook 的方法、构造方法 * @param callback 回调 - * @return [Pair] - ([Member] or null,[Boolean] 是否已经 Hook) + * @return [Pair] - ([YukiHookedMember],[Boolean] 是否已经 Hook) */ - internal fun hookMethod(hookMethod: Member?, callback: YukiHookCallback): Pair { - if (hookedMembers.contains(hookMethod.toString())) return Pair(hookMethod, true) - hookedMembers.add(hookMethod.toString()) - return Pair(XposedBridge.hookMethod(hookMethod, compatCallback(callback))?.hookedMethod, false) + internal fun hookMethod(hookMethod: Member?, callback: YukiHookCallback): Pair { + runCatching { + hookedMembers.takeIf { it.isNotEmpty() }?.forEach { + if (it.member.toString() == hookMethod.toString()) return@runCatching it + } + } + return YukiHookedMember.wrapper(XposedBridge.hookMethod(hookMethod, compatCallback(callback))).let { + hookedMembers.add(it) + Pair(it, false) + } } /** @@ -509,20 +513,22 @@ object YukiHookBridge { * @param hookClass 当前 Hook 的 [Class] * @param methodName 方法名 * @param callback 回调 - * @return [Pair] - ([HashSet] 成功 Hook 的方法数组,[Boolean] 是否已经 Hook) + * @return [Pair] - ([HashSet] 成功 Hook 的 [YukiHookedMember] 数组,[Boolean] 是否已经 Hook) */ - internal fun hookAllMethods(hookClass: Class<*>?, methodName: String, callback: YukiHookCallback): Pair, Boolean> { + internal fun hookAllMethods( + hookClass: Class<*>?, methodName: String, callback: YukiHookCallback + ): Pair, Boolean> { var isAlreadyHook = false - val hookedMembers = HashSet().also { + val hookedMembers = HashSet().also { val allMethodsName = "$hookClass$methodName" if (hookedAllMethods.contains(allMethodsName)) { isAlreadyHook = true - hookClass?.allMethods { _, method -> if (method.name == methodName) it.add(method) } + hookedAllMethods[allMethodsName]?.forEach { e -> it.add(e) } return@also } - hookedAllMethods.add(allMethodsName) - XposedBridge.hookAllMethods(hookClass, methodName, compatCallback(callback)).takeIf { it.isNotEmpty() } - ?.forEach { e -> it.add(e.hookedMethod) } + XposedBridge.hookAllMethods(hookClass, methodName, compatCallback(callback)).takeIf { e -> e.isNotEmpty() } + ?.forEach { e -> it.add(YukiHookedMember.wrapper(e, allMethodsName)) } + hookedAllMethods[allMethodsName] = it } return Pair(hookedMembers, isAlreadyHook) } @@ -533,20 +539,20 @@ object YukiHookBridge { * 对接 [XposedBridge.hookAllConstructors] * @param hookClass 当前 Hook 的 [Class] * @param callback 回调 - * @return [Pair] - ([HashSet] 成功 Hook 的构造方法数组,[Boolean] 是否已经 Hook) + * @return [Pair] - ([HashSet] 成功 Hook 的 [YukiHookedMember] 数组,[Boolean] 是否已经 Hook) */ - internal fun hookAllConstructors(hookClass: Class<*>?, callback: YukiHookCallback): Pair, Boolean> { + internal fun hookAllConstructors(hookClass: Class<*>?, callback: YukiHookCallback): Pair, Boolean> { var isAlreadyHook = false - val hookedMembers = HashSet().also { + val hookedMembers = HashSet().also { val allConstructorsName = "$hookClass" if (hookedAllConstructors.contains(allConstructorsName)) { isAlreadyHook = true - hookClass?.allConstructors { _, constructor -> it.add(constructor) } + hookedAllConstructors[allConstructorsName]?.forEach { e -> it.add(e) } return@also } - hookedAllConstructors.add(allConstructorsName) - XposedBridge.hookAllConstructors(hookClass, compatCallback(callback)).takeIf { it.isNotEmpty() } - ?.forEach { e -> it.add(e.hookedMethod) } + XposedBridge.hookAllConstructors(hookClass, compatCallback(callback)).takeIf { e -> e.isNotEmpty() } + ?.forEach { e -> it.add(YukiHookedMember.wrapper(e, allConstructorsName)) } + hookedAllConstructors[allConstructorsName] = it } return Pair(hookedMembers, isAlreadyHook) } @@ -626,5 +632,41 @@ object YukiHookBridge { * @param priority Hook 优先级 */ internal abstract class YukiHookCallback(open val priority: Int) + + /** + * 已经 Hook 的 [Member] 实现类 + * @param instance 对接 [XC_MethodHook.Unhook] + * @param tag 标识多个 [Member] Hook 的标签 + */ + internal class YukiHookedMember private constructor(private val instance: XC_MethodHook.Unhook, private val tag: String) { + + companion object { + + /** + * 从 [XC_MethodHook.Unhook] 创建 [YukiHookedMember] 实例 + * @param instance [XC_MethodHook.Unhook] 实例 + * @param tag 标识多个 [Member] Hook 的标签 - 默认空 + * @return [YukiHookedMember] + */ + internal fun wrapper(instance: XC_MethodHook.Unhook, tag: String = "") = YukiHookedMember(instance, tag) + } + + /** + * 当前被 Hook 的 [Member] + * @return [Member] or null + */ + internal val member: Member? get() = instance.hookedMethod + + /** 解除 Hook */ + internal fun unhook() { + instance.unhook() + runCatching { + if (tag.isNotBlank()) { + hookedAllMethods.remove(tag) + hookedAllConstructors.remove(tag) + } else hookedMembers.remove(this) + } + } + } } } \ No newline at end of file