mirror of
https://github.com/HighCapable/YukiHookAPI.git
synced 2025-09-05 02:05:31 +08:00
...
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
|
||||
# Get Startted
|
||||
|
||||
- 暂定 1.0 版本,还有很多问题没有解决,暂时不要使用,敬请期待...
|
||||
- 暂定 1.0 版本,可做学习参考但暂时不要 fork 也不要使用,还差 Wiki 没有撰写 demo 没有写完和 API 未提交至 maven 敬请期待...
|
||||
|
||||
# License
|
||||
|
||||
|
@@ -30,6 +30,12 @@ android {
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
kotlin {
|
||||
sourceSets.main {
|
||||
kotlin.srcDir("build/generated/ksp/debug/kotlin")
|
||||
kotlin.srcDir("build/generated/ksp/release/kotlin")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
@@ -1 +1 @@
|
||||
com.highcapable.yukihookapi.demo.InjectTest_YukiHookXposedInit
|
||||
com.highcapable.yukihookapi.demo.hook.inject.MainInjecter_YukiHookXposedInit
|
@@ -30,6 +30,7 @@
|
||||
package com.highcapable.yukihookapi.demo
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
|
||||
@@ -48,7 +49,7 @@ class MainActivity : AppCompatActivity() {
|
||||
.setPositiveButton("下一个") { _, _ ->
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Hook 方法参数测试")
|
||||
.setMessage(test("这是没有更改的文字") + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
|
||||
.setMessage(test("这是没有更改的文字") + "\n${a(content = "这是原文")}\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
|
||||
.setPositiveButton("下一个") { _, _ ->
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Hook 构造方法测试(stub)")
|
||||
@@ -57,13 +58,19 @@ class MainActivity : AppCompatActivity() {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Hook 构造方法测试(名称)")
|
||||
.setMessage(InjectTestName("文字没更改").getString() + "\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
|
||||
.setPositiveButton("完成", null)
|
||||
.setPositiveButton("完成") { _, _ -> toast() }
|
||||
.show()
|
||||
}.show()
|
||||
}.show()
|
||||
}.show()
|
||||
}
|
||||
|
||||
// for test
|
||||
private fun toast() = Toast.makeText(this, "我弹出来了,没有 Hook", Toast.LENGTH_SHORT).show()
|
||||
|
||||
// for test
|
||||
private fun a(content1: String = "前缀", content: String) = "$content1${content}_后面加了一段文字"
|
||||
|
||||
// for test
|
||||
private fun test() = "正常显示的一行文字"
|
||||
|
||||
|
@@ -35,6 +35,7 @@ import com.highcapable.yukihookapi.demo.MainActivity
|
||||
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
|
||||
import com.highcapable.yukihookapi.hook.type.BundleClass
|
||||
import com.highcapable.yukihookapi.hook.type.StringType
|
||||
import com.highcapable.yukihookapi.hook.type.UnitType
|
||||
|
||||
// for test
|
||||
class MainHooker : YukiBaseHooker() {
|
||||
@@ -54,6 +55,24 @@ class MainHooker : YukiBaseHooker() {
|
||||
}.set(instance, "这段文字被修改成功了")
|
||||
}
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "toast"
|
||||
returnType = UnitType
|
||||
}
|
||||
intercept()
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "a"
|
||||
param(StringType, StringType)
|
||||
returnType = StringType
|
||||
}
|
||||
beforeHook {
|
||||
args(index = 0).set("改了前面的")
|
||||
args(index = 1).set("改了后面的")
|
||||
}
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "test"
|
||||
|
@@ -31,28 +31,30 @@ package com.highcapable.yukihookapi.demo.hook.inject
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.widget.Toast
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
|
||||
import com.highcapable.yukihookapi.demo.BuildConfig
|
||||
import com.highcapable.yukihookapi.demo.InjectTest
|
||||
import com.highcapable.yukihookapi.demo.MainActivity
|
||||
import com.highcapable.yukihookapi.demo.hook.MainHooker
|
||||
import com.highcapable.yukihookapi.demo.hook.SecondHooker
|
||||
import com.highcapable.yukihookapi.hook.factory.encase
|
||||
import com.highcapable.yukihookapi.hook.factory.findMethod
|
||||
import com.highcapable.yukihookapi.hook.proxy.YukiHookXposedInitProxy
|
||||
import com.highcapable.yukihookapi.hook.type.ActivityClass
|
||||
import com.highcapable.yukihookapi.hook.type.BundleClass
|
||||
import com.highcapable.yukihookapi.hook.type.StringType
|
||||
import com.highcapable.yukihookapi.hook.type.UnitType
|
||||
|
||||
// for test
|
||||
@InjectYukiHookWithXposed
|
||||
class MainInjecter : YukiHookXposedInitProxy {
|
||||
|
||||
override fun onHook() {
|
||||
// 设置模式
|
||||
YukiHookAPI.isDebug = true
|
||||
// 方案 1
|
||||
encase(MainHooker(), SecondHooker())
|
||||
// encase(MainHooker(), SecondHooker())
|
||||
// 方案 2
|
||||
encase(BuildConfig.APPLICATION_ID) {
|
||||
encase {
|
||||
loadApp(name = BuildConfig.APPLICATION_ID) {
|
||||
MainActivity::class.java.hook {
|
||||
injectMember {
|
||||
@@ -67,6 +69,24 @@ class MainInjecter : YukiHookXposedInitProxy {
|
||||
}.set(instance, "这段文字被修改成功了")
|
||||
}
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "toast"
|
||||
returnType = UnitType
|
||||
}
|
||||
intercept()
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "a"
|
||||
param(StringType, StringType)
|
||||
returnType = StringType
|
||||
}
|
||||
beforeHook {
|
||||
args(index = 0).set("改了前面的")
|
||||
args(index = 1).set("改了后面的")
|
||||
}
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "test"
|
||||
|
@@ -52,6 +52,17 @@ class YukiHookXposedProcessor : SymbolProcessorProvider {
|
||||
/** 插入 Xposed 尾部的名称 */
|
||||
private val xposedClassShortName = "_YukiHookXposedInit"
|
||||
|
||||
/**
|
||||
* 获取父类名称 - 只取最后一个
|
||||
* @return [String]
|
||||
*/
|
||||
private val KSClassDeclaration.superName
|
||||
get() = try {
|
||||
superTypes.last().element.toString()
|
||||
} catch (_: Exception) {
|
||||
""
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个环境方法体方便调用
|
||||
* @param env 方法体
|
||||
@@ -72,14 +83,16 @@ class YukiHookXposedProcessor : SymbolProcessorProvider {
|
||||
resolver.getSymbolsWithAnnotation(InjectYukiHookWithXposed::class.java.name)
|
||||
.asSequence()
|
||||
.filterIsInstance<KSClassDeclaration>().forEach {
|
||||
if (injectOnce) {
|
||||
injectAssets(
|
||||
codePath = (it.location as? FileLocation?)?.filePath ?: "",
|
||||
packageName = it.packageName.asString(),
|
||||
className = it.simpleName.asString()
|
||||
)
|
||||
injectClass(it.packageName.asString(), it.simpleName.asString())
|
||||
} else logger.error(message = "@InjectYukiHookWithXposed only can be use in once times")
|
||||
if (injectOnce)
|
||||
if (it.superName == "YukiHookXposedInitProxy") {
|
||||
injectAssets(
|
||||
codePath = (it.location as? FileLocation?)?.filePath ?: "",
|
||||
packageName = it.packageName.asString(),
|
||||
className = it.simpleName.asString()
|
||||
)
|
||||
injectClass(it.packageName.asString(), it.simpleName.asString())
|
||||
} else logger.error(message = "HookEntryClass \"${it.simpleName.asString()}\" must be implements YukiHookXposedInitProxy")
|
||||
else logger.error(message = "@InjectYukiHookWithXposed only can be use in once times")
|
||||
/** 仅处理第一个标记的类 - 再次处理将拦截并报错 */
|
||||
injectOnce = false
|
||||
}
|
||||
|
@@ -52,6 +52,15 @@ object YukiHookAPI {
|
||||
/** 全局标识 */
|
||||
const val TAG = "YukiHookAPI"
|
||||
|
||||
/**
|
||||
* 是否开启调试模式 - 默认启用
|
||||
*
|
||||
* 启用后将交由日志输出管理器打印详细 Hook 日志到控制台
|
||||
*
|
||||
* 请过滤 [TAG] (YukiHookAPI) 即可找到每条日志
|
||||
*/
|
||||
var isDebug = true
|
||||
|
||||
/**
|
||||
* Xposed Hook API 绑定的模块包名 - 未写将自动生成
|
||||
* - 你不应该设置此变量的名称 - 请使用 [encase] 装载模块包名
|
||||
|
@@ -29,13 +29,16 @@
|
||||
|
||||
package com.highcapable.yukihookapi.hook.core
|
||||
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import com.highcapable.yukihookapi.annotation.DoNotUseMethod
|
||||
import com.highcapable.yukihookapi.hook.core.finder.ConstructorFinder
|
||||
import com.highcapable.yukihookapi.hook.core.finder.FieldFinder
|
||||
import com.highcapable.yukihookapi.hook.core.finder.MethodFinder
|
||||
import com.highcapable.yukihookapi.hook.log.loggerE
|
||||
import com.highcapable.yukihookapi.hook.log.loggerI
|
||||
import com.highcapable.yukihookapi.hook.param.HookParam
|
||||
import com.highcapable.yukihookapi.hook.param.PackageParam
|
||||
import com.highcapable.yukihookapi.hook.utils.runBlocking
|
||||
import de.robv.android.xposed.XC_MethodHook
|
||||
import de.robv.android.xposed.XC_MethodReplacement
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
@@ -120,7 +123,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
|
||||
* 你只能使用一次 [method] 或 [constructor] 方法 - 否则结果会被替换
|
||||
* @param initiate 方法体
|
||||
*/
|
||||
fun method(initiate: MethodFinder.() -> Unit) {
|
||||
fun method(initiate: MethodFinder.() -> Unit) = runBlocking {
|
||||
runCatching {
|
||||
member = MethodFinder(hookClass).apply(initiate).find()
|
||||
}.onFailure {
|
||||
@@ -129,7 +132,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
|
||||
onAllFailureCallback?.invoke(it)
|
||||
if (onNoSuchMemberCallback == null && onAllFailureCallback == null) onHookFailureMsg(it)
|
||||
}
|
||||
}
|
||||
}.result { onHookLogMsg(msg = "Find Method [$member] takes ${it}ms") }
|
||||
|
||||
/**
|
||||
* 查找需要 Hook 的构造类
|
||||
@@ -137,7 +140,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
|
||||
* 你只能使用一次 [method] 或 [constructor] 方法 - 否则结果会被替换
|
||||
* @param initiate 方法体
|
||||
*/
|
||||
fun constructor(initiate: ConstructorFinder.() -> Unit) {
|
||||
fun constructor(initiate: ConstructorFinder.() -> Unit) = runBlocking {
|
||||
runCatching {
|
||||
member = ConstructorFinder(hookClass).apply(initiate).find()
|
||||
}.onFailure {
|
||||
@@ -146,7 +149,7 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
|
||||
onAllFailureCallback?.invoke(it)
|
||||
if (onNoSuchMemberCallback == null && onAllFailureCallback == null) onHookFailureMsg(it)
|
||||
}
|
||||
}
|
||||
}.result { onHookLogMsg(msg = "Find Constructor [$member] takes ${it}ms") }
|
||||
|
||||
/**
|
||||
* 查找 [Field]
|
||||
@@ -155,7 +158,11 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
|
||||
*/
|
||||
fun HookParam.field(initiate: FieldFinder.() -> Unit) =
|
||||
try {
|
||||
FieldFinder(hookClass).apply(initiate).find()
|
||||
var result: FieldFinder.Result? = null
|
||||
runBlocking {
|
||||
result = FieldFinder(hookClass).apply(initiate).find()
|
||||
}.result { onHookLogMsg(msg = "Find Field [${result?.give()}] takes ${it}ms") }
|
||||
result!!
|
||||
} catch (e: Throwable) {
|
||||
isStopHookMode = true
|
||||
onNoSuchMemberCallback?.invoke(e)
|
||||
@@ -277,6 +284,8 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
|
||||
if (baseParam == null) return null
|
||||
return HookParam(baseParam).let { param ->
|
||||
try {
|
||||
if (replaceHookCallback != null)
|
||||
onHookLogMsg(msg = "Replace Hook Member [${member}] done")
|
||||
replaceHookCallback?.invoke(param)
|
||||
} catch (e: Throwable) {
|
||||
onConductFailureCallback?.invoke(param, e)
|
||||
@@ -295,6 +304,8 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
|
||||
HookParam(baseParam).also { param ->
|
||||
runCatching {
|
||||
beforeHookCallback?.invoke(param)
|
||||
if (beforeHookCallback != null)
|
||||
onHookLogMsg(msg = "Before Hook Member [${member}] done")
|
||||
}.onFailure {
|
||||
onConductFailureCallback?.invoke(param, it)
|
||||
onAllFailureCallback?.invoke(it)
|
||||
@@ -309,6 +320,8 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
|
||||
HookParam(baseParam).also { param ->
|
||||
runCatching {
|
||||
afterHookCallback?.invoke(param)
|
||||
if (afterHookCallback != null)
|
||||
onHookLogMsg(msg = "After Hook Member [${member}] done")
|
||||
}.onFailure {
|
||||
onConductFailureCallback?.invoke(param, it)
|
||||
onAllFailureCallback?.invoke(it)
|
||||
@@ -333,6 +346,14 @@ class YukiHookCreater(private val packageParam: PackageParam, val hookClass: Cla
|
||||
private fun onHookFailureMsg(throwable: Throwable) =
|
||||
loggerE(msg = "Try to hook $hookClass[$member] got an Exception", e = throwable)
|
||||
|
||||
/**
|
||||
* Hook 过程中开启了 [YukiHookAPI.isDebug] 输出调试信息
|
||||
* @param msg 调试日志内容
|
||||
*/
|
||||
private fun onHookLogMsg(msg: String) {
|
||||
if (YukiHookAPI.isDebug) loggerI(msg = msg)
|
||||
}
|
||||
|
||||
override fun toString() = "$member#YukiHook"
|
||||
|
||||
/**
|
||||
|
@@ -171,7 +171,7 @@ public class ReflectionUtils {
|
||||
* @param methodName 方法名
|
||||
*/
|
||||
public static Method findMethodNoParam(Class<?> clazz, Class<?> returnType, String methodName) {
|
||||
String fullMethodName = clazz.getName() + '#' + methodName + returnType + "#exact#NoParam";
|
||||
String fullMethodName = "name:[" + methodName + "] in Class [" + clazz.getName() + "] by YukiHook#finder";
|
||||
if (!methodCache.containsKey(fullMethodName)) {
|
||||
Method method = findMethodIfExists(clazz, returnType, methodName);
|
||||
methodCache.put(fullMethodName, method);
|
||||
@@ -190,7 +190,7 @@ public class ReflectionUtils {
|
||||
* @param parameterTypes 方法参数类型数组
|
||||
*/
|
||||
public static Method findMethodBestMatch(Class<?> clazz, Class<?> returnType, String methodName, Class<?>... parameterTypes) {
|
||||
String fullMethodName = clazz.getName() + '#' + methodName + returnType + getParametersString(parameterTypes) + "#exact";
|
||||
String fullMethodName = "name:[" + methodName + "] paramType:[" + getParametersString(parameterTypes) + "] in Class [" + clazz.getName() + "] by YukiHook#finder";
|
||||
if (!methodCache.containsKey(fullMethodName)) {
|
||||
Method method = findMethodIfExists(clazz, returnType, methodName, parameterTypes);
|
||||
methodCache.put(fullMethodName, method);
|
||||
@@ -207,24 +207,24 @@ public class ReflectionUtils {
|
||||
* @param parameterTypes 构造类方法参数类型数组
|
||||
*/
|
||||
public static Constructor<?> findConstructorExact(Class<?> clazz, Class<?>... parameterTypes) {
|
||||
String fullConstructorName = clazz.getName() + getParametersString(parameterTypes) + "#exact";
|
||||
String fullConstructorName = "paramType:[" + getParametersString(parameterTypes) + "in Class [" + clazz.getName() + "] by YukiHook#finder";
|
||||
try {
|
||||
Constructor<?> constructor = clazz.getDeclaredConstructor(parameterTypes);
|
||||
constructor.setAccessible(true);
|
||||
return constructor;
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new NoSuchMethodError("Can't find constructor " + fullConstructorName);
|
||||
throw new NoSuchMethodError("Can't find this constructor --> " + fullConstructorName);
|
||||
}
|
||||
}
|
||||
|
||||
private static Method findMethodExact(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
||||
String fullMethodName = clazz.getName() + '#' + methodName + getParametersString(parameterTypes) + "#zYukiHook#exact";
|
||||
String fullMethodName = "name:[" + methodName + "] paramType:[" + getParametersString(parameterTypes) + "] in Class [" + clazz.getName() + "] by YukiHook#finder";
|
||||
try {
|
||||
Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new NoSuchMethodError("Can't find method " + fullMethodName);
|
||||
throw new NoSuchMethodError("Can't find this method --> " + fullMethodName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,6 +238,6 @@ public class ReflectionUtils {
|
||||
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() + ">");
|
||||
throw new IllegalArgumentException("Can't find this method --> name:[" + methodName + "] returnType:[" + returnType.getName() + "] paramType:[" + getParametersString(parameterTypes) + "] in Class [" + clazz.getName() + "] by YukiHook#finder");
|
||||
}
|
||||
}
|
||||
|
@@ -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/5.
|
||||
*/
|
||||
package com.highcapable.yukihookapi.hook.utils
|
||||
|
||||
/**
|
||||
* 计算方法执行耗时
|
||||
* @param block 方法块
|
||||
* @return [RunBlockResult]
|
||||
*/
|
||||
inline fun <R> runBlocking(block: () -> R): RunBlockResult {
|
||||
val start = System.currentTimeMillis()
|
||||
block()
|
||||
return RunBlockResult(after = System.currentTimeMillis() - start)
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造耗时计算结果类
|
||||
* @param after 耗时
|
||||
*/
|
||||
class RunBlockResult(private val after: Long) {
|
||||
|
||||
/**
|
||||
* 获取耗时计算结果
|
||||
* @param initiate 回调结果 - ([Long] 耗时)
|
||||
*/
|
||||
fun result(initiate: (Long) -> Unit) = initiate(after)
|
||||
}
|
Reference in New Issue
Block a user