mirror of
https://github.com/HighCapable/YukiHookAPI.git
synced 2025-09-06 10:45:47 +08:00
style: lots of changes
- move demo-app, demo-module to samples - rename yukihookapi to yukihookapi-core - optimize code - other small changes
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* YukiHookAPI - An efficient Hook API and Xposed Module solution built in Kotlin.
|
||||
* Copyright (C) 2019-2023 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/2/5.
|
||||
*/
|
||||
@file:Suppress("unused", "KDocUnresolvedReference")
|
||||
|
||||
package com.highcapable.yukihookapi
|
||||
|
||||
import com.google.auto.service.AutoService
|
||||
import com.google.devtools.ksp.processing.Dependencies
|
||||
import com.google.devtools.ksp.processing.Resolver
|
||||
import com.google.devtools.ksp.processing.SymbolProcessor
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
|
||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
|
||||
import com.google.devtools.ksp.symbol.ClassKind
|
||||
import com.google.devtools.ksp.symbol.FileLocation
|
||||
import com.google.devtools.ksp.symbol.KSAnnotated
|
||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
|
||||
import com.highcapable.yukihookapi.bean.GenerateData
|
||||
import com.highcapable.yukihookapi.factory.ClassName
|
||||
import com.highcapable.yukihookapi.factory.PackageName
|
||||
import com.highcapable.yukihookapi.factory.sources
|
||||
import com.highcapable.yukihookapi.generated.YukiHookAPIProperties
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
/**
|
||||
* 这是 [YukiHookAPI] 的自动生成处理类 - 核心基于 KSP
|
||||
*
|
||||
* 可以帮你快速生成 Xposed 入口类和包名
|
||||
*
|
||||
* 你只需要添加 [InjectYukiHookWithXposed] 注解即可完美解决一切问题
|
||||
*/
|
||||
@AutoService(SymbolProcessorProvider::class)
|
||||
class YukiHookXposedProcessor : SymbolProcessorProvider {
|
||||
|
||||
private companion object {
|
||||
|
||||
/** 自动处理程序的 TAG */
|
||||
private const val TAG = YukiHookAPIProperties.PROJECT_NAME
|
||||
|
||||
/** 查找的注解名称 */
|
||||
private const val ANNOTATION_NAME = "com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed"
|
||||
|
||||
/** 插入 Xposed 尾部的名称 */
|
||||
private const val XPOSED_CLASS_SHORT_NAME = "_YukiHookXposedInit"
|
||||
|
||||
/** "kt" 文件扩展名 */
|
||||
private const val KOTLIN_FILE_EXT_NAME = "kt"
|
||||
|
||||
/** "java" 文件扩展名 */
|
||||
private const val JAVA_FILE_EXT_NAME = "java"
|
||||
}
|
||||
|
||||
override fun create(environment: SymbolProcessorEnvironment) = object : SymbolProcessor {
|
||||
|
||||
/**
|
||||
* 创建一个环境方法体方便调用
|
||||
* @param ignored 是否忽略错误 - 默认否
|
||||
* @param env 方法体
|
||||
*/
|
||||
private fun environment(ignored: Boolean = false, env: SymbolProcessorEnvironment.() -> Unit) {
|
||||
if (ignored) runCatching { environment.apply(env) }
|
||||
else environment.apply(env)
|
||||
}
|
||||
|
||||
/**
|
||||
* 终止并报错
|
||||
* @param msg 错误消息
|
||||
* @return [Nothing]
|
||||
*/
|
||||
private fun SymbolProcessorEnvironment.problem(msg: String): Nothing {
|
||||
val helpMsg = "Looking for help? Please see the documentation link below\n" +
|
||||
"- English: https://fankes.github.io/YukiHookAPI/en/config/xposed-using\n" +
|
||||
"- Chinese(Simplified): https://fankes.github.io/YukiHookAPI/zh-cn/config/xposed-using"
|
||||
logger.error(message = "[$TAG] $msg\n$helpMsg")
|
||||
throw RuntimeException("[$TAG] $msg\n$helpMsg")
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建代码文件
|
||||
* @param fileName 文件名
|
||||
* @param packageName 包名
|
||||
* @param content 代码内容
|
||||
* @param extensionName 文件扩展名 - 默认为 [KOTLIN_FILE_EXT_NAME]
|
||||
*/
|
||||
private fun SymbolProcessorEnvironment.createCodeFile(
|
||||
fileName: String,
|
||||
packageName: String,
|
||||
content: String?,
|
||||
extensionName: String = KOTLIN_FILE_EXT_NAME
|
||||
) = codeGenerator.createNewFile(
|
||||
dependencies = Dependencies.ALL_FILES,
|
||||
packageName, fileName, extensionName
|
||||
).apply { content?.toByteArray()?.let { write(it) }; flush() }.close()
|
||||
|
||||
/**
|
||||
* 发出警告
|
||||
* @param msg 错误消息
|
||||
*/
|
||||
private fun SymbolProcessorEnvironment.warn(msg: String) = logger.warn(message = "[$TAG] $msg")
|
||||
|
||||
/**
|
||||
* 移除字符串中的空格与换行符并将双引号替换为单引号
|
||||
* @return [String]
|
||||
*/
|
||||
private fun String.removeSpecialChars() = replace("\\s*|\t|\r|\n".toRegex(), "").replace("\"", "'")
|
||||
|
||||
override fun process(resolver: Resolver) = emptyList<KSAnnotated>().let { startProcess(resolver); it }
|
||||
|
||||
/**
|
||||
* 开始作业入口
|
||||
* @param resolver [Resolver]
|
||||
*/
|
||||
private fun startProcess(resolver: Resolver) = environment {
|
||||
var isInjectOnce = true
|
||||
val data = GenerateData()
|
||||
resolver.getSymbolsWithAnnotation(ANNOTATION_NAME).apply {
|
||||
/**
|
||||
* 检索需要注入的类
|
||||
* @param sourcePath 指定的 source 路径
|
||||
*/
|
||||
fun fetchKSClassDeclaration(sourcePath: String) {
|
||||
asSequence().filterIsInstance<KSClassDeclaration>().forEach {
|
||||
if (isInjectOnce) when {
|
||||
it.superTypes.any { type -> type.element.toString() == "IYukiHookXposedInit" } -> {
|
||||
if ((it.primaryConstructor?.parameters?.size ?: 0) > 0)
|
||||
problem(msg = "The hook entry class \"${it.simpleName.asString()}\" doesn't allowed any constructor parameters")
|
||||
val xInitPatchName = data.xInitClassName.ifBlank { "${it.simpleName.asString()}$XPOSED_CLASS_SHORT_NAME" }
|
||||
if (data.xInitClassName == it.simpleName.asString())
|
||||
problem(msg = "Duplicate entryClassName \"${data.xInitClassName}\"")
|
||||
data.entryPackageName = it.packageName.asString()
|
||||
data.entryClassName = it.simpleName.asString()
|
||||
data.xInitClassName = xInitPatchName
|
||||
data.isEntryClassKindOfObject = when (it.classKind) {
|
||||
ClassKind.CLASS -> false
|
||||
ClassKind.OBJECT -> true
|
||||
else -> problem(msg = "Invalid hook entry class \"${it.simpleName.asString()}\" kind \"${it.classKind}\"")
|
||||
}
|
||||
generateAssetsFile(codePath = (it.location as? FileLocation?)?.filePath ?: "", sourcePath = sourcePath, data)
|
||||
}
|
||||
it.superTypes.any { type -> type.element.toString() == "YukiHookXposedInitProxy" } ->
|
||||
problem(msg = "\"YukiHookXposedInitProxy\" was deprecated, please replace to \"IYukiHookXposedInit\"")
|
||||
else -> problem(msg = "The hook entry class \"${it.simpleName.asString()}\" must be implements \"IYukiHookXposedInit\"")
|
||||
} else problem(msg = "\"@InjectYukiHookWithXposed\" only can be use in once times")
|
||||
/** 仅处理第一个标记的类 - 再次处理将拦截并报错 */
|
||||
isInjectOnce = false
|
||||
}
|
||||
}
|
||||
forEach {
|
||||
it.annotations.forEach { annotation ->
|
||||
var sourcePath = "" // 项目相对路径
|
||||
annotation.arguments.forEach { args ->
|
||||
if (args.name?.asString() == "sourcePath")
|
||||
sourcePath = args.value.toString().trim()
|
||||
if (args.name?.asString() == "modulePackageName")
|
||||
data.customMPackageName = args.value.toString().trim()
|
||||
if (args.name?.asString() == "entryClassName")
|
||||
data.xInitClassName = args.value.toString().trim()
|
||||
if (args.name?.asString() == "isUsingResourcesHook")
|
||||
data.isUsingResourcesHook = args.value as? Boolean ?: true
|
||||
}
|
||||
if ((data.customMPackageName.startsWith(".") ||
|
||||
data.customMPackageName.endsWith(".") ||
|
||||
data.customMPackageName.contains(".").not() ||
|
||||
data.customMPackageName.contains("..")) &&
|
||||
data.customMPackageName.isNotEmpty()
|
||||
) problem(msg = "Invalid modulePackageName \"${data.customMPackageName}\"")
|
||||
if ((Pattern.compile("[*,.:~`'\"|/\\\\?!^()\\[\\]{}%@#$&\\-+=<>]").matcher(data.entryClassName).find() ||
|
||||
true.let { for (i in 0..9) if (data.entryClassName.startsWith(i.toString())) return@let true; false }) &&
|
||||
data.entryClassName.isNotEmpty()
|
||||
) problem(msg = "Invalid entryClassName \"${data.entryClassName}\"")
|
||||
else fetchKSClassDeclaration(sourcePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动生成 Xposed assets 入口文件
|
||||
* @param codePath 注解类的完整代码文件路径
|
||||
* @param sourcePath 指定的 source 路径
|
||||
* @param data 生成的模板数据
|
||||
*/
|
||||
private fun generateAssetsFile(codePath: String, sourcePath: String, data: GenerateData) = environment {
|
||||
if (codePath.isBlank()) problem(msg = "Project CodePath not available")
|
||||
if (sourcePath.isBlank()) problem(msg = "Project SourcePath not available")
|
||||
/**
|
||||
* Gradle 在这里自动处理了 Windows 和 Unix 下的反斜杠路径问题
|
||||
*
|
||||
* 为了防止万一还是做了一下反斜杠处理防止旧版本不支持此用法
|
||||
*/
|
||||
val separator = when {
|
||||
codePath.contains("\\") -> "\\"
|
||||
codePath.contains("/") -> "/"
|
||||
else -> error("Unix File Separator unknown")
|
||||
}
|
||||
var rootPath = ""
|
||||
val projectPath = when {
|
||||
codePath.contains("\\") -> sourcePath.replace("/", "\\")
|
||||
codePath.contains("/") -> sourcePath.replace("\\", "/")
|
||||
else -> error("Unknown Unix File Separator")
|
||||
}.let {
|
||||
if (codePath.contains(it))
|
||||
codePath.split(it)[0].apply { rootPath = this } + it
|
||||
else problem(msg = "Project Source Path \"$it\" not matched")
|
||||
}
|
||||
val gradleFile = File("$rootPath${separator}build.gradle")
|
||||
val gradleKtsFile = File("$rootPath${separator}build.gradle.kts")
|
||||
val manifestFile = File("$projectPath${separator}AndroidManifest.xml")
|
||||
val assetsFolder = File("$projectPath${separator}assets")
|
||||
val metaInfFolder = File("$projectPath${separator}resources${separator}META-INF")
|
||||
if (manifestFile.exists()) {
|
||||
if (assetsFolder.exists().not() || assetsFolder.isDirectory.not()) assetsFolder.apply { delete(); mkdirs() }
|
||||
if (metaInfFolder.exists().not() || metaInfFolder.isDirectory.not()) metaInfFolder.apply { delete(); mkdirs() }
|
||||
data.modulePackageName = parseModulePackageName(manifestFile, gradleFile, gradleKtsFile)
|
||||
if (data.modulePackageName.isBlank() && data.customMPackageName.isBlank())
|
||||
problem(msg = "Cannot identify your Module App's package name, tried AndroidManifest.xml, build.gradle and build.gradle.kts")
|
||||
File("${assetsFolder.absolutePath}${separator}xposed_init")
|
||||
.writeText(text = "${data.entryPackageName}.${data.xInitClassName}")
|
||||
File("${metaInfFolder.absolutePath}${separator}yukihookapi_init")
|
||||
.writeText(text = "${data.entryPackageName}.${data.entryClassName}")
|
||||
/** 移除旧版本 API 创建的入口类名称文件 */
|
||||
File("${assetsFolder.absolutePath}${separator}yukihookapi_init").apply { if (exists()) delete() }
|
||||
generateClassFile(data)
|
||||
} else problem(msg = "Project Source Path \"$sourcePath\" verify failed! Is this an Android Project?")
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动生成指定类文件
|
||||
* @param data 生成的模板数据
|
||||
*/
|
||||
private fun generateClassFile(data: GenerateData) = environment(ignored = true) {
|
||||
if (data.customMPackageName.isNotBlank()) warn(
|
||||
msg = "You set the customize module package name to \"${data.customMPackageName}\", " +
|
||||
"please check for yourself if it is correct"
|
||||
)
|
||||
/** 插入 YukiHookAPI_Impl 代码 */
|
||||
createCodeFile(
|
||||
fileName = ClassName.YukiHookAPI_Impl,
|
||||
packageName = PackageName.YukiHookAPI_Impl,
|
||||
content = data.sources()[ClassName.YukiHookAPI_Impl]
|
||||
)
|
||||
/** 插入 ModuleApplication_Impl 代码 */
|
||||
createCodeFile(
|
||||
fileName = ClassName.ModuleApplication_Impl,
|
||||
packageName = PackageName.ModuleApplication_Impl,
|
||||
content = data.sources()[ClassName.ModuleApplication_Impl]
|
||||
)
|
||||
/** 插入 YukiXposedModuleStatus_Impl 代码 */
|
||||
createCodeFile(
|
||||
fileName = ClassName.YukiXposedModuleStatus_Impl,
|
||||
packageName = PackageName.YukiXposedModuleStatus_Impl,
|
||||
content = data.sources()[ClassName.YukiXposedModuleStatus_Impl]
|
||||
)
|
||||
/** 插入 HandlerDelegateImpl_Impl 代码 */
|
||||
createCodeFile(
|
||||
fileName = ClassName.HandlerDelegateImpl_Impl,
|
||||
packageName = PackageName.HandlerDelegateImpl_Impl,
|
||||
content = data.sources()[ClassName.HandlerDelegateImpl_Impl]
|
||||
)
|
||||
/** 插入 HandlerDelegateClass 代码 */
|
||||
createCodeFile(
|
||||
fileName = ClassName.HandlerDelegateClass,
|
||||
packageName = PackageName.HandlerDelegateClass,
|
||||
content = data.sources()[ClassName.HandlerDelegateClass]
|
||||
)
|
||||
/** 插入 IActivityManagerProxyImpl_Impl 代码 */
|
||||
createCodeFile(
|
||||
fileName = ClassName.IActivityManagerProxyImpl_Impl,
|
||||
packageName = PackageName.IActivityManagerProxyImpl_Impl,
|
||||
content = data.sources()[ClassName.IActivityManagerProxyImpl_Impl]
|
||||
)
|
||||
/** 插入 IActivityManagerProxyClass 代码 */
|
||||
createCodeFile(
|
||||
fileName = ClassName.IActivityManagerProxyClass,
|
||||
packageName = PackageName.IActivityManagerProxyClass,
|
||||
content = data.sources()[ClassName.IActivityManagerProxyClass]
|
||||
)
|
||||
/** 插入 xposed_init 代码 */
|
||||
createCodeFile(
|
||||
fileName = data.xInitClassName,
|
||||
packageName = data.entryPackageName,
|
||||
content = data.sources()[ClassName.XposedInit]
|
||||
)
|
||||
/** 插入 xposed_init_Impl 代码 */
|
||||
createCodeFile(
|
||||
fileName = "${data.entryClassName}_Impl",
|
||||
packageName = data.entryPackageName,
|
||||
content = data.sources()[ClassName.XposedInit_Impl]
|
||||
)
|
||||
/* 插入 FreeReflection 代码 */
|
||||
createCodeFile(
|
||||
fileName = ClassName.BootstrapClass,
|
||||
packageName = PackageName.BootstrapReflectionClass,
|
||||
content = data.sources()[ClassName.BootstrapClass],
|
||||
extensionName = JAVA_FILE_EXT_NAME
|
||||
)
|
||||
/* 插入 FreeReflection 代码 */
|
||||
createCodeFile(
|
||||
fileName = ClassName.Reflection,
|
||||
packageName = PackageName.BootstrapReflectionClass,
|
||||
content = data.sources()[ClassName.Reflection],
|
||||
extensionName = JAVA_FILE_EXT_NAME
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析模块包名
|
||||
* @param manifestFile AndroidManifest.xml 文件
|
||||
* @param gradleFile build.gradle 文件
|
||||
* @param gradleKtsFile build.gradle.kts 文件
|
||||
* @return [String] 模块包名
|
||||
*/
|
||||
private fun parseModulePackageName(manifestFile: File, gradleFile: File, gradleKtsFile: File) = when {
|
||||
gradleFile.exists() -> runCatching {
|
||||
gradleFile.readText()
|
||||
.removeSpecialChars()
|
||||
.split("namespace'")[1]
|
||||
.split("'")[0]
|
||||
}.getOrNull()
|
||||
gradleKtsFile.exists() -> runCatching {
|
||||
gradleKtsFile.readText()
|
||||
.removeSpecialChars()
|
||||
.replace("varnamespace", "")
|
||||
.replace("valnamespace", "")
|
||||
.split("namespace='")[1]
|
||||
.split("'")[0]
|
||||
}.getOrNull()
|
||||
else -> null
|
||||
} ?: runCatching {
|
||||
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(manifestFile).let { document ->
|
||||
document.getElementsByTagName("manifest").let { nodeList ->
|
||||
nodeList.item(0).let { node ->
|
||||
if (node.nodeType == Node.ELEMENT_NODE)
|
||||
(node as? Element?)?.getAttribute("package") ?: ""
|
||||
else ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}.getOrNull() ?: ""
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* YukiHookAPI - An efficient Hook API and Xposed Module solution built in Kotlin.
|
||||
* Copyright (C) 2019-2023 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/20.
|
||||
*/
|
||||
package com.highcapable.yukihookapi.bean
|
||||
|
||||
/**
|
||||
* 生成的模板数据实例
|
||||
* @param entryPackageName 入口类包名
|
||||
* @param modulePackageName 模块包名 (命名空间)
|
||||
* @param customMPackageName 自定义模块包名
|
||||
* @param entryClassName 入口类名
|
||||
* @param xInitClassName xposed_init 入口类名
|
||||
* @param isEntryClassKindOfObject 入口类种类 (类型) 是否为 object (单例)
|
||||
* @param isUsingResourcesHook 是否启用 Resources Hook
|
||||
*/
|
||||
data class GenerateData(
|
||||
var entryPackageName: String = "",
|
||||
var modulePackageName: String = "",
|
||||
var customMPackageName: String = "",
|
||||
var entryClassName: String = "",
|
||||
var xInitClassName: String = "",
|
||||
var isEntryClassKindOfObject: Boolean = false,
|
||||
var isUsingResourcesHook: Boolean = true
|
||||
)
|
@@ -0,0 +1,540 @@
|
||||
/*
|
||||
* YukiHookAPI - An efficient Hook API and Xposed Module solution built in Kotlin.
|
||||
* Copyright (C) 2019-2023 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/20.
|
||||
*/
|
||||
@file:Suppress("ConstPropertyName")
|
||||
|
||||
package com.highcapable.yukihookapi.factory
|
||||
|
||||
import com.highcapable.yukihookapi.bean.GenerateData
|
||||
import com.highcapable.yukihookapi.generated.YukiHookAPIProperties
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* 包名常量定义类
|
||||
*/
|
||||
object PackageName {
|
||||
const val YukiHookAPI_Impl = "com.highcapable.yukihookapi"
|
||||
const val ModuleApplication_Impl = "com.highcapable.yukihookapi.hook.xposed.application"
|
||||
const val YukiXposedModuleStatus_Impl = "com.highcapable.yukihookapi.hook.xposed.bridge.status"
|
||||
const val HandlerDelegateImpl_Impl = "com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.impl"
|
||||
const val HandlerDelegateClass = "com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate"
|
||||
const val IActivityManagerProxyImpl_Impl = "com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.impl"
|
||||
const val IActivityManagerProxyClass = "com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate"
|
||||
const val BootstrapReflectionClass = "com.highcapable.yukihookapi.thirdparty.me.weishu.reflection"
|
||||
}
|
||||
|
||||
/**
|
||||
* 类名常量定义类
|
||||
*/
|
||||
object ClassName {
|
||||
const val YukiHookAPI_Impl = "YukiHookAPI_Impl"
|
||||
const val ModuleApplication_Impl = "ModuleApplication_Impl"
|
||||
const val YukiXposedModuleStatus_Impl = "YukiXposedModuleStatus_Impl"
|
||||
const val HandlerDelegateImpl_Impl = "HandlerDelegateImpl_Impl"
|
||||
const val HandlerDelegateClass = "HandlerDelegate"
|
||||
const val IActivityManagerProxyImpl_Impl = "IActivityManagerProxyImpl_Impl"
|
||||
const val IActivityManagerProxyClass = "IActivityManagerProxy"
|
||||
const val XposedInit = "xposed_init"
|
||||
const val XposedInit_Impl = "xposed_init_Impl"
|
||||
const val BootstrapClass = "BootstrapClass"
|
||||
const val Reflection = "Reflection"
|
||||
}
|
||||
|
||||
/**
|
||||
* 外部调用者包名和类名定义类
|
||||
*/
|
||||
object ExternalCallerName {
|
||||
val YukiGenerateApiAnnotation = Pair(
|
||||
"com.highcapable.yukihookapi.annotation.YukiGenerateApi",
|
||||
"YukiGenerateApi"
|
||||
)
|
||||
val HandlerDelegateCaller = Pair(
|
||||
"com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.caller.HandlerDelegateCaller",
|
||||
"HandlerDelegateCaller"
|
||||
)
|
||||
val IActivityManagerProxyCaller = Pair(
|
||||
"com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.caller.IActivityManagerProxyCaller",
|
||||
"IActivityManagerProxyCaller"
|
||||
)
|
||||
val YukiXposedEventCaller = Pair(
|
||||
"com.highcapable.yukihookapi.hook.xposed.bridge.event.caller.YukiXposedEventCaller",
|
||||
"YukiXposedEventCaller"
|
||||
)
|
||||
val YukiXposedModuleCaller = Pair(
|
||||
"com.highcapable.yukihookapi.hook.xposed.bridge.caller.YukiXposedModuleCaller",
|
||||
"YukiXposedModuleCaller"
|
||||
)
|
||||
val YukiXposedResourcesCaller = Pair(
|
||||
"com.highcapable.yukihookapi.hook.xposed.bridge.resources.caller.YukiXposedResourcesCaller",
|
||||
"YukiXposedResourcesCaller"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* YukiXposedModuleStatus 方法名称定义类
|
||||
*/
|
||||
object YukiXposedModuleStatusJvmName {
|
||||
const val IS_ACTIVE_METHOD_NAME = "__--"
|
||||
const val IS_SUPPORT_RESOURCES_HOOK_METHOD_NAME = "_--_"
|
||||
const val GET_EXECUTOR_NAME_METHOD_NAME = "_-_-"
|
||||
const val GET_EXECUTOR_API_LEVEL_METHOD_NAME = "-__-"
|
||||
const val GET_EXECUTOR_VERSION_NAME_METHOD_NAME = "-_-_"
|
||||
const val GET_EXECUTOR_VERSION_CODE_METHOD_NAME = "___-"
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建尾部包名名称
|
||||
* @param name 前置名称
|
||||
* @return [String]
|
||||
*/
|
||||
private fun GenerateData.tailPackageName(name: String) = "${name}_${modulePackageName.replace(".", "_")}"
|
||||
|
||||
/**
|
||||
* 创建文件注释
|
||||
* @param currrentClassTag 当前注入类标签
|
||||
* @return [String]
|
||||
*/
|
||||
private fun createCommentContent(currrentClassTag: String) =
|
||||
"""
|
||||
/**
|
||||
* $currrentClassTag Class
|
||||
*
|
||||
* Compiled from YukiHookXposedProcessor
|
||||
*
|
||||
* Generate Date: ${SimpleDateFormat.getDateTimeInstance().format(Date())}
|
||||
*
|
||||
* Powered by YukiHookAPI (C) HighCapable 2019-2023
|
||||
*
|
||||
* Project URL: [${YukiHookAPIProperties.PROJECT_NAME}](${YukiHookAPIProperties.PROJECT_URL})
|
||||
*/
|
||||
""".trimIndent()
|
||||
|
||||
/**
|
||||
* 获得注入文件代码内容
|
||||
* @return [Map]<[String], [String]>
|
||||
*/
|
||||
fun GenerateData.sources() = mapOf(
|
||||
ClassName.YukiHookAPI_Impl to """
|
||||
@file:Suppress("ClassName")
|
||||
|
||||
package ${PackageName.YukiHookAPI_Impl}
|
||||
""".trimIndent() + "\n\n" + createCommentContent(ClassName.YukiHookAPI_Impl) + "\n" + """
|
||||
object ${ClassName.YukiHookAPI_Impl} {
|
||||
|
||||
val compiledTimestamp get() = ${System.currentTimeMillis()}
|
||||
}
|
||||
""".trimIndent(),
|
||||
ClassName.ModuleApplication_Impl to """
|
||||
@file:Suppress("ClassName")
|
||||
|
||||
package ${PackageName.ModuleApplication_Impl}
|
||||
|
||||
import $entryPackageName.$entryClassName
|
||||
""".trimIndent() + "\n\n" + createCommentContent(ClassName.ModuleApplication_Impl) + "\n" + """
|
||||
object ${ClassName.ModuleApplication_Impl} {
|
||||
|
||||
fun callHookEntryInit() = try {
|
||||
${if (isEntryClassKindOfObject) "$entryClassName.onInit()" else "$entryClassName().onInit()"}
|
||||
} catch (_: Throwable) {
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
ClassName.YukiXposedModuleStatus_Impl to """
|
||||
@file:Suppress("ClassName")
|
||||
|
||||
package ${PackageName.YukiXposedModuleStatus_Impl}
|
||||
|
||||
import android.util.Log
|
||||
import androidx.annotation.Keep
|
||||
""".trimIndent() + "\n\n" + createCommentContent(ClassName.YukiXposedModuleStatus_Impl) + "\n" + """
|
||||
@Keep
|
||||
object ${ClassName.YukiXposedModuleStatus_Impl} {
|
||||
|
||||
@JvmName("${YukiXposedModuleStatusJvmName.IS_ACTIVE_METHOD_NAME}")
|
||||
fun isActive(): Boolean {
|
||||
placeholderExecution()
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmName("${YukiXposedModuleStatusJvmName.IS_SUPPORT_RESOURCES_HOOK_METHOD_NAME}")
|
||||
fun isSupportResourcesHook(): Boolean {
|
||||
placeholderExecution()
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmName("${YukiXposedModuleStatusJvmName.GET_EXECUTOR_NAME_METHOD_NAME}")
|
||||
fun getExecutorName(): String {
|
||||
placeholderExecution()
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
@JvmName("${YukiXposedModuleStatusJvmName.GET_EXECUTOR_API_LEVEL_METHOD_NAME}")
|
||||
fun getExecutorApiLevel(): Int {
|
||||
placeholderExecution()
|
||||
return -1
|
||||
}
|
||||
|
||||
@JvmName("${YukiXposedModuleStatusJvmName.GET_EXECUTOR_VERSION_NAME_METHOD_NAME}")
|
||||
fun getExecutorVersionName(): String {
|
||||
placeholderExecution()
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
@JvmName("${YukiXposedModuleStatusJvmName.GET_EXECUTOR_VERSION_CODE_METHOD_NAME}")
|
||||
fun getExecutorVersionCode(): Int {
|
||||
placeholderExecution()
|
||||
return -1
|
||||
}
|
||||
|
||||
private fun placeholderExecution() {
|
||||
/** Consume a long method body */
|
||||
if (System.currentTimeMillis() == 0L) Log.d("${(1000..9999).random()}", "${(100000..999999).random()}")
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
ClassName.HandlerDelegateImpl_Impl to """
|
||||
@file:Suppress("ClassName")
|
||||
|
||||
package ${PackageName.HandlerDelegateImpl_Impl}
|
||||
|
||||
import android.os.Handler
|
||||
import ${ExternalCallerName.YukiGenerateApiAnnotation.first}
|
||||
import ${PackageName.HandlerDelegateClass}.${tailPackageName(ClassName.HandlerDelegateClass)}
|
||||
""".trimIndent() + "\n\n" + createCommentContent(ClassName.HandlerDelegateImpl_Impl) + "\n" + """
|
||||
@${ExternalCallerName.YukiGenerateApiAnnotation.second}
|
||||
object ${ClassName.HandlerDelegateImpl_Impl} {
|
||||
|
||||
val wrapperClassName get() = "${PackageName.HandlerDelegateClass}.${tailPackageName(ClassName.HandlerDelegateClass)}"
|
||||
|
||||
fun createWrapper(baseInstance: Handler.Callback? = null): Handler.Callback = ${tailPackageName(ClassName.HandlerDelegateClass)}(baseInstance)
|
||||
}
|
||||
""".trimIndent(),
|
||||
ClassName.HandlerDelegateClass to """
|
||||
@file:Suppress("ClassName")
|
||||
|
||||
package ${PackageName.HandlerDelegateClass}
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Message
|
||||
import androidx.annotation.Keep
|
||||
import ${ExternalCallerName.YukiGenerateApiAnnotation.first}
|
||||
import ${ExternalCallerName.HandlerDelegateCaller.first}
|
||||
""".trimIndent() + "\n\n" + createCommentContent(ClassName.HandlerDelegateClass) + "\n" + """
|
||||
@Keep
|
||||
@${ExternalCallerName.YukiGenerateApiAnnotation.second}
|
||||
class ${tailPackageName(ClassName.HandlerDelegateClass)}(private val baseInstance: Handler.Callback?) : Handler.Callback {
|
||||
|
||||
override fun handleMessage(msg: Message) = ${ExternalCallerName.HandlerDelegateCaller.second}.callHandleMessage(baseInstance, msg)
|
||||
}
|
||||
""".trimIndent(),
|
||||
ClassName.IActivityManagerProxyImpl_Impl to """
|
||||
@file:Suppress("ClassName")
|
||||
|
||||
package ${PackageName.IActivityManagerProxyImpl_Impl}
|
||||
|
||||
import android.os.Handler
|
||||
import ${ExternalCallerName.YukiGenerateApiAnnotation.first}
|
||||
import ${ExternalCallerName.IActivityManagerProxyCaller.first}
|
||||
import ${PackageName.IActivityManagerProxyClass}.${tailPackageName(ClassName.IActivityManagerProxyClass)}
|
||||
import java.lang.reflect.Proxy
|
||||
""".trimIndent() + "\n\n" + createCommentContent(ClassName.IActivityManagerProxyImpl_Impl) + "\n" + """
|
||||
@${ExternalCallerName.YukiGenerateApiAnnotation.second}
|
||||
object ${ClassName.IActivityManagerProxyImpl_Impl} {
|
||||
|
||||
fun createWrapper(clazz: Class<*>?, instance: Any) =
|
||||
Proxy.newProxyInstance(${ExternalCallerName.IActivityManagerProxyCaller.second}.currentClassLoader, arrayOf(clazz), ${tailPackageName(
|
||||
ClassName.IActivityManagerProxyClass
|
||||
)}(instance))
|
||||
}
|
||||
""".trimIndent(),
|
||||
ClassName.IActivityManagerProxyClass to """
|
||||
@file:Suppress("ClassName")
|
||||
|
||||
package ${PackageName.IActivityManagerProxyClass}
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import ${ExternalCallerName.YukiGenerateApiAnnotation.first}
|
||||
import ${ExternalCallerName.IActivityManagerProxyCaller.first}
|
||||
import java.lang.reflect.InvocationHandler
|
||||
import java.lang.reflect.Method
|
||||
import java.lang.reflect.Proxy
|
||||
""".trimIndent() + "\n\n" + createCommentContent(ClassName.IActivityManagerProxyClass) + "\n" + """
|
||||
@Keep
|
||||
@${ExternalCallerName.YukiGenerateApiAnnotation.second}
|
||||
class ${tailPackageName(ClassName.IActivityManagerProxyClass)}(private val baseInstance: Any) : InvocationHandler {
|
||||
|
||||
override fun invoke(proxy: Any?, method: Method?, args: Array<Any>?) = ${ExternalCallerName.IActivityManagerProxyCaller.second}.callInvoke(baseInstance, method, args)
|
||||
}
|
||||
""".trimIndent(),
|
||||
ClassName.XposedInit to """
|
||||
@file:Suppress("ClassName")
|
||||
|
||||
package $entryPackageName
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import ${ExternalCallerName.YukiXposedEventCaller.first}
|
||||
import ${ExternalCallerName.YukiGenerateApiAnnotation.first}
|
||||
${if (isUsingResourcesHook) "import de.robv.android.xposed.IXposedHookInitPackageResources" else ""}
|
||||
import de.robv.android.xposed.IXposedHookLoadPackage
|
||||
import de.robv.android.xposed.IXposedHookZygoteInit
|
||||
${if (isUsingResourcesHook) "import de.robv.android.xposed.callbacks.XC_InitPackageResources" else ""}
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||
""".trimIndent() + "\n\n" + createCommentContent("Xposed Init") + "\n" + """
|
||||
@Keep
|
||||
@${ExternalCallerName.YukiGenerateApiAnnotation.second}
|
||||
class $xInitClassName : IXposedHookZygoteInit, IXposedHookLoadPackage${if (isUsingResourcesHook) ", IXposedHookInitPackageResources" else ""} {
|
||||
|
||||
override fun initZygote(sparam: IXposedHookZygoteInit.StartupParam?) {
|
||||
${entryClassName}_Impl.callInitZygote(sparam)
|
||||
${ExternalCallerName.YukiXposedEventCaller.second}.callInitZygote(sparam)
|
||||
}
|
||||
|
||||
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
|
||||
${entryClassName}_Impl.callHandleLoadPackage(lpparam)
|
||||
${ExternalCallerName.YukiXposedEventCaller.second}.callHandleLoadPackage(lpparam)
|
||||
}
|
||||
""".trimIndent() +
|
||||
(if (isUsingResourcesHook)
|
||||
"\n" + """
|
||||
|
||||
override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam?) {
|
||||
${entryClassName}_Impl.callHandleInitPackageResources(resparam)
|
||||
${ExternalCallerName.YukiXposedEventCaller.second}.callHandleInitPackageResources(resparam)
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
else "\n}"),
|
||||
ClassName.XposedInit_Impl to """
|
||||
@file:Suppress("ClassName")
|
||||
|
||||
package $entryPackageName
|
||||
|
||||
import ${ExternalCallerName.YukiGenerateApiAnnotation.first}
|
||||
import ${ExternalCallerName.YukiXposedModuleCaller.first}
|
||||
import ${ExternalCallerName.YukiXposedResourcesCaller.first}
|
||||
import com.highcapable.yukihookapi.hook.xposed.bridge.type.HookEntryType
|
||||
import de.robv.android.xposed.IXposedHookZygoteInit
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage
|
||||
${if (customMPackageName.isBlank()) "import $modulePackageName.BuildConfig" else ""}
|
||||
""".trimIndent() + "\n\n" + createCommentContent("Xposed Init Impl") + "\n" + """
|
||||
@${ExternalCallerName.YukiGenerateApiAnnotation.second}
|
||||
object ${entryClassName}_Impl {
|
||||
|
||||
private const val modulePackageName = ${if (customMPackageName.isNotBlank()) "\"$customMPackageName\"" else "BuildConfig.APPLICATION_ID"}
|
||||
private var isZygoteCalled = false
|
||||
private val hookEntry = ${if (isEntryClassKindOfObject) entryClassName else "$entryClassName()"}
|
||||
|
||||
private fun callOnXposedModuleLoaded(
|
||||
isZygoteLoaded: Boolean = false,
|
||||
lpparam: XC_LoadPackage.LoadPackageParam? = null,
|
||||
resparam: XC_InitPackageResources.InitPackageResourcesParam? = null
|
||||
) {
|
||||
if (isZygoteCalled.not()) runCatching {
|
||||
hookEntry.onXposedEvent()
|
||||
hookEntry.onInit()
|
||||
if (${ExternalCallerName.YukiXposedModuleCaller.second}.isXposedCallbackSetUp) {
|
||||
${ExternalCallerName.YukiXposedModuleCaller.second}.internalLoggerE("You cannot load a hooker in \"onInit\" or \"onXposedEvent\" method! Aborted")
|
||||
return
|
||||
}
|
||||
hookEntry.onHook()
|
||||
${ExternalCallerName.YukiXposedModuleCaller.second}.callOnFinishLoadModule()
|
||||
}.onFailure { ${ExternalCallerName.YukiXposedModuleCaller.second}.internalLoggerE("YukiHookAPI try to load hook entry class failed", it) }
|
||||
${ExternalCallerName.YukiXposedModuleCaller.second}.callOnPackageLoaded(
|
||||
type = when {
|
||||
isZygoteLoaded -> HookEntryType.ZYGOTE
|
||||
lpparam != null -> HookEntryType.PACKAGE
|
||||
resparam != null -> HookEntryType.RESOURCES
|
||||
else -> HookEntryType.ZYGOTE
|
||||
},
|
||||
packageName = lpparam?.packageName ?: resparam?.packageName,
|
||||
processName = lpparam?.processName,
|
||||
appClassLoader = lpparam?.classLoader ?: runCatching { XposedBridge.BOOTCLASSLOADER }.getOrNull(),
|
||||
appInfo = lpparam?.appInfo,
|
||||
appResources = ${ExternalCallerName.YukiXposedResourcesCaller.second}.createYukiResourcesFromXResources(resparam?.res)
|
||||
)
|
||||
}
|
||||
|
||||
fun callInitZygote(sparam: IXposedHookZygoteInit.StartupParam?) {
|
||||
if (sparam == null) return
|
||||
runCatching {
|
||||
${ExternalCallerName.YukiXposedModuleCaller.second}.callOnStartLoadModule(modulePackageName, sparam.modulePath)
|
||||
callOnXposedModuleLoaded(isZygoteLoaded = true)
|
||||
isZygoteCalled = true
|
||||
}.onFailure { ${ExternalCallerName.YukiXposedModuleCaller.second}.internalLoggerE("An exception occurred when YukiHookAPI loading Xposed Module", it) }
|
||||
}
|
||||
|
||||
fun callHandleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
|
||||
if (lpparam != null && isZygoteCalled) callOnXposedModuleLoaded(lpparam = lpparam)
|
||||
}
|
||||
|
||||
fun callHandleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam?) {
|
||||
if (resparam != null && isZygoteCalled) callOnXposedModuleLoaded(resparam = resparam)
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
ClassName.BootstrapClass to ("package ${PackageName.BootstrapReflectionClass};\n" +
|
||||
"\n" +
|
||||
"import static android.os.Build.VERSION.SDK_INT;\n" +
|
||||
"\n" +
|
||||
"import android.os.Build;\n" +
|
||||
"import android.util.Log;\n" +
|
||||
"\n" +
|
||||
"import androidx.annotation.Keep;\n" +
|
||||
"\n" +
|
||||
"import java.lang.reflect.Method;\n" +
|
||||
"\n" +
|
||||
createCommentContent(currrentClassTag = ClassName.BootstrapClass) +
|
||||
"@Keep\n" +
|
||||
"public final class BootstrapClass {\n" +
|
||||
"\n" +
|
||||
" private static final String TAG = \"BootstrapClass\";\n" +
|
||||
" private static Object sVmRuntime;\n" +
|
||||
" private static Method setHiddenApiExemptions;\n" +
|
||||
"\n" +
|
||||
" static {\n" +
|
||||
" if (SDK_INT >= Build.VERSION_CODES.P) {\n" +
|
||||
" try {\n" +
|
||||
" Method forName = Class.class.getDeclaredMethod(\"forName\", String.class);\n" +
|
||||
" Method getDeclaredMethod = Class.class.getDeclaredMethod(\"getDeclaredMethod\", String.class, Class[].class);\n" +
|
||||
" Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, \"dalvik.system.VMRuntime\");\n" +
|
||||
" Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, \"getRuntime\", null);\n" +
|
||||
" setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, \"setHiddenApiExemptions\", new Class[]{String[].class});\n" +
|
||||
" sVmRuntime = getRuntime.invoke(null);\n" +
|
||||
" } catch (Throwable e) {\n" +
|
||||
" Log.w(TAG, \"reflect bootstrap failed:\", e);\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" public static boolean exempt(String method) {\n" +
|
||||
" return exempt(new String[]{method});\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" public static boolean exempt(String... methods) {\n" +
|
||||
" if (sVmRuntime == null || setHiddenApiExemptions == null) {\n" +
|
||||
" return false;\n" +
|
||||
" }\n" +
|
||||
" try {\n" +
|
||||
" setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{methods});\n" +
|
||||
" return true;\n" +
|
||||
" } catch (Throwable e) {\n" +
|
||||
" return false;\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" public static boolean exemptAll() {\n" +
|
||||
" return exempt(new String[]{\"L\"});\n" +
|
||||
" }\n" +
|
||||
"}"),
|
||||
ClassName.Reflection to ("package ${PackageName.BootstrapReflectionClass};\n" +
|
||||
"\n" +
|
||||
"import static android.os.Build.VERSION.SDK_INT;\n" +
|
||||
"import static com.highcapable.yukihookapi.thirdparty.me.weishu.reflection.BootstrapClass.exemptAll;\n" +
|
||||
"\n" +
|
||||
"import android.content.Context;\n" +
|
||||
"import android.text.TextUtils;\n" +
|
||||
"import android.util.Base64;\n" +
|
||||
"\n" +
|
||||
"import androidx.annotation.Keep;\n" +
|
||||
"\n" +
|
||||
"import java.io.File;\n" +
|
||||
"import java.io.FileOutputStream;\n" +
|
||||
"import java.lang.reflect.Method;\n" +
|
||||
"\n" +
|
||||
"import dalvik.system.DexFile;\n" +
|
||||
"\n" +
|
||||
createCommentContent(currrentClassTag = ClassName.Reflection) +
|
||||
"@Keep\n" +
|
||||
"public class Reflection {\n" +
|
||||
"\n" +
|
||||
" private static final String TAG = \"Reflection\";\n" +
|
||||
" private static final String DEX = \"ZGV4CjAzNQCXDT0vQ44GJqsrjm32y0qlQmxUevbk56r0CwAAcAAAAHhWNBIAAAAAAAAAADwLAABDAAAAcAAAABMAAAB8AQAACwAAAMgBAAAMAAAATAIAAA8AAACsAgAAAwAAACQDAABwCAAAhAMAAIQDAACGAwAAiwMAAJUDAACdAwAArQMAALkDAADJAwAA3gMAAPADAAD3AwAA/wMAAAIEAAAGBAAACgQAABAEAAATBAAAGAQAADMEAABZBAAAdQQAAIkEAADYBAAAJgUAAHAFAACDBQAAmQUAAK0FAADBBQAA1QUAAOwFAAAIBgAAFAYAACUGAAAuBgAAMwYAADYGAABEBgAAUgYAAFYGAABZBgAAXQYAAHEGAACGBgAAmwYAAKQGAAC9BgAAwAYAAMgGAADTBgAA3AYAAO0GAAABBwAAFAcAACAHAAAoBwAANQcAAE8HAABXBwAAYAcAAHsHAACEBwAAkAcAAKgHAAC6BwAAwgcAANAHAAALAAAAEQAAABIAAAATAAAAFAAAABUAAAAWAAAAFwAAABgAAAAaAAAAGwAAABwAAAAdAAAAHgAAACMAAAAnAAAAKQAAACoAAAArAAAADAAAAAAAAAD4BwAADQAAAAAAAAAMCAAADgAAAAAAAAAACAAADwAAAAIAAAAAAAAAEAAAAAkAAAAUCAAAEAAAAA0AAADoBwAAIwAAAA4AAAAAAAAAJgAAAA4AAADgBwAAJwAAAA8AAAAAAAAAKAAAAA8AAADgBwAAKAAAAA8AAADwBwAAAgAAAD8AAAADAAAAIQAAAAUACgAEAAAABQAKAAUAAAAFAA8ACQAAAAUACgAKAAAABQAAACQAAAAFAAoAJQAAAAYACgAiAAAABgAJAD0AAAAGAA0APgAAAAcACgAiAAAAAQADADMAAAAEAAIALgAAAAUABgADAAAABgAGAAIAAAAGAAYAAwAAAAYACQAvAAAABgAKAC8AAAAGAAgAMAAAAAcABgADAAAABwABAEAAAAAHAAAAQQAAAAgABQA0AAAACQAGAAMAAAALAAcANwAAAA0ABAA2AAAABQAAABEAAAAJAAAAAAAAAAgAAAAAAAAA7AoAAB8IAAAGAAAAEQAAAAkAAAAAAAAABwAAAAAAAAACCwAAHAgAAAcAAAABAAAACQAAAAAAAAAgAAAAAAAAACULAAArCAAAAAADMS4wAAg8Y2xpbml0PgAGPGluaXQ+AA5BUFBMSUNBVElPTl9JRAAKQlVJTERfVFlQRQAOQm9vdHN0cmFwQ2xhc3MAE0Jvb3RzdHJhcENsYXNzLmphdmEAEEJ1aWxkQ29uZmlnLmphdmEABURFQlVHAAZGTEFWT1IAAUkAAklJAAJJTAAESUxMTAABTAADTExMABlMYW5kcm9pZC9jb250ZW50L0NvbnRleHQ7ACRMYW5kcm9pZC9jb250ZW50L3BtL0FwcGxpY2F0aW9uSW5mbzsAGkxhbmRyb2lkL29zL0J1aWxkJFZFUlNJT047ABJMYW5kcm9pZC91dGlsL0xvZzsATUxjb20vaGlnaGNhcGFibGUveXVraWhvb2thcGkvdGhpcmRwYXJ0eS9tZS93ZWlzaHUvZnJlZXJlZmxlY3Rpb24vQnVpbGRDb25maWc7AExMY29tL2hpZ2hjYXBhYmxlL3l1a2lob29rYXBpL3RoaXJkcGFydHkvbWUvd2Vpc2h1L3JlZmxlY3Rpb24vQm9vdHN0cmFwQ2xhc3M7AEhMY29tL2hpZ2hjYXBhYmxlL3l1a2lob29rYXBpL3RoaXJkcGFydHkvbWUvd2Vpc2h1L3JlZmxlY3Rpb24vUmVmbGVjdGlvbjsAEUxqYXZhL2xhbmcvQ2xhc3M7ABRMamF2YS9sYW5nL0NsYXNzPCo+OwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwAVTGphdmEvbGFuZy9UaHJvd2FibGU7ABpMamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kOwAKUmVmbGVjdGlvbgAPUmVmbGVjdGlvbi5qYXZhAAdTREtfSU5UAANUQUcAAVYADFZFUlNJT05fQ09ERQAMVkVSU0lPTl9OQU1FAAJWTAABWgACWkwAEltMamF2YS9sYW5nL0NsYXNzOwATW0xqYXZhL2xhbmcvT2JqZWN0OwATW0xqYXZhL2xhbmcvU3RyaW5nOwAHY29udGV4dAAXZGFsdmlrLnN5c3RlbS5WTVJ1bnRpbWUAAWUABmV4ZW1wdAAJZXhlbXB0QWxsAAdmb3JOYW1lAA9mcmVlLXJlZmxlY3Rpb24AEmdldEFwcGxpY2F0aW9uSW5mbwARZ2V0RGVjbGFyZWRNZXRob2QACmdldFJ1bnRpbWUABmludm9rZQALbG9hZExpYnJhcnkAGG1lLndlaXNodS5mcmVlcmVmbGVjdGlvbgAGbWV0aG9kAAdtZXRob2RzABlyZWZsZWN0IGJvb3RzdHJhcCBmYWlsZWQ6AAdyZWxlYXNlAApzVm1SdW50aW1lABZzZXRIaWRkZW5BcGlFeGVtcHRpb25zABB0YXJnZXRTZGtWZXJzaW9uAAZ1bnNlYWwADHVuc2VhbE5hdGl2ZQAOdm1SdW50aW1lQ2xhc3MAAQAAAAoAAAACAAAACgAQAAEAAAASAAAAAQAAAAAAAAADAAAACgAKAAwAAAABAAAAAQAAAAIAAAAJABEAARcGBhc4FzwfFwAEARcBARcfAAAAAAAABgAHDgAWAAcOav8DATIOARUQAwI1DvAEBEMJGgESDwMDNg4BGw+pBQIFAwUEGR4DAC8NAA4ABw4ALAE6Bw4ANgE7ByydGuIBAQMALw0eAEgABw4ADQAHDgATAS0HHXIZa1oAAAEAAQABAAAANAgAAAQAAABwEAwAAAAOAAoAAAADAAEAOQgAAHsAAABgBQEAEwYcADRlbQAcBQgAGgYxABIXI3cQABIIHAkKAE0JBwhuMAsAZQcMARwFCAAaBjQAEicjdxAAEggcCQoATQkHCBIYHAkQAE0JBwhuMAsAZQcMAhIFEhYjZhEAEgcaCC0ATQgGB24wDgBRBgwEHwQIABIlI1URABIGGgc1AE0HBQYSFhIHTQcFBm4wDgBCBQwDHwMNABIlI1URABIGGgc+AE0HBQYSFhIXI3cQABIIHAkSAE0JBwhNBwUGbjAOAEIFDAUfBQ0AaQUKABIFEgYjZhEAbjAOAFMGDAVpBQkADgANABoFBgAaBjsAcTABAGUAKPcAAAYAAABrAAEAAQEMcgEAAQABAAAAaAgAAAQAAABwEAwAAAAOAAMAAQABAAAAbQgAAAsAAAASECMAEgASAU0CAAFxEAYAAAAKAA8AAAAIAAEAAwABAHMIAAAdAAAAEhESAmIDCQA4AwYAYgMKADkDBAABIQ8BYgMKAGIECQASFSNVEQASBk0HBQZuMA4AQwUo8g0AASEo7wAADAAAAA0AAQABAQwaAwAAAAEAAACDCAAADQAAABIQIwASABIBGgIPAE0CAAFxEAYAAAAKAA8AAAABAAEAAQAAAIgIAAAEAAAAcBAMAAAADgAEAAEAAQAAAI0IAAAeAAAAEgBgAQEAEwIcADUhAwAPAHEABwAAAAoBOQH7/xoAMgBxEA0AAABuEAAAAwAMAFIAAABxEAoAAAAKACjqBgABAAIZARkBGQEZARkBGQKBgASYEQMABQAIGgEKAQoDiIAEsBEBgYAExBMBCdwTAYkBhBQBCdwUAQADAAsaCIGABIgVAQmgFQGKAgAAAAAPAAAAAAAAAAEAAAAAAAAAAQAAAEMAAABwAAAAAgAAABMAAAB8AQAAAwAAAAsAAADIAQAABAAAAAwAAABMAgAABQAAAA8AAACsAgAABgAAAAMAAAAkAwAAAiAAAEMAAACEAwAAARAAAAcAAADgBwAABSAAAAMAAAAcCAAAAxAAAAEAAAAwCAAAAyAAAAgAAAA0CAAAASAAAAgAAACYCAAAACAAAAMAAADsCgAAABAAAAEAAAA8CwAA\";\n" +
|
||||
"\n" +
|
||||
" private static native int unsealNative(int targetSdkVersion);\n" +
|
||||
"\n" +
|
||||
" public static int unseal(Context context) {\n" +
|
||||
" if (SDK_INT < 28) {\n" +
|
||||
" // Below Android P, ignore\n" +
|
||||
" return 0;\n" +
|
||||
" }\n" +
|
||||
" // try exempt API first.\n" +
|
||||
" if (exemptAll()) {\n" +
|
||||
" return 0;\n" +
|
||||
" }\n" +
|
||||
" if (unsealByDexFile(context)) {\n" +
|
||||
" return 0;\n" +
|
||||
" }\n" +
|
||||
" return -1;\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" @SuppressWarnings({\"deprecation\", \"ResultOfMethodCallIgnored\"})\n" +
|
||||
" private static boolean unsealByDexFile(Context context) {\n" +
|
||||
" byte[] bytes = Base64.decode(DEX, Base64.NO_WRAP);\n" +
|
||||
" File codeCacheDir = getCodeCacheDir(context);\n" +
|
||||
" if (codeCacheDir == null) {\n" +
|
||||
" return false;\n" +
|
||||
" }\n" +
|
||||
" File code = new File(codeCacheDir, \"__temp_\" + System.currentTimeMillis() + \".dex\");\n" +
|
||||
" try {\n" +
|
||||
" try (FileOutputStream fos = new FileOutputStream(code)) {\n" +
|
||||
" fos.write(bytes);\n" +
|
||||
" }\n" +
|
||||
" DexFile dexFile = new DexFile(code);\n" +
|
||||
" // This class is hardcoded in the dex, Don't use BootstrapClass.class to reference it\n" +
|
||||
" // it maybe obfuscated!!\n" +
|
||||
" Class<?> bootstrapClass = dexFile.loadClass(\"com.highcapable.yukihookapi.thirdparty.me.weishu.reflection.BootstrapClass\", null);\n" +
|
||||
" Method exemptAll = bootstrapClass.getDeclaredMethod(\"exemptAll\");\n" +
|
||||
" return (boolean) exemptAll.invoke(null);\n" +
|
||||
" } catch (Throwable e) {\n" +
|
||||
" e.printStackTrace();\n" +
|
||||
" return false;\n" +
|
||||
" } finally {\n" +
|
||||
" if (code.exists()) {\n" +
|
||||
" code.delete();\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" private static File getCodeCacheDir(Context context) {\n" +
|
||||
" if (context != null) {\n" +
|
||||
" return context.getCodeCacheDir();\n" +
|
||||
" }\n" +
|
||||
" String tmpDir = System.getProperty(\"java.io.tmpdir\");\n" +
|
||||
" if (TextUtils.isEmpty(tmpDir)) {\n" +
|
||||
" return null;\n" +
|
||||
" }\n" +
|
||||
" File tmp = new File(tmpDir);\n" +
|
||||
" if (!tmp.exists()) {\n" +
|
||||
" return null;\n" +
|
||||
" }\n" +
|
||||
" return tmp;\n" +
|
||||
" }\n" +
|
||||
"}")
|
||||
)
|
Reference in New Issue
Block a user