This commit is contained in:
2022-02-03 03:31:26 +08:00
parent e34b9658d2
commit c9cc80c351
17 changed files with 1068 additions and 209 deletions

View File

@@ -0,0 +1,35 @@
/**
* MIT License
*
* Copyright (C) 2022 HighCapable
*
* This file is part of YukiHookAPI.
*
* 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/2/3.
*/
package com.highcapable.yukihookapi.demo
// for test
class InjectTest(private val string: String) {
// for test
fun getString() = string
}

View File

@@ -0,0 +1,35 @@
/**
* MIT License
*
* Copyright (C) 2022 HighCapable
*
* This file is part of YukiHookAPI.
*
* 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/2/3.
*/
package com.highcapable.yukihookapi.demo
// for test
class InjectTestName(private val string: String) {
// for test
fun getString() = string
}

View File

@@ -25,11 +25,14 @@
*
* This file is Created by fankes on 2022/1/29.
*/
@file:Suppress("SameParameterValue")
package com.highcapable.yukihookapi.demo
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
class MainActivity : AppCompatActivity() {
@@ -38,11 +41,30 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
// for test
AlertDialog.Builder(this)
.setMessage(hello())
.setPositiveButton("OK", null)
.show()
.setTitle("Hook 方法返回值测试")
.setMessage(test() + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
.setPositiveButton("下一个") { _, _ ->
AlertDialog.Builder(this)
.setTitle("Hook 方法参数测试")
.setMessage(test("这是没有更改的文字") + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
.setPositiveButton("下一个") { _, _ ->
AlertDialog.Builder(this)
.setTitle("Hook 构造方法测试(stub)")
.setMessage(InjectTest("文字未更改").getString() + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
.setPositiveButton("下一个") { _, _ ->
AlertDialog.Builder(this)
.setTitle("Hook 构造方法测试(名称)")
.setMessage(InjectTestName("文字没更改").getString() + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
.setPositiveButton("完成", null)
.show()
}.show()
}.show()
}.show()
}
// for test
private fun hello() = "正常显示的一行文字"
private fun test() = "正常显示的一行文字"
// for test
private fun test(string: String) = string
}

View File

@@ -25,29 +25,68 @@
*
* This file is Created by fankes on 2022/2/2.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.demo.hook
import android.app.Activity
import android.app.AlertDialog
import android.widget.Toast
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.demo.InjectTest
import com.highcapable.yukihookapi.demo.MainActivity
import com.highcapable.yukihookapi.hook.factory.encase
import com.highcapable.yukihookapi.hook.proxy.YukiHookInitializeProxy
import com.highcapable.yukihookapi.hook.type.ActivityClass
import com.highcapable.yukihookapi.hook.type.BundleClass
import com.highcapable.yukihookapi.hook.type.StringType
@InjectYukiHookWithXposed
class HookMain : YukiHookInitializeProxy {
override fun onHook() = encase {
loadApp(name = "com.highcapable.yukihookapi.demo") {
loadClass(name = "$packageName.MainActivity").hook {
grabMember = hookClass.loadMethod(name = "hello")
replaceTo(any = "这是一段 Hook 的文字内容")
private val moduleName = "com.highcapable.yukihookapi.demo"
override fun onHook() = encase(moduleName) {
loadApp(name = moduleName) {
MainActivity::class.java.hook {
injectMethod {
name = "test"
returnType = StringType
replaceTo("这段文字已被 Hook 成功")
}
injectMethod {
name = "test"
param(StringType)
returnType = StringType
beforeHook { args().set("方法参数已被 Hook 成功") }
}
}
InjectTest::class.java.hook {
injectConstructor {
param(StringType)
beforeHook { args().set("构造方法已被 Hook 成功") }
}
}
findClass(name = "$packageName.InjectTestName").hook {
injectConstructor {
param(StringType)
beforeHook { args().set("构造方法已被 Hook 成功 [2]") }
}
}
}
loadApp(name = "com.android.browser") {
ActivityClass.hook {
grabMember = hookClass.loadMethod(name = "onCreate", BundleClass)
afterHook {
Toast.makeText(thisAny as Activity, "Hook Success", Toast.LENGTH_SHORT).show()
injectMethod {
name = "onCreate"
param(BundleClass)
afterHook {
AlertDialog.Builder(instance())
.setCancelable(false)
.setTitle("测试 Hook")
.setMessage("Hook 已成功")
.setPositiveButton("OK") { _, _ ->
Toast.makeText(instance(), "Hook Success", Toast.LENGTH_SHORT).show()
}.show()
}
}
}
}

View File

@@ -30,17 +30,26 @@
package com.highcapable.yukihookapi
import android.content.pm.ApplicationInfo
import com.highcapable.yukihookapi.YukiHook.encase
import com.highcapable.yukihookapi.YukiHookAPI.encase
import com.highcapable.yukihookapi.annotation.DoNotUseField
import com.highcapable.yukihookapi.param.CustomParam
import com.highcapable.yukihookapi.param.PackageParam
/**
* YukiHook 的装载 API 调用类
*
* 可以实现作为模块装载和自定义 Hook 装载两种方式
*
* 模块装载方式已经自动对接 Xposed API - 可直接调用 [encase] 完成操作
*/
object YukiHook {
object YukiHookAPI {
/** 全局标识 */
const val TAG = "YukiHookAPI"
/** Xposed Hook API 绑定的模块包名 */
@DoNotUseField
internal var modulePackageName = ""
/** Xposed Hook API 方法体回调 */
@DoNotUseField
@@ -48,9 +57,11 @@ object YukiHook {
/**
* 作为模块装载调用入口方法 - Xposed API
* @param moduleName 模块包名 - 不填将无法实现监听模块激活状态
* @param initiate Hook 方法体
*/
fun encase(initiate: PackageParam.() -> Unit) {
fun encase(moduleName: String = "", initiate: PackageParam.() -> Unit) {
modulePackageName = moduleName
packageParamCallback = initiate
}
@@ -66,5 +77,5 @@ object YukiHook {
packageName: String,
appInfo: ApplicationInfo,
initiate: PackageParam.() -> Unit
) = initiate.invoke(PackageParam(customInstance = CustomParam(classLoader, appInfo, packageName)))
) = initiate.invoke(PackageParam(customParam = CustomParam(classLoader, appInfo, packageName)))
}

View File

@@ -0,0 +1,45 @@
/**
* MIT License
*
* Copyright (C) 2022 HighCapable
*
* This file is part of YukiHookAPI.
*
* 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/2/3.
*/
package com.highcapable.yukihookapi.annotation.xposed
import androidx.annotation.Keep
import com.highcapable.yukihookapi.hook.proxy.YukiHookInitializeProxy
import com.highcapable.yukihookapi.hook.xposed.YukiHookLoadPackage
/**
* 标识注入 YukiHook 的类
*
* 此类将使用 [YukiHookLoadPackage] 自动调用 XposedInit
*
* 你可以将被注释的类继承于 [YukiHookInitializeProxy] 接口实现 [YukiHookInitializeProxy.onHook] 方法
*
* 只能拥有一个 Hook 入口 - 多个入口将以首个得到的入口为准
*/
@Target(AnnotationTarget.CLASS)
@Keep
annotation class InjectYukiHookWithXposed

View File

@@ -25,11 +25,14 @@
*
* This file is Created by fankes on 2022/2/2.
*/
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE")
package com.highcapable.yukihookapi.hook.core
import android.util.Log
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.DoNotUseMethod
import com.highcapable.yukihookapi.hook.utils.ReflectionUtils
import com.highcapable.yukihookapi.param.HookParam
import com.highcapable.yukihookapi.param.PackageParam
import de.robv.android.xposed.XC_MethodHook
@@ -39,105 +42,33 @@ import java.lang.reflect.Member
/**
* YukiHook 核心类实现方法
*
* 这是一个 API 对接类 - 实现原生对接 [XposedBridge]
* @param instance 需要传入 [PackageParam] 实现方法调用
* @param packageParam 需要传入 [PackageParam] 实现方法调用
* @param hookClass 要 Hook 的 [Class]
*/
class YukiHookCreater(private val instance: PackageParam, val hookClass: Class<*>) {
/** @call Base Field */
private var beforeHookCallback: (HookParam.() -> Unit)? = null
/** @call Base Field */
private var afterHookCallback: (HookParam.() -> Unit)? = null
/** @call Base Field */
private var replaceHookCallback: (HookParam.() -> Any?)? = null
/** 是否为替换模式 */
private var isReplaceMode = false
class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Class<*>) {
/** 设置要 Hook 的方法、构造类 */
var grabMember: Member? = null
private var hookMembers = HashMap<String, MemberHookCreater>()
/**
* 在方法执行完成前 Hook
* 不可与 [replaceAny] [replaceVoid] [replaceTo] 同时使用
* @param initiate [HookParam] 方法体
* 注入要 Hook 的方法
* @param initiate 方法体
*/
fun beforeHook(initiate: HookParam.() -> Unit) {
isReplaceMode = false
beforeHookCallback = initiate
}
fun injectMethod(initiate: MemberHookCreater.() -> Unit) =
MemberHookCreater(isConstructor = false).apply(initiate).apply {
hookMembers[toString()] = this
}.create()
/**
* 在方法执行完成后 Hook
* 不可与 [replaceAny] [replaceVoid] [replaceTo] 同时使用
* @param initiate [HookParam] 方法体
* 注入要 Hook 的构造类
* @param initiate 方法体
*/
fun afterHook(initiate: HookParam.() -> Unit) {
isReplaceMode = false
afterHookCallback = initiate
}
/**
* 替换此方法内容 - 给出返回值
* 不可与 [beforeHook] [afterHook] 同时使用
* @param initiate [HookParam] 方法体
*/
fun replaceAny(initiate: HookParam.() -> Any?) {
isReplaceMode = true
replaceHookCallback = initiate
}
/**
* 替换此方法内容 - 没有返回值 (Void)
* 不可与 [beforeHook] [afterHook] 同时使用
* @param initiate [HookParam] 方法体
*/
fun replaceVoid(initiate: HookParam.() -> Unit) {
isReplaceMode = true
replaceHookCallback = initiate
}
/**
* 替换方法返回值
* 不可与 [beforeHook] [afterHook] 同时使用
* @param any 要替换为的返回值对象
*/
fun replaceTo(any: Any?) {
isReplaceMode = true
replaceHookCallback = { any }
}
/**
* 替换方法返回值为 true
* 确保替换方法的返回对象为 [Boolean]
* 不可与 [beforeHook] [afterHook] 同时使用
*/
fun replaceToTrue() {
isReplaceMode = true
replaceHookCallback = { true }
}
/**
* 替换方法返回值为 false
* 确保替换方法的返回对象为 [Boolean]
* 不可与 [beforeHook] [afterHook] 同时使用
*/
fun replaceToFalse() {
isReplaceMode = true
replaceHookCallback = { false }
}
/**
* 拦截此方法
* 不可与 [beforeHook] [afterHook] 同时使用
*/
fun intercept() {
isReplaceMode = true
replaceHookCallback = { null }
}
fun injectConstructor(initiate: MemberHookCreater.() -> Unit) =
MemberHookCreater(isConstructor = true).apply(initiate).apply {
hookMembers[toString()] = this
}.create()
/**
* Hook 执行入口 - 不可在外部调用
@@ -145,25 +76,241 @@ class YukiHookCreater(private val instance: PackageParam, val hookClass: Class<*
*/
@DoNotUseMethod
fun hook() {
if (grabMember == null) error("Target hook method cannot be null")
if (isReplaceMode)
XposedBridge.hookMethod(grabMember, object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any? {
if (param == null) return null
return replaceHookCallback?.invoke(HookParam(param))
}
})
else
XposedBridge.hookMethod(grabMember, object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam?) {
if (param == null) return
beforeHookCallback?.invoke(HookParam(param))
}
if (hookMembers.isEmpty()) error("Hook Members is empty,hook aborted")
hookMembers.forEach { (_, hooker) -> hooker.findAndHook() }
}
override fun afterHookedMethod(param: MethodHookParam?) {
if (param == null) return
afterHookCallback?.invoke(HookParam(param))
}
})
/**
* 智能全局方法、构造类查找类实现方法
*
* 处理需要 Hook 的方法
* @param isConstructor 是否为构造方法
*/
inner class MemberHookCreater(private val isConstructor: Boolean) {
/** @call Base Field */
private var beforeHookCallback: (HookParam.() -> Unit)? = null
/** @call Base Field */
private var afterHookCallback: (HookParam.() -> Unit)? = null
/** @call Base Field */
private var replaceHookCallback: (HookParam.() -> Any?)? = null
/** @call Base Field */
private var onFailureCallback: ((HookParam?, Throwable) -> Unit)? = null
/** 是否为替换模式 */
private var isReplaceMode = false
/** 方法参数 */
private var params: Array<out Class<*>>? = null
/** 方法名 */
var name = ""
/** 方法返回值 */
var returnType: Class<*>? = null
/**
* 方法参数
* @param param 参数数组
*/
fun param(vararg param: Class<*>) {
params = param
}
/**
* 在方法执行完成前 Hook
*
* 不可与 [replaceAny]、[replaceUnit]、[replaceTo] 同时使用
* @param initiate [HookParam] 方法体
*/
fun beforeHook(initiate: HookParam.() -> Unit) {
isReplaceMode = false
beforeHookCallback = initiate
}
/**
* 在方法执行完成后 Hook
*
* 不可与 [replaceAny]、[replaceUnit]、[replaceTo] 同时使用
* @param initiate [HookParam] 方法体
*/
fun afterHook(initiate: HookParam.() -> Unit) {
isReplaceMode = false
afterHookCallback = initiate
}
/**
* 替换此方法内容 - 给出返回值
*
* 不可与 [beforeHook]、[afterHook] 同时使用
* @param initiate [HookParam] 方法体
*/
fun replaceAny(initiate: HookParam.() -> Any?) {
isReplaceMode = true
replaceHookCallback = initiate
}
/**
* 替换此方法内容 - 没有返回值 ([Unit])
*
* 不可与 [beforeHook]、[afterHook] 同时使用
* @param initiate [HookParam] 方法体
*/
fun replaceUnit(initiate: HookParam.() -> Unit) {
isReplaceMode = true
replaceHookCallback = initiate
}
/**
* 替换方法返回值
*
* 不可与 [beforeHook]、[afterHook] 同时使用
* @param any 要替换为的返回值对象
*/
fun replaceTo(any: Any?) {
isReplaceMode = true
replaceHookCallback = { any }
}
/**
* 替换方法返回值为 true
*
* 确保替换方法的返回对象为 [Boolean]
*
* 不可与 [beforeHook]、[afterHook] 同时使用
*/
fun replaceToTrue() {
isReplaceMode = true
replaceHookCallback = { true }
}
/**
* 替换方法返回值为 false
*
* 确保替换方法的返回对象为 [Boolean]
*
* 不可与 [beforeHook]、[afterHook] 同时使用
*/
fun replaceToFalse() {
isReplaceMode = true
replaceHookCallback = { false }
}
/**
* 拦截此方法
*
* 这将会禁止此方法执行并返回 null
*
* 不可与 [beforeHook]、[afterHook] 同时使用
*/
fun intercept() {
isReplaceMode = true
replaceHookCallback = { null }
}
/**
* 得到需要 Hook 的方法
* @return [Member]
* @throws NoSuchMethodError 如果找不到方法
*/
private val hookMember: Member by lazy {
when {
name.isBlank() && !isConstructor -> error("Method name cannot be empty")
isConstructor ->
if (params != null)
ReflectionUtils.findConstructorExact(hookClass, *params!!)
else ReflectionUtils.findConstructorExact(hookClass)
else ->
if (params != null)
ReflectionUtils.findMethodBestMatch(hookClass, returnType, name, *params!!)
else ReflectionUtils.findMethodNoParam(hookClass, returnType, name)
}
}
/**
* Hook 创建入口 - 不可在外部调用
* @return [MemberHookResult]
*/
@DoNotUseMethod
fun create() = MemberHookResult()
/**
* Hook 执行入口 - 不可在外部调用
* @throws IllegalStateException 如果必要参数没有被设置
*/
@DoNotUseMethod
fun findAndHook() = runCatching {
hookMember.also { member ->
if (isReplaceMode)
XposedBridge.hookMethod(member, object : XC_MethodReplacement() {
override fun replaceHookedMethod(baseParam: MethodHookParam?): Any? {
if (baseParam == null) return null
return HookParam(baseParam).let { param ->
try {
replaceHookCallback?.invoke(param)
} catch (e: Throwable) {
onFailureCallback?.invoke(param, e) ?: onHookFailure(e)
null
}
}
}
})
else
XposedBridge.hookMethod(member, object : XC_MethodHook() {
override fun beforeHookedMethod(baseParam: MethodHookParam?) {
if (baseParam == null) return
HookParam(baseParam).also { param ->
runCatching {
beforeHookCallback?.invoke(param)
}.onFailure {
onFailureCallback?.invoke(param, it) ?: onHookFailure(it)
}
}
}
override fun afterHookedMethod(baseParam: MethodHookParam?) {
if (baseParam == null) return
HookParam(baseParam).also { param ->
runCatching {
afterHookCallback?.invoke(param)
}.onFailure {
onFailureCallback?.invoke(param, it) ?: onHookFailure(it)
}
}
}
})
}
}.onFailure {
onFailureCallback?.invoke(null, it) ?: onHookFailure(it)
}
/**
* Hook 失败但未设置 [onFailureCallback] 将默认输出失败信息
* @param throwable 异常信息
*/
private fun onHookFailure(throwable: Throwable) {
Log.e(YukiHookAPI.TAG, "Try to hook $hookClass[$hookMember] got an Exception", throwable)
}
override fun toString() = "$name$returnType$params$isConstructor$hookMember$hookClass#YukiHook"
/**
* 监听 Hook 结果实现类
*
* 可在这里处理失败事件
*/
inner class MemberHookResult {
/**
* 监听 Hook 过程发生错误的回调方法
* @param initiate 回调错误 - ([HookParam] 当前 Hook 实例 or null,[Throwable] 异常)
*/
fun onFailure(initiate: (HookParam?, Throwable) -> Unit) {
onFailureCallback = initiate
}
}
}
}

View File

@@ -0,0 +1,133 @@
/**
* MIT License
*
* Copyright (C) 2022 HighCapable
*
* This file is part of YukiHookAPI.
*
* 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/2/2.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.factory
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Method
/**
* 字符串转换为实体类
* @return [Class]
* @throws NoClassDefFoundError
*/
val String.clazz: Class<*> get() = Class.forName(this)
/**
* 查找方法是否存在
* @param name 名称
* @param clazz params
* @return [Boolean] 是否存在
*/
fun Class<*>.hasMethod(name: String, vararg clazz: Class<*>): Boolean =
try {
getDeclaredMethod(name, *clazz)
true
} catch (_: Throwable) {
false
}
/**
* 查找静态 [Field] 的实例
* @param name 名称
* @return [Any] 实例对象
* @throws NoSuchFieldError
*/
fun Class<*>.findStaticField(name: String): Any? = getDeclaredField(name).apply { isAccessible = true }[null]
/**
* 查找 [Field] 的实例 - 不能是静态
* @param any 对象
* @param name 名称
* @return [Any] 实例对象
* @throws NoSuchFieldError
*/
fun Class<*>.findField(any: Any?, name: String): Any? = getDeclaredField(name).apply { isAccessible = true }[any]
/**
* 设置 [Field] - 不能是静态
* @param any 对象
* @param name 名称
* @param value 值
* @throws NoSuchFieldError
*/
fun Class<*>.modifyField(any: Any?, name: String, value: Any?) {
getDeclaredField(name).apply {
isAccessible = true
set(any, value)
}
}
/**
* 查找目标变量
* @param name 方法名
* @return [Field]
* @throws NoSuchFieldError 如果找不到变量会报错
*/
fun Class<*>.findField(name: String): Field =
getDeclaredField(name).apply { isAccessible = true }
/**
* 得到方法
* @param name 方法名称
* @param clazz params
* @return [Method]
* @throws NoSuchMethodError
*/
fun Class<*>.findMethod(name: String, vararg clazz: Class<*>): Method? =
getDeclaredMethod(name, *clazz).apply { isAccessible = true }
/**
* 得到构造类
* @param parameterTypes params
* @return [Constructor]
* @throws NoSuchMethodError
*/
fun Class<*>.findConstructor(vararg parameterTypes: Class<*>?): Constructor<out Any>? =
getDeclaredConstructor(*parameterTypes).apply { isAccessible = true }
/**
* 执行方法 - 静态
* @param anys 方法参数
* @return [T]
* @throws IllegalStateException 如果 [T] 类型错误
*/
inline fun <reified T> Method.invokeStatic(vararg anys: Any) =
invoke(null, anys) as? T? ?: error("Method ReturnType cannot cast to ${T::class.java}")
/**
* 执行方法 - 非静态
* @param any 目标对象
* @param anys 方法参数
* @return [T]
* @throws IllegalStateException 如果 [T] 类型错误
*/
inline fun <reified T> Method.invokeAny(any: Any?, vararg anys: Any) =
invoke(any, anys) as? T? ?: error("Method ReturnType cannot cast to ${T::class.java}")

View File

@@ -29,12 +29,45 @@
package com.highcapable.yukihookapi.hook.factory
import com.highcapable.yukihookapi.YukiHook
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.proxy.YukiHookInitializeProxy
import com.highcapable.yukihookapi.param.PackageParam
/**
* 在 [YukiHookInitializeProxy] 中装载 [YukiHook]
* 在 [YukiHookInitializeProxy] 中装载 [YukiHookAPI]
* @param moduleName 模块包名 - 不填将无法实现监听模块激活状态
* @param initiate Hook 方法体
*/
fun YukiHookInitializeProxy.encase(initiate: PackageParam.() -> Unit) = YukiHook.encase(initiate)
fun YukiHookInitializeProxy.encase(moduleName: String = "", initiate: PackageParam.() -> Unit) =
YukiHookAPI.encase(moduleName, initiate)
/**
* 判断模块是否在太极、无极中激活
* @return [Boolean] 是否激活
*/
val Context.isTaichiModuleActive: Boolean
get() {
var isModuleActive = false
runCatching {
var result: Bundle? = null
Uri.parse("content://me.weishu.exposed.CP/").also { uri ->
runCatching {
result = contentResolver.call(uri, "active", null, null)
}.onFailure {
// TaiChi is killed, try invoke
runCatching {
startActivity(Intent("me.weishu.exp.ACTION_ACTIVE").apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
}.onFailure { return false }
}
if (result == null)
result = contentResolver.call(Uri.parse("content://me.weishu.exposed.CP/"), "active", null, null)
if (result == null) return false
}
isModuleActive = result?.getBoolean("active", false) == true
}
return isModuleActive
}

View File

@@ -30,19 +30,38 @@
package com.highcapable.yukihookapi.hook.proxy
import androidx.annotation.Keep
import com.highcapable.yukihookapi.YukiHook
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.factory.encase
/**
* YukiHook 的 Xposed 装载 API 调用接口
* 自动调用 [onHook] 完成 Hook 开始操作
*
* Hook 开始时将自动调用 [onHook] 方法
*
* 请在 [onHook] 中调用 [YukiHookAPI.encase] 或直接调用 [encase]
*
* 可写作如下形式:
*
* override fun onHook() = YukiHookAPI.encase(moduleName = "模块包名") {
*
* // Your code here.
*
* }
*
* 还可写作如下形式:
*
* override fun onHook() = encase(moduleName = "模块包名") {
*
* // Your code here.
*
* }
*
* 详情请参考 https://github.com/fankes/YukiHookAPI/wiki
*/
@Keep
interface YukiHookInitializeProxy {
/**
* 作为模块装载调用入口方法 - Xposed API
* 请在此方法中调用 [YukiHook.encase]
*/
/** 模块装载调用入口方法 - Xposed API */
@Keep
fun onHook()
}

View File

@@ -37,6 +37,12 @@ import java.io.Serializable
*/
val AnyType get() = Any::class.java
/**
* 获得 [Unit] 类型
* @return [Class]
*/
val UnitType get() = Unit::class.javaPrimitiveType
/**
* 获得 [Boolean] 类型
* @return [Class]

View File

@@ -0,0 +1,243 @@
/*
* MIT License
*
* Copyright (C) 2022 HighCapable
*
* This file is part of YukiHookAPI.
*
* 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 zpp0196 on 2019/1/24 0024.
* This file is Modified by fankes on 2022/2/2 2240.
*/
package com.highcapable.yukihookapi.hook.utils;
import android.text.TextUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import de.robv.android.xposed.XposedHelpers;
@SuppressWarnings("ALL")
public class ReflectionUtils {
private static final HashMap<String, Field> fieldCache = new HashMap<>();
private static final HashMap<String, Method> methodCache = new HashMap<>();
private static String getParametersString(Class<?>... clazzes) {
StringBuilder sb = new StringBuilder("(");
boolean first = true;
for (Class<?> clazz : clazzes) {
if (first)
first = false;
else
sb.append(",");
if (clazz != null)
sb.append(clazz.getCanonicalName());
else
sb.append("null");
}
sb.append(")");
return sb.toString();
}
@Deprecated
public static <T> T getStaticObjectIfExists(Class<?> clazz, Class<?> fieldType, String fieldName) {
return getObjectIfExists(clazz, fieldType, fieldName, null);
}
public static <T> T getObjectIfExists(Class<?> clazz, Class<?> fieldType, String fieldName, Object obj) {
return getObjectIfExists(clazz, fieldType.getName(), fieldName, obj);
}
public static <T> T getObjectIfExists(Class<?> clazz, String typeName, String fieldName, Object obj) {
try {
Field field = findFieldIfExists(clazz, typeName, fieldName);
return field == null ? null : (T) field.get(obj);
} catch (Exception e) {
return null;
}
}
@Deprecated
public static void setStaticObjectField(Class<?> clazz, Class<?> fieldType, String fieldName, Object value)
throws NoSuchFieldException, IllegalAccessException {
findFieldIfExists(clazz, fieldType, fieldName).set(null, value);
}
@Deprecated
public static void setObjectField(Object obj, Class<?> fieldType, String fieldName, Object value)
throws NoSuchFieldException, IllegalAccessException {
if (obj != null) {
Field field = findFieldIfExists(obj.getClass(), fieldType, fieldName);
if (field != null) {
field.set(obj, value);
}
}
}
public static Field findFieldIfExists(Class<?> clazz, Class<?> fieldType, String fieldName)
throws NoSuchFieldException {
return findFieldIfExists(clazz, fieldType.getName(), fieldName);
}
public static boolean isCallingFrom(String className) {
StackTraceElement[] stackTraceElements = Thread.currentThread()
.getStackTrace();
for (StackTraceElement element : stackTraceElements) {
if (element.getClassName()
.contains(className)) {
return true;
}
}
return false;
}
public static boolean isCallingFromEither(String... classname) {
StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stackTraceElements) {
for (String name : classname) {
if (element.toString().contains(name)) {
return true;
}
}
}
return false;
}
public static Field findField(Class<?> clazz, String typeName, String fieldName) {
try {
return findFieldIfExists(clazz, typeName, fieldName);
} catch (NoSuchFieldException e) {
throw new IllegalArgumentException("Can't find field <" + clazz.getName() + "#" + typeName + "#" + fieldName + ">");
}
}
public static Field findFieldIfExists(Class<?> clazz, String typeName, String fieldName)
throws NoSuchFieldException {
String fullFieldName = clazz.getName() + "#" + fieldName + "#" + typeName;
if (!fieldCache.containsKey(fullFieldName)) {
if (clazz != null && !TextUtils.isEmpty(typeName) && !TextUtils.isEmpty(fieldName)) {
Class<?> clz = clazz;
do {
for (Field field : clz.getDeclaredFields()) {
if (field.getType()
.getName()
.equals(typeName) && field.getName()
.equals(fieldName)) {
field.setAccessible(true);
fieldCache.put(fullFieldName, field);
return field;
}
}
} while ((clz = clz.getSuperclass()) != null);
throw new NoSuchFieldException(clazz.getName() + "#" + typeName + " " + fieldName);
}
return null;
} else {
Field field = fieldCache.get(fullFieldName);
if (field == null)
throw new NoSuchFieldError(fullFieldName);
return field;
}
}
/**
* 适用于查找混淆类型的 abcd 方法 - 无 param
*
* @param clazz 方法所在类
* @param returnType 返回类型
* @param methodName 方法名
*/
public static Method findMethodNoParam(Class<?> clazz, Class<?> returnType, String methodName) {
String fullMethodName = clazz.getName() + '#' + methodName + returnType + "#exact#NoParam";
if (!methodCache.containsKey(fullMethodName)) {
Method method = findMethodIfExists(clazz, returnType, methodName);
methodCache.put(fullMethodName, method);
return method;
} else {
return methodCache.get(fullMethodName);
}
}
/**
* 不区分 param 整个类搜索 - 适用于混淆方法 abcd
*
* @param clazz 方法所在类
* @param returnType 返回类型
* @param methodName 方法名
* @param parameterTypes 方法参数类型数组
*/
public static Method findMethodBestMatch(Class<?> clazz, Class<?> returnType, String methodName, Class<?>... parameterTypes) {
String fullMethodName = clazz.getName() + '#' + methodName + returnType + getParametersString(parameterTypes) + "#exact";
if (!methodCache.containsKey(fullMethodName)) {
Method method = findMethodIfExists(clazz, returnType, methodName, parameterTypes);
methodCache.put(fullMethodName, method);
return method;
} else {
return methodCache.get(fullMethodName);
}
}
/**
* 查找构造方法
*
* @param clazz 构造类所在类
* @param parameterTypes 构造类方法参数类型数组
*/
public static Constructor<?> findConstructorExact(Class<?> clazz, Class<?>... parameterTypes) {
String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#exact";
try {
Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
constructor.setAccessible(true);
return constructor;
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError("Can't find constructor " + fullConstructorName);
}
}
private static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#zYukiHook#exact";
try {
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
return method;
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError("Can't find method " + fullMethodName);
}
}
private static Method findMethodIfExists(Class<?> clazz, Class<?> returnType, String methodName, Class<?>... parameterTypes) {
long l = System.currentTimeMillis();
if (clazz != null && !TextUtils.isEmpty(methodName)) {
Class<?> clz = clazz;
if (returnType == null) return findMethodExact(clazz, methodName, parameterTypes);
do {
Method[] methods = XposedHelpers.findMethodsByExactParameters(clazz, returnType, parameterTypes);
for (Method method : methods) if (method.getName().equals(methodName)) return method;
} while ((clz = clz.getSuperclass()) != null);
}
throw new IllegalArgumentException("Method not found <name:" + methodName + " returnType:" + returnType.getName() + " paramType:" + getParametersString(parameterTypes) + "> in Class <" + clazz.getName() + ">");
}
}

View File

@@ -31,35 +31,54 @@ package com.highcapable.yukihookapi.hook.xposed
import android.util.Log
import androidx.annotation.Keep
import com.highcapable.yukihookapi.YukiHook
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.proxy.YukiHookInitializeProxy
import com.highcapable.yukihookapi.hook.type.BooleanType
import com.highcapable.yukihookapi.param.PackageParam
import de.robv.android.xposed.IXposedHookLoadPackage
import de.robv.android.xposed.callbacks.XC_LoadPackage
/**
* 接管 Xposed 的 [IXposedHookLoadPackage] 入口
* 你可以使用 [YukiHook.encase] 来监听模块开始装载
*
* 你可以使用 [YukiHookAPI.encase] 或在 [YukiHookInitializeProxy] 中监听模块开始装载
*
* 需要标识 Hook 入口的类 - 请声明注释 [InjectYukiHookWithXposed]
*/
@Keep
class YukiHookLoadPackage : IXposedHookLoadPackage {
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
if (lpparam == null) return
try {
runCatching {
/** 执行入口方法 */
Class.forName(hookEntryClassName()).apply {
getDeclaredMethod("onHook").apply { isAccessible = true }
.invoke(getDeclaredConstructor().apply { isAccessible = true }.newInstance())
}
} catch (e: Throwable) {
Log.e("YukiHookAPI", "Try to load ${hookEntryClassName()} Failed", e)
}.onFailure {
Log.e(YukiHookAPI.TAG, "Try to load ${hookEntryClassName()} Failed", it)
}
/** 装载 APP Hook 实体类 */
PackageParam(lpparam).apply {
/** Hook 模块激活状态 */
loadApp(name = YukiHookAPI.modulePackageName) {
YukiHookModuleStatus::class.java.hook {
injectMethod {
name = "isActive"
returnType = BooleanType
replaceToTrue()
}.onFailure { _, t -> Log.e(YukiHookAPI.TAG, "Try to Hook ModuleStatus Failed", t) }
}
}
/** 设置装载回调 */
YukiHookAPI.packageParamCallback?.invoke(this)
}
/** 设置装载回调 */
YukiHook.packageParamCallback?.invoke(PackageParam(lpparam))
}
/**
* 获得目标装载类名 - AOP
* 获得目标装载类名 - 通过 APT 自动设置 TODO 待实现
* @return [String] 目标装载类名
*/
@Keep

View File

@@ -0,0 +1,52 @@
/**
* MIT License
*
* Copyright (C) 2022 HighCapable
*
* This file is part of YukiHookAPI.
*
* 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/2/3.
*/
package com.highcapable.yukihookapi.hook.xposed
import android.util.Log
import androidx.annotation.Keep
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus.isActive
/**
* 这是一个 Xposed 模块 Hook 状态类
*
* 我们需要监听自己的模块是否被激活 - 可直接调用这个类的 [isActive] 方法
*/
@Keep
object YukiHookModuleStatus {
/**
* 此方法经过 Hook 后返回 true 即模块已激活
* @return [Boolean]
*/
@Keep
fun isActive(): Boolean {
Log.d(YukiHookAPI.TAG, "hook this method got active status")
return false
}
}

View File

@@ -35,4 +35,8 @@ import android.content.pm.ApplicationInfo
* @param appInfo APP [ApplicationInfo]
* @param packageName 包名
*/
class CustomParam(var appClassLoader: ClassLoader, var appInfo: ApplicationInfo, var packageName: String)
class CustomParam(
var appClassLoader: ClassLoader,
var appInfo: ApplicationInfo,
var packageName: String
)

View File

@@ -35,86 +35,105 @@ import java.lang.reflect.Method
/**
* Hook 方法、构造类的目标对象实现类
* @param instance 对接 Xposed API 的 [XC_MethodHook.MethodHookParam]
* @param baseParam 对接 Xposed API 的 [XC_MethodHook.MethodHookParam]
*/
class HookParam(private val instance: XC_MethodHook.MethodHookParam) {
class HookParam(private val baseParam: XC_MethodHook.MethodHookParam) {
/**
* 获取 Hook 方法的参数对象数组
* 获取当前 [method] or [constructor] 的参数对象数组
* @return [Array]
*/
val args get() = instance.args ?: arrayOf(0)
val args get() = baseParam.args ?: arrayOf(0)
/**
* 获取 Hook 方法的参数对象数组第一位
* 获取当前 [method] or [constructor] 的参数对象数组第一位
* @return [Array]
* @throws IllegalStateException 如果数组为空或对象为空
*/
val firstArgs get() = args[0] ?: error("HookParam args[0] with a non-null object")
/**
* 获取 Hook 方法的参数对象数组最后一位
* 获取当前 [method] or [constructor] 的参数对象数组最后一位
* @return [Array]
* @throws IllegalStateException 如果数组为空或对象为空
*/
val lastArgs get() = args[args.lastIndex] ?: error("HookParam args[lastIndex] with a non-null object")
/**
* 获取 Hook 实例的 Class
* @return [Class]
*/
val thisClass get() = instance.thisObject.javaClass
/**
* 获取 Hook 实例的对象
* 获取当前 Hook 实例的对象
* @return [Any]
* @throws IllegalStateException 如果对象为空
*/
val thisAny get() = instance.thisObject ?: error("HookParam must with a non-null object")
val instance get() = baseParam.thisObject ?: error("HookParam must with a non-null object")
/**
* 获取 Hook 当前普通方法
* 获取当前 Hook 实例的类对象
* @return [Class]
*/
val instanceClass get() = instance.javaClass
/**
* 获取当前 Hook 的方法
* @return [Method]
* @throws IllegalStateException 如果方法为空或方法类型不是 [Method]
*/
val method get() = instance.method as? Method? ?: error("Current hook method type is wrong or null")
val method get() = baseParam.method as? Method? ?: error("Current hook method type is wrong or null")
/**
* 获取 Hook 当前构造方法
* 获取当前 Hook 构造方法
* @return [Constructor]
* @throws IllegalStateException 如果方法为空或方法类型不是 [Constructor]
*/
val constructor get() = instance.method as? Constructor<*>? ?: error("Current hook constructor type is wrong or null")
val constructor get() = baseParam.method as? Constructor<*>? ?: error("Current hook constructor type is wrong or null")
/**
* 获取、设置 Hook 方法的返回值
* 获取、设置当前 [method] or [constructor] 的返回值
* @return [Any] or null
*/
var result: Any?
get() = instance.result
get() = baseParam.result
set(value) {
instance.result = value
baseParam.result = value
}
/**
* 获取 Hook 实例的对象 [T]
* @return [Any]
* 获取当前 Hook 实例的对象 [T]
* @return [T]
* @throws IllegalStateException 如果对象为空或对象类型不是 [T]
*/
inline fun <reified T> thisAny() = thisAny as? T? ?: error("HookParam object cannot cast to ${T::class.java.name}")
inline fun <reified T> instance() = instance as? T? ?: error("HookParam object cannot cast to ${T::class.java.name}")
/**
* 获取 Hook 方法的参数实例化对象类
* 获取当前 [method] or [constructor] 的参数实例化对象类
* @param index 参数对象数组下标 - 默认是 0
* @return [Array]
*/
fun args(index: Int = 0) = ArgsModifyer(index)
/**
* 拦截整个方法体
* 设置 [result] 返回值为 true
*
* 请确保返回值类型为 [Boolean]
*/
fun resultTrue() {
result = true
}
/**
* 设置 [result] 返回值为 false
*
* 请确保返回值类型为 [Boolean]
*/
fun resultFalse() {
result = false
}
/**
* 设置返回值为 null
*
* 此方法将强制设置方法体的 [result] 为 null
*/
fun intercept() {
fun resultNull() {
result = null
}
@@ -132,24 +151,27 @@ class HookParam(private val instance: XC_MethodHook.MethodHookParam) {
fun <T> set(any: T?) {
if (args.isEmpty()) error("HookParam method args is empty,mabe not has args")
if (index > args.lastIndex) error("HookParam method args index out of bounds,max is ${args.lastIndex}")
instance.args[index] = any
baseParam.args[index] = any
}
/**
* 设置方法参数的实例对象为 null
*
* 此方法可以将任何被 Hook 的目标对象设置为空
*/
fun setNull() = set(null)
/**
* 设置方法参数的实例对象为 true
* 请确保目标对象的类型是 [Boolean] 不然会出错
*
* 请确保目标对象的类型是 [Boolean] 不然会发生意想不到的问题
*/
fun setTrue() = set(true)
/**
* 设置方法参数的实例对象为 false
* 请确保目标对象的类型是 [Boolean] 不然会出错
*
* 请确保目标对象的类型是 [Boolean] 不然会发生意想不到的问题
*/
fun setFalse() = set(false)
}

View File

@@ -32,53 +32,57 @@ package com.highcapable.yukihookapi.param
import android.content.pm.ApplicationInfo
import com.highcapable.yukihookapi.hook.core.YukiHookCreater
import de.robv.android.xposed.callbacks.XC_LoadPackage
import java.lang.reflect.Constructor
import java.lang.reflect.Method
/**
* 装载 Hook 的目标 APP 入口对象实现类
* 如果是侵入式 Hook 自身 APP 可将参数 [instance] 置空获得当前类的 [ClassLoader]
* @param instance 对接 Xposed API 的 [XC_LoadPackage.LoadPackageParam] - 默认空
* @param customInstance 自定义装载类 - 默认空
*
* 如果你想将 YukiHook 作为 Hook API 使用 - 你可自定义 [customParam]
*
* ⚠️ 特别注意如果 [baseParam] 和 [customParam] 都为空将发生问题
* @param baseParam 对接 Xposed API 的 [XC_LoadPackage.LoadPackageParam] - 默认空
* @param customParam 自定义装载类 - 默认空
*/
class PackageParam(
private val instance: XC_LoadPackage.LoadPackageParam? = null,
private val customInstance: CustomParam? = null
private val baseParam: XC_LoadPackage.LoadPackageParam? = null,
private val customParam: CustomParam? = null
) {
/**
* 获取当前 APP 的 [ClassLoader]
* @return [ClassLoader]
* @throws IllegalStateException 如果 ClassLoader 是空的
* @throws IllegalStateException 如果 [ClassLoader] 是空的
*/
val appClassLoader
get() = instance?.classLoader ?: customInstance?.appClassLoader ?: javaClass.classLoader
get() = baseParam?.classLoader ?: customParam?.appClassLoader ?: javaClass.classLoader
?: error("PackageParam ClassLoader is null")
/**
* 获取当前 APP 的 [ApplicationInfo]
* @return [ApplicationInfo]
*/
val appInfo get() = instance?.appInfo ?: customInstance?.appInfo ?: ApplicationInfo()
val appInfo get() = baseParam?.appInfo ?: customParam?.appInfo ?: ApplicationInfo()
/**
* 获取当前 APP 的进程名称
* 默认的进程名称是 [packageName]
*
* 默认的进程名称是 [packageName] 如果自定义了 [customParam] 将返回包名
* @return [String]
*/
val processName get() = instance?.processName ?: ""
val processName get() = baseParam?.processName ?: customParam?.packageName ?: ""
/**
* 获取当前 APP 的包名
* @return [String]
*/
val packageName get() = instance?.packageName ?: customInstance?.packageName ?: ""
val packageName get() = baseParam?.packageName ?: customParam?.packageName ?: ""
/**
* 获取当前 APP 是否为第一个 Application
*
* 若自定义了 [customParam] 将永远返回 true
* @return [Boolean]
*/
val isFirstApplication get() = instance?.isFirstApplication ?: true
val isFirstApplication get() = baseParam?.isFirstApplication ?: true
/**
* 装载并 Hook 指定包名的 APP
@@ -90,36 +94,26 @@ class PackageParam(
}
/**
* 通过字符串装载 [Class]
* 将目标 [Class] 绑定到 [appClassLoader]
*
* ⚠️ 请注意未绑定到 [appClassLoader] 的 [Class] 不能被装载 - 调用 [hook] 方法会自动绑定
* @return [Class]
* @throws NoClassDefFoundError 如果找不到类会报错
*/
fun Class<*>.bind(): Class<*> = appClassLoader.loadClass(name)
/**
* 通过 [appClassLoader] 查询并装载 [Class]
* @param name 类名
* @return [Class]
* @throws NoClassDefFoundError 如果找不到类会报错
*/
fun loadClass(name: String): Class<*> = appClassLoader.loadClass(name)
/**
* 查找目标方法
* @param name 方法名
* @param params 方法参数 - 没有可空
* @return [Method]
* @throws NoSuchMethodError 如果找不到方法会报错
*/
fun Class<*>.loadMethod(name: String, vararg params: Class<*>): Method =
getDeclaredMethod(name, *params).apply { isAccessible = true }
/**
* 查找目标构造类
* @param params 方法参数 - 没有可空
* @return [Constructor]
* @throws NoSuchMethodError 如果找不到方法会报错
*/
fun Class<*>.loadConstructor(vararg params: Class<*>): Constructor<*> =
getDeclaredConstructor(*params).apply { isAccessible = true }
fun findClass(name: String): Class<*> = appClassLoader.loadClass(name)
/**
* Hook 方法、构造类
* @param initiate 方法体
*/
fun Class<*>.hook(initiate: YukiHookCreater.() -> Unit) =
YukiHookCreater(instance = this@PackageParam, hookClass = this).apply(initiate).hook()
YukiHookCreater(packageParam = this@PackageParam, hookClass = bind()).apply(initiate).hook()
}