From 41546b2c9f22f4c3f26ddc7918fddd6acd1b9a75 Mon Sep 17 00:00:00 2001 From: fankesyooni Date: Wed, 21 Sep 2022 14:30:17 +0800 Subject: [PATCH] Added DexClassFinder function allowed ClassLoader to search classes (Beta Feature) --- docs-source/src/.vuepress/configs/template.ts | 7 + .../core/finder/classes/DexClassFinder.md | 725 ++++++++++++++++++ .../finder/classes/rules/ConstructorRules.md | 123 +++ .../core/finder/classes/rules/FieldRules.md | 77 ++ .../core/finder/classes/rules/MemberRules.md | 33 + .../core/finder/classes/rules/MethodRules.md | 167 ++++ .../classes/rules/result/MemberRulesResult.md | 73 ++ .../hook/factory/ReflectionFactory.md | 24 + .../yukihookapi/hook/param/PackageParam.md | 24 + .../hook/core/finder/base/ClassBaseFinder.kt | 75 ++ .../core/finder/classes/DexClassFinder.kt | 620 +++++++++++++++ .../finder/classes/data/ClassRulesData.kt | 183 +++++ .../finder/classes/rules/ConstructorRules.kt | 145 ++++ .../core/finder/classes/rules/FieldRules.kt | 92 +++ .../core/finder/classes/rules/MemberRules.kt | 58 ++ .../core/finder/classes/rules/MethodRules.kt | 178 +++++ .../finder/classes/rules/base/BaseRules.kt | 90 +++ .../classes/rules/result/MemberRulesResult.kt | 89 +++ .../hook/core/finder/tools/ReflectionTool.kt | 161 +++- .../finder/type/factory/TypeAliasFactory.kt | 4 + .../hook/factory/ReflectionFactory.kt | 22 +- .../yukihookapi/hook/param/PackageParam.kt | 18 + .../hook/store/ReflectsCacheStore.kt | 45 +- 23 files changed, 3025 insertions(+), 8 deletions(-) create mode 100644 docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/DexClassFinder.md create mode 100644 docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/ConstructorRules.md create mode 100644 docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/FieldRules.md create mode 100644 docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MemberRules.md create mode 100644 docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MethodRules.md create mode 100644 docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/result/MemberRulesResult.md create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/base/ClassBaseFinder.kt create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/DexClassFinder.kt create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/data/ClassRulesData.kt create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/ConstructorRules.kt create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/FieldRules.kt create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MemberRules.kt create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MethodRules.kt create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/base/BaseRules.kt create mode 100644 yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/result/MemberRulesResult.kt diff --git a/docs-source/src/.vuepress/configs/template.ts b/docs-source/src/.vuepress/configs/template.ts index 0e58016d..6d98c01d 100644 --- a/docs-source/src/.vuepress/configs/template.ts +++ b/docs-source/src/.vuepress/configs/template.ts @@ -56,6 +56,12 @@ const navigationLinks = { baseApiPath + 'hook/core/finder/members/MethodFinder', baseApiPath + 'hook/core/finder/members/ConstructorFinder', baseApiPath + 'hook/core/finder/members/FieldFinder', + baseApiPath + 'hook/core/finder/classes/DexClassFinder', + baseApiPath + 'hook/core/finder/classes/rules/result/MemberRulesResult', + baseApiPath + 'hook/core/finder/classes/rules/MemberRules', + baseApiPath + 'hook/core/finder/classes/rules/FieldRules', + baseApiPath + 'hook/core/finder/classes/rules/MethodRules', + baseApiPath + 'hook/core/finder/classes/rules/ConstructorRules', baseApiPath + 'hook/core/finder/base/BaseFinder', baseApiPath + 'hook/core/finder/base/rules/CountRules', baseApiPath + 'hook/core/finder/base/rules/ModifierRules', @@ -63,6 +69,7 @@ const navigationLinks = { baseApiPath + 'hook/bean/HookClass', baseApiPath + 'hook/bean/VariousClass', baseApiPath + 'hook/bean/CurrentClass', + baseApiPath + 'hook/bean/GenericClass', baseApiPath + 'hook/bean/HookResources' ], specialFeature: [ diff --git a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/DexClassFinder.md b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/DexClassFinder.md new file mode 100644 index 00000000..dc80236b --- /dev/null +++ b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/DexClassFinder.md @@ -0,0 +1,725 @@ +--- +pageClass: code-page +--- + +# DexClassFinder - class + +```kotlin:no-line-numbers +class DexClassFinder internal constructor( + internal var name: String, + internal var async: Boolean, + override val loaderSet: ClassLoader? +) : ClassBaseFinder +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> `Class` 查找类。 + +可使用 `BaseDexClassLoader` 通过指定条件查找指定 `Class` 或一组 `Class`。 + +::: warning + +此功能尚在试验阶段,性能与稳定性可能仍然存在问题,使用过程遇到问题请向我们报告并帮助我们改进。 + +::: + +## companion object - object + +**变更记录** + +`v1.1.0` `新增` + +### clearCache - method + +```kotlin:no-line-numbers +fun clearCache(context: Context?, versionName: String?, versionCode: Long?) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 清除当前 `DexClassFinder` 的 `Class` 缓存。 + +适用于全部通过 [ClassLoader.searchClass](../../../factory/ReflectionFactory#classloader-searchclass-ext-method) 或 [PackageParam.searchClass](../../../param/PackageParam#searchclass-method) 获取的 `DexClassFinder`。 + +## fullName - field + +```kotlin:no-line-numbers +var fullName: String +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 完整名称。 + +只会查询匹配到的 `Class.getName`。 + +例如 `com.demo.Test` 需要填写 `com.demo.Test`。 + +## simpleName - field + +```kotlin:no-line-numbers +var simpleName: String +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 简单名称。 + +只会查询匹配到的 `Class.getSimpleName`。 + +例如 `com.demo.Test` 只需要填写 `Test`。 + +对于匿名类例如 `com.demo.Test$InnerTest` 会为空,此时你可以使用 [singleName](#singlename-field)。 + +## singleName - field + +```kotlin:no-line-numbers +var singleName: String +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 独立名称。 + +设置后将首先使用 `Class.getSimpleName`,若为空则会使用 `Class.getName` 进行处理。 + +例如 `com.demo.Test` 只需要填写 `Test`。 + +对于匿名类例如 `com.demo.Test$InnerTest` 只需要填写 `Test$InnerTest`。 + +## from - method + +```kotlin:no-line-numbers +fun from(vararg name: String): FromPackageRules +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置在指定包名范围查询当前 `Class`。 + +设置后仅会在当前 `name` 开头匹配的包名路径下进行查询,可提升查询速度。 + +例如 ↓ + +`com.demo.test` + +`com.demo.test.demo` + +::: warning + +建议设置此参数指定查询范围,否则 **Class** 过多时将会非常慢。 + +::: + +## modifiers - method + +```kotlin:no-line-numbers +fun modifiers(conditions: ModifierConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 标识符筛选条件。 + +可不设置筛选条件。 + +## fullName - method + +```kotlin:no-line-numbers +fun fullName(value: String): ClassNameRules +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 完整名称。 + +只会查询匹配到的 `Class.getName`。 + +例如 `com.demo.Test` 需要填写 `com.demo.Test`。 + +## simpleName - method + +```kotlin:no-line-numbers +fun simpleName(value: String): ClassNameRules +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 简单名称。 + +只会查询匹配到的 `Class.getSimpleName`。 + +例如 `com.demo.Test` 只需要填写 `Test`。 + +对于匿名类例如 `com.demo.Test$InnerTest 会为空`,此时你可以使用 [singleName](#singlename-method)。 + +## singleName - method + +```kotlin:no-line-numbers +fun singleName(value: String): ClassNameRules +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 独立名称。 + +设置后将首先使用 `Class.getSimpleName`,若为空则会使用 `Class.getName` 进行处理。 + +例如 `com.demo.Test` 只需要填写 `Test`。 + +对于匿名类例如 `com.demo.Test$InnerTest` 只需要填写 `Test$InnerTest`。 + +## fullName - method + +```kotlin:no-line-numbers +fun fullName(conditions: NameConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 完整名称条件。 + +只会查询匹配到的 `Class.getName`。 + +## simpleName - method + +```kotlin:no-line-numbers +fun simpleName(conditions: NameConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 简单名称条件。 + +只会查询匹配到的 `Class.getSimpleName`。 + +## singleName - method + +```kotlin:no-line-numbers +fun singleName(conditions: NameConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 独立名称条件。 + +设置后将首先使用 `Class.getSimpleName`,若为空则会使用 `Class.getName` 进行处理。 + +## extends - method + +```kotlin:no-line-numbers +inline fun extends() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 继承的父类。 + +## extends - method + +```kotlin:no-line-numbers +fun extends(vararg name: String) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 继承的父类。 + +会同时查询 `name` 中所有匹配的父类。 + +## implements - method + +```kotlin:no-line-numbers +inline fun implements() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 实现的接口类。 + +## implements - method + +```kotlin:no-line-numbers +fun implements(vararg name: String) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 实现的接口类。 + +会同时查询 `name` 中所有匹配的接口类。 + +## anonymous - method + +```kotlin:no-line-numbers +fun anonymous() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 标识 `Class` 为匿名类。 + +例如 `com.demo.Test$1` 或 `com.demo.Test$InnerTest`。 + +标识后你可以使用 [enclosing](#enclosing-method) 来进一步指定匿名类的 (封闭类) 主类。 + +## noExtends - method + +```kotlin:no-line-numbers +fun noExtends() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 没有任何继承。 + +此时 `Class` 只应该继承于 `Any`。 + +::: warning + +设置此条件后 [extends](#extends-method) 将失效。 + +::: + +## noImplements - method + +```kotlin:no-line-numbers +fun noImplements() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 没有任何接口。 + +::: warning + +设置此条件后 [implements](#implements-method) 将失效。 + +::: + +## noSuper - method + +```kotlin:no-line-numbers +fun noSuper() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 没有任何继承与接口。 + +此时 `Class` 只应该继承于 `Any`。 + +::: warning + +设置此条件后 [extends](#extends-method) 与 [implements](#implements-method) 将失效。 + +::: + +## enclosing - method + +```kotlin:no-line-numbers +inline fun enclosing() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 匿名类的 (封闭类) 主类。 + +## enclosing - method + +```kotlin:no-line-numbers +fun enclosing(vararg name: String) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 匿名类的 (封闭类) 主类。 + +会同时查询 `name` 中所有匹配的 (封闭类) 主类。 + +## FromPackageRules - class + +```kotlin:no-line-numbers +inner class FromPackageRules internal constructor(private val packages: ArrayList) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 包名范围名称过滤匹配条件实现类。 + +### absolute - method + +```kotlin:no-line-numbers +fun absolute() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置包名绝对匹配。 + +例如有如下包名 ↓ + +`com.demo.test.a` + +`com.demo.test.a.b` + +`com.demo.test.active` + +若包名条件为 `com.demo.test.a` 则绝对匹配仅能匹配到第一个。 + +相反地,不设置以上示例会全部匹配。 + +## ClassNameRules - class + +```kotlin:no-line-numbers +inner class ClassNameRules internal constructor(private val name: ClassRulesData.NameRulesData) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 类名匹配条件实现类。 + +### optional - method + +```kotlin:no-line-numbers +fun optional() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置类名可选。 + +例如有如下类名 ↓ + +`com.demo.Test` **fullName** / `Test` **simpleName** + +`defpackage.a` **fullName** / `a` **simpleName** + +这两个类名都是同一个类,但是在有些版本中被混淆有些版本没有。 + +此时可设置类名为 `com.demo.Test` **fullName** / `Test` **simpleName**。 + +这样就可在完全匹配类名情况下使用类名而忽略其它查询条件,否则忽略此条件继续使用其它查询条件。 + +## member - method + +```kotlin:no-line-numbers +inline fun member(initiate: MemberRules.() -> Unit): MemberRulesResult +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 满足的 `Member` 条件。 + +## field - method + +```kotlin:no-line-numbers +inline fun field(initiate: FieldRules.() -> Unit): MemberRulesResult +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 满足的 `Field` 条件。 + +## method - method + +```kotlin:no-line-numbers +inline fun method(initiate: MethodRules.() -> Unit): MemberRulesResult +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 满足的 `Method` 条件。 + +## constructor - method + +```kotlin:no-line-numbers +inline fun constructor(initiate: ConstructorRules.() -> Unit): MemberRulesResult +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Class` 满足的 `Constructor` 条件。 + +## Result - class + +```kotlin:no-line-numbers +inner class Result internal constructor(internal var isNotFound: Boolean, internal var throwable: Throwable?) : BaseResult +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> `Class` 查找结果实现类。 + +### result - method + +```kotlin:no-line-numbers +inline fun result(initiate: Result.() -> Unit): Result +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 创建监听结果事件方法体。 + +### get - method + +```kotlin:no-line-numbers +fun get(): Class<*>? +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 得到 `Class` 本身。 + +若有多个 `Class` 结果只会返回第一个。 + +在查询条件找不到任何结果的时候将返回 `null`。 + +若你设置了 `async` 请使用 [wait](#wait-method) 方法。 + +### all - method + +```kotlin:no-line-numbers +fun all(): HashSet> +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 得到 `Class` 本身数组。 + +返回全部查询条件匹配的多个 `Class` 实例。 + +在查询条件找不到任何结果的时候将返回空的 `HashSet`。 + +若你设置了 `async` 请使用 [waitAll](#waitall-method) 方法。 + +### all - method + +```kotlin:no-line-numbers +fun all(result: (Class<*>) -> Unit): Result +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 得到 `Class` 本身数组 (依次遍历)。 + +回调全部查询条件匹配的多个 `Class` 实例。 + +在查询条件找不到任何结果的时候将不会执行。 + +若你设置了 `async` 请使用 [waitAll](#waitall-method) 方法。 + +### wait - method + +```kotlin:no-line-numbers +fun wait(result: (Class<*>?) -> Unit): Result +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 得到 `Class` 本身 (异步)。 + +若有多个 `Class` 结果只会回调第一个。 + +在查询条件找不到任何结果的时候将回调 null。 + +你需要设置 `async` 后此方法才会被回调,否则请使用 [get](#get-method) 方法。 + +### waitAll - method + +```kotlin:no-line-numbers +fun waitAll(result: (HashSet>) -> Unit): Result +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 得到 `Class` 本身数组 (异步)。 + +回调全部查询条件匹配的多个 `Class` 实例。 + +在查询条件找不到任何结果的时候将回调空的 `HashSet`。 + +你需要设置 `async` 后此方法才会被回调,否则请使用 [all](#all-method) 方法。 + +### onNoClassDefFoundError - method + +```kotlin:no-line-numbers +fun onNoClassDefFoundError(result: (Throwable) -> Unit): Result +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 监听找不到 `Class` 时。 + +### ignored - method + +```kotlin:no-line-numbers +fun ignored(): Result +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 忽略异常并停止打印任何错误日志。 + +此时若要监听异常结果,你需要手动实现 [onNoClassDefFoundError](#onnoclassdeffounderror-method) 方法。 \ No newline at end of file diff --git a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/ConstructorRules.md b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/ConstructorRules.md new file mode 100644 index 00000000..f801d1f0 --- /dev/null +++ b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/ConstructorRules.md @@ -0,0 +1,123 @@ +--- +pageClass: code-page +--- + +# ConstructorRules - class + +```kotlin:no-line-numbers +class ConstructorRules internal constructor(internal val rulesData: ConstructorRulesData) : BaseRules +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> `Constructor` 查询条件实现类。 + +## paramCount - field + +```kotlin:no-line-numbers +var paramCount: Int +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Constructor` 参数个数。 + +你可以不使用 `param` 指定参数类型而是仅使用此变量指定参数个数。 + +若参数个数小于零则忽略并使用 `param`。 + +## modifiers - method + +```kotlin:no-line-numbers +fun modifiers(conditions: ModifierConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Constructor` 标识符筛选条件。 + +可不设置筛选条件。 + +## emptyParam - method + +```kotlin:no-line-numbers +fun emptyParam() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Constructor` 空参数、无参数。 + +## param - method + +```kotlin:no-line-numbers +fun param(vararg paramType: Any) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Constructor` 参数。 + +如果同时使用了 `paramCount` 则 `paramType` 的数量必须与 `paramCount` 完全匹配。 + +如果 `Constructor` 中存在一些无意义又很长的类型,你可以使用 `VagueType` 来替代它。 + +::: danger + +无参 **Constructor** 请使用 **emptyParam** 设置查询条件。 + +有参 **Constructor** 必须使用此方法设定参数或使用 **paramCount** 指定个数。 + +::: + +## paramCount - method + +```kotlin:no-line-numbers +fun paramCount(numRange: IntRange) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Constructor` 参数个数范围。 + +你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数范围。 + +## paramCount - method + +```kotlin:no-line-numbers +fun paramCount(conditions: CountConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Constructor` 参数个数条件。 + +你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数条件。 \ No newline at end of file diff --git a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/FieldRules.md b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/FieldRules.md new file mode 100644 index 00000000..48008a50 --- /dev/null +++ b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/FieldRules.md @@ -0,0 +1,77 @@ +--- +pageClass: code-page +--- + +# FieldRules - class + +```kotlin:no-line-numbers +class FieldRules internal constructor(internal val rulesData: FieldRulesData) : BaseRules +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> `Field` 查询条件实现类。 + +## name - field + +```kotlin:no-line-numbers +var name: String +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Field` 名称。 + +## type - field + +```kotlin:no-line-numbers +var type: Any? +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Field` 类型。 + +可不填写类型。 + +## modifiers - method + +```kotlin:no-line-numbers +fun modifiers(conditions: ModifierConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Field` 标识符筛选条件。 + +可不设置筛选条件。 + +## name - method + +```kotlin:no-line-numbers +fun name(conditions: NameConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Field` 名称条件。 \ No newline at end of file diff --git a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MemberRules.md b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MemberRules.md new file mode 100644 index 00000000..da0149c6 --- /dev/null +++ b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MemberRules.md @@ -0,0 +1,33 @@ +--- +pageClass: code-page +--- + +# MemberRules - class + +```kotlin:no-line-numbers +class MemberRules internal constructor(internal val rulesData: MemberRulesData) : BaseRules +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> `Member` 查询条件实现类。 + +## modifiers - method + +```kotlin:no-line-numbers +fun modifiers(conditions: ModifierConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Member` 标识符筛选条件。 + +可不设置筛选条件。 \ No newline at end of file diff --git a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MethodRules.md b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MethodRules.md new file mode 100644 index 00000000..24dcf3a6 --- /dev/null +++ b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MethodRules.md @@ -0,0 +1,167 @@ +--- +pageClass: code-page +--- + +# MethodRules - class + +```kotlin:no-line-numbers +class MethodRules internal constructor(internal val rulesData: MethodRulesData) : BaseRules +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> `Method` 查询条件实现类。 + +## name - field + +```kotlin:no-line-numbers +var name: String +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Method` 名称。 + +## paramCount - field + +```kotlin:no-line-numbers +var paramCount: Int +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Method` 参数个数。 + +你可以不使用 `param` 指定参数类型而是仅使用此变量指定参数个数。 + +若参数个数小于零则忽略并使用 `param`。 + +## returnType - field + +```kotlin:no-line-numbers +var returnType: Any? +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Method` 返回值。 + +可不填写返回值。 + +## modifiers - method + +```kotlin:no-line-numbers +fun modifiers(conditions: ModifierConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Method` 标识符筛选条件。 + +可不设置筛选条件。 + +## emptyParam - method + +```kotlin:no-line-numbers +fun emptyParam() +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Method` 空参数、无参数。 + +## param - method + +```kotlin:no-line-numbers +fun param(vararg paramType: Any) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Method` 参数。 + +如果同时使用了 `paramCount` 则 `paramType` 的数量必须与 `paramCount` 完全匹配。 + +如果 `Method` 中存在一些无意义又很长的类型,你可以使用 `VagueType` 来替代它。 + +::: danger + +无参 **Method** 请使用 **emptyParam** 设置查询条件。 + +有参 **Method** 必须使用此方法设定参数或使用 **paramCount** 指定个数。 + +::: + +## name - method + +```kotlin:no-line-numbers +fun name(conditions: NameConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Method` 名称条件。 + +## paramCount - method + +```kotlin:no-line-numbers +fun paramCount(numRange: IntRange) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Method` 参数个数范围。 + +你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数范围。 + +## paramCount - method + +```kotlin:no-line-numbers +fun paramCount(conditions: CountConditions) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置 `Method` 参数个数条件。 + +你可以不使用 `param` 指定参数类型而是仅使用此方法指定参数个数条件。 \ No newline at end of file diff --git a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/result/MemberRulesResult.md b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/result/MemberRulesResult.md new file mode 100644 index 00000000..7789add3 --- /dev/null +++ b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/core/finder/classes/rules/result/MemberRulesResult.md @@ -0,0 +1,73 @@ +--- +pageClass: code-page +--- + +# MemberRulesResult - class + +```kotlin:no-line-numbers +class MemberRulesResult internal constructor(private val rulesData: MemberRulesData) +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 当前 `Member` 查询条件结果实现类。 + +## none - method + +```kotlin:no-line-numbers +fun none(): MemberRulesResult +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置当前 `Member` 在查询条件中个数为 `0`。 + +## count - method + +```kotlin:no-line-numbers +fun count(num: Int): MemberRulesResult +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置当前 `Member` 在查询条件中需要全部匹配的个数。 + +## count - method + +```kotlin:no-line-numbers +fun count(numRange: IntRange): MemberRulesResult +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置当前 `Member` 在查询条件中需要全部匹配的个数范围。 + +## count - method + +```kotlin:no-line-numbers +fun count(conditions: CountConditions): MemberRulesResult +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 设置当前 `Member` 在查询条件中需要全部匹配的个数条件。 \ No newline at end of file diff --git a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.md b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.md index 3ec9fed0..f5753efe 100644 --- a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.md +++ b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.md @@ -68,6 +68,30 @@ CONSTRUCTOR > 全部 `Constructor`。 +## ClassLoader.searchClass - ext-method + +```kotlin:no-line-numbers +inline fun ClassLoader.searchClass(name: String, async: Boolean, initiate: ClassConditions): DexClassFinder.Result +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 通过当前 `ClassLoader` 按指定条件查找并得到 **Dex** 中的 `Class`。 + +::: danger + +此方法在 **Class** 数量过多及查找条件复杂时会非常耗时。 + +建议启用 **async** 或设置 **name** 参数,**name** 参数将在 Hook APP (宿主) 不同版本中自动进行本地缓存以提升效率。 + +此功能尚在试验阶段,性能与稳定性可能仍然存在问题,使用过程遇到问题请向我们报告并帮助我们改进。 + +::: + ## ClassLoader.onLoadClass - ext-method ```kotlin:no-line-numbers diff --git a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/param/PackageParam.md b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/param/PackageParam.md index f73ec3c8..1249f2f7 100644 --- a/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/param/PackageParam.md +++ b/docs-source/src/zh-cn/api/public/com/highcapable/yukihookapi/hook/param/PackageParam.md @@ -472,6 +472,30 @@ fun loadHooker(hooker: YukiBaseHooker) 你可以填入 `hooker` 在 Hooker 中继续装载 Hooker。 +## searchClass - method + +```kotlin:no-line-numbers +inline fun searchClass(name: String, async: Boolean, initiate: ClassConditions): DexClassFinder.Result +``` + +**变更记录** + +`v1.1.0` `新增` + +**功能描述** + +> 通过 `appClassLoader` 按指定条件查找并得到当前 Hook APP **Dex** 中的 `Class`。 + +::: danger + +此方法在 **Class** 数量过多及查找条件复杂时会非常耗时。 + +建议启用 **async** 或设置 **name** 参数,**name** 参数将在 Hook APP (宿主) 不同版本中自动进行本地缓存以提升效率。 + +此功能尚在试验阶段,性能与稳定性可能仍然存在问题,使用过程遇到问题请向我们报告并帮助我们改进。 + +::: +

String+VariousClass.clazz - i-ext-field

**变更记录** diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/base/ClassBaseFinder.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/base/ClassBaseFinder.kt new file mode 100644 index 00000000..19a528af --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/base/ClassBaseFinder.kt @@ -0,0 +1,75 @@ +/* + * 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/9/4. + */ +package com.highcapable.yukihookapi.hook.core.finder.base + +import com.highcapable.yukihookapi.YukiHookAPI +import com.highcapable.yukihookapi.annotation.YukiPrivateApi +import com.highcapable.yukihookapi.hook.log.yLoggerE +import com.highcapable.yukihookapi.hook.log.yLoggerI +import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge + +/** + * 这是 [Class] 查找类功能的基本类实现 + * @param loaderSet 当前使用的 [ClassLoader] 实例 + */ +abstract class ClassBaseFinder internal constructor(internal open val loaderSet: ClassLoader? = null) : BaseFinder() { + + /** 当前找到的 [Class] 数组 */ + internal var classInstances = HashSet>() + + /** 是否开启忽略错误警告功能 */ + internal var isShutErrorPrinting = false + + /** + * 将目标类型转换为可识别的兼容类型 + * @param any 当前需要转换的实例 + * @param tag 当前查找类的标识 + * @return [Class] or null + */ + internal fun compatType(any: Any?, tag: String) = any?.compat(tag, loaderSet) + + /** + * 在开启 [YukiHookAPI.Configs.isDebug] 且在 [YukiHookBridge.hasXposedBridge] 情况下输出调试信息 + * @param msg 调试日志内容 + */ + internal fun onDebuggingMsg(msg: String) { + if (YukiHookAPI.Configs.isDebug && YukiHookBridge.hasXposedBridge) yLoggerI(msg = msg) + } + + /** + * 发生错误时输出日志 + * @param throwable 错误 + */ + internal fun onFailureMsg(throwable: Throwable? = null) { + if (isShutErrorPrinting) return + yLoggerE(msg = "NoClassDefFound happend in [$loaderSet]", e = throwable) + } + + @YukiPrivateApi + override fun failure(throwable: Throwable?) = error("DexClassFinder does not contain this usage") +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/DexClassFinder.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/DexClassFinder.kt new file mode 100644 index 00000000..48e9524a --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/DexClassFinder.kt @@ -0,0 +1,620 @@ +/* + * 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/9/4. + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package com.highcapable.yukihookapi.hook.core.finder.classes + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.os.SystemClock +import androidx.core.content.pm.PackageInfoCompat +import com.highcapable.yukihookapi.annotation.YukiPrivateApi +import com.highcapable.yukihookapi.hook.core.finder.base.ClassBaseFinder +import com.highcapable.yukihookapi.hook.core.finder.classes.data.ClassRulesData +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.ConstructorRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.FieldRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.MemberRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.MethodRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.base.BaseRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.result.MemberRulesResult +import com.highcapable.yukihookapi.hook.core.finder.tools.ReflectionTool +import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions +import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions +import com.highcapable.yukihookapi.hook.factory.hasClass +import com.highcapable.yukihookapi.hook.factory.searchClass +import com.highcapable.yukihookapi.hook.factory.toClass +import com.highcapable.yukihookapi.hook.log.yLoggerW +import com.highcapable.yukihookapi.hook.param.PackageParam +import com.highcapable.yukihookapi.hook.utils.await +import com.highcapable.yukihookapi.hook.utils.runBlocking +import com.highcapable.yukihookapi.hook.xposed.helper.YukiHookAppHelper +import dalvik.system.BaseDexClassLoader +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.Member +import java.lang.reflect.Method + +/** + * [Class] 查找类 + * + * 可使用 [BaseDexClassLoader] 通过指定条件查找指定 [Class] 或一组 [Class] + * + * - ❗此功能尚在试验阶段 - 性能与稳定性可能仍然存在问题 - 使用过程遇到问题请向我们报告并帮助我们改进 + * @param name 标识当前 [Class] 缓存的名称 - 不设置将不启用缓存 - 启用缓存必须启用 [async] + * @param async 是否启用异步 + * @param loaderSet 当前使用的 [ClassLoader] 实例 + */ +class DexClassFinder @PublishedApi internal constructor( + internal var name: String, + internal var async: Boolean, + override val loaderSet: ClassLoader? +) : ClassBaseFinder(loaderSet) { + + companion object { + + /** 缓存的存储文件名 */ + private const val CACHE_FILE_NAME = "config_yukihook_cache_obfuscate_classes" + + /** + * 通过 [Context] 获取当前 [SharedPreferences] + * @param versionName 版本名称 - 默认空 + * @param versionCode 版本号 - 默认空 + * @return [SharedPreferences] + */ + private fun Context.currentSp(versionName: String? = null, versionCode: Long? = null) = + getSharedPreferences(packageManager?.getPackageInfo(packageName, PackageManager.GET_META_DATA) + ?.let { "${CACHE_FILE_NAME}_${versionName ?: it.versionName}_${versionCode ?: PackageInfoCompat.getLongVersionCode(it)}" } + ?: "${CACHE_FILE_NAME}_unknown", + Context.MODE_PRIVATE) + + /** + * 清除当前 [DexClassFinder] 的 [Class] 缓存 + * + * 适用于全部通过 [ClassLoader.searchClass] or [PackageParam.searchClass] 获取的 [DexClassFinder] + * @param context 当前 [Context] - 不填默认获取 [YukiHookAppHelper.currentApplication] + * @param versionName 版本名称 - 默认空 + * @param versionCode 版本号 - 默认空 + */ + fun clearCache(context: Context? = YukiHookAppHelper.currentApplication(), versionName: String? = null, versionCode: Long? = null) { + context?.currentSp(versionName, versionCode)?.edit()?.clear()?.apply() + ?: yLoggerW(msg = "Cannot clear cache for DexClassFinder because got null context instance") + } + } + + @PublishedApi + override var rulesData = ClassRulesData() + + /** + * 设置 [Class] 完整名称 + * + * 只会查询匹配到的 [Class.getName] + * + * 例如 com.demo.Test 需要填写 com.demo.Test + * @return [String] + */ + var fullName + get() = rulesData.fullName?.name ?: "" + set(value) { + rulesData.fullName = rulesData.createNameRulesData(value) + } + + /** + * 设置 [Class] 简单名称 + * + * 只会查询匹配到的 [Class.getSimpleName] + * + * 例如 com.demo.Test 只需要填写 Test + * + * 对于匿名类例如 com.demo.Test$InnerTest 会为空 - 此时你可以使用 [singleName] + * @return [String] + */ + var simpleName + get() = rulesData.simpleName?.name ?: "" + set(value) { + rulesData.simpleName = rulesData.createNameRulesData(value) + } + + /** + * 设置 [Class] 独立名称 + * + * 设置后将首先使用 [Class.getSimpleName] - 若为空则会使用 [Class.getName] 进行处理 + * + * 例如 com.demo.Test 只需要填写 Test + * + * 对于匿名类例如 com.demo.Test$InnerTest 只需要填写 Test$InnerTest + * @return [String] + */ + var singleName + get() = rulesData.singleName?.name ?: "" + set(value) { + rulesData.singleName = rulesData.createNameRulesData(value) + } + + /** + * 设置在指定包名范围查询当前 [Class] + * + * 设置后仅会在当前 [name] 开头匹配的包名路径下进行查询 - 可提升查询速度 + * + * 例如 ↓ + * + * com.demo.test + * + * com.demo.test.demo + * + * - ❗建议设置此参数指定查询范围 - 否则 [Class] 过多时将会非常慢 + * @param name 指定包名 + * @return [FromPackageRules] 可设置 [FromPackageRules.absolute] 标识包名绝对匹配 + */ + fun from(vararg name: String) = FromPackageRules(arrayListOf().also { + name.takeIf { e -> e.isNotEmpty() }?.forEach { e -> it.add(rulesData.createPackageRulesData(e)) } + if (it.isNotEmpty()) rulesData.fromPackages.addAll(it) + }) + + /** + * 设置 [Class] 标识符筛选条件 + * + * - 可不设置筛选条件 + * @param conditions 条件方法体 + */ + fun modifiers(conditions: ModifierConditions) { + rulesData.modifiers = conditions + } + + /** + * 设置 [Class] 完整名称 + * + * 只会查询匹配到的 [Class.getName] + * + * 例如 com.demo.Test 需要填写 com.demo.Test + * @param value 名称 + * @return [ClassNameRules] 可设置 [ClassNameRules.optional] 标识类名可选 + */ + fun fullName(value: String) = rulesData.createNameRulesData(value).let { + rulesData.fullName = it + ClassNameRules(it) + } + + /** + * 设置 [Class] 简单名称 + * + * 只会查询匹配到的 [Class.getSimpleName] + * + * 例如 com.demo.Test 只需要填写 Test + * + * 对于匿名类例如 com.demo.Test$InnerTest 会为空 - 此时你可以使用 [singleName] + * @param value 名称 + * @return [ClassNameRules] 可设置 [ClassNameRules.optional] 标识类名可选 + */ + fun simpleName(value: String) = rulesData.createNameRulesData(value).let { + rulesData.simpleName = it + ClassNameRules(it) + } + + /** + * 设置 [Class] 独立名称 + * + * 设置后将首先使用 [Class.getSimpleName] - 若为空则会使用 [Class.getName] 进行处理 + * + * 例如 com.demo.Test 只需要填写 Test + * + * 对于匿名类例如 com.demo.Test$InnerTest 只需要填写 Test$InnerTest + * @param value 名称 + * @return [ClassNameRules] 可设置 [ClassNameRules.optional] 标识类名可选 + */ + fun singleName(value: String) = rulesData.createNameRulesData(value).let { + rulesData.singleName = it + ClassNameRules(it) + } + + /** + * 设置 [Class] 完整名称条件 + * + * 只会查询匹配到的 [Class.getName] + * @param conditions 条件方法体 + */ + fun fullName(conditions: NameConditions) { + rulesData.fullNameConditions = conditions + } + + /** + * 设置 [Class] 简单名称条件 + * + * 只会查询匹配到的 [Class.getSimpleName] + * @param conditions 条件方法体 + */ + fun simpleName(conditions: NameConditions) { + rulesData.simpleNameConditions = conditions + } + + /** + * 设置 [Class] 独立名称条件 + * + * 设置后将首先使用 [Class.getSimpleName] - 若为空则会使用 [Class.getName] 进行处理 + * @param conditions 条件方法体 + */ + fun singleName(conditions: NameConditions) { + rulesData.singleNameConditions = conditions + } + + /** 设置 [Class] 继承的父类 */ + inline fun extends() { + rulesData.extendsClass.add(T::class.java.name) + } + + /** + * 设置 [Class] 继承的父类 + * + * 会同时查询 [name] 中所有匹配的父类 + * @param name [Class] 完整名称 + */ + fun extends(vararg name: String) { + rulesData.extendsClass.addAll(name.toList()) + } + + /** 设置 [Class] 实现的接口类 */ + inline fun implements() { + rulesData.implementsClass.add(T::class.java.name) + } + + /** + * 设置 [Class] 实现的接口类 + * + * 会同时查询 [name] 中所有匹配的接口类 + * @param name [Class] 完整名称 + */ + fun implements(vararg name: String) { + rulesData.implementsClass.addAll(name.toList()) + } + + /** + * 标识 [Class] 为匿名类 + * + * 例如 com.demo.Test$1 或 com.demo.Test$InnerTest + * + * 标识后你可以使用 [enclosing] 来进一步指定匿名类的 (封闭类) 主类 + */ + fun anonymous() { + rulesData.isAnonymousClass = true + } + + /** + * 设置 [Class] 没有任何继承 + * + * 此时 [Class] 只应该继承于 [Any] + * + * - ❗设置此条件后 [extends] 将失效 + */ + fun noExtends() { + rulesData.isNoExtendsClass = true + } + + /** + * 设置 [Class] 没有任何接口 + * + * - ❗设置此条件后 [implements] 将失效 + */ + fun noImplements() { + rulesData.isNoImplementsClass = true + } + + /** + * 设置 [Class] 没有任何继承与接口 + * + * 此时 [Class] 只应该继承于 [Any] + * + * - ❗设置此条件后 [extends] 与 [implements] 将失效 + */ + fun noSuper() { + noExtends() + noImplements() + } + + /** 设置 [Class] 匿名类的 (封闭类) 主类 */ + inline fun enclosing() { + rulesData.enclosingClass.add(T::class.java.name) + } + + /** + * 设置 [Class] 匿名类的 (封闭类) 主类 + * + * 会同时查询 [name] 中所有匹配的 (封闭类) 主类 + * @param name [Class] 完整名称 + */ + fun enclosing(vararg name: String) { + rulesData.enclosingClass.addAll(name.toList()) + } + + /** + * 包名范围名称过滤匹配条件实现类 + * @param packages 包名数组 + */ + inner class FromPackageRules internal constructor(private val packages: ArrayList) { + + /** + * 设置包名绝对匹配 + * + * 例如有如下包名 ↓ + * + * com.demo.test.a + * + * com.demo.test.a.b + * + * com.demo.test.active + * + * 若包名条件为 "com.demo.test.a" 则绝对匹配仅能匹配到第一个 + * + * 相反地 - 不设置以上示例会全部匹配 + */ + fun absolute() = packages.takeIf { it.isNotEmpty() }?.forEach { it.isAbsolute = true } + } + + /** + * 类名匹配条件实现类 + * @param name 类名匹配实例 + */ + inner class ClassNameRules internal constructor(private val name: ClassRulesData.NameRulesData) { + + /** + * 设置类名可选 + * + * 例如有如下类名 ↓ + * + * com.demo.Test (fullName) / Test (simpleName) + * + * defpackage.a (fullName) / a (simpleName) + * + * 这两个类名都是同一个类 - 但是在有些版本中被混淆有些版本没有 + * + * 此时可设置类名为 "com.demo.Test" (fullName) / "Test" (simpleName) + * + * 这样就可在完全匹配类名情况下使用类名而忽略其它查询条件 - 否则忽略此条件继续使用其它查询条件 + */ + fun optional() { + name.isOptional = true + } + } + + /** + * 设置 [Class] 满足的 [Member] 条件 + * @param initiate 条件方法体 + * @return [MemberRulesResult] + */ + inline fun member(initiate: MemberRules.() -> Unit = {}) = BaseRules.createMemberRules(this).apply(initiate).build() + + /** + * 设置 [Class] 满足的 [Field] 条件 + * @param initiate 条件方法体 + * @return [MemberRulesResult] + */ + inline fun field(initiate: FieldRules.() -> Unit = {}) = BaseRules.createFieldRules(this).apply(initiate).build() + + /** + * 设置 [Class] 满足的 [Method] 条件 + * @param initiate 条件方法体 + * @return [MemberRulesResult] + */ + inline fun method(initiate: MethodRules.() -> Unit = {}) = BaseRules.createMethodRules(this).apply(initiate).build() + + /** + * 设置 [Class] 满足的 [Constructor] 条件 + * @param initiate 查找方法体 + * @return [MemberRulesResult] + */ + inline fun constructor(initiate: ConstructorRules.() -> Unit = {}) = BaseRules.createConstructorRules(this).apply(initiate).build() + + /** + * 得到 [Class] 或一组 [Class] + * @return [HashSet]<[Class]> + * @throws NoClassDefFoundError 如果找不到 [Class] + */ + private val result get() = ReflectionTool.findClasses(loaderSet, rulesData) + + /** + * 从本地缓存读取 [Class] 数据 + * @return [HashSet]<[Class]> + */ + private fun readFromCache(): HashSet> = + if (async && name.isNotBlank()) YukiHookAppHelper.currentApplication()?.let { + hashSetOf>().also { classes -> + it.currentSp().getStringSet(name, emptySet())?.takeIf { it.isNotEmpty() } + ?.forEach { className -> if (className.hasClass(loaderSet)) classes.add(className.toClass(loaderSet)) } + } + } ?: let { SystemClock.sleep(1); readFromCache() } else hashSetOf() + + /** + * 将当前 [Class] 数组名称保存到本地缓存 + * @throws IllegalStateException 如果当前包名为 "android" + */ + private fun HashSet>.saveToCache() { + if (name.isNotBlank() && isNotEmpty()) hashSetOf().also { names -> + takeIf { it.isNotEmpty() }?.forEach { names.add(it.name) } + YukiHookAppHelper.currentApplication()?.also { + if (it.packageName == "android") error("Cannot create classes cache for \"android\", please remove \"name\" param") + it.currentSp().edit().apply { putStringSet(name, names) }.apply() + } + } + } + + /** + * 设置实例 + * @param classes 当前找到的 [Class] 数组 + */ + private fun setInstance(classes: HashSet>) { + classInstances.clear() + classes.takeIf { it.isNotEmpty() }?.forEach { classInstances.add(it) } + } + + @YukiPrivateApi + override fun build() = runCatching { + if (loaderSet != null) { + /** 开始任务 */ + fun startProcess() { + runBlocking { + setInstance(readFromCache().takeIf { it.isNotEmpty() } ?: result) + }.result { ms -> classInstances.takeIf { it.isNotEmpty() }?.forEach { onDebuggingMsg(msg = "Find Class [$it] takes ${ms}ms") } } + } + Result().also { e -> + if (async) e.await { + runCatching { + startProcess() + it.waitResultCallback?.invoke(it.get()) + it.waitAllResultCallback?.invoke(it.all()) + classInstances.saveToCache() + }.onFailure { e -> + it.isNotFound = true + it.throwable = e + it.noClassDefFoundErrorCallback?.invoke() + onFailureMsg(throwable = e) + } + } else startProcess() + } + } else Result(isNotFound = true, Throwable("loaderSet is null")).await { onFailureMsg() } + }.getOrElse { e -> Result(isNotFound = true, e).await { onFailureMsg(throwable = e) } } + + /** + * [Class] 查找结果实现类 + * @param isNotFound 是否没有找到 [Class] - 默认否 + * @param throwable 错误信息 + */ + inner class Result internal constructor( + @PublishedApi internal var isNotFound: Boolean = false, + @PublishedApi internal var throwable: Throwable? = null + ) : BaseResult { + + /** 异步方法体回调结果 */ + internal var waitResultCallback: ((Class<*>?) -> Unit)? = null + + /** 异步方法体回调数组结果 */ + internal var waitAllResultCallback: ((HashSet>) -> Unit)? = null + + /** 异常结果重新回调方法体 */ + internal var noClassDefFoundErrorCallback: (() -> Unit)? = null + + /** + * 创建监听结果事件方法体 + * @param initiate 方法体 + * @return [Result] 可继续向下监听 + */ + inline fun result(initiate: Result.() -> Unit) = apply(initiate) + + /** + * 得到 [Class] 本身 + * + * - 若有多个 [Class] 结果只会返回第一个 + * + * - 在查询条件找不到任何结果的时候将返回 null + * + * - ❗若你设置了 [async] 请使用 [wait] 方法 + * @return [Class] or null + */ + fun get() = all().takeIf { it.isNotEmpty() }?.first() + + /** + * 得到 [Class] 本身数组 + * + * - 返回全部查询条件匹配的多个 [Class] 实例 + * + * - 在查询条件找不到任何结果的时候将返回空的 [HashSet] + * + * - ❗若你设置了 [async] 请使用 [waitAll] 方法 + * @return [HashSet]<[Class]> + */ + fun all() = classInstances + + /** + * 得到 [Class] 本身数组 (依次遍历) + * + * - 回调全部查询条件匹配的多个 [Class] 实例 + * + * - 在查询条件找不到任何结果的时候将不会执行 + * + * - ❗若你设置了 [async] 请使用 [waitAll] 方法 + * @param result 回调每个结果 + * @return [Result] 可继续向下监听 + */ + fun all(result: (Class<*>) -> Unit): Result { + all().takeIf { it.isNotEmpty() }?.forEach(result) + return this + } + + /** + * 得到 [Class] 本身 (异步) + * + * - 若有多个 [Class] 结果只会回调第一个 + * + * - 在查询条件找不到任何结果的时候将回调 null + * + * - ❗你需要设置 [async] 后此方法才会被回调 - 否则请使用 [get] 方法 + * @param result 回调 - ([Class] or null) + * @return [Result] 可继续向下监听 + */ + fun wait(result: (Class<*>?) -> Unit): Result { + waitResultCallback = result + return this + } + + /** + * 得到 [Class] 本身数组 (异步) + * + * - 回调全部查询条件匹配的多个 [Class] 实例 + * + * - 在查询条件找不到任何结果的时候将回调空的 [HashSet] + * + * - ❗你需要设置 [async] 后此方法才会被回调 - 否则请使用 [all] 方法 + * @param result 回调 - ([HashSet]<[Class]>) + * @return [Result] 可继续向下监听 + */ + fun waitAll(result: (HashSet>) -> Unit): Result { + waitAllResultCallback = result + return this + } + + /** + * 监听找不到 [Class] 时 + * @param result 回调错误 + * @return [Result] 可继续向下监听 + */ + fun onNoClassDefFoundError(result: (Throwable) -> Unit): Result { + noClassDefFoundErrorCallback = { if (isNotFound) result(throwable ?: Throwable("Initialization Error")) } + noClassDefFoundErrorCallback?.invoke() + return this + } + + /** + * 忽略异常并停止打印任何错误日志 + * + * - ❗此时若要监听异常结果 - 你需要手动实现 [onNoClassDefFoundError] 方法 + * @return [Result] 可继续向下监听 + */ + fun ignored(): Result { + isShutErrorPrinting = true + return this + } + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/data/ClassRulesData.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/data/ClassRulesData.kt new file mode 100644 index 00000000..b59ca99c --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/data/ClassRulesData.kt @@ -0,0 +1,183 @@ +/* + * 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/9/5. + */ +@file:Suppress("PropertyName") + +package com.highcapable.yukihookapi.hook.core.finder.classes.data + +import com.highcapable.yukihookapi.hook.core.finder.base.data.BaseRulesData +import com.highcapable.yukihookapi.hook.core.finder.base.rules.ModifierRules +import com.highcapable.yukihookapi.hook.core.finder.members.data.ConstructorRulesData +import com.highcapable.yukihookapi.hook.core.finder.members.data.FieldRulesData +import com.highcapable.yukihookapi.hook.core.finder.members.data.MemberRulesData +import com.highcapable.yukihookapi.hook.core.finder.members.data.MethodRulesData +import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions +import java.lang.reflect.Constructor +import java.lang.reflect.Field +import java.lang.reflect.Member +import java.lang.reflect.Method + +/** + * [Class] 规则查询数据类 + * @param fromPackages 指定包名范围名称数组 + * @param fullName 完整名称 + * @param simpleName 简单名称 + * @param singleName 独立名称 + * @param fullNameConditions 完整名称规则 + * @param simpleNameConditions 简单名称规则 + * @param singleNameConditions 独立名称规则 + * @param isAnonymousClass 匿名类 + * @param isNoExtendsClass 无继承的父类 + * @param isNoImplementsClass 无继承的实现的接口类 + * @param extendsClass 继承的父类名称数组 + * @param implementsClass 实现的接口类名称数组 + * @param enclosingClass 包含的封闭类 (主类) 名称数组 + * @param memberRules [Member] 查询条件数据数组 + * @param fieldRules [Field] 查询条件数据数组 + * @param methodRules [Method] 查询条件数据数组 + * @param constroctorRules [Constructor] 查询条件数据数组 + */ +@PublishedApi +internal class ClassRulesData internal constructor( + var fromPackages: ArrayList = arrayListOf(), + var fullName: NameRulesData? = null, + var simpleName: NameRulesData? = null, + var singleName: NameRulesData? = null, + var fullNameConditions: NameConditions? = null, + var simpleNameConditions: NameConditions? = null, + var singleNameConditions: NameConditions? = null, + var isAnonymousClass: Boolean? = null, + var isNoExtendsClass: Boolean? = null, + var isNoImplementsClass: Boolean? = null, + var extendsClass: ArrayList = arrayListOf(), + var implementsClass: ArrayList = arrayListOf(), + var enclosingClass: ArrayList = arrayListOf(), + var memberRules: ArrayList = arrayListOf(), + var fieldRules: ArrayList = arrayListOf(), + var methodRules: ArrayList = arrayListOf(), + var constroctorRules: ArrayList = arrayListOf() +) : BaseRulesData() { + + /** + * 创建类名匹配条件查询数据类 + * @param name 包名 + * @return [NameRulesData] + */ + internal fun createNameRulesData(name: String) = NameRulesData(name) + + /** + * 创建包名范围名称过滤匹配条件查询数据类 + * @param name 包名 + * @return [PackageRulesData] + */ + internal fun createPackageRulesData(name: String) = PackageRulesData(name) + + /** + * 获取 [Class.getSimpleName] 与 [Class.getName] 的独立名称 + * @param instance 当前 [Class] 实例 + * @return [String] + */ + internal fun classSingleName(instance: Class<*>) = instance.simpleName.takeIf { it.isNotBlank() } + ?: instance.enclosingClass?.let { it.simpleName + instance.name.replace(it.name, newValue = "") } ?: "" + + /** + * 类名匹配条件查询数据类 + * @param name 包名 + * @param isOptional 是否可选 - 默认否 + */ + inner class NameRulesData internal constructor(var name: String, var isOptional: Boolean = false) { + + /** [Class.getName] */ + internal val TYPE_NAME = 0 + + /** [Class.getSimpleName] */ + internal val TYPE_SIMPLE_NAME = 1 + + /** [Class.getSimpleName] or [Class.getName] */ + internal val TYPE_SINGLE_NAME = 2 + + /** + * 匹配当前 [Class] 实例 + * @param instance 当前 [Class] 实例 + * @param type 判断类型 + * @return [Boolean] + */ + internal fun equals(instance: Class<*>, type: Int) = when (type) { + TYPE_NAME -> instance.name == name + TYPE_SIMPLE_NAME -> instance.simpleName == name + TYPE_SINGLE_NAME -> classSingleName(instance) == name + else -> false + } + + override fun toString() = "$name optional($isOptional)" + } + + /** + * 包名范围名称过滤匹配条件查询数据类 + * @param name 包名 + * @param isAbsolute 是否绝对匹配 - 默认否 + */ + inner class PackageRulesData internal constructor(var name: String, var isAbsolute: Boolean = false) { + override fun toString() = "$name absolute($isAbsolute)" + } + + override val templates + get() = arrayOf( + fromPackages.takeIf { it.isNotEmpty() }?.let { "from:$it" } ?: "", + fullName?.let { "fullName:[$it]" } ?: "", + simpleName?.let { "simpleName:[$it]" } ?: "", + singleName?.let { "singleName:[$it]" } ?: "", + fullNameConditions?.let { "fullNameConditions:[existed]" } ?: "", + simpleNameConditions?.let { "simpleNameConditions:[existed]" } ?: "", + singleNameConditions?.let { "singleNameConditions:[existed]" } ?: "", + modifiers?.let { "modifiers:${ModifierRules.templates(uniqueValue)}" } ?: "", + isAnonymousClass?.let { "isAnonymousClass:[$it]" } ?: "", + isNoExtendsClass?.let { "isNoExtendsClass:[$it]" } ?: "", + isNoImplementsClass?.let { "isNoImplementsClass:[$it]" } ?: "", + extendsClass.takeIf { it.isNotEmpty() }?.let { "extendsClass:$it" } ?: "", + implementsClass.takeIf { it.isNotEmpty() }?.let { "implementsClass:$it" } ?: "", + enclosingClass.takeIf { it.isNotEmpty() }?.let { "enclosingClass:$it" } ?: "", + memberRules.takeIf { it.isNotEmpty() }?.let { "memberRules:[${it.size} existed]" } ?: "", + fieldRules.takeIf { it.isNotEmpty() }?.let { "fieldRules:[${it.size} existed]" } ?: "", + methodRules.takeIf { it.isNotEmpty() }?.let { "methodRules:[${it.size} existed]" } ?: "", + constroctorRules.takeIf { it.isNotEmpty() }?.let { "constroctorRules:[${it.size} existed]" } ?: "" + ) + + override val objectName get() = "Class" + + override val isInitialize + get() = super.isInitialize || fromPackages.isNotEmpty() || fullName != null || simpleName != null || singleName != null || + fullNameConditions != null || simpleNameConditions != null || singleNameConditions != null || isAnonymousClass != null || + isNoExtendsClass != null || isNoImplementsClass != null || extendsClass.isNotEmpty() || enclosingClass.isNotEmpty() || + memberRules.isNotEmpty() || fieldRules.isNotEmpty() || methodRules.isNotEmpty() || constroctorRules.isNotEmpty() + + override fun hashCode(other: Any?) = super.hashCode(other) + toString().hashCode() + + override fun toString() = "[$fromPackages][$fullName][$simpleName][$singleName][$fullNameConditions][$simpleNameConditions]" + + "[$singleNameConditions][$modifiers][$isAnonymousClass][$isNoExtendsClass][$isNoImplementsClass][$extendsClass][$implementsClass]" + + "[$enclosingClass][$memberRules][$fieldRules][$methodRules][$constroctorRules]" +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/ConstructorRules.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/ConstructorRules.kt new file mode 100644 index 00000000..6812e7e9 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/ConstructorRules.kt @@ -0,0 +1,145 @@ +/* + * 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/9/12. + */ +@file:Suppress("MemberVisibilityCanBePrivate") + +package com.highcapable.yukihookapi.hook.core.finder.classes.rules + +import com.highcapable.yukihookapi.hook.bean.VariousClass +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.base.BaseRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.result.MemberRulesResult +import com.highcapable.yukihookapi.hook.core.finder.members.data.ConstructorRulesData +import com.highcapable.yukihookapi.hook.core.finder.type.factory.CountConditions +import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions +import com.highcapable.yukihookapi.hook.type.defined.UndefinedType +import com.highcapable.yukihookapi.hook.type.defined.VagueType +import java.lang.reflect.Constructor + +/** + * [Constructor] 查询条件实现类 + * @param rulesData 当前查询条件规则数据 + */ +class ConstructorRules internal constructor(@PublishedApi internal val rulesData: ConstructorRulesData) : BaseRules() { + + /** + * 设置 [Constructor] 参数个数 + * + * 你可以不使用 [param] 指定参数类型而是仅使用此变量指定参数个数 + * + * 若参数个数小于零则忽略并使用 [param] + * @return [Int] + */ + var paramCount + get() = rulesData.paramCount + set(value) { + rulesData.paramCount = value + } + + /** + * 设置 [Constructor] 标识符筛选条件 + * + * - 可不设置筛选条件 + * @param conditions 条件方法体 + */ + fun modifiers(conditions: ModifierConditions) { + rulesData.modifiers = conditions + } + + /** 设置 [Constructor] 空参数、无参数 */ + fun emptyParam() { + rulesData.paramCount = 0 + } + + /** + * 设置 [Constructor] 参数 + * + * 如果同时使用了 [paramCount] 则 [paramType] 的数量必须与 [paramCount] 完全匹配 + * + * 如果 [Constructor] 中存在一些无意义又很长的类型 - 你可以使用 [VagueType] 来替代它 + * + * 例如下面这个参数结构 ↓ + * + * ```java + * Foo(String var1, boolean var2, com.demo.Test var3, int var4) + * ``` + * + * 此时就可以简单地写作 ↓ + * + * ```kotlin + * param(StringType, BooleanType, VagueType, IntType) + * ``` + * + * - ❗无参 [Constructor] 请使用 [emptyParam] 设置查询条件 + * + * - ❗有参 [Constructor] 必须使用此方法设定参数或使用 [paramCount] 指定个数 + * @param paramType 参数类型数组 - ❗只能是 [Class]、[String]、[VariousClass] + */ + fun param(vararg paramType: Any) { + if (paramType.isEmpty()) error("paramTypes is empty, please use emptyParam() instead") + rulesData.paramTypes = + arrayListOf>().apply { paramType.forEach { add(it.compat(tag = "Constructor") ?: UndefinedType) } }.toTypedArray() + } + + /** + * 设置 [Constructor] 参数个数范围 + * + * 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数范围 + * + * 使用示例如下 ↓ + * + * ```kotlin + * paramCount(1..5) + * ``` + * @param numRange 个数范围 + */ + fun paramCount(numRange: IntRange) { + rulesData.paramCountRange = numRange + } + + /** + * 设置 [Constructor] 参数个数条件 + * + * 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数条件 + * + * 使用示例如下 ↓ + * + * ```kotlin + * paramCount { it >= 5 || it.isZero() } + * ``` + * @param conditions 条件方法体 + */ + fun paramCount(conditions: CountConditions) { + rulesData.paramCountConditions = conditions + } + + /** + * 返回结果实现类 + * @return [MemberRulesResult] + */ + @PublishedApi + internal fun build() = MemberRulesResult(rulesData) +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/FieldRules.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/FieldRules.kt new file mode 100644 index 00000000..4c570eb3 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/FieldRules.kt @@ -0,0 +1,92 @@ +/* + * 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/9/12. + */ +package com.highcapable.yukihookapi.hook.core.finder.classes.rules + +import com.highcapable.yukihookapi.hook.bean.VariousClass +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.base.BaseRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.result.MemberRulesResult +import com.highcapable.yukihookapi.hook.core.finder.members.data.FieldRulesData +import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions +import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions +import java.lang.reflect.Field + +/** + * [Field] 查询条件实现类 + * @param rulesData 当前查询条件规则数据 + */ +class FieldRules internal constructor(@PublishedApi internal val rulesData: FieldRulesData) : BaseRules() { + + /** + * 设置 [Field] 名称 + * @return [String] + */ + var name + get() = rulesData.name + set(value) { + rulesData.name = value + } + + /** + * 设置 [Field] 类型 + * + * - ❗只能是 [Class]、[String]、[VariousClass] + * + * - 可不填写类型 + * @return [Any] or null + */ + var type + get() = rulesData.type + set(value) { + rulesData.type = value?.compat(tag = "Field") + } + + /** + * 设置 [Field] 标识符筛选条件 + * + * - 可不设置筛选条件 + * @param conditions 条件方法体 + */ + fun modifiers(conditions: ModifierConditions) { + rulesData.modifiers = conditions + } + + /** + * 设置 [Field] 名称条件 + * @param conditions 条件方法体 + */ + fun name(conditions: NameConditions) { + rulesData.nameConditions = conditions + } + + /** + * 返回结果实现类 + * @return [MemberRulesResult] + */ + @PublishedApi + internal fun build() = MemberRulesResult(rulesData) +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MemberRules.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MemberRules.kt new file mode 100644 index 00000000..ef5e82a4 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MemberRules.kt @@ -0,0 +1,58 @@ +/* + * 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/9/12. + */ +package com.highcapable.yukihookapi.hook.core.finder.classes.rules + +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.base.BaseRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.result.MemberRulesResult +import com.highcapable.yukihookapi.hook.core.finder.members.data.MemberRulesData +import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions +import java.lang.reflect.Member + +/** + * [Member] 查询条件实现类 + * @param rulesData 当前查询条件规则数据 + */ +class MemberRules internal constructor(@PublishedApi internal val rulesData: MemberRulesData) : BaseRules() { + + /** + * 设置 [Member] 标识符筛选条件 + * + * - 可不设置筛选条件 + * @param conditions 条件方法体 + */ + fun modifiers(conditions: ModifierConditions) { + rulesData.modifiers = conditions + } + + /** + * 返回结果实现类 + * @return [MemberRulesResult] + */ + @PublishedApi + internal fun build() = MemberRulesResult(rulesData) +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MethodRules.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MethodRules.kt new file mode 100644 index 00000000..7c32ebd3 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/MethodRules.kt @@ -0,0 +1,178 @@ +/* + * 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/9/12. + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package com.highcapable.yukihookapi.hook.core.finder.classes.rules + +import com.highcapable.yukihookapi.hook.bean.VariousClass +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.base.BaseRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.result.MemberRulesResult +import com.highcapable.yukihookapi.hook.core.finder.members.data.MethodRulesData +import com.highcapable.yukihookapi.hook.core.finder.type.factory.CountConditions +import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions +import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions +import com.highcapable.yukihookapi.hook.type.defined.UndefinedType +import com.highcapable.yukihookapi.hook.type.defined.VagueType +import java.lang.reflect.Method + +/** + * [Method] 查询条件实现类 + * @param rulesData 当前查询条件规则数据 + */ +class MethodRules internal constructor(@PublishedApi internal val rulesData: MethodRulesData) : BaseRules() { + + /** + * 设置 [Method] 名称 + * @return [String] + */ + var name + get() = rulesData.name + set(value) { + rulesData.name = value + } + + /** + * 设置 [Method] 参数个数 + * + * 你可以不使用 [param] 指定参数类型而是仅使用此变量指定参数个数 + * + * 若参数个数小于零则忽略并使用 [param] + * @return [Int] + */ + var paramCount + get() = rulesData.paramCount + set(value) { + rulesData.paramCount = value + } + + /** + * 设置 [Method] 返回值 + * + * - ❗只能是 [Class]、[String]、[VariousClass] + * + * - 可不填写返回值 + * @return [Any] or null + */ + var returnType + get() = rulesData.returnType + set(value) { + rulesData.returnType = value.compat(tag = "Method") + } + + /** + * 设置 [Method] 标识符筛选条件 + * + * - 可不设置筛选条件 + * @param conditions 条件方法体 + */ + fun modifiers(conditions: ModifierConditions) { + rulesData.modifiers = conditions + } + + /** 设置 [Method] 空参数、无参数 */ + fun emptyParam() { + rulesData.paramCount = 0 + } + + /** + * 设置 [Method] 参数 + * + * 如果同时使用了 [paramCount] 则 [paramType] 的数量必须与 [paramCount] 完全匹配 + * + * 如果 [Method] 中存在一些无意义又很长的类型 - 你可以使用 [VagueType] 来替代它 + * + * 例如下面这个参数结构 ↓ + * + * ```java + * void foo(String var1, boolean var2, com.demo.Test var3, int var4) + * ``` + * + * 此时就可以简单地写作 ↓ + * + * ```kotlin + * param(StringType, BooleanType, VagueType, IntType) + * ``` + * + * - ❗无参 [Method] 请使用 [emptyParam] 设置查询条件 + * + * - ❗有参 [Method] 必须使用此方法设定参数或使用 [paramCount] 指定个数 + * @param paramType 参数类型数组 - ❗只能是 [Class]、[String]、[VariousClass] + */ + fun param(vararg paramType: Any) { + if (paramType.isEmpty()) error("paramTypes is empty, please use emptyParam() instead") + rulesData.paramTypes = + arrayListOf>().apply { paramType.forEach { add(it.compat(tag = "Method") ?: UndefinedType) } }.toTypedArray() + } + + /** + * 设置 [Method] 名称条件 + * @param conditions 条件方法体 + */ + fun name(conditions: NameConditions) { + rulesData.nameConditions = conditions + } + + /** + * 设置 [Method] 参数个数范围 + * + * 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数范围 + * + * 使用示例如下 ↓ + * + * ```kotlin + * paramCount(1..5) + * ``` + * @param numRange 个数范围 + */ + fun paramCount(numRange: IntRange) { + rulesData.paramCountRange = numRange + } + + /** + * 设置 [Method] 参数个数条件 + * + * 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数条件 + * + * 使用示例如下 ↓ + * + * ```kotlin + * paramCount { it >= 5 || it.isZero() } + * ``` + * @param conditions 条件方法体 + */ + fun paramCount(conditions: CountConditions) { + rulesData.paramCountConditions = conditions + } + + /** + * 返回结果实现类 + * @return [MemberRulesResult] + */ + @PublishedApi + internal fun build() = MemberRulesResult(rulesData) +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/base/BaseRules.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/base/BaseRules.kt new file mode 100644 index 00000000..66561a70 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/base/BaseRules.kt @@ -0,0 +1,90 @@ +/* + * 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/9/12. + */ +package com.highcapable.yukihookapi.hook.core.finder.classes.rules.base + +import com.highcapable.yukihookapi.hook.core.finder.classes.DexClassFinder +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.ConstructorRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.FieldRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.MemberRules +import com.highcapable.yukihookapi.hook.core.finder.classes.rules.MethodRules +import com.highcapable.yukihookapi.hook.core.finder.members.data.ConstructorRulesData +import com.highcapable.yukihookapi.hook.core.finder.members.data.FieldRulesData +import com.highcapable.yukihookapi.hook.core.finder.members.data.MemberRulesData +import com.highcapable.yukihookapi.hook.core.finder.members.data.MethodRulesData +import java.lang.reflect.Member + +/** + * [Member] 查询条件实现父类 + * @param instance 当前查找类实例 + */ +open class BaseRules internal constructor(internal var instance: DexClassFinder? = null) { + + @PublishedApi + internal companion object { + + /** + * 创建查询条件规则数据 + * @param instance 当前查找类实例 + * @return [MemberRulesData] + */ + @PublishedApi + internal fun createMemberRules(instance: DexClassFinder) = + MemberRules(MemberRulesData().apply { instance.rulesData.memberRules.add(this) }).apply { this.instance = instance } + + /** + * 创建查询条件规则数据 + * @return [FieldRulesData] + */ + @PublishedApi + internal fun createFieldRules(instance: DexClassFinder) = + FieldRules(FieldRulesData().apply { instance.rulesData.fieldRules.add(this) }).apply { this.instance = instance } + + /** + * 创建查询条件规则数据 + * @return [MethodRulesData] + */ + @PublishedApi + internal fun createMethodRules(instance: DexClassFinder) = + MethodRules(MethodRulesData().apply { instance.rulesData.methodRules.add(this) }).apply { this.instance = instance } + + /** + * 创建查询条件规则数据 + * @return [ConstructorRulesData] + */ + @PublishedApi + internal fun createConstructorRules(instance: DexClassFinder) = + ConstructorRules(ConstructorRulesData().apply { instance.rulesData.constroctorRules.add(this) }).apply { this.instance = instance } + } + + /** + * 将目标类型转换为可识别的兼容类型 + * @param tag 当前查找类的标识 + * @return [Class] or null + */ + internal fun Any?.compat(tag: String) = instance?.compatType(any = this, tag) +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/result/MemberRulesResult.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/result/MemberRulesResult.kt new file mode 100644 index 00000000..d8539299 --- /dev/null +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/classes/rules/result/MemberRulesResult.kt @@ -0,0 +1,89 @@ +/* + * 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/9/12. + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package com.highcapable.yukihookapi.hook.core.finder.classes.rules.result + +import com.highcapable.yukihookapi.hook.core.finder.members.data.MemberRulesData +import com.highcapable.yukihookapi.hook.core.finder.type.factory.CountConditions +import java.lang.reflect.Member + +/** + * 当前 [Member] 查询条件结果实现类 + * @param rulesData 当前查询条件规则数据 + */ +class MemberRulesResult internal constructor(private val rulesData: MemberRulesData) { + + /** + * 设置当前 [Member] 在查询条件中个数为 0 + * @return [MemberRulesResult] 可继续向下监听 + */ + fun none() = count(num = 0) + + /** + * 设置当前 [Member] 在查询条件中需要全部匹配的个数 + * @param num 个数 + * @return [MemberRulesResult] 可继续向下监听 + */ + fun count(num: Int): MemberRulesResult { + rulesData.matchCount = num + return this + } + + /** + * 设置当前 [Member] 在查询条件中需要全部匹配的个数范围 + * + * 使用示例如下 ↓ + * + * ```kotlin + * count(1..5) + * ``` + * @param numRange 个数范围 + * @return [MemberRulesResult] 可继续向下监听 + */ + fun count(numRange: IntRange): MemberRulesResult { + rulesData.matchCountRange = numRange + return this + } + + /** + * 设置当前 [Member] 在查询条件中需要全部匹配的个数条件 + * + * 使用示例如下 ↓ + * + * ```kotlin + * count { it >= 5 || it.isZero() } + * ``` + * @param conditions 条件方法体 + * @return [MemberRulesResult] 可继续向下监听 + */ + fun count(conditions: CountConditions): MemberRulesResult { + rulesData.matchCountConditions = conditions + return this + } +} \ No newline at end of file diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/tools/ReflectionTool.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/tools/ReflectionTool.kt index ef3bde14..a61fc566 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/tools/ReflectionTool.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/tools/ReflectionTool.kt @@ -30,28 +30,34 @@ package com.highcapable.yukihookapi.hook.core.finder.tools import com.highcapable.yukihookapi.hook.core.finder.base.data.BaseRulesData +import com.highcapable.yukihookapi.hook.core.finder.base.rules.CountRules +import com.highcapable.yukihookapi.hook.core.finder.classes.data.ClassRulesData import com.highcapable.yukihookapi.hook.core.finder.members.data.ConstructorRulesData import com.highcapable.yukihookapi.hook.core.finder.members.data.FieldRulesData import com.highcapable.yukihookapi.hook.core.finder.members.data.MemberRulesData import com.highcapable.yukihookapi.hook.core.finder.members.data.MethodRulesData -import com.highcapable.yukihookapi.hook.factory.hasExtends +import com.highcapable.yukihookapi.hook.factory.* import com.highcapable.yukihookapi.hook.log.yLoggerW import com.highcapable.yukihookapi.hook.store.ReflectsCacheStore import com.highcapable.yukihookapi.hook.type.defined.UndefinedType import com.highcapable.yukihookapi.hook.type.defined.VagueType +import com.highcapable.yukihookapi.hook.type.java.DalvikBaseDexClassLoader import com.highcapable.yukihookapi.hook.type.java.NoClassDefFoundErrorClass import com.highcapable.yukihookapi.hook.type.java.NoSuchFieldErrorClass import com.highcapable.yukihookapi.hook.type.java.NoSuchMethodErrorClass import com.highcapable.yukihookapi.hook.utils.conditions import com.highcapable.yukihookapi.hook.utils.let import com.highcapable.yukihookapi.hook.utils.takeIf +import com.highcapable.yukihookapi.hook.utils.value import com.highcapable.yukihookapi.hook.xposed.bridge.YukiHookBridge import com.highcapable.yukihookapi.hook.xposed.bridge.factory.YukiHookHelper import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics +import dalvik.system.BaseDexClassLoader import java.lang.reflect.Constructor import java.lang.reflect.Field import java.lang.reflect.Member import java.lang.reflect.Method +import java.util.* import kotlin.math.abs /** @@ -62,6 +68,23 @@ internal object ReflectionTool { /** 当前工具类的标签 */ private const val TAG = "YukiHookAPI#ReflectionTool" + /** + * 写出当前 [ClassLoader] 下所有 [Class] 名称数组 + * @param loader 当前使用的 [ClassLoader] + * @return [List]<[String]> + * @throws IllegalStateException 如果 [loader] 不是 [BaseDexClassLoader] + */ + private fun findDexClassList(loader: ClassLoader?) = ReflectsCacheStore.findDexClassList(loader.hashCode()) + ?: DalvikBaseDexClassLoader.field { name = "pathList" }.ignored().get(loader.value().let { + while (it.value !is BaseDexClassLoader) { + if (it.value?.parent != null) it.value = it.value?.parent + else error("ClassLoader [$loader] is not a DexClassLoader") + }; it.value ?: error("ClassLoader [$loader] load failed") + }).current(ignored = true)?.field { name = "dexElements" }?.array()?.flatMap { element -> + element.current(ignored = true).field { name = "dexFile" }.current(ignored = true) + ?.method { name = "entries" }?.invoke>()?.toList().orEmpty() + }.orEmpty().also { if (it.isNotEmpty()) ReflectsCacheStore.putDexClassList(loader.hashCode(), it) } + /** * 使用字符串类名查询 [Class] 是否存在 * @param name [Class] 完整名称 @@ -89,6 +112,140 @@ internal object ReflectionTool { }.getOrNull() ?: throw createException(loader ?: AppParasitics.baseClassLoader, name = "Class", "name:[$name]") } + /** + * 查找任意 [Class] 或一组 [Class] + * @param loaderSet 类所在 [ClassLoader] + * @param rulesData 规则查询数据 + * @return [HashSet]<[Class]> + * @throws IllegalStateException 如果 [loaderSet] 为 null 或未设置任何条件 + * @throws NoClassDefFoundError 如果找不到 [Class] + */ + internal fun findClasses(loaderSet: ClassLoader?, rulesData: ClassRulesData) = rulesData.createResult { + ReflectsCacheStore.findClasses(hashCode(loaderSet)) ?: hashSetOf>().also { classes -> + /** + * 开始查询作业 + * @param instance 当前 [Class] 实例 + */ + fun startProcess(instance: Class<*>) { + conditions { + fromPackages.takeIf { it.isNotEmpty() }?.also { and(true) } + fullName?.also { it.equals(instance, it.TYPE_NAME).also { e -> if (it.isOptional) opt(e) else and(e) } } + simpleName?.also { it.equals(instance, it.TYPE_SIMPLE_NAME).also { e -> if (it.isOptional) opt(e) else and(e) } } + singleName?.also { it.equals(instance, it.TYPE_SINGLE_NAME).also { e -> if (it.isOptional) opt(e) else and(e) } } + fullNameConditions?.also { instance.name.also { n -> and(it(n.cast(), n)) } } + simpleNameConditions?.also { instance.simpleName.also { n -> and(it(n.cast(), n)) } } + singleNameConditions?.also { classSingleName(instance).also { n -> and(it(n.cast(), n)) } } + modifiers?.also { and(it(instance.cast())) } + extendsClass.takeIf { it.isNotEmpty() }?.also { and(instance.hasExtends && it.contains(instance.superclass.name)) } + implementsClass.takeIf { it.isNotEmpty() } + ?.also { and(instance.interfaces.isNotEmpty() && instance.interfaces.any { e -> it.contains(e.name) }) } + enclosingClass.takeIf { it.isNotEmpty() } + ?.also { and(instance.enclosingClass != null && it.contains(instance.enclosingClass.name)) } + isAnonymousClass?.also { and(instance.isAnonymousClass && it) } + isNoExtendsClass?.also { and(instance.hasExtends.not() && it) } + isNoImplementsClass?.also { and(instance.interfaces.isEmpty() && it) } + /** + * 匹配 [MemberRulesData] + * @param size [Member] 个数 + * @param result 回调是否匹配 + */ + fun MemberRulesData.matchCount(size: Int, result: (Boolean) -> Unit) { + takeIf { it.isInitializeOfMatch }?.also { rule -> + rule.conditions { + value.matchCount.takeIf { it >= 0 }?.also { and(it == size) } + value.matchCountRange.takeIf { it.isEmpty().not() }?.also { and(size in it) } + value.matchCountConditions?.also { and(it(CountRules.with(size), size)) } + }.finally { result(true) }.without { result(false) } + } ?: result(true) + } + + /** + * 检查类型中的 [Class] 是否存在 - 即不存在 [UndefinedType] + * @param type 类型 + * @return [Boolean] + */ + fun MemberRulesData.exists(vararg type: Any?): Boolean { + if (type.isEmpty()) return true + for (i in type.indices) if (type[i] == UndefinedType) { + yLoggerW(msg = "$objectName type[$i] mistake, it will be ignored in current conditions") + return false + } + return true + } + memberRules.takeIf { it.isNotEmpty() }?.forEach { rule -> + instance.existMembers?.apply { + var numberOfFound = 0 + if (rule.isInitializeOfSuper) forEach { member -> + rule.conditions { + value.modifiers?.also { and(it(member.cast())) } + }.finally { numberOfFound++ } + }.run { rule.matchCount(numberOfFound) { and(it && numberOfFound > 0) } } + else rule.matchCount(size) { and(it) } + } + } + fieldRules.takeIf { it.isNotEmpty() }?.forEach { rule -> + instance.existFields?.apply { + var numberOfFound = 0 + if (rule.isInitialize) forEach { field -> + rule.conditions { + value.type?.also { value.exists(it) and (it == field.type) } + value.name.takeIf { it.isNotBlank() }?.also { and(it == field.name) } + value.modifiers?.also { and(it(field.cast())) } + value.nameConditions?.also { field.name.also { n -> and(it(n.cast(), n)) } } + }.finally { numberOfFound++ } + }.run { rule.matchCount(numberOfFound) { and(it && numberOfFound > 0) } } + else rule.matchCount(size) { and(it) } + } + } + methodRules.takeIf { it.isNotEmpty() }?.forEach { rule -> + instance.existMethods?.apply { + var numberOfFound = 0 + if (rule.isInitialize) forEach { method -> + rule.conditions { + value.name.takeIf { it.isNotBlank() }?.also { and(it == method.name) } + value.returnType?.also { value.exists(it) and (it == method.returnType) } + value.paramCount.takeIf { it >= 0 }?.also { and(method.parameterTypes.size == it) } + value.paramCountRange.takeIf { it.isEmpty().not() }?.also { and(method.parameterTypes.size in it) } + value.paramCountConditions?.also { method.parameterTypes.size.also { s -> and(it(s.cast(), s)) } } + value.paramTypes?.also { value.exists(*it) and (paramTypesEq(it, method.parameterTypes)) } + value.modifiers?.also { and(it(method.cast())) } + value.nameConditions?.also { method.name.also { n -> and(it(n.cast(), n)) } } + }.finally { numberOfFound++ } + }.run { rule.matchCount(numberOfFound) { and(it && numberOfFound > 0) } } + else rule.matchCount(size) { and(it) } + } + } + constroctorRules.takeIf { it.isNotEmpty() }?.forEach { rule -> + instance.existConstructors?.apply { + var numberOfFound = 0 + if (rule.isInitialize) forEach { constructor -> + rule.conditions { + value.paramCount.takeIf { it >= 0 }?.also { and(constructor.parameterTypes.size == it) } + value.paramCountRange.takeIf { it.isEmpty().not() }?.also { and(constructor.parameterTypes.size in it) } + value.paramCountConditions?.also { constructor.parameterTypes.size.also { s -> and(it(s.cast(), s)) } } + value.paramTypes?.also { value.exists(*it) and (paramTypesEq(it, constructor.parameterTypes)) } + value.modifiers?.also { and(it(constructor.cast())) } + }.finally { numberOfFound++ } + }.run { rule.matchCount(numberOfFound) { and(it && numberOfFound > 0) } } + else rule.matchCount(size) { and(it) } + } + } + }.finally { classes.add(instance) } + } + findDexClassList(loaderSet).takeIf { it.isNotEmpty() }?.forEach { className -> + /** 分离包名 → com.demo.Test → com.demo (获取最后一个 "." + 简单类名的长度) → 由于末位存在 "." 最后要去掉 1 个长度 */ + (if (className.contains(other = ".")) + className.substring(0, className.length - className.split(".").let { it[it.lastIndex] }.length - 1) + else className).also { packageName -> + if ((fromPackages.isEmpty() || fromPackages.any { + if (it.isAbsolute) packageName == it.name else packageName.startsWith(it.name) + }) && className.hasClass(loaderSet) + ) startProcess(className.toClass(loaderSet)) + } + } + }.takeIf { it.isNotEmpty() }?.also { ReflectsCacheStore.putClasses(hashCode(loaderSet), it) } ?: throwNotFoundError(loaderSet) + } + /** * 查找任意 [Field] 或一组 [Field] * @param classSet [Field] 所在类 @@ -333,6 +490,7 @@ internal object ReflectionTool { is FieldRulesData -> isInitialize.not() is MethodRulesData -> isInitialize.not() is ConstructorRulesData -> isInitialize.not() + is ClassRulesData -> isInitialize.not() else -> true }.takeIf { it }?.also { error("You must set a condition when finding a $objectName") } return result(this) @@ -374,6 +532,7 @@ internal object ReflectionTool { is FieldRulesData -> throw createException(instanceSet, objectName, *templates) is MethodRulesData -> throw createException(instanceSet, objectName, *templates) is ConstructorRulesData -> throw createException(instanceSet, objectName, *templates) + is ClassRulesData -> throw createException(instanceSet ?: AppParasitics.baseClassLoader, objectName, *templates) else -> error("Type [$this] not allowed") } diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/type/factory/TypeAliasFactory.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/type/factory/TypeAliasFactory.kt index 6c5f9999..f34b3d73 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/type/factory/TypeAliasFactory.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/core/finder/type/factory/TypeAliasFactory.kt @@ -30,10 +30,14 @@ package com.highcapable.yukihookapi.hook.core.finder.type.factory import com.highcapable.yukihookapi.hook.core.finder.base.rules.CountRules import com.highcapable.yukihookapi.hook.core.finder.base.rules.ModifierRules import com.highcapable.yukihookapi.hook.core.finder.base.rules.NameRules +import com.highcapable.yukihookapi.hook.core.finder.classes.DexClassFinder import com.highcapable.yukihookapi.hook.core.finder.members.ConstructorFinder import com.highcapable.yukihookapi.hook.core.finder.members.FieldFinder import com.highcapable.yukihookapi.hook.core.finder.members.MethodFinder +/** 定义 [DexClassFinder] 方法体类型 */ +internal typealias ClassConditions = DexClassFinder.() -> Unit + /** 定义 [FieldFinder] 方法体类型 */ internal typealias FieldConditions = FieldFinder.() -> Unit diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt index b182ee7a..a64ebd7d 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/factory/ReflectionFactory.kt @@ -33,14 +33,12 @@ import com.highcapable.yukihookapi.YukiHookAPI import com.highcapable.yukihookapi.hook.bean.CurrentClass import com.highcapable.yukihookapi.hook.bean.GenericClass import com.highcapable.yukihookapi.hook.core.finder.base.rules.ModifierRules +import com.highcapable.yukihookapi.hook.core.finder.classes.DexClassFinder import com.highcapable.yukihookapi.hook.core.finder.members.ConstructorFinder import com.highcapable.yukihookapi.hook.core.finder.members.FieldFinder import com.highcapable.yukihookapi.hook.core.finder.members.MethodFinder import com.highcapable.yukihookapi.hook.core.finder.tools.ReflectionTool -import com.highcapable.yukihookapi.hook.core.finder.type.factory.ConstructorConditions -import com.highcapable.yukihookapi.hook.core.finder.type.factory.FieldConditions -import com.highcapable.yukihookapi.hook.core.finder.type.factory.MethodConditions -import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions +import com.highcapable.yukihookapi.hook.core.finder.type.factory.* import com.highcapable.yukihookapi.hook.xposed.bridge.status.YukiHookModuleStatus import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics import java.lang.reflect.* @@ -59,6 +57,22 @@ enum class MembersType { CONSTRUCTOR } +/** + * 通过当前 [ClassLoader] 按指定条件查找并得到 Dex 中的 [Class] + * + * - ❗此方法在 [Class] 数量过多及查找条件复杂时会非常耗时 + * + * - ❗建议启用 [async] 或设置 [name] 参数 - [name] 参数将在 Hook APP (宿主) 不同版本中自动进行本地缓存以提升效率 + * + * - ❗此功能尚在试验阶段 - 性能与稳定性可能仍然存在问题 - 使用过程遇到问题请向我们报告并帮助我们改进 + * @param name 标识当前 [Class] 缓存的名称 - 不设置将不启用缓存 - 启用缓存自动启用 [async] + * @param async 是否启用异步 - 默认否 + * @param initiate 方法体 + * @return [DexClassFinder.Result] + */ +inline fun ClassLoader.searchClass(name: String = "", async: Boolean = false, initiate: ClassConditions) = + DexClassFinder(name, async = async || name.isNotBlank(), loaderSet = this).apply(initiate).build() + /** * 监听当前 [ClassLoader] 的 [ClassLoader.loadClass] 方法装载 * diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/PackageParam.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/PackageParam.kt index 7953e01f..8be55a73 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/PackageParam.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/param/PackageParam.kt @@ -42,7 +42,9 @@ import com.highcapable.yukihookapi.hook.bean.HookResources import com.highcapable.yukihookapi.hook.bean.VariousClass import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreator import com.highcapable.yukihookapi.hook.core.YukiResourcesHookCreator +import com.highcapable.yukihookapi.hook.core.finder.classes.DexClassFinder import com.highcapable.yukihookapi.hook.core.finder.tools.ReflectionTool +import com.highcapable.yukihookapi.hook.core.finder.type.factory.ClassConditions import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker import com.highcapable.yukihookapi.hook.param.type.HookEntryType import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper @@ -292,6 +294,22 @@ open class PackageParam internal constructor(@PublishedApi internal var wrapper: */ fun loadHooker(hooker: YukiBaseHooker) = hooker.assignInstance(packageParam = this) + /** + * 通过 [appClassLoader] 按指定条件查找并得到当前 Hook APP Dex 中的 [Class] + * + * - ❗此方法在 [Class] 数量过多及查找条件复杂时会非常耗时 + * + * - ❗建议启用 [async] 或设置 [name] 参数 - [name] 参数将在 Hook APP (宿主) 不同版本中自动进行本地缓存以提升效率 + * + * - ❗此功能尚在试验阶段 - 性能与稳定性可能仍然存在问题 - 使用过程遇到问题请向我们报告并帮助我们改进 + * @param name 标识当前 [Class] 缓存的名称 - 不设置将不启用缓存 - 启用缓存自动启用 [async] + * @param async 是否启用异步 - 默认否 + * @param initiate 方法体 + * @return [DexClassFinder.Result] + */ + inline fun searchClass(name: String = "", async: Boolean = false, initiate: ClassConditions) = + DexClassFinder(name, async = async || name.isNotBlank(), appClassLoader).apply(initiate).build() + /** * 通过字符串类名转换为当前 Hook APP 的实体类 * diff --git a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/store/ReflectsCacheStore.kt b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/store/ReflectsCacheStore.kt index 8ef216f9..176ea651 100644 --- a/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/store/ReflectsCacheStore.kt +++ b/yukihookapi/src/api/kotlin/com/highcapable/yukihookapi/hook/store/ReflectsCacheStore.kt @@ -38,13 +38,21 @@ import java.lang.reflect.Method * * 为防止 [Class]、[Member] 复用过高造成的系统 GC 问题 * - * 查询后的 [Class]、[Member] 在 [YukiHookAPI.Configs.isEnableMemberCache] 启用后自动进入缓存 + * 查找后的 [Class] 自动进入缓存 - 不受任何控制 + * + * 查找后的 [Member] 在 [YukiHookAPI.Configs.isEnableMemberCache] 启用后自动进入缓存 */ internal object ReflectsCacheStore { - /** 缓存的 [Class] */ + /** 缓存的 [Class] 列表 */ + private val dexClassListData = HashMap>() + + /** 缓存的 [Class] 对象 */ private val classData = HashMap?>() + /** 缓存的 [Class] 数组 */ + private val classesData = HashMap>>() + /** 缓存的 [Method] 数组 */ private val methodsData = HashMap>() @@ -54,6 +62,13 @@ internal object ReflectsCacheStore { /** 缓存的 [Field] 数组 */ private val fieldsData = HashMap>() + /** + * 查找缓存中的 [Class] 列表 + * @param hashCode 标识符 + * @return [List]<[Class]> + */ + internal fun findDexClassList(hashCode: Int) = dexClassListData[hashCode] + /** * 查找缓存中的 [Class] * @param hashCode 标识符 @@ -61,6 +76,13 @@ internal object ReflectsCacheStore { */ internal fun findClass(hashCode: Int) = classData[hashCode] + /** + * 查找缓存中的 [Class] 数组 + * @param hashCode 标识符 + * @return [HashSet]<[Class]> or null + */ + internal fun findClasses(hashCode: Int) = classesData[hashCode] + /** * 查找缓存中的 [Method] 数组 * @param hashCode 标识符 @@ -82,16 +104,33 @@ internal object ReflectsCacheStore { */ internal fun findFields(hashCode: Int) = fieldsData[hashCode] + /** + * 写入 [Class] 列表到缓存 + * @param hashCode 标识符 + * @param instance 实例 + */ + internal fun putDexClassList(hashCode: Int, instance: List) { + dexClassListData[hashCode] = instance + } + /** * 写入 [Class] 到缓存 * @param hashCode 标识符 * @param instance 实例 */ internal fun putClass(hashCode: Int, instance: Class<*>?) { - if (YukiHookAPI.Configs.isEnableMemberCache.not()) return classData[hashCode] = instance } + /** + * 写入 [Class] 数组到缓存 + * @param hashCode 标识符 + * @param instance 实例 + */ + internal fun putClasses(hashCode: Int, instance: HashSet>) { + classesData[hashCode] = instance + } + /** * 写入 [Method] 数组到缓存 * @param hashCode 标识符