Added initZygote、Resources Hook function and fix more bugs

This commit is contained in:
2022-05-01 09:52:37 +08:00
parent b48ad4a850
commit 8d226bc42d
29 changed files with 1786 additions and 339 deletions

View File

@@ -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()
}

View File

@@ -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))
}

View File

@@ -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
*

View File

@@ -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)
}
}

View File

@@ -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"
}

View File

@@ -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<MemberHookCreater>()
internal var preHookMembers = HashSet<MemberHookCreater>()
/**
* 得到当前被 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 结果实现类

View File

@@ -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<ResourcesHookCreater>()
/**
* 注入要 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"
}
}

View File

@@ -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 {

View File

@@ -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"))

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)

View File

@@ -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] 是否激活

View File

@@ -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

View File

@@ -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 <T> Member.invokeOriginal(vararg args: Any?) = wrapper.invokeOriginalMember(member = this, *args) as? T?
fun <T> 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)
}
/**

View File

@@ -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]

View File

@@ -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
}

View File

@@ -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<Any?>? get() = baseParam.args
val args: Array<Any?>? 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]"
}

View File

@@ -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"
}

View File

@@ -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<Int, Boolean>?,
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<out Class<*>>?
paramTypes: Array<out Class<*>>?,
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<Int, Boolean>?,
modifiers: ModifierRules?,
paramCount: Int,
paramTypes: Array<out Class<*>>?
paramTypes: Array<out Class<*>>?,
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"

View File

@@ -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]

View File

@@ -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<String>()
/** 当前 [PackageParamWrapper] 实例数组 */
private var packageParamWrappers = HashMap<String, PackageParamWrapper>()
/** 当前 [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<Member>().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<Member>().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)
}
}

View File

@@ -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))
}
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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 <reified T : View> 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 <reified T : View> findViewByIdentifier(name: String) = currentView.findViewByIdentifier<T>(name)
override fun toString() = "LayoutInflatedParam by $baseParam"
}
override fun toString() = "YukiResources by $baseInstance"
}

View File

@@ -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)
}
}
}

View File

@@ -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<String, String>()
@@ -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()

View File

@@ -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() {}
}