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:
2023-09-21 03:19:13 +08:00
parent dd7912a577
commit 8ba166dab9
179 changed files with 919 additions and 682 deletions

1
yukihookapi-core/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/build

View File

21
yukihookapi-core/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@@ -0,0 +1,482 @@
/*
* 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/2.
*/
@file:Suppress("MemberVisibilityCanBePrivate", "unused")
package com.highcapable.yukihookapi
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.content.res.Resources
import com.highcapable.yukihookapi.YukiHookAPI.Configs.debugLog
import com.highcapable.yukihookapi.YukiHookAPI.configs
import com.highcapable.yukihookapi.YukiHookAPI.encase
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.generated.YukiHookAPIProperties
import com.highcapable.yukihookapi.hook.core.api.compat.HookApiCategoryHelper
import com.highcapable.yukihookapi.hook.core.api.compat.HookApiProperty
import com.highcapable.yukihookapi.hook.core.api.compat.type.ExecutorType
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.isTaiChiModuleActive
import com.highcapable.yukihookapi.hook.factory.processName
import com.highcapable.yukihookapi.hook.log.YukiHookLogger
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.log.yLoggerI
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.bridge.status.YukiXposedModuleStatus
import com.highcapable.yukihookapi.hook.xposed.bridge.type.HookEntryType
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookPrefsBridge
import java.lang.reflect.Member
/**
* [YukiHookAPI] 的装载调用类
*
* 可以实现作为模块装载和自定义 Hook 装载两种方式
*
* Xposed 模块装载方式已经自动对接相关 API - 可直接调用 [encase] 完成操作
*
* 你可以调用 [configs] 对 [YukiHookAPI] 进行配置
*/
object YukiHookAPI {
/** 是否还未输出欢迎信息 */
private var isShowSplashLogOnceTime = true
/** 标识是否从自定义 Hook API 装载 */
internal var isLoadedFromBaseContext = false
/** 获取当前 [YukiHookAPI] 的版本 */
const val API_VERSION_NAME = YukiHookAPIProperties.PROJECT_YUKIHOOKAPI_CORE_VERSION
/** 获取当前 [YukiHookAPI] 的版本号 */
const val API_VERSION_CODE = 44
/**
* 当前 [YukiHookAPI] 的状态
*/
object Status {
/**
* 获取项目编译完成的时间戳 (当前本地时间)
* @return [Long]
*/
val compiledTimestamp get() = runCatching { YukiHookAPI_Impl.compiledTimestamp }.getOrNull() ?: 0L
/**
* 获取当前是否为 (Xposed) 宿主环境
* @return [Boolean]
*/
val isXposedEnvironment get() = YukiXposedModule.isXposedEnvironment
/**
* 获取当前 Hook Framework 名称
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [Executor.name]
* @return [String]
*/
@Deprecated(
message = "请使用新方式来实现此功能",
ReplaceWith("Executor.name", "com.highcapable.yukihookapi.YukiHookAPI.Status.Executor")
)
val executorName get() = Executor.name
/**
* 获取当前 Hook Framework 版本
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [Executor.apiLevel]、[Executor.versionName]、[Executor.versionCode]
* @return [Int]
*/
@Deprecated(
message = "请使用新方式来实现此功能",
ReplaceWith("Executor.apiLevel", "com.highcapable.yukihookapi.YukiHookAPI.Status.Executor")
)
val executorVersion get() = Executor.apiLevel
/**
* 判断模块是否在 Xposed 或太极、无极中激活
*
* - ❗在模块环境中你需要将 [Application] 继承于 [ModuleApplication]
*
* - ❗在模块环境中需要启用 [Configs.isEnableHookModuleStatus]
*
* - ❗在 (Xposed) 宿主环境中仅返回非 [isTaiChiModuleActive] 的激活状态
* @return [Boolean] 是否激活
*/
val isModuleActive get() = isXposedEnvironment || YukiXposedModuleStatus.isActive || isTaiChiModuleActive
/**
* 仅判断模块是否在 Xposed 中激活
*
* - ❗在模块环境中需要启用 [Configs.isEnableHookModuleStatus]
*
* - ❗在 (Xposed) 宿主环境中始终返回 true
* @return [Boolean] 是否激活
*/
val isXposedModuleActive get() = isXposedEnvironment || YukiXposedModuleStatus.isActive
/**
* 仅判断模块是否在太极、无极中激活
*
* - ❗在模块环境中你需要将 [Application] 继承于 [ModuleApplication]
*
* - ❗在 (Xposed) 宿主环境中始终返回 false
* @return [Boolean] 是否激活
*/
val isTaiChiModuleActive get() = isXposedEnvironment.not() && (ModuleApplication.currentContext?.isTaiChiModuleActive ?: false)
/**
* 判断当前 Hook Framework 是否支持资源钩子(Resources Hook)
*
* - ❗在模块环境中需要启用 [Configs.isEnableHookModuleStatus]
*
* - ❗在 (Xposed) 宿主环境中可能会延迟等待事件回调后才会返回 true
*
* - ❗请注意你需要确保 [InjectYukiHookWithXposed.isUsingResourcesHook] 已启用 - 否则始终返回 false
* @return [Boolean] 是否支持
*/
val isSupportResourcesHook
get() = YukiXposedModule.isSupportResourcesHook.takeIf { isXposedEnvironment } ?: YukiXposedModuleStatus.isSupportResourcesHook
/**
* 当前 [YukiHookAPI] 使用的 Hook Framework 相关信息
*/
object Executor {
/**
* 获取当前 Hook Framework 名称
*
* - ❗在模块环境中需要启用 [Configs.isEnableHookModuleStatus]
* @return [String] 无法获取会返回 unknown - 获取失败会返回 invalid
*/
val name
get() = HookApiProperty.name.takeIf { isXposedEnvironment } ?: when {
isXposedModuleActive -> YukiXposedModuleStatus.executorName
isTaiChiModuleActive -> HookApiProperty.TAICHI_XPOSED_NAME
else -> YukiXposedModuleStatus.executorName
}
/**
* 获取当前 Hook Framework 类型
*
* - ❗在模块环境中需要启用 [Configs.isEnableHookModuleStatus]
* @return [ExecutorType]
*/
val type get() = HookApiProperty.type.takeIf { isXposedEnvironment } ?: HookApiProperty.type(YukiXposedModuleStatus.executorName)
/**
* 获取当前 Hook Framework 的 API 版本
*
* - ❗在模块环境中需要启用 [Configs.isEnableHookModuleStatus]
* @return [Int] 无法获取会返回 -1
*/
val apiLevel get() = HookApiProperty.apiLevel.takeIf { isXposedEnvironment } ?: YukiXposedModuleStatus.executorApiLevel
/**
* 获取当前 Hook Framework 版本名称
*
* - ❗在模块环境中需要启用 [Configs.isEnableHookModuleStatus]
* @return [String] 无法获取会返回 unknown - 不支持会返回 unsupported
*/
val versionName get() = HookApiProperty.versionName.takeIf { isXposedEnvironment } ?: YukiXposedModuleStatus.executorVersionName
/**
* 获取当前 Hook Framework 版本号
*
* - ❗在模块环境中需要启用 [Configs.isEnableHookModuleStatus]
* @return [Int] 无法获取会返回 -1 - 不支持会返回 0
*/
val versionCode get() = HookApiProperty.versionCode.takeIf { isXposedEnvironment } ?: YukiXposedModuleStatus.executorVersionCode
}
}
/**
* 配置 [YukiHookAPI]
*/
object Configs {
/**
* 配置 [YukiHookLogger.Configs] 相关参数
* @param initiate 方法体
*/
inline fun debugLog(initiate: YukiHookLogger.Configs.() -> Unit) = YukiHookLogger.Configs.apply(initiate).build()
/**
* 这是一个调试日志的全局标识
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [debugLog] 并使用 [YukiHookLogger.Configs.tag]
*/
@Deprecated(message = "请使用新方式来实现此功能")
var debugTag
get() = YukiHookLogger.Configs.tag
set(value) {
YukiHookLogger.Configs.tag = value
}
/**
* 是否开启调试模式 - 默认启用
*
* 启用后将交由日志输出管理器打印详细 Hook 日志到控制台
*
* 关闭后将只输出 Error 级别的日志
*
* 请过滤 [debugTag] 即可找到每条日志
*
* 当 [YukiHookLogger.Configs.isEnable] 关闭后 [isDebug] 也将同时关闭
*/
var isDebug = true
/**
* 是否启用调试日志的输出功能
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [debugLog] 并使用 [YukiHookLogger.Configs.isEnable]
*/
@Deprecated(message = "请使用新方式来实现此功能")
var isAllowPrintingLogs
get() = YukiHookLogger.Configs.isEnable
set(value) {
YukiHookLogger.Configs.isEnable = value
}
/**
* 是否启用 [YukiHookPrefsBridge] 的键值缓存功能
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [isEnablePrefsBridgeCache]
*/
@Deprecated(message = "请使用新的命名方法来实现此功能", ReplaceWith("isEnablePrefsBridgeCache"))
var isEnableModulePrefsCache = false
/**
* 是否启用 [YukiHookPrefsBridge] 的键值缓存功能
*
* - ❗此方法及功能已被移除 - 在之后的版本中将直接被删除
*
* - ❗键值的直接缓存功能已被移除 - 因为其存在内存溢出 (OOM) 问题
*/
@Deprecated(message = "此方法及功能已被移除,请删除此方法")
var isEnablePrefsBridgeCache = false
/**
* 是否启用当前 Xposed 模块自身 [Resources] 缓存功能
*
* - 为防止内存复用过高问题 - 此功能默认启用
*
* - ❗关闭后每次使用 [PackageParam.moduleAppResources] 都会重新创建 - 可能会造成运行缓慢
*
* 你可以手动调用 [PackageParam.refreshModuleAppResources] 来刷新缓存
*/
var isEnableModuleAppResourcesCache = true
/**
* 是否启用 Hook Xposed 模块激活等状态功能
*
* - 为原生支持 Xposed 模块激活状态检测 - 此功能默认启用
*
* - ❗关闭后你将不能再在模块环境中使用 [YukiHookAPI.Status] 中的功能
*/
var isEnableHookModuleStatus = true
/**
* 是否启用 Hook [SharedPreferences]
*
* 启用后将在模块启动时强制将 [SharedPreferences] 文件权限调整为 [Context.MODE_WORLD_READABLE] (0664)
*
* - ❗这是一个可选的实验性功能 - 此功能默认不启用
*
* - 仅用于修复某些系统可能会出现在启用了 New XSharedPreferences 后依然出现文件权限错误问题 - 若你能正常使用 [YukiHookPrefsBridge] 就不建议启用此功能
*/
var isEnableHookSharedPreferences = false
/**
* 是否启用当前 Xposed 模块与宿主交互的 [YukiHookDataChannel] 功能
*
* 请确保 Xposed 模块的 [Application] 继承于 [ModuleApplication] 才能有效
*
* - 此功能默认启用 - 关闭后将不会在功能初始化的时候装载 [YukiHookDataChannel]
*/
var isEnableDataChannel = true
/**
* 是否启用 [Member] 缓存功能
*
* - ❗此方法及功能已被移除 - 在之后的版本中将直接被删除
*
* - ❗[Member] 的直接缓存功能已被移除 - 因为其存在内存溢出 (OOM) 问题
*/
@Deprecated(message = "此方法及功能已被移除,请删除此方法")
var isEnableMemberCache = false
}
/**
* 配置 [YukiHookAPI] 相关参数
*
* 详情请参考 [configs 方法](https://fankes.github.io/YukiHookAPI/zh-cn/config/api-example#configs-%E6%96%B9%E6%B3%95)
*
* For English version, see [configs Method](https://fankes.github.io/YukiHookAPI/en/config/api-example#configs-method)
* @param initiate 方法体
*/
inline fun configs(initiate: Configs.() -> Unit) {
Configs.apply(initiate)
}
/**
* 作为 Xposed 模块装载调用入口方法
*
* 用法请参考 [API 文档](https://fankes.github.io/YukiHookAPI/zh-cn/api/home)
*
* For English version, see [API Document](https://fankes.github.io/YukiHookAPI/en/api/home)
*
* 配置请参考 [通过 lambda 创建](https://fankes.github.io/YukiHookAPI/zh-cn/config/api-example#%E9%80%9A%E8%BF%87-lambda-%E5%88%9B%E5%BB%BA)
*
* For English version, see [Created by lambda](https://fankes.github.io/YukiHookAPI/en/config/api-example#created-by-lambda)
* @param initiate Hook 方法体
*/
fun encase(initiate: PackageParam.() -> Unit) {
isLoadedFromBaseContext = false
if (YukiXposedModule.isXposedEnvironment)
YukiXposedModule.packageParamCallback = initiate
else printNotFoundHookApiError()
}
/**
* 作为 Xposed 模块装载调用入口方法
*
* 用法请参考 [API 文档](https://fankes.github.io/YukiHookAPI/zh-cn/api/home)
*
* For English version, see [API Document](https://fankes.github.io/YukiHookAPI/en/api/home)
*
* 配置请参考 [通过自定义 Hooker 创建](https://fankes.github.io/YukiHookAPI/zh-cn/config/api-example#%E9%80%9A%E8%BF%87%E8%87%AA%E5%AE%9A%E4%B9%89-hooker-%E5%88%9B%E5%BB%BA)
*
* For English version, see [Created by Custom Hooker](https://fankes.github.io/YukiHookAPI/en/config/api-example#created-by-custom-hooker)
* @param hooker Hook 子类数组 - 必填不能为空
* @throws IllegalStateException 如果 [hooker] 是空的
*/
fun encase(vararg hooker: YukiBaseHooker) {
isLoadedFromBaseContext = false
if (YukiXposedModule.isXposedEnvironment)
YukiXposedModule.packageParamCallback = {
if (hooker.isNotEmpty())
hooker.forEach { it.assignInstance(packageParam = this) }
else yLoggerE(msg = "Failed to passing \"encase\" method because your hooker param is empty", isImplicit = true)
}
else printNotFoundHookApiError()
}
/**
* 作为 [Application] 装载调用入口方法
*
* 请在 [Application.attachBaseContext] 中实现 [YukiHookAPI] 的装载
*
* 详情请参考 [作为 Hook API 使用](https://fankes.github.io/YukiHookAPI/zh-cn/guide/quick-start#%E4%BD%9C%E4%B8%BA-hook-api-%E4%BD%BF%E7%94%A8)
*
* For English version, see [Use as Hook API](https://fankes.github.io/YukiHookAPI/en/guide/quick-start#use-as-hook-api)
*
* 用法请参考 [API 文档](https://fankes.github.io/YukiHookAPI/zh-cn/api/home)
*
* For English version, see [API Document](https://fankes.github.io/YukiHookAPI/en/api/home)
*
* 配置请参考 [通过 lambda 创建](https://fankes.github.io/YukiHookAPI/zh-cn/config/api-example#%E9%80%9A%E8%BF%87-lambda-%E5%88%9B%E5%BB%BA)
*
* For English version, see [Created by lambda](https://fankes.github.io/YukiHookAPI/en/config/api-example#created-by-lambda)
* @param baseContext attachBaseContext
* @param initiate Hook 方法体
*/
fun encase(baseContext: Context?, initiate: PackageParam.() -> Unit) {
isLoadedFromBaseContext = true
when {
HookApiCategoryHelper.hasAvailableHookApi && baseContext != null ->
initiate(baseContext.createPackageParam().apply { printSplashInfo() })
else -> printNotFoundHookApiError()
}
}
/**
* 作为 [Application] 装载调用入口方法
*
* 请在 [Application.attachBaseContext] 中实现 [YukiHookAPI] 的装载
*
* 详情请参考 [作为 Hook API 使用](https://fankes.github.io/YukiHookAPI/zh-cn/guide/quick-start#%E4%BD%9C%E4%B8%BA-hook-api-%E4%BD%BF%E7%94%A8)
*
* For English version, see [Use as Hook API](https://fankes.github.io/YukiHookAPI/en/guide/quick-start#use-as-hook-api)
*
* 用法请参考 [API 文档](https://fankes.github.io/YukiHookAPI/zh-cn/api/home)
*
* For English version, see [API Document](https://fankes.github.io/YukiHookAPI/en/api/home)
*
* 配置请参考 [通过自定义 Hooker 创建](https://fankes.github.io/YukiHookAPI/zh-cn/config/api-example#%E9%80%9A%E8%BF%87%E8%87%AA%E5%AE%9A%E4%B9%89-hooker-%E5%88%9B%E5%BB%BA)
*
* For English version, see [Created by Custom Hooker](https://fankes.github.io/YukiHookAPI/en/config/api-example#created-by-custom-hooker)
* @param baseContext attachBaseContext
* @param hooker Hook 子类数组 - 必填不能为空
* @throws IllegalStateException 如果 [hooker] 是空的
*/
fun encase(baseContext: Context?, vararg hooker: YukiBaseHooker) {
isLoadedFromBaseContext = true
if (HookApiCategoryHelper.hasAvailableHookApi)
(if (baseContext != null)
if (hooker.isNotEmpty()) {
printSplashInfo()
hooker.forEach { it.assignInstance(packageParam = baseContext.createPackageParam()) }
} else yLoggerE(msg = "Failed to passing \"encase\" method because your hooker param is empty", isImplicit = true))
else printNotFoundHookApiError()
}
/** 输出欢迎信息调试日志 */
internal fun printSplashInfo() {
if (Configs.isDebug.not() || isShowSplashLogOnceTime.not()) return
isShowSplashLogOnceTime = false
yLoggerI(
msg = "Welcome to YukiHookAPI $API_VERSION_NAME($API_VERSION_CODE)! Using ${Status.Executor.name} API ${Status.Executor.apiLevel}",
isImplicit = true
)
}
/** 输出找不到 Hook API 的错误日志 */
private fun printNotFoundHookApiError() =
yLoggerE(msg = "Could not found any available Hook APIs in current environment! Aborted", isImplicit = true)
/**
* 通过 baseContext 创建 Hook 入口类
* @return [PackageParam]
*/
private fun Context.createPackageParam() =
PackageParam(PackageParamWrapper(HookEntryType.PACKAGE, packageName, processName, classLoader, applicationInfo))
}

View File

@@ -0,0 +1,55 @@
/*
* 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/4/3.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.annotation
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@MustBeDocumented
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FUNCTION,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.PROPERTY,
AnnotationTarget.FIELD,
AnnotationTarget.LOCAL_VARIABLE,
AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.TYPEALIAS
)
@Retention(AnnotationRetention.BINARY)
/**
* - ❗标记为不规范使用可能会引发问题的 API
*
* 此功能用于规范代码调用域 - 非调用域内的 API 将会在 IDE 中显示警告
*
* 此功能除继承和接口外不应该在这里被调用
*/
annotation class CauseProblemsApi

View File

@@ -0,0 +1,53 @@
/*
* 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/4/3.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.annotation
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
@MustBeDocumented
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FUNCTION,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.PROPERTY,
AnnotationTarget.FIELD,
AnnotationTarget.LOCAL_VARIABLE,
AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.TYPEALIAS
)
@Retention(AnnotationRetention.BINARY)
/**
* - ❗标记为自动生成调用的 API
*
* 此功能除继承和接口外不应该在这里被调用
*/
annotation class YukiGenerateApi

View File

@@ -0,0 +1,53 @@
/*
* 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/4/3.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.annotation
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
@MustBeDocumented
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.CONSTRUCTOR,
AnnotationTarget.FUNCTION,
AnnotationTarget.ANNOTATION_CLASS,
AnnotationTarget.PROPERTY,
AnnotationTarget.FIELD,
AnnotationTarget.LOCAL_VARIABLE,
AnnotationTarget.VALUE_PARAMETER,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER,
AnnotationTarget.TYPEALIAS
)
@Retention(AnnotationRetention.BINARY)
/**
* - ❗标记功能为私有功能性 API
*
* 此功能除继承和接口外不应该在这里被调用
*/
internal annotation class YukiPrivateApi

View File

@@ -0,0 +1,67 @@
/*
* 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/3.
*/
package com.highcapable.yukihookapi.annotation.xposed
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
import de.robv.android.xposed.IXposedHookInitPackageResources
/**
* 标识 [YukiHookAPI] 注入 Xposed 入口的类注解
*
* - 你的项目 source 目录默认为 "src/main/" 可在 [sourcePath] 中进行自定义 - 自动处理程序将只检查 ../[sourcePath]/java.. 中间部分
*
* - 自动处理程序将自动在 ../[sourcePath]/assets/ 下建立 xposed_init 文件
*
* 你的 xposed_init 入口将被自动生成为 --> 你的入口类完整包名/你的入口类名_YukiHookXposedInit 或自定义 [entryClassName]
*
* - 你可以在 [modulePackageName] 自定义你的模块包名 - 未定义的情况下会使用 AndroidManifest.xml 与 build.gradle/kts 进行分析 - 失败编译会报错
*
* - 为了防止模块包名无法正常被识别 - 自定义 [modulePackageName] 会在编译时产生警告
*
* - ❗最后这一点很重要:请不要随意修改项目 ../[sourcePath]/assets/xposed_init 中的内容 - 否则可能会导致模块无法装载
*
* - ❗你必须将被注解的类继承于 [IYukiHookXposedInit] 接口实现 [IYukiHookXposedInit.onHook] 方法 - 否则编译会报错
*
* - ❗只能拥有一个 Hook 入口 - 若存在多个注解编译会报错
*
* 详情请参考 [InjectYukiHookWithXposed 注解](https://fankes.github.io/YukiHookAPI/zh-cn/config/xposed-using#injectyukihookwithxposed-%E6%B3%A8%E8%A7%A3)
*
* For English version, see [InjectYukiHookWithXposed Annotation](https://fankes.github.io/YukiHookAPI/en/config/xposed-using#injectyukihookwithxposed-annotation)
* @param sourcePath 你的项目 source 相对路径 - 默认为 ..src/main..
* @param modulePackageName 模块包名 - 不填默认自动生成
* @param entryClassName 定义 [YukiHookAPI] 自动生成 Xposed 模块入口类的名称 - 不填默认使用 "入口类名_YukiHookXposedInit" 进行生成
* @param isUsingResourcesHook 是否启用 Resources Hook (资源钩子) - 启用后将自动注入 [IXposedHookInitPackageResources] - 默认是
*/
@Target(AnnotationTarget.CLASS)
annotation class InjectYukiHookWithXposed(
val sourcePath: String = "src/main",
val modulePackageName: String = "",
val entryClassName: String = "",
val isUsingResourcesHook: Boolean = true
)

View File

@@ -0,0 +1,156 @@
/*
* 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/4/4.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.bean
import com.highcapable.yukihookapi.hook.core.finder.members.FieldFinder
import com.highcapable.yukihookapi.hook.core.finder.members.MethodFinder
import com.highcapable.yukihookapi.hook.core.finder.type.factory.FieldConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.MethodConditions
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.generic
import com.highcapable.yukihookapi.hook.factory.method
/**
* 当前实例的类操作对象
* @param classSet 当前实例的 [Class]
* @param instance 当前实例本身
*/
class CurrentClass @PublishedApi internal constructor(@PublishedApi internal val classSet: Class<*>, @PublishedApi internal val instance: Any) {
/** 是否开启忽略错误警告功能 */
@PublishedApi
internal var isShutErrorPrinting = false
/**
* 获得当前 [classSet] 的 [Class.getName]
* @return [String]
*/
val name get() = classSet.name ?: instance.javaClass.name ?: ""
/**
* 获得当前 [classSet] 的 [Class.getSimpleName]
* @return [String]
*/
val simpleName get() = classSet.simpleName ?: instance.javaClass.simpleName ?: ""
/**
* 获得当前实例中的泛型父类
*
* 如果当前实例不存在泛型将返回 null
* @return [GenericClass] or null
*/
fun generic() = classSet.generic()
/**
* 获得当前实例中的泛型父类
*
* 如果当前实例不存在泛型将返回 null
* @param initiate 实例方法体
* @return [GenericClass] or null
*/
inline fun generic(initiate: GenericClass.() -> Unit) = classSet.generic(initiate)
/**
* 调用父类实例
* @return [SuperClass]
*/
fun superClass() = SuperClass(classSet.superclass)
/**
* 调用当前实例中的变量
* @param initiate 查找方法体
* @return [FieldFinder.Result.Instance]
*/
inline fun field(initiate: FieldConditions) = classSet.field(initiate).result { if (isShutErrorPrinting) ignored() }.get(instance)
/**
* 调用当前实例中的方法
* @param initiate 查找方法体
* @return [MethodFinder.Result.Instance]
*/
inline fun method(initiate: MethodConditions) = classSet.method(initiate).result { if (isShutErrorPrinting) ignored() }.get(instance)
/**
* 当前类的父类实例的类操作对象
*
* - ❗请使用 [superClass] 方法来获取 [SuperClass]
* @param superClassSet 父类 [Class] 对象
*/
inner class SuperClass internal constructor(@PublishedApi internal val superClassSet: Class<*>) {
/**
* 获得当前 [classSet] 中父类的 [Class.getName]
* @return [String]
*/
val name get() = superClassSet.name ?: ""
/**
* 获得当前 [classSet] 中父类的 [Class.getSimpleName]
* @return [String]
*/
val simpleName get() = superClassSet.simpleName ?: ""
/**
* 获得当前实例父类中的泛型父类
*
* 如果当前实例不存在泛型将返回 null
* @return [GenericClass] or null
*/
fun generic() = superClassSet.generic()
/**
* 获得当前实例父类中的泛型父类
*
* 如果当前实例不存在泛型将返回 null
* @param initiate 实例方法体
* @return [GenericClass] or null
*/
inline fun generic(initiate: GenericClass.() -> Unit) = superClassSet.generic(initiate)
/**
* 调用父类实例中的变量
* @param initiate 查找方法体
* @return [FieldFinder.Result.Instance]
*/
inline fun field(initiate: FieldConditions) = superClassSet.field(initiate).result { if (isShutErrorPrinting) ignored() }.get(instance)
/**
* 调用父类实例中的方法
* @param initiate 查找方法体
* @return [MethodFinder.Result.Instance]
*/
inline fun method(initiate: MethodConditions) =
superClassSet.method(initiate).result { if (isShutErrorPrinting) ignored() }.get(instance)
override fun toString() = "CurrentClass super [$superClassSet]"
}
override fun toString() = "CurrentClass [$classSet]"
}

View File

@@ -0,0 +1,56 @@
/*
* 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("unused", "UNCHECKED_CAST")
package com.highcapable.yukihookapi.hook.bean
import java.lang.reflect.ParameterizedType
/**
* 当前 [Class] 的泛型父类操作对象
* @param type 类型声明实例
*/
class GenericClass internal constructor(@PublishedApi internal val type: ParameterizedType) {
/**
* 获得泛型参数数组下标的 [Class] 实例
* @param index 数组下标 - 默认 0
* @return [Class]
*/
fun argument(index: Int = 0) = type.actualTypeArguments[index] as Class<*>
/**
* 获得泛型参数数组下标的 [Class] 实例
* @param index 数组下标 - 默认 0
* @return [Class]<[T]>
* @throws IllegalStateException 如果 [Class] 的类型不为 [T]
*/
@JvmName("argument_Generics")
inline fun <reified T> argument(index: Int = 0) =
type.actualTypeArguments[index] as? Class<T> ?: error("Target Class type cannot cast to ${T::class.java}")
}

View File

@@ -0,0 +1,43 @@
/*
* 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/9.
*/
package com.highcapable.yukihookapi.hook.bean
/**
* 创建一个当前 Hook 的 [Class] 接管类
* @param instance 实例
* @param name 完整名称
* @param throwable 异常
*/
class HookClass internal constructor(
@PublishedApi internal var instance: Class<*>? = null,
@PublishedApi internal var name: String,
@PublishedApi internal var throwable: Throwable? = null
) {
override fun toString() = "[class] $name [throwable] $throwable [instance] $instance"
}

View File

@@ -0,0 +1,39 @@
/*
* 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/5/1.
*/
package com.highcapable.yukihookapi.hook.bean
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiResources
/**
* 创建一个当前 Hook 的 [YukiResources] 接管类
* @param instance 实例
*/
class HookResources internal constructor(var instance: YukiResources? = null) {
override fun toString() = "[instance] $instance"
}

View File

@@ -0,0 +1,77 @@
/*
* 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/10.
*/
package com.highcapable.yukihookapi.hook.bean
import com.highcapable.yukihookapi.hook.factory.toClassOrNull
/**
* 这是一个不确定性 [Class] 类名装载器
* @param name 可指定多个类名 - 将会自动匹配存在的第一个类名
*/
class VariousClass(private vararg val name: String) {
/**
* 获取匹配的实体类
*
* - 使用当前 [loader] 装载目标 [Class]
* @param loader 当前 [ClassLoader] - 若留空使用默认 [ClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class]
* @throws IllegalStateException 如果任何 [Class] 都没有匹配到
*/
fun get(loader: ClassLoader? = null, initialize: Boolean = false): Class<*> {
var finalClass: Class<*>? = null
if (name.isNotEmpty()) run {
name.forEach {
finalClass = it.toClassOrNull(loader, initialize)
if (finalClass != null) return@run
}
}
return finalClass ?: error("VariousClass match failed of those $this")
}
/**
* 获取匹配的实体类
*
* - 使用当前 [loader] 装载目标 [Class]
*
* 匹配不到 [Class] 会返回 null - 不会抛出异常
* @param loader 当前 [ClassLoader] - 若留空使用默认 [ClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class] or null
*/
fun getOrNull(loader: ClassLoader? = null, initialize: Boolean = false) = runCatching { get(loader, initialize) }.getOrNull()
override fun toString(): String {
var result = ""
return if (name.isNotEmpty()) {
name.forEach { result += "\"$it\"," }
"[${result.substring(0, result.lastIndex)}]"
} else "[]"
}
}

View File

@@ -0,0 +1,929 @@
/*
* 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/2.
*/
@file:Suppress("MemberVisibilityCanBePrivate", "UnusedReceiverParameter", "unused", "PropertyName")
package com.highcapable.yukihookapi.hook.core
import android.util.ArrayMap
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
import com.highcapable.yukihookapi.hook.bean.HookClass
import com.highcapable.yukihookapi.hook.core.api.compat.HookApiCategoryHelper
import com.highcapable.yukihookapi.hook.core.api.helper.YukiHookHelper
import com.highcapable.yukihookapi.hook.core.api.priority.YukiHookPriority
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiMemberHook
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiMemberReplacement
import com.highcapable.yukihookapi.hook.core.api.result.YukiHookResult
import com.highcapable.yukihookapi.hook.core.finder.base.MemberBaseFinder
import com.highcapable.yukihookapi.hook.core.finder.members.ConstructorFinder
import com.highcapable.yukihookapi.hook.core.finder.members.FieldFinder
import com.highcapable.yukihookapi.hook.core.finder.members.MethodFinder
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ConstructorConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.FieldConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.MethodConditions
import com.highcapable.yukihookapi.hook.factory.MembersType
import com.highcapable.yukihookapi.hook.factory.allConstructors
import com.highcapable.yukihookapi.hook.factory.allMethods
import com.highcapable.yukihookapi.hook.factory.constructor
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.factory.notExtends
import com.highcapable.yukihookapi.hook.factory.notImplements
import com.highcapable.yukihookapi.hook.factory.toJavaPrimitiveType
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.log.yLoggerI
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.param.HookParam
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.type.java.AnyClass
import com.highcapable.yukihookapi.hook.type.java.JavaClass
import com.highcapable.yukihookapi.hook.type.java.JavaClassLoader
import com.highcapable.yukihookapi.hook.type.java.JavaConstructorClass
import com.highcapable.yukihookapi.hook.type.java.JavaFieldClass
import com.highcapable.yukihookapi.hook.type.java.JavaMemberClass
import com.highcapable.yukihookapi.hook.type.java.JavaMethodClass
import com.highcapable.yukihookapi.hook.utils.RandomSeed
import com.highcapable.yukihookapi.hook.utils.await
import com.highcapable.yukihookapi.hook.utils.conditions
import com.highcapable.yukihookapi.hook.xposed.bridge.type.HookEntryType
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
/**
* [YukiHookAPI] 的 [Member] 核心 Hook 实现类
*
* 核心 API 对接 [YukiHookHelper] 实现
* @param packageParam 需要传入 [PackageParam] 实现方法调用
* @param hookClass 要 Hook 的 [HookClass] 实例
*/
class YukiMemberHookCreator @PublishedApi internal constructor(
@PublishedApi internal val packageParam: PackageParam,
@PublishedApi internal val hookClass: HookClass
) {
/** 默认 Hook 回调优先级 */
val PRIORITY_DEFAULT = 0x0
/** 延迟回调 Hook 方法结果 */
val PRIORITY_LOWEST = 0x1
/** 更快回调 Hook 方法结果 */
val PRIORITY_HIGHEST = 0x2
/** Hook 操作选项内容 */
private var hookOption = ""
/** [hookClass] 找不到时出现的错误回调 */
private var onHookClassNotFoundFailureCallback: ((Throwable) -> Unit)? = null
/** 当前 [YukiMemberHookCreator] 禁止执行 Hook 操作的条件数组 */
private val disableCreatorRunHookReasons = HashSet<Boolean>()
/** 是否对当前 [YukiMemberHookCreator] 禁止执行 Hook 操作 */
private var isDisableCreatorRunHook = false
/** 设置要 Hook 的 [Method]、[Constructor] */
@PublishedApi
internal var preHookMembers = ArrayMap<String, MemberHookCreator>()
/**
* 更新当前 [YukiMemberHookCreator] 禁止执行 Hook 操作的条件
* @param reason 当前条件
*/
@PublishedApi
internal fun updateDisableCreatorRunHookReasons(reason: Boolean) {
disableCreatorRunHookReasons.add(reason)
conditions {
disableCreatorRunHookReasons.forEach { and(it) }
}.finally { isDisableCreatorRunHook = true }.without { isDisableCreatorRunHook = false }
}
/**
* 得到当前被 Hook 的 [Class]
*
* - ❗不推荐直接使用 - 万一得不到 [Class] 对象则会无法处理异常导致崩溃
* @return [Class]
* @throws IllegalStateException 如果当前 [Class] 未被正确装载
*/
val instanceClass
get() = hookClass.instance ?: error("Cannot get hook class \"${hookClass.name}\" cause ${hookClass.throwable?.message}")
/**
* 注入要 Hook 的 [Method]、[Constructor]
* @param priority Hook 优先级 - 默认 [PRIORITY_DEFAULT]
* @param tag 可设置标签 - 在发生错误时方便进行调试
* @param initiate 方法体
* @return [MemberHookCreator.Result]
*/
inline fun injectMember(priority: Int = PRIORITY_DEFAULT, tag: String = "Default", initiate: MemberHookCreator.() -> Unit) =
MemberHookCreator(priority, tag).apply(initiate).apply { preHookMembers[toString()] = this }.build()
/**
* 允许 Hook 过程中的所有危险行为
*
* 请在 [option] 中键入 "Yes do as I say!" 代表你同意允许所有危险行为
*
* 你还需要在整个调用域中声明注解 [CauseProblemsApi] 以消除警告
*
* - ❗若你不知道允许此功能会带来何种后果 - 请勿使用
* @param option 操作选项内容
*/
@CauseProblemsApi
fun useDangerousOperation(option: String) {
hookOption = option
}
/**
* Hook 执行入口
* @return [Result]
*/
@PublishedApi
internal fun hook() = when {
HookApiCategoryHelper.hasAvailableHookApi.not() -> Result()
/** 过滤 [HookEntryType.ZYGOTE] and [HookEntryType.PACKAGE] or [HookParam.isCallbackCalled] 已被执行 */
packageParam.wrapper?.type == HookEntryType.RESOURCES && HookParam.isCallbackCalled.not() -> Result()
preHookMembers.isEmpty() -> Result().also { yLoggerW(msg = "Hook Members is empty in [${hookClass.name}], hook aborted") }
else -> Result().await {
when {
isDisableCreatorRunHook.not() && hookClass.instance != null -> runCatching {
hookClass.instance?.checkingDangerous()
it.onPrepareHook?.invoke()
preHookMembers.forEach { (_, m) -> m.hook() }
}.onFailure {
if (onHookClassNotFoundFailureCallback == null)
yLoggerE(msg = "Hook initialization failed because got an Exception", e = it)
else onHookClassNotFoundFailureCallback?.invoke(it)
}
isDisableCreatorRunHook.not() && hookClass.instance == null ->
if (onHookClassNotFoundFailureCallback == null)
yLoggerE(msg = "HookClass [${hookClass.name}] not found", e = hookClass.throwable)
else onHookClassNotFoundFailureCallback?.invoke(hookClass.throwable ?: Throwable("[${hookClass.name}] not found"))
}
}
}
/**
* 检查不应该被 Hook 警告范围内的 [HookClass] 对象
* @throws UnsupportedOperationException 如果遇到警告范围内的 [HookClass] 对象
*/
private fun Class<*>.checkingDangerous() {
/**
* 警告并抛出异常
* @param name 对象名称
* @param content 警告内容
* @throws UnsupportedOperationException 抛出警告异常
*/
fun throwProblem(name: String, content: String) {
if (hookOption != "Yes do as I say!") throw UnsupportedOperationException(
"!!!DANGEROUS!!! Hook [$name] Class is a dangerous behavior! $content\n" +
"The hook request was rejected, if you still want to use it, " +
"call \"useDangerousOperation\" and type \"Yes do as I say!\""
)
}
when (hookClass.name) {
AnyClass.name -> throwProblem(
name = "Object",
content = "This is the parent Class of all objects, if you hook it, it may cause a lot of memory leaks"
)
JavaClassLoader.name -> throwProblem(
name = "ClassLoader",
content = "If you only want to listen to \"loadClass\", just use \"ClassLoader.onLoadClass\" instead it"
)
JavaClass.name, JavaMethodClass.name, JavaFieldClass.name,
JavaConstructorClass.name, JavaMemberClass.name -> throwProblem(
name = "Class/Method/Field/Constructor/Member",
content = "Those Class should not be hooked, it may cause StackOverflow errors"
)
}
}
/**
* 转换到 [YukiHookPriority] 优先级
* @return [YukiHookPriority]
* @throws IllegalStateException 如果优先级不为 [PRIORITY_DEFAULT]、[PRIORITY_LOWEST]、[PRIORITY_HIGHEST]
*/
private fun Int.toPriority() = when (this) {
PRIORITY_DEFAULT -> YukiHookPriority.DEFAULT
PRIORITY_LOWEST -> YukiHookPriority.LOWEST
PRIORITY_HIGHEST -> YukiHookPriority.HIGHEST
else -> error("Invalid Hook Priority $this")
}
/**
* Hook 核心功能实现类
*
* 查找和处理需要 Hook 的 [Method]、[Constructor]
* @param priority Hook 优先级
* @param tag 当前设置的标签
*/
inner class MemberHookCreator @PublishedApi internal constructor(private val priority: Int, internal val tag: String) {
/** Hook 结果实例 */
private var result: Result? = null
/** 是否已经执行 Hook */
private var isHooked = false
/** [beforeHook] 回调方法体 ID */
private val beforeHookId = RandomSeed.createString()
/** [afterHook] 回调方法体 ID */
private val afterHookId = RandomSeed.createString()
/** [replaceAny]、[replaceUnit] 回调方法体 ID */
private val replaceHookId = RandomSeed.createString()
/** [beforeHook] 回调 */
private var beforeHookCallback: (HookParam.() -> Unit)? = null
/** [afterHook] 回调 */
private var afterHookCallback: (HookParam.() -> Unit)? = null
/** [replaceAny]、[replaceUnit] 回调 */
private var replaceHookCallback: (HookParam.() -> Any?)? = null
/** Hook 成功时回调 */
private var onHookedCallback: ((Member) -> Unit)? = null
/** 重复 Hook 时回调 */
private var onAlreadyHookedCallback: ((Member) -> Unit)? = null
/** 找不到 [members] 出现错误回调 */
private var onNoSuchMemberFailureCallback: ((Throwable) -> Unit)? = null
/** Hook 过程中出现错误回调 */
private var onConductFailureCallback: ((HookParam, Throwable) -> Unit)? = null
/** Hook 开始时出现错误回调 */
private var onHookingFailureCallback: ((Throwable) -> Unit)? = null
/** 全部错误回调 */
private var onAllFailureCallback: ((Throwable) -> Unit)? = null
/** 发生异常时是否将异常抛出给当前 Hook APP */
private var isOnFailureThrowToApp = false
/** 是否为替换 Hook 模式 */
private var isReplaceHookMode = false
/** 是否对当前 [MemberHookCreator] 禁止执行 Hook 操作 */
@PublishedApi
internal var isDisableMemberRunHook = false
/** 查找过程中发生的异常 */
@PublishedApi
internal var findingThrowable: Throwable? = null
/** 标识是否已经设置了要 Hook 的 [members] */
@PublishedApi
internal var isHookMemberSetup = false
/** 当前的查找实例 */
@PublishedApi
internal var finder: MemberBaseFinder? = null
/** 当前被 Hook 的 [Method]、[Constructor] 实例数组 */
private val hookedMembers = HashSet<YukiMemberHook.HookedMember>()
/** 当前需要 Hook 的 [Method]、[Constructor] */
internal val members = HashSet<Member>()
/**
* 手动指定要 Hook 的 [Method]、[Constructor]
*
* 你可以调用 [instanceClass] 来手动查找要 Hook 的 [Method]、[Constructor]
*
* - ❗不建议使用此方法设置目标需要 Hook 的 [Member] 对象 - 你可以使用 [method] or [constructor] 方法
*
* - ❗在同一个 [injectMember] 中你只能使用一次 [members]、[allMembers]、[method]、[constructor] 方法 - 否则结果会被替换
* @param member 要指定的 [Member] or [Member] 数组
* @throws IllegalStateException 如果 [member] 参数为空
*/
fun members(vararg member: Member?) {
if (member.isEmpty()) error("Custom Hooking Members is empty")
members.clear()
member.forEach { it?.also { members.add(it) } }
}
/**
* 查找并 Hook [hookClass] 中指定 [name] 的全部 [Method]
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [MethodFinder] or [allMembers]
* @param name 方法名称
* @return [ArrayList]<[MethodFinder.Result.Instance]>
*/
@Deprecated(message = "请使用新方式来实现 Hook 所有方法", ReplaceWith("method { this.name = name }.all()"))
fun allMethods(name: String) = method { this.name = name }.all()
/**
* 查找并 Hook [hookClass] 中的全部 [Constructor]
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [ConstructorFinder] or [allMembers]
* @return [ArrayList]<[ConstructorFinder.Result.Instance]>
*/
@Deprecated(
message = "请使用新方式来实现 Hook 所有构造方法",
ReplaceWith("allMembers(MembersType.CONSTRUCTOR)", "com.highcapable.yukihookapi.hook.factory.MembersType")
)
fun allConstructors() = allMembers(MembersType.CONSTRUCTOR)
/**
* 查找并 Hook [hookClass] 中的全部 [Method]、[Constructor]
*
* - ❗在同一个 [injectMember] 中你只能使用一次 [members]、[allMembers]、[method]、[constructor] 方法 - 否则结果会被替换
*
* - ❗警告:无法准确处理每个 [Member] 的返回值和 param - 建议使用 [method] or [constructor] 对每个 [Member] 单独 Hook
*
* - ❗如果 [hookClass] 中没有 [Member] 可能会发生错误
* @param type 过滤 [Member] 类型 - 默认为 [MembersType.ALL]
*/
fun allMembers(type: MembersType = MembersType.ALL) {
members.clear()
if (type == MembersType.ALL || type == MembersType.CONSTRUCTOR)
hookClass.instance?.allConstructors { _, constructor -> members.add(constructor) }
if (type == MembersType.ALL || type == MembersType.METHOD)
hookClass.instance?.allMethods { _, method -> members.add(method) }
isHookMemberSetup = true
}
/**
* 查找 [hookClass] 需要 Hook 的 [Method]
*
* - ❗在同一个 [injectMember] 中你只能使用一次 [members]、[allMembers]、[method]、[constructor] 方法 - 否则结果会被替换
* @param initiate 方法体
* @return [MethodFinder.Process]
*/
inline fun method(initiate: MethodConditions) = runCatching {
isHookMemberSetup = true
MethodFinder.fromHooker(hookInstance = this, hookClass.instance).apply(initiate).apply { finder = this }.process()
}.getOrElse {
findingThrowable = it
MethodFinder.fromHooker(hookInstance = this).denied(it)
}
/**
* 查找 [hookClass] 需要 Hook 的 [Constructor]
*
* - ❗在同一个 [injectMember] 中你只能使用一次 [members]、[allMembers]、[method]、[constructor] 方法 - 否则结果会被替换
* @param initiate 方法体
* @return [ConstructorFinder.Process]
*/
inline fun constructor(initiate: ConstructorConditions = { emptyParam() }) = runCatching {
isHookMemberSetup = true
ConstructorFinder.fromHooker(hookInstance = this, hookClass.instance).apply(initiate).apply { finder = this }.process()
}.getOrElse {
findingThrowable = it
ConstructorFinder.fromHooker(hookInstance = this).denied(it)
}
/**
* 使用当前 [hookClass] 查找并得到 [Field]
* @param initiate 方法体
* @return [FieldFinder.Result]
*/
inline fun HookParam.field(initiate: FieldConditions) =
if (hookClass.instance == null) FieldFinder.fromHooker(hookInstance = this@MemberHookCreator).failure(hookClass.throwable)
else FieldFinder.fromHooker(hookInstance = this@MemberHookCreator, hookClass.instance).apply(initiate).build()
/**
* 使用当前 [hookClass] 查找并得到 [Method]
* @param initiate 方法体
* @return [MethodFinder.Result]
*/
inline fun HookParam.method(initiate: MethodConditions) =
if (hookClass.instance == null) MethodFinder.fromHooker(hookInstance = this@MemberHookCreator).failure(hookClass.throwable)
else MethodFinder.fromHooker(hookInstance = this@MemberHookCreator, hookClass.instance).apply(initiate).build()
/**
* 使用当前 [hookClass] 查找并得到 [Constructor]
* @param initiate 方法体
* @return [ConstructorFinder.Result]
*/
inline fun HookParam.constructor(initiate: ConstructorConditions = { emptyParam() }) =
if (hookClass.instance == null) ConstructorFinder.fromHooker(hookInstance = this@MemberHookCreator).failure(hookClass.throwable)
else ConstructorFinder.fromHooker(hookInstance = this@MemberHookCreator, hookClass.instance).apply(initiate).build()
/**
* 注入要 Hook 的 [Method]、[Constructor] (嵌套 Hook)
* @param priority Hook 优先级 - 默认 [PRIORITY_DEFAULT]
* @param tag 可设置标签 - 在发生错误时方便进行调试
* @param initiate 方法体
* @return [MemberHookCreator.Result]
*/
inline fun HookParam.injectMember(
priority: Int = PRIORITY_DEFAULT,
tag: String = "InnerDefault",
initiate: MemberHookCreator.() -> Unit
) = this@YukiMemberHookCreator.injectMember(priority, tag, initiate).also { this@YukiMemberHookCreator.hook() }
/**
* 在 [Member] 执行完成前 Hook
*
* - 不可与 [replaceAny]、[replaceUnit]、[replaceTo] 同时使用
* @param initiate [HookParam] 方法体
* @return [HookCallback]
*/
fun beforeHook(initiate: HookParam.() -> Unit): HookCallback {
isReplaceHookMode = false
beforeHookCallback = initiate
return HookCallback()
}
/**
* 在 [Member] 执行完成后 Hook
*
* - 不可与 [replaceAny]、[replaceUnit]、[replaceTo] 同时使用
* @param initiate [HookParam] 方法体
* @return [HookCallback]
*/
fun afterHook(initiate: HookParam.() -> Unit): HookCallback {
isReplaceHookMode = false
afterHookCallback = initiate
return HookCallback()
}
/**
* 拦截并替换此 [Member] 内容 - 给出返回值
*
* - 不可与 [beforeHook]、[afterHook] 同时使用
* @param initiate [HookParam] 方法体
*/
fun replaceAny(initiate: HookParam.() -> Any?) {
isReplaceHookMode = true
replaceHookCallback = initiate
}
/**
* 拦截并替换此 [Member] 内容 - 没有返回值 ([Unit])
*
* - 不可与 [beforeHook]、[afterHook] 同时使用
* @param initiate [HookParam] 方法体
*/
fun replaceUnit(initiate: HookParam.() -> Unit) {
isReplaceHookMode = true
replaceHookCallback = initiate
}
/**
* 拦截并替换 [Member] 返回值
*
* - 不可与 [beforeHook]、[afterHook] 同时使用
* @param any 要替换为的返回值对象
*/
fun replaceTo(any: Any?) {
isReplaceHookMode = true
replaceHookCallback = { any }
}
/**
* 拦截并替换 [Member] 返回值为 true
*
* - ❗确保替换 [Member] 的返回对象为 [Boolean]
*
* - 不可与 [beforeHook]、[afterHook] 同时使用
*/
fun replaceToTrue() {
isReplaceHookMode = true
replaceHookCallback = { true }
}
/**
* 拦截并替换 [Member] 返回值为 false
*
* - ❗确保替换 [Member] 的返回对象为 [Boolean]
*
* - 不可与 [beforeHook]、[afterHook] 同时使用
*/
fun replaceToFalse() {
isReplaceHookMode = true
replaceHookCallback = { false }
}
/**
* 拦截此 [Member]
*
* - ❗这将会禁止此 [Member] 执行并返回 null
*
* - ❗注意:例如 [Int]、[Long]、[Boolean] 常量返回值的 [Member] 一旦被设置为 null 可能会造成 Hook APP 抛出异常
*
* - 不可与 [beforeHook]、[afterHook] 同时使用
*/
fun intercept() {
isReplaceHookMode = true
replaceHookCallback = { null }
}
/**
* 移除当前注入的 Hook [Method]、[Constructor] (解除 Hook)
*
* - ❗你只能在 Hook 回调方法中使用此功能
* @param result 回调是否成功
*/
fun removeSelf(result: (Boolean) -> Unit = {}) = this.result?.remove(result) ?: result(false)
/**
* Hook 创建入口
* @return [Result]
*/
@PublishedApi
internal fun build() = Result().apply { result = this }
/** Hook 执行入口 */
@PublishedApi
internal fun hook() {
if (HookApiCategoryHelper.hasAvailableHookApi.not() || isHooked || isDisableMemberRunHook) return
isHooked = true
finder?.printLogIfExist()
if (hookClass.instance == null) {
(hookClass.throwable ?: Throwable("HookClass [${hookClass.name}] not found")).also {
onHookingFailureCallback?.invoke(it)
onAllFailureCallback?.invoke(it)
if (isNotIgnoredHookingFailure) onHookFailureMsg(it)
}
return
}
members.takeIf { it.isNotEmpty() }?.forEach { member ->
runCatching {
member.hook().also {
when {
it.hookedMember?.member == null -> error("Hook Member [$member] failed")
it.isAlreadyHooked -> onAlreadyHookedCallback?.invoke(it.hookedMember.member!!)
else -> {
hookedMembers.add(it.hookedMember)
onHookedCallback?.invoke(it.hookedMember.member!!)
}
}
}
}.onFailure {
onHookingFailureCallback?.invoke(it)
onAllFailureCallback?.invoke(it)
if (isNotIgnoredHookingFailure) onHookFailureMsg(it, member)
}
} ?: Throwable("Finding Error isSetUpMember [$isHookMemberSetup] [$tag]").also {
onNoSuchMemberFailureCallback?.invoke(it)
onHookingFailureCallback?.invoke(it)
onAllFailureCallback?.invoke(it)
if (isNotIgnoredNoSuchMemberFailure) yLoggerE(
msg = (if (isHookMemberSetup)
"Hooked Member with a finding error by $hookClass [$tag]"
else "Hooked Member cannot be non-null by $hookClass [$tag]"),
e = findingThrowable ?: it
)
}
}
/**
* Hook [Method]、[Constructor]
* @return [YukiHookResult]
*/
private fun Member.hook(): YukiHookResult {
/** 定义替换 Hook 的 [HookParam] */
val replaceHookParam = HookParam(creatorInstance = this@YukiMemberHookCreator)
/** 定义替换 Hook 回调方法体 */
val replaceMent = object : YukiMemberReplacement(priority.toPriority()) {
override fun replaceHookedMember(param: Param) =
replaceHookParam.assign(replaceHookId, param).let { assign ->
runCatching {
replaceHookCallback?.invoke(assign).also {
checkingReturnType((param.member as? Method?)?.returnType, it?.javaClass)
if (replaceHookCallback != null) onHookLogMsg(msg = "Replace Hook Member [${this@hook}] done [$tag]")
HookParam.invoke()
}
}.getOrElse {
onConductFailureCallback?.invoke(assign, it)
onAllFailureCallback?.invoke(it)
if (onConductFailureCallback == null && onAllFailureCallback == null) onHookFailureMsg(it, member = this@hook)
/** 若发生异常则会自动调用未经 Hook 的原始 [Member] 保证 Hook APP 正常运行 */
assign.callOriginal()
}
}
}
/** 定义前 Hook 的 [HookParam] */
val beforeHookParam = HookParam(creatorInstance = this@YukiMemberHookCreator)
/** 定义后 Hook 的 [HookParam] */
val afterHookParam = HookParam(creatorInstance = this@YukiMemberHookCreator)
/** 定义前后 Hook 回调方法体 */
val beforeAfterHook = object : YukiMemberHook(priority.toPriority()) {
override fun beforeHookedMember(param: Param) {
beforeHookParam.assign(beforeHookId, param).also { assign ->
runCatching {
beforeHookCallback?.invoke(assign)
checkingReturnType((param.member as? Method?)?.returnType, param.result?.javaClass)
if (beforeHookCallback != null) onHookLogMsg(msg = "Before Hook Member [${this@hook}] done [$tag]")
HookParam.invoke()
}.onFailure {
onConductFailureCallback?.invoke(assign, it)
onAllFailureCallback?.invoke(it)
if (onConductFailureCallback == null && onAllFailureCallback == null) onHookFailureMsg(it, member = this@hook)
if (isOnFailureThrowToApp) param.throwable = it
}
}
}
override fun afterHookedMember(param: Param) {
afterHookParam.assign(afterHookId, param).also { assign ->
runCatching {
afterHookCallback?.invoke(assign)
if (afterHookCallback != null) onHookLogMsg(msg = "After Hook Member [${this@hook}] done [$tag]")
HookParam.invoke()
}.onFailure {
onConductFailureCallback?.invoke(assign, it)
onAllFailureCallback?.invoke(it)
if (onConductFailureCallback == null && onAllFailureCallback == null) onHookFailureMsg(it, member = this@hook)
if (isOnFailureThrowToApp) param.throwable = it
}
}
}
}
return YukiHookHelper.hookMember(member = this, if (isReplaceHookMode) replaceMent else beforeAfterHook)
}
/**
* 检查被 Hook [Member] 的返回值
* @param origin 原始返回值
* @param target 目标返回值
* @throws IllegalStateException 如果返回值不正确
*/
private fun checkingReturnType(origin: Class<*>?, target: Class<*>?) {
if (origin == null || target == null) return
origin.toJavaPrimitiveType().also { o ->
target.toJavaPrimitiveType().also { t ->
if (o notExtends t && t notExtends o && o notImplements t && t notImplements o)
error("Hooked method return type match failed, required [$origin] but got [$target]")
}
}
}
/**
* Hook 过程中开启了 [YukiHookAPI.Configs.isDebug] 输出调试信息
* @param msg 调试日志内容
*/
private fun onHookLogMsg(msg: String) {
if (YukiHookAPI.Configs.isDebug) yLoggerI(msg = msg)
}
/**
* Hook 失败但未设置 [onAllFailureCallback] 将默认输出失败信息
* @param throwable 异常信息
* @param member 异常 [Member] - 可空
*/
private fun onHookFailureMsg(throwable: Throwable, member: Member? = null) = yLoggerE(
msg = "Try to hook [${hookClass.instance ?: hookClass.name}]${member?.let { "[$it]" } ?: ""} got an Exception [$tag]",
e = throwable
)
/**
* 判断是否没有设置 Hook 过程中的任何异常拦截
* @return [Boolean] 没有设置任何异常拦截
*/
private val isNotIgnoredHookingFailure get() = onHookingFailureCallback == null && onAllFailureCallback == null
/**
* 判断是否没有设置 Hook 过程中 [members] 找不到的任何异常拦截
* @return [Boolean] 没有设置任何异常拦截
*/
internal val isNotIgnoredNoSuchMemberFailure get() = onNoSuchMemberFailureCallback == null && isNotIgnoredHookingFailure
override fun toString() = "[tag] $tag [priority] $priority [class] $hookClass [members] $members"
/**
* Hook 方法体回调实现类
*/
inner class HookCallback internal constructor() {
/** 当回调方法体内发生异常时将异常抛出给当前 Hook APP */
fun onFailureThrowToApp() {
isOnFailureThrowToApp = true
}
}
/**
* 监听 Hook 结果实现类
*
* 可在这里处理失败事件监听
*/
inner class Result internal constructor() {
/**
* 创建监听事件方法体
* @param initiate 方法体
* @return [Result] 可继续向下监听
*/
inline fun result(initiate: Result.() -> Unit) = apply(initiate)
/**
* 添加执行 Hook 需要满足的条件
*
* 不满足条件将直接停止 Hook
* @param condition 条件方法体
* @return [Result] 可继续向下监听
*/
inline fun by(condition: () -> Boolean): Result {
isDisableMemberRunHook = (runCatching { condition() }.getOrNull() ?: false).not()
if (isDisableMemberRunHook) ignoredAllFailure()
return this
}
/**
* 监听 [members] Hook 成功的回调方法
*
* 在首次 Hook 成功后回调
*
* 在重复 Hook 时会回调 [onAlreadyHooked]
* @param result 回调被 Hook 的 [Member]
* @return [Result] 可继续向下监听
*/
fun onHooked(result: (Member) -> Unit): Result {
onHookedCallback = result
return this
}
/**
* 监听 [members] 重复 Hook 的回调方法
*
* - ❗同一个 [hookClass] 中的同一个 [members] 不会被 API 重复 Hook - 若由于各种原因重复 Hook 会回调此方法
* @param result 回调被重复 Hook 的 [Member]
* @return [Result] 可继续向下监听
*/
fun onAlreadyHooked(result: (Member) -> Unit): Result {
onAlreadyHookedCallback = result
return this
}
/**
* 监听 [members] 不存在发生错误的回调方法
* @param result 回调错误
* @return [Result] 可继续向下监听
*/
fun onNoSuchMemberFailure(result: (Throwable) -> Unit): Result {
onNoSuchMemberFailureCallback = result
return this
}
/**
* 忽略 [members] 不存在发生的错误
* @return [Result] 可继续向下监听
*/
fun ignoredNoSuchMemberFailure() = onNoSuchMemberFailure {}
/**
* 监听 Hook 进行过程中发生错误的回调方法
* @param result 回调错误 - ([HookParam] 当前 Hook 实例,[Throwable] 异常)
* @return [Result] 可继续向下监听
*/
fun onConductFailure(result: (HookParam, Throwable) -> Unit): Result {
onConductFailureCallback = result
return this
}
/**
* 忽略 Hook 进行过程中发生的错误
* @return [Result] 可继续向下监听
*/
fun ignoredConductFailure() = onConductFailure { _, _ -> }
/**
* 监听 Hook 开始时发生错误的回调方法
* @param result 回调错误
* @return [Result] 可继续向下监听
*/
fun onHookingFailure(result: (Throwable) -> Unit): Result {
onHookingFailureCallback = result
return this
}
/**
* 忽略 Hook 开始时发生的错误
* @return [Result] 可继续向下监听
*/
fun ignoredHookingFailure() = onHookingFailure {}
/**
* 监听全部 Hook 过程发生错误的回调方法
* @param result 回调错误
* @return [Result] 可继续向下监听
*/
fun onAllFailure(result: (Throwable) -> Unit): Result {
onAllFailureCallback = result
return this
}
/**
* 忽略全部 Hook 过程发生的错误
* @return [Result] 可继续向下监听
*/
fun ignoredAllFailure() = onAllFailure {}
/**
* 移除当前注入的 Hook [Method]、[Constructor] (解除 Hook)
*
* - ❗你只能在 Hook 成功后才能解除 Hook - 可监听 [onHooked] 事件
* @param result 回调是否成功
*/
fun remove(result: (Boolean) -> Unit = {}) {
hookedMembers.takeIf { it.isNotEmpty() }?.apply {
forEach {
it.remove()
onHookLogMsg(msg = "Remove Hooked Member [${it.member}] done [$tag]")
}
runCatching { preHookMembers.remove(this@MemberHookCreator.toString()) }
clear()
result(true)
} ?: result(false)
}
}
}
/**
* 监听全部 Hook 结果实现类
*
* 可在这里处理失败事件监听
*/
inner class Result internal constructor() {
/** Hook 开始时的监听事件回调 */
internal var onPrepareHook: (() -> Unit)? = null
/**
* 创建监听事件方法体
* @param initiate 方法体
* @return [Result] 可继续向下监听
*/
inline fun result(initiate: Result.() -> Unit) = apply(initiate)
/**
* 添加执行 Hook 需要满足的条件
*
* 不满足条件将直接停止 Hook
* @param condition 条件方法体
* @return [Result] 可继续向下监听
*/
inline fun by(condition: () -> Boolean): Result {
updateDisableCreatorRunHookReasons((runCatching { condition() }.getOrNull() ?: false).not())
return this
}
/**
* 监听 [hookClass] 存在时准备开始 Hook 的操作
* @param callback 准备开始 Hook 后回调
* @return [Result] 可继续向下监听
*/
fun onPrepareHook(callback: () -> Unit): Result {
onPrepareHook = callback
return this
}
/**
* 监听 [hookClass] 找不到时发生错误的回调方法
* @param result 回调错误
* @return [Result] 可继续向下监听
*/
fun onHookClassNotFoundFailure(result: (Throwable) -> Unit): Result {
onHookClassNotFoundFailureCallback = result
return this
}
/**
* 忽略 [hookClass] 找不到时出现的错误
* @return [Result] 可继续向下监听
*/
fun ignoredHookClassNotFoundFailure(): Result {
by { hookClass.instance != null }
return this
}
}
}

View File

@@ -0,0 +1,384 @@
/*
* 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/5/1.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.highcapable.yukihookapi.hook.core
import android.content.res.Resources
import android.util.ArrayMap
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.bean.HookResources
import com.highcapable.yukihookapi.hook.core.api.compat.HookApiCategoryHelper
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.log.yLoggerI
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiResources
import com.highcapable.yukihookapi.hook.xposed.bridge.type.HookEntryType
/**
* [YukiHookAPI] 的 [Resources] 核心 Hook 实现类
*
* @param packageParam 需要传入 [PackageParam] 实现方法调用
* @param hookResources 要 Hook 的 [HookResources] 实例
*/
class YukiResourcesHookCreator @PublishedApi internal constructor(
@PublishedApi internal val packageParam: PackageParam,
@PublishedApi internal val hookResources: HookResources
) {
/** 设置要 Hook 的 Resources */
@PublishedApi
internal var preHookResources = ArrayMap<String, ResourcesHookCreator>()
/**
* 注入要 Hook 的 Resources
* @param tag 可设置标签 - 在发生错误时方便进行调试
* @param initiate 方法体
* @return [ResourcesHookCreator.Result]
*/
inline fun injectResource(tag: String = "Default", initiate: ResourcesHookCreator.() -> Unit) =
ResourcesHookCreator(tag).apply(initiate).apply { preHookResources[toString()] = this }.build()
/** Hook 执行入口 */
@PublishedApi
internal fun hook() {
if (HookApiCategoryHelper.hasAvailableHookApi.not()) return
/** 过滤 [HookEntryType.ZYGOTE] 与 [HookEntryType.RESOURCES] */
if (packageParam.wrapper?.type == HookEntryType.PACKAGE) return
if (preHookResources.isEmpty()) return yLoggerW(msg = "Hook Resources is empty, hook aborted")
preHookResources.forEach { (_, r) -> r.hook() }
}
/**
* Hook 核心功能实现类
*
* 查找和处理需要 Hook 的 Resources
* @param tag 当前设置的标签
*/
inner class ResourcesHookCreator @PublishedApi internal constructor(private val tag: String) {
/** 是否已经执行 Hook */
private var isHooked = false
/**
* 模块 APP Resources 替换实例
* @param resId Resources Id
*/
private inner class ModuleResFwd(var resId: Int)
/** 是否对当前 [ResourcesHookCreator] 禁止执行 Hook 操作 */
@PublishedApi
internal var isDisableCreatorRunHook = false
/** 当前的查找条件 */
@PublishedApi
internal var conditions: ConditionFinder? = null
/** Hook 出现错误回调 */
private var onHookFailureCallback: ((Throwable) -> Unit)? = null
/** 当前的替换值实例 */
private var replaceInstance: Any? = null
/** 当前的布局注入实例 */
private var layoutInstance: (YukiResources.LayoutInflatedParam.() -> Unit)? = null
/** 直接设置需要替换的 Resources Id */
var resourceId = -1
/**
* 设置 Resources 查找条件
*
* 若你设置了 [resourceId] 则此方法将不会被使用
* @param initiate 条件方法体
*/
inline fun conditions(initiate: ConditionFinder.() -> Unit) {
conditions = ConditionFinder().apply(initiate).build()
}
/**
* 替换指定 Resources 为指定的值
* @param any 可以是任何你想替换的类型 - 但要注意若当前类型不支持可能会报错
*/
fun replaceTo(any: Any) {
replaceInstance = any
}
/**
* 替换指定 Resources 为 true
*
* - ❗确保目标替换 Resources 的类型为 [Boolean]
*/
fun replaceToTrue() = replaceTo(any = true)
/**
* 替换指定 Resources 为 false
*
* - ❗确保目标替换 Resources 的类型为 [Boolean]
*/
fun replaceToFalse() = replaceTo(any = false)
/**
* 替换为当前 Xposed 模块的 Resources
*
* 你可以直接使用模块的 R.string.xxx、R.mipmap.xxx、R.drawable.xxx 替换 Hook APP 的 Resources
* @param resId 当前 Xposed 模块的 Resources Id
*/
fun replaceToModuleResource(resId: Int) {
replaceInstance = ModuleResFwd(resId)
}
/**
* 作为装载的布局注入
* @param initiate [YukiResources.LayoutInflatedParam] 方法体
*/
fun injectAsLayout(initiate: YukiResources.LayoutInflatedParam.() -> Unit) {
layoutInstance = initiate
}
/**
* 自动兼容当前替换的 Resources 类型
* @param any 替换的任意类型
* @return [Any]
*/
private fun compat(any: Any?) = if (any is ModuleResFwd) packageParam.moduleAppResources.fwd(any.resId) else any
/**
* Hook 创建入口
* @return [Result]
*/
@PublishedApi
internal fun build() = Result()
/** Hook 执行入口 */
@PublishedApi
internal fun hook() {
if (isHooked) return
isHooked = true
if (isDisableCreatorRunHook.not()) runCatching {
when {
conditions == null -> yLoggerE(msg = "You must set the conditions before hook a Resources [$tag]")
replaceInstance == null && layoutInstance == null -> yLoggerE(msg = "Resources Hook got null replaceInstance [$tag]")
packageParam.wrapper?.type == HookEntryType.RESOURCES && hookResources.instance != null ->
if (resourceId == -1) when {
layoutInstance != null ->
hookResources.instance?.hookLayout(
packageParam.packageName, conditions!!.type,
conditions!!.name, layoutInstance!!
) { onHookLogMsg(msg = "Hook Resources Layout $conditions done [$tag]") }
else -> hookResources.instance?.setReplacement(
packageParam.packageName, conditions!!.type,
conditions!!.name, compat(replaceInstance)
) { onHookLogMsg(msg = "Hook Resources Value $conditions done [$tag]") }
} else when {
layoutInstance != null -> hookResources.instance?.hookLayout(resourceId, layoutInstance!!) {
onHookLogMsg(msg = "Hook Resources Layout Id $resourceId done [$tag]")
}
else -> hookResources.instance?.setReplacement(resourceId, compat(replaceInstance)) {
onHookLogMsg(msg = "Hook Resources Value Id $resourceId done [$tag]")
}
}
packageParam.wrapper?.type == HookEntryType.ZYGOTE ->
if (resourceId == -1) when {
layoutInstance != null ->
YukiResources.hookSystemWideLayout(
packageParam.packageName, conditions!!.type,
conditions!!.name, layoutInstance!!
) { onHookLogMsg(msg = "Hook Wide Resources Layout $conditions done [$tag]") }
else -> YukiResources.setSystemWideReplacement(
packageParam.packageName, conditions!!.type,
conditions!!.name, compat(replaceInstance)
) { onHookLogMsg(msg = "Hook Wide Resources Value $conditions done [$tag]") }
} else when {
layoutInstance != null -> YukiResources.hookSystemWideLayout(resourceId, layoutInstance!!) {
onHookLogMsg(msg = "Hook Wide Resources Layout Id $resourceId done [$tag]")
}
else -> YukiResources.setSystemWideReplacement(resourceId, compat(replaceInstance)) {
onHookLogMsg(msg = "Hook Wide Resources Value Id $resourceId done [$tag]")
}
}
else -> yLoggerE(msg = "Resources Hook type is invalid [$tag]")
}
}.onFailure {
if (onHookFailureCallback == null)
yLoggerE(msg = "Resources Hook got an Exception [$tag]", e = it)
else onHookFailureCallback?.invoke(it)
}
}
/**
* Hook 过程中开启了 [YukiHookAPI.Configs.isDebug] 输出调试信息
* @param msg 调试日志内容
*/
private fun onHookLogMsg(msg: String) {
if (YukiHookAPI.Configs.isDebug) yLoggerI(msg = msg)
}
/**
* Resources 查找条件实现类
*/
inner class ConditionFinder @PublishedApi internal constructor() {
/** Resources 类型 */
internal var type = ""
/** 设置 Resources 名称 */
var name = ""
/** 设置 Resources 类型为动画 */
fun anim() {
type = "anim"
}
/** 设置 Resources 类型为属性动画 */
fun animator() {
type = "animator"
}
/** 设置 Resources 类型为布朗 (Boolean) */
fun bool() {
type = "bool"
}
/** 设置 Resources 类型为颜色 (Color) */
fun color() {
type = "color"
}
/** 设置 Resources 类型为尺寸 (Dimention) */
fun dimen() {
type = "dimen"
}
/** 设置 Resources 类型为 Drawable */
fun drawable() {
type = "drawable"
}
/** 设置 Resources 类型为整型 (Integer) */
fun integer() {
type = "integer"
}
/** 设置 Resources 类型为布局 (Layout) */
fun layout() {
type = "layout"
}
/** 设置 Resources 类型为 Plurals */
fun plurals() {
type = "plurals"
}
/** 设置 Resources 类型为字符串 (String) */
fun string() {
type = "string"
}
/** 设置 Resources 类型为 Xml */
fun xml() {
type = "xml"
}
/** 设置 Resources 类型为位图 (Mipmap) */
fun mipmap() {
type = "mipmap"
}
/** 设置 Resources 类型为数组 (Array) */
fun array() {
type = "array"
}
/**
* 创建查找对象实例
* @return [ConditionFinder]
* @throws IllegalStateException 如果没有设置 [name] or [type]
*/
@PublishedApi
internal fun build(): ConditionFinder {
when {
name.isBlank() -> error("Resources Hook condition name cannot be empty [$tag]")
type.isBlank() -> error("Resources Hook condition type cannot be empty [$tag]")
}
return this
}
override fun toString() = "[${if (packageParam.wrapper?.type == HookEntryType.ZYGOTE) "android." else ""}R.$type.$name]"
}
/**
* 监听全部 Hook 结果实现类
*
* 可在这里处理失败事件监听
*/
inner class Result internal constructor() {
/**
* 创建监听事件方法体
* @param initiate 方法体
* @return [Result] 可继续向下监听
*/
inline fun result(initiate: Result.() -> Unit) = apply(initiate)
/**
* 添加执行 Hook 需要满足的条件
*
* 不满足条件将直接停止 Hook
* @param condition 条件方法体
* @return [Result] 可继续向下监听
*/
inline fun by(condition: () -> Boolean): Result {
isDisableCreatorRunHook = (runCatching { condition() }.getOrNull() ?: false).not()
return this
}
/**
* 监听 Hook 过程发生错误的回调方法
* @param result 回调错误
* @return [Result] 可继续向下监听
*/
fun onHookingFailure(result: (Throwable) -> Unit): Result {
onHookFailureCallback = result
return this
}
/**
* 忽略 Hook 过程出现的错误
* @return [Result] 可继续向下监听
*/
fun ignoredHookingFailure(): Result {
onHookingFailure {}
return this
}
}
override fun toString() = "[tag] $tag [conditions] $conditions [replaceInstance] $replaceInstance [layoutInstance] $layoutInstance"
}
}

View File

@@ -0,0 +1,39 @@
/*
* 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 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.compat
/**
* Hook API 类型定义类
*/
internal enum class HookApiCategory {
/** 原版 Xposed API */
ROVO89_XPOSED,
/** 未知类型的 API */
UNKNOWN
}

View File

@@ -0,0 +1,64 @@
/*
* 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 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.compat
import de.robv.android.xposed.XposedBridge
/**
* Hook API 类型工具类
*/
internal object HookApiCategoryHelper {
/** 目前支持的 API 类型定义 - 按优先级正序排列 */
private val supportedCategories = arrayOf(HookApiCategory.ROVO89_XPOSED)
/**
* 获取当前支持的 API 类型
* @return [HookApiCategory]
*/
internal val currentCategory
get() = supportedCategories.let { categories ->
categories.forEach { if (hasCategory(it)) return@let it }
HookApiCategory.UNKNOWN
}
/**
* 获取当前环境是否存在可用的 Hook API
* @return [Boolean]
*/
internal val hasAvailableHookApi get() = currentCategory != HookApiCategory.UNKNOWN
/**
* 判断当前运行环境是否存在当前 Hook API 类型
* @return [Boolean]
*/
private fun hasCategory(category: HookApiCategory) = when (category) {
HookApiCategory.ROVO89_XPOSED -> runCatching { XposedBridge.getXposedVersion(); true }.getOrNull() ?: false
else -> false
}
}

View File

@@ -0,0 +1,131 @@
/*
* 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 2023/1/9.
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package com.highcapable.yukihookapi.hook.core.api.compat
import com.highcapable.yukihookapi.hook.core.api.compat.type.ExecutorType
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.hasClass
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import de.robv.android.xposed.XposedBridge
/**
* Hook API 相关属性
*/
internal object HookApiProperty {
/** Xposed 框架名称 */
internal const val XPOSED_NAME = "Xposed"
/** LSPosed 框架名称 */
internal const val LSPOSED_NAME = "LSPosed"
/** EdXposed 框架名称 */
internal const val ED_XPOSED_NAME = "EdXposed"
/** TaiChi (太极) Xposed 框架名称 */
internal const val TAICHI_XPOSED_NAME = "TaiChi"
/** BugXposed (应用转生) Xposed 框架名称 */
internal const val BUG_XPOSED_NAME = "BugXposed"
/** TaiChi (太极) ExposedBridge 完整类名 */
internal const val EXPOSED_BRIDGE_CLASS_NAME = "me.weishu.exposed.ExposedBridge"
/** BugXposed (应用转生) BugLoad 完整类名 */
internal const val BUG_LOAD_CLASS_NAME = "com.bug.load.BugLoad"
/**
* 获取当前 Hook Framework 名称
* @return [String] 无法获取会返回 unknown - 获取失败会返回 invalid
*/
internal val name
get() = when (HookApiCategoryHelper.currentCategory) {
HookApiCategory.ROVO89_XPOSED -> when {
EXPOSED_BRIDGE_CLASS_NAME.hasClass(AppParasitics.currentApplication?.classLoader) -> TAICHI_XPOSED_NAME
BUG_LOAD_CLASS_NAME.hasClass(AppParasitics.currentApplication?.classLoader) -> BUG_XPOSED_NAME
else -> runCatching {
classOf<XposedBridge>().field { name = "TAG" }.ignored().get().string().takeIf { it.isNotBlank() }
?.replace("Bridge", "")?.replace("-", "")?.trim() ?: "unknown"
}.getOrNull() ?: "invalid"
}
HookApiCategory.UNKNOWN -> "unknown"
}
/**
* 获取当前 Hook Framework 类型
* @return [ExecutorType]
*/
internal val type get() = type()
/**
* 获取当前 Hook Framework 类型
* @param executorName Hook Framework 名称 - 默认为 [name]
* @return [ExecutorType]
*/
internal fun type(executorName: String = name) = when (executorName) {
BUG_XPOSED_NAME -> ExecutorType.BUG_XPOSED
TAICHI_XPOSED_NAME -> ExecutorType.TAICHI_XPOSED
ED_XPOSED_NAME -> ExecutorType.ED_XPOSED
LSPOSED_NAME -> ExecutorType.LSPOSED_LSPATCH
XPOSED_NAME -> ExecutorType.XPOSED
else -> ExecutorType.UNKNOWN
}
/**
* 获取当前 Hook Framework 的 API 版本
* @return [Int] 无法获取会返回 -1
*/
internal val apiLevel
get() = when (HookApiCategoryHelper.currentCategory) {
HookApiCategory.ROVO89_XPOSED -> runCatching { XposedBridge.getXposedVersion() }.getOrNull() ?: -1
HookApiCategory.UNKNOWN -> -1
}
/**
* 获取当前 Hook Framework 版本名称
* @return [String] 无法获取会返回 unknown - 不支持会返回 unsupported
*/
internal val versionName
get() = when (HookApiCategoryHelper.currentCategory) {
HookApiCategory.ROVO89_XPOSED -> "unsupported"
HookApiCategory.UNKNOWN -> "unknown"
}
/**
* 获取当前 Hook Framework 版本号
* @return [Int] 无法获取会返回 -1 - 不支持会返回 0
*/
internal val versionCode
get() = when (HookApiCategoryHelper.currentCategory) {
HookApiCategory.ROVO89_XPOSED -> 0
HookApiCategory.UNKNOWN -> -1
}
}

View File

@@ -0,0 +1,144 @@
/*
* 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 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.compat
import com.highcapable.yukihookapi.hook.core.api.factory.YukiHookCallbackDelegate
import com.highcapable.yukihookapi.hook.core.api.factory.callAfterHookedMember
import com.highcapable.yukihookapi.hook.core.api.factory.callBeforeHookedMember
import com.highcapable.yukihookapi.hook.core.api.priority.YukiHookPriority
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiHookCallback
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiMemberHook
import de.robv.android.xposed.XC_MethodHook
import de.robv.android.xposed.XposedBridge
import java.lang.reflect.Member
/**
* Hook API 兼容层处理工具类
*/
internal object HookCompatHelper {
/**
* [HookApiCategory.ROVO89_XPOSED]
*
* 兼容对接已 Hook 的 [Member] 接口
* @return [YukiMemberHook.HookedMember]
*/
private fun XC_MethodHook.Unhook.compat() =
YukiHookCallbackDelegate.createHookedMemberCallback(
member = { hookedMethod },
onRemove = { unhook() }
)
/**
* [HookApiCategory.ROVO89_XPOSED]
*
* 兼容对接 Hook 结果回调接口
* @return [YukiHookCallback.Param]
*/
private fun XC_MethodHook.MethodHookParam.compat() =
YukiHookCallbackDelegate.createParamCallback(
member = { method },
instance = { thisObject },
args = { args },
hasThrowable = { hasThrowable() },
result = { it, assign -> if (assign) result = it; result },
throwable = { it, assign -> if (assign) throwable = it; throwable }
)
/**
* 兼容对接 Hook 回调接口
* @return [Any] 原始接口
*/
private fun YukiHookCallback.compat() = when (HookApiCategoryHelper.currentCategory) {
HookApiCategory.ROVO89_XPOSED -> object : XC_MethodHook(
when (priority) {
YukiHookPriority.DEFAULT -> 50
YukiHookPriority.LOWEST -> -10000
YukiHookPriority.HIGHEST -> 10000
}
) {
override fun beforeHookedMethod(param: MethodHookParam?) {
if (param == null) return
this@compat.callBeforeHookedMember(param.compat())
}
override fun afterHookedMethod(param: MethodHookParam?) {
if (param == null) return
this@compat.callAfterHookedMember(param.compat())
}
}
HookApiCategory.UNKNOWN -> throwUnsupportedHookApiError()
}
/**
* Hook [Member]
* @param member 需要 Hook 的方法、构造方法
* @param callback 回调
* @return [YukiMemberHook.HookedMember] or null
*/
internal fun hookMember(member: Member?, callback: YukiHookCallback): YukiMemberHook.HookedMember? {
if (member == null) return null
return when (HookApiCategoryHelper.currentCategory) {
HookApiCategory.ROVO89_XPOSED -> XposedBridge.hookMethod(member, callback.compat()).compat()
HookApiCategory.UNKNOWN -> throwUnsupportedHookApiError()
}
}
/**
* 执行未进行 Hook 的原始 [Member]
* @param member 实例
* @param args 参数实例
* @return [Any] or null
*/
internal fun invokeOriginalMember(member: Member?, instance: Any?, args: Array<out Any?>?): Any? {
if (member == null) return null
return when (HookApiCategoryHelper.currentCategory) {
HookApiCategory.ROVO89_XPOSED -> XposedBridge.invokeOriginalMethod(member, instance, args)
HookApiCategory.UNKNOWN -> throwUnsupportedHookApiError()
}
}
/**
* 使用当前 Hook API 自带的日志功能打印日志
* @param msg 日志打印的内容
* @param e 异常堆栈信息 - 默认空
*/
internal fun logByHooker(msg: String, e: Throwable? = null) {
when (HookApiCategoryHelper.currentCategory) {
HookApiCategory.ROVO89_XPOSED -> {
XposedBridge.log(msg)
e?.also { XposedBridge.log(it) }
}
HookApiCategory.UNKNOWN -> throwUnsupportedHookApiError()
}
}
/** 抛出不支持的 API 类型异常 */
private fun throwUnsupportedHookApiError(): Nothing =
error("YukiHookAPI cannot support current Hook API or cannot found any available Hook APIs in current environment")
}

View File

@@ -0,0 +1,57 @@
/*
* 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 2023/4/16.
*/
package com.highcapable.yukihookapi.hook.core.api.compat.type
/**
* Hook Framework 类型定义
*
* 定义了目前已知使用频率较高的 Hook Framework
*
* 后期根据 Hook Framework 特征和使用情况将会继续添加新的类型
*
* 无法识别的 Hook Framework 将被定义为 [UNKNOWN]
*/
enum class ExecutorType {
/** 未知类型 */
UNKNOWN,
/** 原版、第三方 Xposed */
XPOSED,
/** LSPosed、LSPatch */
LSPOSED_LSPATCH,
/** EdXposed */
ED_XPOSED,
/** TaiChi (太极) */
TAICHI_XPOSED,
/** BugXposed (应用转生) */
BUG_XPOSED
}

View File

@@ -0,0 +1,109 @@
/*
* 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 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.factory
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiHookCallback
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiMemberHook
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiMemberReplacement
import com.highcapable.yukihookapi.hook.core.api.store.YukiHookCacheStore
import java.lang.reflect.Member
/**
* Hook API 回调事件代理类
*/
internal object YukiHookCallbackDelegate {
/**
* 创建 [YukiMemberHook.HookedMember] 实例
* @param member [Member] 实例 (代理回调)
* @param onRemove 回调解除 Hook 事件 (代理回调)
* @return [YukiMemberHook.HookedMember]
*/
internal fun createHookedMemberCallback(member: () -> Member?, onRemove: () -> Unit) =
object : YukiMemberHook.HookedMember() {
override val member get() = member()
override fun remove() {
onRemove()
runCatching { YukiHookCacheStore.hookedMembers.remove(this) }
}
}
/**
* 创建 [YukiHookCallback.Param] 实例
* @param member [Member] 实例 (代理回调)
* @param instance 当前实例对象 (代理回调)
* @param args 方法、构造方法数组 (代理回调)
* @param hasThrowable 是否存在设置过的方法调用抛出异常 (代理回调)
* @param result 当前 Hook 方法返回值 (结果) (代理回调)
* @param throwable 当前 Hook 方法调用抛出的异常 (代理回调)
* @return [YukiHookCallback.Param]
*/
internal fun createParamCallback(
member: () -> Member?,
instance: () -> Any?,
args: () -> Array<Any?>?,
hasThrowable: () -> Boolean,
result: (Any?, Boolean) -> Any?,
throwable: (Throwable?, Boolean) -> Throwable?
) = object : YukiHookCallback.Param {
override val member get() = member()
override val instance get() = instance()
override val args get() = args()
override val hasThrowable get() = hasThrowable()
override var result
get() = result(null, false)
set(value) {
result(value, true)
}
override var throwable
get() = throwable(null, false)
set(value) {
throwable(value, true)
}
}
}
/**
* 调用 [YukiMemberHook.beforeHookedMember] 事件
* @param param Hook 结果回调接口
*/
internal fun YukiHookCallback.callBeforeHookedMember(param: YukiHookCallback.Param) {
if (this !is YukiMemberHook) error("Invalid YukiHookCallback type")
if (this is YukiMemberReplacement)
param.result = replaceHookedMember(param)
else beforeHookedMember(param)
}
/**
* 调用 [YukiMemberHook.afterHookedMember] 事件
* @param param Hook 结果回调接口
*/
internal fun YukiHookCallback.callAfterHookedMember(param: YukiHookCallback.Param) {
if (this !is YukiMemberHook) error("Invalid YukiHookCallback type")
afterHookedMember(param)
}

View File

@@ -0,0 +1,114 @@
/*
* 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 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.helper
import com.highcapable.yukihookapi.hook.core.api.compat.HookApiCategoryHelper
import com.highcapable.yukihookapi.hook.core.api.compat.HookCompatHelper
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiHookCallback
import com.highcapable.yukihookapi.hook.core.api.result.YukiHookResult
import com.highcapable.yukihookapi.hook.core.api.store.YukiHookCacheStore
import com.highcapable.yukihookapi.hook.core.finder.base.BaseFinder
import com.highcapable.yukihookapi.hook.core.finder.members.ConstructorFinder
import com.highcapable.yukihookapi.hook.core.finder.members.MethodFinder
import com.highcapable.yukihookapi.hook.log.yLoggerE
import java.lang.reflect.Member
/**
* Hook 核心功能实现工具类
*/
internal object YukiHookHelper {
/**
* Hook [BaseFinder.BaseResult]
* @param traction 直接调用 [BaseFinder.BaseResult]
* @param callback 回调
* @return [YukiHookResult]
*/
internal fun hook(traction: BaseFinder.BaseResult, callback: YukiHookCallback) = runCatching {
val member: Member? = when (traction) {
is MethodFinder.Result -> traction.ignored().give()
is ConstructorFinder.Result -> traction.ignored().give()
else -> error("Unexpected BaseFinder result interface type")
}
hookMember(member, callback)
}.onFailure { yLoggerE(msg = "An exception occurred when hooking internal function", e = it) }.getOrNull() ?: YukiHookResult()
/**
* Hook [Member]
* @param member 需要 Hook 的方法、构造方法
* @param callback 回调
* @return [YukiHookResult]
*/
internal fun hookMember(member: Member?, callback: YukiHookCallback): YukiHookResult {
runCatching {
YukiHookCacheStore.hookedMembers.takeIf { it.isNotEmpty() }?.forEach {
if (it.member.toString() == member?.toString()) return YukiHookResult(isAlreadyHooked = true, it)
}
}
return HookCompatHelper.hookMember(member, callback).let {
if (it != null) YukiHookCacheStore.hookedMembers.add(it)
YukiHookResult(hookedMember = it)
}
}
/**
* 获取当前 [Member] 是否被 Hook
* @param member 实例
* @return [Boolean]
*/
internal fun isMemberHooked(member: Member?): Boolean {
if (member == null) return false
return HookApiCategoryHelper.hasAvailableHookApi && YukiHookCacheStore.hookedMembers.any { it.member.toString() == member.toString() }
}
/**
* 执行原始 [Member]
*
* 未进行 Hook 的 [Member]
* @param member 实例
* @param args 参数实例
* @return [Any] or null
* @throws IllegalStateException 如果 [Member] 参数个数不正确
*/
internal fun invokeOriginalMember(member: Member?, instance: Any?, args: Array<out Any?>?) =
if (isMemberHooked(member)) member?.let {
runCatching { HookCompatHelper.invokeOriginalMember(member, instance, args) }.onFailure {
if (it.message?.lowercase()?.contains("wrong number of arguments") == true) error(it.message ?: it.toString())
yLoggerE(msg = "Invoke original Member [$member] failed", e = it)
}.getOrNull()
} else null
/**
* 使用当前 Hook API 自带的日志功能打印日志
* @param msg 日志打印的内容
* @param e 异常堆栈信息 - 默认空
*/
internal fun logByHooker(msg: String, e: Throwable? = null) {
if (HookApiCategoryHelper.hasAvailableHookApi) HookCompatHelper.logByHooker(msg, e)
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.priority
/**
* Hook 回调优先级配置类
*/
internal enum class YukiHookPriority {
/** 默认 Hook 回调优先级 */
DEFAULT,
/** 延迟回调 Hook 方法结果 */
LOWEST,
/** 更快回调 Hook 方法结果 */
HIGHEST
}

View File

@@ -0,0 +1,82 @@
/*
* 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/4/9.
* This file is Modified by fankes on 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.proxy
import com.highcapable.yukihookapi.hook.core.api.priority.YukiHookPriority
import java.lang.reflect.Member
/**
* Hook 回调接口抽象类
* @param priority Hook 优先级
*/
internal abstract class YukiHookCallback(internal open val priority: YukiHookPriority) {
/**
* Hook 结果回调接口
*/
internal interface Param {
/**
* [Member] 实例
* @return [Member] or null
*/
val member: Member?
/**
* 当前实例对象
* @return [Any] or null
*/
val instance: Any?
/**
* 方法、构造方法数组
* @return [Array] or null
*/
val args: Array<Any?>?
/**
* 获取、设置方法返回值 (结果)
* @return [Any] or null
*/
var result: Any?
/**
* 是否存在设置过的方法调用抛出异常
* @return [Boolean]
*/
val hasThrowable: Boolean
/**
* 获取、设置方法调用抛出的异常
* @return [Throwable] or null
* @throws Throwable
*/
var throwable: Throwable?
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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/4/9.
* This file is Modified by fankes on 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.proxy
import com.highcapable.yukihookapi.hook.core.api.priority.YukiHookPriority
import java.lang.reflect.Member
/**
* Hook 方法回调接口抽象类
* @param priority Hook 优先级 - 默认 [YukiHookPriority.DEFAULT]
*/
internal abstract class YukiMemberHook(override val priority: YukiHookPriority = YukiHookPriority.DEFAULT) : YukiHookCallback(priority) {
/**
* 在方法执行之前注入
* @param param Hook 结果回调接口
*/
internal open fun beforeHookedMember(param: Param) {}
/**
* 在方法执行之后注入
* @param param Hook 结果回调接口
*/
internal open fun afterHookedMember(param: Param) {}
/**
* 已经 Hook 且可被解除 Hook 的 [Member] 实现接口抽象类
*/
internal abstract class HookedMember internal constructor() {
/**
* 当前被 Hook 的 [Member]
* @return [Member] or null
*/
internal abstract val member: Member?
/** 解除 Hook */
internal abstract fun remove()
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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/4/9.
* This file is Modified by fankes on 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.proxy
import com.highcapable.yukihookapi.hook.core.api.priority.YukiHookPriority
/**
* Hook 替换方法回调接口抽象类
* @param priority Hook 优先级- 默认 [YukiHookPriority.DEFAULT]
*/
internal abstract class YukiMemberReplacement(override val priority: YukiHookPriority = YukiHookPriority.DEFAULT) : YukiMemberHook(priority) {
override fun beforeHookedMember(param: Param) {
param.result = replaceHookedMember(param)
}
override fun afterHookedMember(param: Param) {}
/**
* 拦截替换为指定结果
* @param param Hook 结果回调接口
* @return [Any] or null
*/
abstract fun replaceHookedMember(param: Param): Any?
}

View File

@@ -0,0 +1,37 @@
/*
* 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 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.result
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiMemberHook
/**
* Hook 结果实现类
* @param isAlreadyHooked 是否已经被 Hook - 默认否
* @param hookedMember 当前 Hook 的实例对象 - 默认空
*/
internal data class YukiHookResult(val isAlreadyHooked: Boolean = false, val hookedMember: YukiMemberHook.HookedMember? = null)

View File

@@ -0,0 +1,41 @@
/*
* 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/7/28.
* This file is Modified by fankes on 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.core.api.store
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiMemberHook
import java.lang.reflect.Member
/**
* Hook 过程的功能缓存实现类
*/
internal object YukiHookCacheStore {
/** 已经 Hook 的 [Member] 数组 */
internal val hookedMembers = HashSet<YukiMemberHook.HookedMember>()
}

View File

@@ -0,0 +1,146 @@
/*
* 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/4.
*/
package com.highcapable.yukihookapi.hook.core.finder.base
import com.highcapable.yukihookapi.annotation.YukiPrivateApi
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.core.finder.base.data.BaseRulesData
import com.highcapable.yukihookapi.hook.factory.toClass
import com.highcapable.yukihookapi.hook.type.defined.UndefinedType
import java.lang.reflect.Member
import kotlin.math.abs
/**
* 这是 [Class] 与 [Member] 查找类功能的基本类实现
*/
abstract class BaseFinder {
/** 当前查找条件规则数据 */
@PublishedApi
internal abstract val rulesData: BaseRulesData
/**
* 字节码、数组下标筛选数据类型
*/
@PublishedApi
internal enum class IndexConfigType { ORDER, MATCH }
/**
* 字节码、数组下标筛选实现类
* @param type 类型
*/
inner class IndexTypeCondition @PublishedApi internal constructor(private val type: IndexConfigType) {
/**
* 设置下标
*
* 若 index 小于零则为倒序 - 此时可以使用 [IndexTypeConditionSort.reverse] 方法实现
*
* 可使用 [IndexTypeConditionSort.first] 和 [IndexTypeConditionSort.last] 设置首位和末位筛选条件
* @param num 下标
*/
fun index(num: Int) = when (type) {
IndexConfigType.ORDER -> rulesData.orderIndex = Pair(num, true)
IndexConfigType.MATCH -> rulesData.matchIndex = Pair(num, true)
}
/**
* 得到下标
* @return [IndexTypeConditionSort]
*/
fun index() = IndexTypeConditionSort()
/**
* 字节码、数组下标排序实现类
*
* - ❗请使用 [index] 方法来获取 [IndexTypeConditionSort]
*/
inner class IndexTypeConditionSort internal constructor() {
/** 设置满足条件的第一个*/
fun first() = index(num = 0)
/** 设置满足条件的最后一个*/
fun last() = when (type) {
IndexConfigType.ORDER -> rulesData.orderIndex = Pair(0, false)
IndexConfigType.MATCH -> rulesData.matchIndex = Pair(0, false)
}
/**
* 设置倒序下标
* @param num 下标
*/
fun reverse(num: Int) = when {
num < 0 -> index(abs(num))
num == 0 -> index().last()
else -> index(-num)
}
}
}
/**
* 将目标类型转换为可识别的兼容类型
* @param tag 当前查找类的标识
* @param loader 使用的 [ClassLoader]
* @return [Class] or null
*/
internal fun Any?.compat(tag: String, loader: ClassLoader?) = when (this) {
null -> null
is Class<*> -> this
is String -> runCatching { toClass(loader) }.getOrNull() ?: UndefinedType
is VariousClass -> runCatching { get(loader) }.getOrNull() ?: UndefinedType
else -> error("$tag match type \"$javaClass\" not allowed")
} as Class<*>?
/**
* 返回结果实现类
*
* - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法
* @return [BaseResult]
*/
@YukiPrivateApi
abstract fun build(): BaseResult
/**
* 返回只有异常的结果实现类
*
* - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法
* @param throwable 异常
* @return [BaseResult]
*/
@YukiPrivateApi
abstract fun failure(throwable: Throwable?): BaseResult
/**
* 查找结果实现、处理类接口
*
* - ❗此功能交由方法体自动完成 - 你不应该手动继承此接口
*/
@YukiPrivateApi
interface BaseResult
}

View File

@@ -0,0 +1,83 @@
/*
* 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/4.
*/
package com.highcapable.yukihookapi.hook.core.finder.base
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.YukiPrivateApi
import com.highcapable.yukihookapi.hook.core.api.compat.HookApiCategoryHelper
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.log.yLoggerI
/**
* 这是 [Class] 查找类功能的基本类实现
* @param loaderSet 当前使用的 [ClassLoader] 实例
*/
abstract class ClassBaseFinder internal constructor(internal open val loaderSet: ClassLoader? = null) : BaseFinder() {
internal companion object {
/** [loaderSet] 为 null 的提示 */
internal const val LOADERSET_IS_NULL = "loaderSet is null"
}
/** 当前找到的 [Class] 数组 */
internal var classInstances = HashSet<Class<*>>()
/** 是否开启忽略错误警告功能 */
internal var isShutErrorPrinting = false
/**
* 将目标类型转换为可识别的兼容类型
* @param any 当前需要转换的实例
* @param tag 当前查找类的标识
* @return [Class] or null
*/
internal fun compatType(any: Any?, tag: String) = any?.compat(tag, loaderSet)
/**
* 在开启 [YukiHookAPI.Configs.isDebug] 且在 [HookApiCategoryHelper.hasAvailableHookApi] 情况下输出调试信息
* @param msg 调试日志内容
*/
internal fun onDebuggingMsg(msg: String) {
if (YukiHookAPI.Configs.isDebug && HookApiCategoryHelper.hasAvailableHookApi) yLoggerI(msg = msg)
}
/**
* 发生错误时输出日志
* @param throwable 错误
*/
internal fun onFailureMsg(throwable: Throwable? = null) {
if (isShutErrorPrinting) return
/** 判断是否为 [LOADERSET_IS_NULL] */
if (throwable?.message == LOADERSET_IS_NULL) return
yLoggerE(msg = "NoClassDefFound happend in [$loaderSet]", e = throwable)
}
@YukiPrivateApi
override fun failure(throwable: Throwable?) = error("DexClassFinder does not contain this usage")
}

View File

@@ -0,0 +1,203 @@
/*
* 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/18.
*/
package com.highcapable.yukihookapi.hook.core.finder.base
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.YukiPrivateApi
import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreator
import com.highcapable.yukihookapi.hook.core.api.compat.HookApiCategoryHelper
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.log.yLoggerI
import com.highcapable.yukihookapi.hook.utils.await
import com.highcapable.yukihookapi.hook.utils.unit
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
/**
* 这是 [Member] 查找类功能的基本类实现
* @param tag 当前查找类的标识
* @param classSet 当前需要查找的 [Class] 实例
*/
abstract class MemberBaseFinder internal constructor(
private val tag: String,
@PublishedApi internal open val classSet: Class<*>? = null
) : BaseFinder() {
internal companion object {
/** [classSet] 为 null 的提示 */
internal const val CLASSSET_IS_NULL = "classSet is null"
}
/** 当前 [MemberHookerManager] */
@PublishedApi
internal var hookerManager = MemberHookerManager()
/** 是否使用了重查找功能 */
@PublishedApi
internal var isUsingRemedyPlan = false
/** 是否开启忽略错误警告功能 */
internal var isShutErrorPrinting = false
/** 当前找到的 [Member] 数组 */
internal var memberInstances = HashSet<Member>()
/** 需要输出的日志内容 */
private var loggingContent: Pair<String, Throwable?>? = null
/**
* 将 [HashSet]<[Member]> 转换为 [HashSet]<[Field]>
* @return [HashSet]<[Field]>
*/
internal fun HashSet<Member>.fields() =
hashSetOf<Field>().also { takeIf { e -> e.isNotEmpty() }?.forEach { e -> (e as? Field?)?.also { f -> it.add(f) } } }
/**
* 将 [HashSet]<[Member]> 转换为 [HashSet]<[Method]>
* @return [HashSet]<[Method]>
*/
internal fun HashSet<Member>.methods() =
hashSetOf<Method>().also { takeIf { e -> e.isNotEmpty() }?.forEach { e -> (e as? Method?)?.also { m -> it.add(m) } } }
/**
* 将 [HashSet]<[Member]> 转换为 [HashSet]<[Constructor]>
* @return [HashSet]<[Constructor]>
*/
internal fun HashSet<Member>.constructors() =
hashSetOf<Constructor<*>>().also { takeIf { e -> e.isNotEmpty() }?.forEach { e -> (e as? Constructor<*>?)?.also { c -> it.add(c) } } }
/**
* 将目标类型转换为可识别的兼容类型
* @return [Class] or null
*/
internal fun Any?.compat() = compat(tag, classSet?.classLoader)
/**
* 发生错误时输出日志
* @param msg 消息日志
* @param throwable 错误
* @param isAlwaysPrint 忽略条件每次都打印错误
*/
internal fun onFailureMsg(msg: String = "", throwable: Throwable? = null, isAlwaysPrint: Boolean = false) {
/** 创建日志 */
fun build() {
if (hookerManager.isNotIgnoredNoSuchMemberFailure && isUsingRemedyPlan.not() && isShutErrorPrinting.not())
loggingContent = Pair(msg, throwable)
}
/** 判断是否为 [CLASSSET_IS_NULL] */
if (throwable?.message == CLASSSET_IS_NULL) return
/** 判断绑定到 Hooker 时仅创建日志 */
if (hookerManager.instance != null) return await { build() }.unit()
/** 判断始终输出日志或等待结果后输出日志 */
if (isAlwaysPrint) build().run { printLogIfExist() }
else await { build().run { printLogIfExist() } }
}
/** 存在日志时输出日志 */
internal fun printLogIfExist() {
if (loggingContent != null) yLoggerE(
msg = "NoSuch$tag happend in [$classSet] ${loggingContent?.first}${hookerManager.tailTag}",
e = loggingContent?.second
)
/** 仅输出一次 - 然后清掉日志 */
loggingContent = null
}
/**
* 在开启 [YukiHookAPI.Configs.isDebug] 且在 [HookApiCategoryHelper.hasAvailableHookApi] 且在 Hook 过程中情况下输出调试信息
* @param msg 调试日志内容
*/
internal fun onDebuggingMsg(msg: String) {
if (YukiHookAPI.Configs.isDebug && HookApiCategoryHelper.hasAvailableHookApi && hookerManager.instance != null)
yLoggerI(msg = "$msg${hookerManager.tailTag}")
}
/**
* 返回结果处理类并设置到目标 [YukiMemberHookCreator.MemberHookCreator]
*
* - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法
* @return [BaseFinder.BaseResult]
*/
@YukiPrivateApi
abstract fun process(): BaseResult
/**
* 返回只有异常的结果处理类并作用于目标 [YukiMemberHookCreator.MemberHookCreator]
*
* - ❗此功能交由方法体自动完成 - 你不应该手动调用此方法
* @param throwable 异常
* @return [BaseFinder.BaseResult]
*/
@YukiPrivateApi
abstract fun denied(throwable: Throwable?): BaseResult
/**
* 当前 Hooker 管理实现类
*/
internal inner class MemberHookerManager {
/** 当前 Hooker */
internal var instance: YukiMemberHookCreator.MemberHookCreator? = null
/** 当前 [Member] 是否设置到当前 Hooker */
internal var isMemberBinded = false
/**
* 判断是否没有设置 Hook 过程中 方法、构造方法、变量 找不到的任何异常拦截
* @return [Boolean] 没有设置任何异常拦截
*/
internal val isNotIgnoredNoSuchMemberFailure get() = instance?.isNotIgnoredNoSuchMemberFailure ?: true
/**
* 获取当前日志尾部打印的 TAG 用于标识当前 Hook 实例
* @return [String]
*/
internal val tailTag get() = instance?.tag?.let { if (it.isNotBlank()) " [$it]" else "" } ?: ""
/**
* 绑定当前 [Member] 到当前 Hooker
* @param member 当前 [Member]
*/
internal fun bindMember(member: Member?) {
instance?.members?.clear()
member?.also { instance?.members?.add(it) }
}
/**
* 绑定 [Member] 数组到当前 Hooker
* @param members 当前 [Member] 数组
*/
internal fun bindMembers(members: HashSet<Member>) {
instance?.members?.clear()
members.forEach { instance?.members?.add(it) }
}
}
}

View File

@@ -0,0 +1,143 @@
/*
* 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/8.
*/
package com.highcapable.yukihookapi.hook.core.finder.base.data
import com.highcapable.yukihookapi.hook.core.finder.base.rules.CountRules
import com.highcapable.yukihookapi.hook.core.finder.base.rules.ModifierRules
import com.highcapable.yukihookapi.hook.core.finder.base.rules.NameRules
import com.highcapable.yukihookapi.hook.core.finder.base.rules.ObjectRules
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions
import com.highcapable.yukihookapi.hook.type.defined.VagueType
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
/**
* 这是 [Class] 与 [Member] 规则查找数据基本类实现
* @param modifiers 描述符条件
* @param orderIndex 字节码、数组顺序下标
* @param matchIndex 字节码、数组筛选下标
*/
@PublishedApi
internal abstract class BaseRulesData internal constructor(
var modifiers: ModifierConditions? = null,
var orderIndex: Pair<Int, Boolean>? = null,
var matchIndex: Pair<Int, Boolean>? = null
) {
/** 当前类唯一标识值 */
internal var uniqueValue = 0L
init {
uniqueValue = System.currentTimeMillis()
}
/**
* [String] 转换为 [NameRules]
* @return [NameRules]
*/
internal fun String.cast() = NameRules.with(this)
/**
* [Int] 转换为 [CountRules]
* @return [CountRules]
*/
internal fun Int.cast() = CountRules.with(this)
/**
* [Class] 转换为 [ModifierRules]
* @return [ModifierRules]
*/
internal fun Class<*>.cast() = ModifierRules.with(instance = this, uniqueValue)
/**
* [Member] 转换为 [ModifierRules]
* @return [ModifierRules]
*/
internal fun Member.cast() = ModifierRules.with(instance = this, uniqueValue)
/**
* [Field.getType] 转换为 [ObjectRules]
* @return [ObjectRules]
*/
internal fun Field.type() = ObjectRules.with(type)
/**
* [Method.getParameterTypes] 转换为 [ObjectRules]
* @return [ObjectRules]
*/
internal fun Method.paramTypes() = ObjectRules.with(parameterTypes)
/**
* [Method.getReturnType] 转换为 [ObjectRules]
* @return [ObjectRules]
*/
internal fun Method.returnType() = ObjectRules.with(returnType)
/**
* [Constructor.getParameterTypes] 转换为 [ObjectRules]
* @return [ObjectRules]
*/
internal fun Constructor<*>.paramTypes() = ObjectRules.with(parameterTypes)
/**
* 获取参数数组文本化内容
* @return [String]
*/
internal fun Array<out Class<*>>?.typeOfString() =
StringBuilder("(").also { sb ->
var isFirst = true
if (this == null || isEmpty()) return "()"
forEach {
if (isFirst) isFirst = false else sb.append(", ")
sb.append(it.takeIf { it.canonicalName != VagueType.canonicalName }?.canonicalName ?: "*vague*")
}
sb.append(")")
}.toString()
/**
* 获取规则对象模板字符串数组
* @return [Array]<[String]>
*/
internal abstract val templates: Array<String>
/**
* 获取规则对象名称
* @return [String]
*/
internal abstract val objectName: String
/**
* 判断规则是否已经初始化 (设置了任意一个参数)
* @return [Boolean]
*/
internal open val isInitialize get() = modifiers != null || orderIndex != null || matchIndex != null
override fun toString() = "[$modifiers][$orderIndex][$matchIndex]"
}

View File

@@ -0,0 +1,82 @@
/*
* 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/14.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.core.finder.base.rules
import java.lang.reflect.Member
/**
* 这是一个模糊 [Class]、[Member] 数组 (下标) 个数条件实现类
*
* 可对 R8 混淆后的 [Class]、[Member] 进行更加详细的定位
* @param instance 当前实例对象
*/
class CountRules private constructor(private val instance: Int) {
@PublishedApi
internal companion object {
/**
* 创建实例
* @param instance 实例对象
* @return [CountRules]
*/
@PublishedApi
internal fun with(instance: Int) = CountRules(instance)
}
/**
* 是否为 0
* @return [Boolean]
*/
fun Int.isZero() = this == 0
/**
* 大于 [count]
* @param count 目标对象
* @return [Boolean]
*/
fun Int.moreThan(count: Int) = this > count
/**
* 小于 [count]
* @param count 目标对象
* @return [Boolean]
*/
fun Int.lessThan(count: Int) = this < count
/**
* 在 [countRange] 区间 A ≤ this ≤ B
* @param countRange 区间
* @return [Boolean]
*/
fun Int.inInterval(countRange: IntRange) = this in countRange
override fun toString() = "CountRules [$instance]"
}

View File

@@ -0,0 +1,239 @@
/*
* 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/3/27.
* This file is Modified by fankes on 2022/9/14.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.core.finder.base.rules
import android.util.ArrayMap
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
import java.lang.reflect.Modifier
/**
* 这是一个 [Class]、[Member] 描述符条件实现类
*
* 可对 R8 混淆后的 [Class]、[Member] 进行更加详细的定位
* @param instance 当前实例对象
*/
class ModifierRules private constructor(private val instance: Any) {
@PublishedApi
internal companion object {
/** 当前实例数组 */
private val instances = ArrayMap<Long, ModifierRules>()
/**
* 获取模板字符串数组
* @param value 唯一标识值
* @return [ArrayList]<[String]>
*/
internal fun templates(value: Long) = instances[value]?.templates ?: arrayListOf()
/**
* 创建实例
* @param instance 实例对象
* @param value 唯一标识值 - 默认 0
* @return [ModifierRules]
*/
@PublishedApi
internal fun with(instance: Any, value: Long = 0) = ModifierRules(instance).apply { instances[value] = this }
}
/** 当前模板字符串数组 */
private val templates = ArrayList<String>()
/**
* [Class]、[Member] 类型是否包含 public
*
* 如下所示 ↓
*
* public class/void/int/String...
*
* ^^^
* @return [Boolean]
*/
val isPublic get() = Modifier.isPublic(modifiers).also { templates.add("<isPublic> ($it)") }
/**
* [Class]、[Member] 类型是否包含 private
*
* 如下所示 ↓
*
* private class/void/int/String...
*
* ^^^
* @return [Boolean]
*/
val isPrivate get() = Modifier.isPrivate(modifiers).also { templates.add("<isPrivate> ($it)") }
/**
* [Class]、[Member] 类型是否包含 protected
*
* 如下所示 ↓
*
* protected class/void/int/String...
*
* ^^^
* @return [Boolean]
*/
val isProtected get() = Modifier.isProtected(modifiers).also { templates.add("<isProtected> ($it)") }
/**
* [Class]、[Member] 类型是否包含 static
*
* 对于任意的静态 [Class]、[Member] 可添加此描述进行确定
*
* 如下所示 ↓
*
* static class/void/int/String...
*
* ^^^
*
* - ❗注意 Kotlin → Jvm 后的 object 类中的方法并不是静态的
* @return [Boolean]
*/
val isStatic get() = Modifier.isStatic(modifiers).also { templates.add("<isStatic> ($it)") }
/**
* [Class]、[Member] 类型是否包含 final
*
* 如下所示 ↓
*
* final class/void/int/String...
*
* ^^^
*
* - ❗注意 Kotlin → Jvm 后没有 open 标识的 [Class]、[Member] 和没有任何关联的 [Class]、[Member] 都将为 final
* @return [Boolean]
*/
val isFinal get() = Modifier.isFinal(modifiers).also { templates.add("<isFinal> ($it)") }
/**
* [Class]、[Member] 类型是否包含 synchronized
*
* 如下所示 ↓
*
* synchronized class/void/int/String...
*
* ^^^
* @return [Boolean]
*/
val isSynchronized get() = Modifier.isSynchronized(modifiers).also { templates.add("<isSynchronized> ($it)") }
/**
* [Field] 类型是否包含 volatile
*
* 如下所示 ↓
*
* volatile int/String...
*
* ^^^
* @return [Boolean]
*/
val isVolatile get() = Modifier.isVolatile(modifiers).also { templates.add("<isVolatile> ($it)") }
/**
* [Field] 类型是否包含 transient
*
* 如下所示 ↓
*
* transient int/String...
*
* ^^^
* @return [Boolean]
*/
val isTransient get() = Modifier.isTransient(modifiers).also { templates.add("<isTransient> ($it)") }
/**
* [Method] 类型是否包含 native
*
* 对于任意 JNI 对接的 [Method] 可添加此描述进行确定
*
* 如下所示 ↓
*
* native void/int/String...
*
* ^^^
* @return [Boolean]
*/
val isNative get() = Modifier.isNative(modifiers).also { templates.add("<isNative> ($it)") }
/**
* [Class] 类型是否包含 interface
*
* 如下所示 ↓
*
* interface ...
*
* ^^^
* @return [Boolean]
*/
val isInterface get() = Modifier.isInterface(modifiers).also { templates.add("<isInterface> ($it)") }
/**
* [Class]、[Member] 类型是否包含 abstract
*
* 对于任意的抽象 [Class]、[Member] 可添加此描述进行确定
*
* 如下所示 ↓
*
* abstract class/void/int/String...
*
* ^^^
* @return [Boolean]
*/
val isAbstract get() = Modifier.isAbstract(modifiers).also { templates.add("<isAbstract> ($it)") }
/**
* [Class]、[Member] 类型是否包含 strictfp
*
* 如下所示 ↓
*
* strictfp class/void/int/String...
*
* ^^^
* @return [Boolean]
*/
val isStrict get() = Modifier.isStrict(modifiers).also { templates.add("<isStrict> ($it)") }
/**
* 获取当前对象的类型描述符
* @return [Int]
*/
private val modifiers
get() = when (instance) {
is Member -> instance.modifiers
is Class<*> -> instance.modifiers
else -> 0
}
override fun toString() = "ModifierRules [$instance]"
}

View File

@@ -0,0 +1,125 @@
/*
* 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/5/16.
* This file is Modified by fankes on 2022/9/14.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.highcapable.yukihookapi.hook.core.finder.base.rules
import java.lang.reflect.Member
/**
* 这是一个模糊 [Class]、[Member] 名称条件实现类
*
* 可对 R8 混淆后的 [Class]、[Member] 进行更加详细的定位
* @param instance 当前实例对象
*/
class NameRules private constructor(private val instance: String) {
@PublishedApi
internal companion object {
/**
* 创建实例
* @param instance 实例对象
* @return [NameRules]
*/
@PublishedApi
internal fun with(instance: String) = NameRules(instance)
}
/**
* 是否为匿名类的主类调用对象名称
*
* 它的名称形态通常为this$[index]
* @param index 下标 - 默认 0
* @return [Boolean]
*/
fun String.isSynthetic(index: Int = 0) = this == "this$$index"
/**
* 是否只有符号
*
* 筛选仅包含 _、-、?、!、,、.、<、> 等符号以及特殊符号
*
* 你可以使用 [matches] 方法进行更详细的正则匹配
* @return [Boolean]
*/
fun String.isOnlySymbols() = matches("[*,.:~`'\"|/\\\\?!^()\\[\\]{}%@#$&\\-_+=<>]+".toRegex())
/**
* 是否只有字母
*
* 在没有 [isOnlyLowercase] 以及 [isOnlyUppercase] 的条件下筛选仅包含 26 个大小写英文字母
*
* 你可以使用 [matches] 方法进行更详细的正则匹配
* @return [Boolean]
*/
fun String.isOnlyLetters() = matches("[a-zA-Z]+".toRegex())
/**
* 是否只有数字
*
* 筛选仅包含 0-9 阿拉伯数字
*
* 你可以使用 [matches] 方法进行更详细的正则匹配
* @return [Boolean]
*/
fun String.isOnlyNumbers() = matches("\\d+".toRegex())
/**
* 是否只有字母或数字
*
* 融合条件 [isOnlyLetters] 和 [isOnlyNumbers]
*
* 你可以使用 [matches] 方法进行更详细的正则匹配
* @return [Boolean]
*/
fun String.isOnlyLettersNumbers() = matches("[a-zA-Z\\d]+".toRegex())
/**
* 是否只有小写字母
*
* 在没有其它条件的情况下设置此条件允许判断对象存在字母以外的字符
*
* 你可以使用 [matches] 方法进行更详细的正则匹配
* @return [Boolean]
*/
fun String.isOnlyLowercase() = matches("[a-z]+".toRegex())
/**
* 是否只有大写字母
*
* 在没有其它条件的情况下设置此条件允许判断对象存在字母以外的字符
*
* 你可以使用 [matches] 方法进行更详细的正则匹配
* @return [Boolean]
*/
fun String.isOnlyUppercase() = matches("[A-Z]+".toRegex())
override fun toString() = "NameRules [$instance]"
}

View File

@@ -0,0 +1,55 @@
/*
* 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/12/30.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.core.finder.base.rules
import java.lang.reflect.Member
/**
* 这是一个任意对象条件实现类
*
* 可对 R8 混淆后的 [Class]、[Member] 进行更加详细的定位
* @param instance 当前实例对象
*/
class ObjectRules private constructor(private val instance: Any) {
@PublishedApi
internal companion object {
/**
* 创建实例
* @param instance 实例对象
* @return [ObjectRules]
*/
@PublishedApi
internal fun with(instance: Any) = ObjectRules(instance)
}
override fun toString() = "ObjectRules [$instance]"
}

View File

@@ -0,0 +1,627 @@
/*
* 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/4.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.highcapable.yukihookapi.hook.core.finder.classes
import android.content.Context
import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.os.SystemClock
import androidx.core.content.pm.PackageInfoCompat
import com.highcapable.yukihookapi.annotation.YukiPrivateApi
import com.highcapable.yukihookapi.hook.core.finder.base.ClassBaseFinder
import com.highcapable.yukihookapi.hook.core.finder.classes.data.ClassRulesData
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.ConstructorRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.FieldRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.MemberRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.MethodRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.base.BaseRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.result.MemberRulesResult
import com.highcapable.yukihookapi.hook.core.finder.tools.ReflectionTool
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions
import com.highcapable.yukihookapi.hook.factory.hasClass
import com.highcapable.yukihookapi.hook.factory.searchClass
import com.highcapable.yukihookapi.hook.factory.toClass
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.utils.await
import com.highcapable.yukihookapi.hook.utils.runBlocking
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import dalvik.system.BaseDexClassLoader
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
/**
* [Class] 查找类
*
* 可使用 [BaseDexClassLoader] 通过指定条件查找指定 [Class] 或一组 [Class]
*
* - ❗此功能尚在试验阶段 - 性能与稳定性可能仍然存在问题 - 使用过程遇到问题请向我们报告并帮助我们改进
* @param name 标识当前 [Class] 缓存的名称 - 不设置将不启用缓存 - 启用缓存必须启用 [async]
* @param async 是否启用异步
* @param loaderSet 当前使用的 [ClassLoader] 实例
*/
class DexClassFinder @PublishedApi internal constructor(
internal var name: String,
internal var async: Boolean,
override val loaderSet: ClassLoader?
) : ClassBaseFinder(loaderSet) {
companion object {
/** 缓存的存储文件名 */
private const val CACHE_FILE_NAME = "config_yukihook_cache_obfuscate_classes"
/**
* 获取当前运行环境的 [Context]
* @return [Context] or null
*/
private val currentContext get() = AppParasitics.hostApplication ?: AppParasitics.currentApplication
/**
* 通过 [Context] 获取当前 [SharedPreferences]
* @param versionName 版本名称 - 默认空
* @param versionCode 版本号 - 默认空
* @return [SharedPreferences]
*/
private fun Context.currentSp(versionName: String? = null, versionCode: Long? = null) =
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
getSharedPreferences(packageManager?.getPackageInfo(packageName, PackageManager.GET_META_DATA)
?.let { "${CACHE_FILE_NAME}_${versionName ?: it.versionName}_${versionCode ?: PackageInfoCompat.getLongVersionCode(it)}" }
?: "${CACHE_FILE_NAME}_unknown",
Context.MODE_PRIVATE)
/**
* 清除当前 [DexClassFinder] 的 [Class] 缓存
*
* 适用于全部通过 [ClassLoader.searchClass] or [PackageParam.searchClass] 获取的 [DexClassFinder]
* @param context 当前 [Context] - 不填默认获取 [currentContext]
* @param versionName 版本名称 - 默认空
* @param versionCode 版本号 - 默认空
*/
fun clearCache(context: Context? = currentContext, versionName: String? = null, versionCode: Long? = null) {
context?.currentSp(versionName, versionCode)?.edit()?.clear()?.apply()
?: yLoggerW(msg = "Cannot clear cache for DexClassFinder because got null context instance")
}
}
@PublishedApi
override var rulesData = ClassRulesData()
/**
* 设置 [Class] 完整名称
*
* 只会查找匹配到的 [Class.getName]
*
* 例如 com.demo.Test 需要填写 com.demo.Test
* @return [String]
*/
var fullName
get() = rulesData.fullName?.name ?: ""
set(value) {
rulesData.fullName = rulesData.createNameRulesData(value)
}
/**
* 设置 [Class] 简单名称
*
* 只会查找匹配到的 [Class.getSimpleName]
*
* 例如 com.demo.Test 只需要填写 Test
*
* 对于匿名类例如 com.demo.Test$InnerTest 会为空 - 此时你可以使用 [singleName]
* @return [String]
*/
var simpleName
get() = rulesData.simpleName?.name ?: ""
set(value) {
rulesData.simpleName = rulesData.createNameRulesData(value)
}
/**
* 设置 [Class] 独立名称
*
* 设置后将首先使用 [Class.getSimpleName] - 若为空则会使用 [Class.getName] 进行处理
*
* 例如 com.demo.Test 只需要填写 Test
*
* 对于匿名类例如 com.demo.Test$InnerTest 只需要填写 Test$InnerTest
* @return [String]
*/
var singleName
get() = rulesData.singleName?.name ?: ""
set(value) {
rulesData.singleName = rulesData.createNameRulesData(value)
}
/**
* 设置在指定包名范围查找当前 [Class]
*
* 设置后仅会在当前 [name] 开头匹配的包名路径下进行查找 - 可提升查找速度
*
* 例如 ↓
*
* com.demo.test
*
* com.demo.test.demo
*
* - ❗建议设置此参数指定查找范围 - 否则 [Class] 过多时将会非常慢
* @param name 指定包名
* @return [FromPackageRules] 可设置 [FromPackageRules.absolute] 标识包名绝对匹配
*/
fun from(vararg name: String) = FromPackageRules(arrayListOf<ClassRulesData.PackageRulesData>().also {
name.takeIf { e -> e.isNotEmpty() }?.forEach { e -> it.add(rulesData.createPackageRulesData(e)) }
if (it.isNotEmpty()) rulesData.fromPackages.addAll(it)
})
/**
* 设置 [Class] 标识符筛选条件
*
* - 可不设置筛选条件
* @param conditions 条件方法体
*/
fun modifiers(conditions: ModifierConditions) {
rulesData.modifiers = conditions
}
/**
* 设置 [Class] 完整名称
*
* 只会查找匹配到的 [Class.getName]
*
* 例如 com.demo.Test 需要填写 com.demo.Test
* @param value 名称
* @return [ClassNameRules] 可设置 [ClassNameRules.optional] 标识类名可选
*/
fun fullName(value: String) = rulesData.createNameRulesData(value).let {
rulesData.fullName = it
ClassNameRules(it)
}
/**
* 设置 [Class] 简单名称
*
* 只会查找匹配到的 [Class.getSimpleName]
*
* 例如 com.demo.Test 只需要填写 Test
*
* 对于匿名类例如 com.demo.Test$InnerTest 会为空 - 此时你可以使用 [singleName]
* @param value 名称
* @return [ClassNameRules] 可设置 [ClassNameRules.optional] 标识类名可选
*/
fun simpleName(value: String) = rulesData.createNameRulesData(value).let {
rulesData.simpleName = it
ClassNameRules(it)
}
/**
* 设置 [Class] 独立名称
*
* 设置后将首先使用 [Class.getSimpleName] - 若为空则会使用 [Class.getName] 进行处理
*
* 例如 com.demo.Test 只需要填写 Test
*
* 对于匿名类例如 com.demo.Test$InnerTest 只需要填写 Test$InnerTest
* @param value 名称
* @return [ClassNameRules] 可设置 [ClassNameRules.optional] 标识类名可选
*/
fun singleName(value: String) = rulesData.createNameRulesData(value).let {
rulesData.singleName = it
ClassNameRules(it)
}
/**
* 设置 [Class] 完整名称条件
*
* 只会查找匹配到的 [Class.getName]
* @param conditions 条件方法体
*/
fun fullName(conditions: NameConditions) {
rulesData.fullNameConditions = conditions
}
/**
* 设置 [Class] 简单名称条件
*
* 只会查找匹配到的 [Class.getSimpleName]
* @param conditions 条件方法体
*/
fun simpleName(conditions: NameConditions) {
rulesData.simpleNameConditions = conditions
}
/**
* 设置 [Class] 独立名称条件
*
* 设置后将首先使用 [Class.getSimpleName] - 若为空则会使用 [Class.getName] 进行处理
* @param conditions 条件方法体
*/
fun singleName(conditions: NameConditions) {
rulesData.singleNameConditions = conditions
}
/** 设置 [Class] 继承的父类 */
inline fun <reified T> extends() {
rulesData.extendsClass.add(T::class.java.name)
}
/**
* 设置 [Class] 继承的父类
*
* 会同时查找 [name] 中所有匹配的父类
* @param name [Class] 完整名称
*/
fun extends(vararg name: String) {
rulesData.extendsClass.addAll(name.toList())
}
/** 设置 [Class] 实现的接口类 */
inline fun <reified T> implements() {
rulesData.implementsClass.add(T::class.java.name)
}
/**
* 设置 [Class] 实现的接口类
*
* 会同时查找 [name] 中所有匹配的接口类
* @param name [Class] 完整名称
*/
fun implements(vararg name: String) {
rulesData.implementsClass.addAll(name.toList())
}
/**
* 标识 [Class] 为匿名类
*
* 例如 com.demo.Test$1 或 com.demo.Test$InnerTest
*
* 标识后你可以使用 [enclosing] 来进一步指定匿名类的 (封闭类) 主类
*/
fun anonymous() {
rulesData.isAnonymousClass = true
}
/**
* 设置 [Class] 没有任何继承
*
* 此时 [Class] 只应该继承于 [Any]
*
* - ❗设置此条件后 [extends] 将失效
*/
fun noExtends() {
rulesData.isNoExtendsClass = true
}
/**
* 设置 [Class] 没有任何接口
*
* - ❗设置此条件后 [implements] 将失效
*/
fun noImplements() {
rulesData.isNoImplementsClass = true
}
/**
* 设置 [Class] 没有任何继承与接口
*
* 此时 [Class] 只应该继承于 [Any]
*
* - ❗设置此条件后 [extends] 与 [implements] 将失效
*/
fun noSuper() {
noExtends()
noImplements()
}
/** 设置 [Class] 匿名类的 (封闭类) 主类 */
inline fun <reified T> enclosing() {
rulesData.enclosingClass.add(T::class.java.name)
}
/**
* 设置 [Class] 匿名类的 (封闭类) 主类
*
* 会同时查找 [name] 中所有匹配的 (封闭类) 主类
* @param name [Class] 完整名称
*/
fun enclosing(vararg name: String) {
rulesData.enclosingClass.addAll(name.toList())
}
/**
* 包名范围名称过滤匹配条件实现类
* @param packages 包名数组
*/
inner class FromPackageRules internal constructor(private val packages: ArrayList<ClassRulesData.PackageRulesData>) {
/**
* 设置包名绝对匹配
*
* 例如有如下包名 ↓
*
* com.demo.test.a
*
* com.demo.test.a.b
*
* com.demo.test.active
*
* 若包名条件为 "com.demo.test.a" 则绝对匹配仅能匹配到第一个
*
* 相反地 - 不设置以上示例会全部匹配
*/
fun absolute() = packages.takeIf { it.isNotEmpty() }?.forEach { it.isAbsolute = true }
}
/**
* 类名匹配条件实现类
* @param name 类名匹配实例
*/
inner class ClassNameRules internal constructor(private val name: ClassRulesData.NameRulesData) {
/**
* 设置类名可选
*
* 例如有如下类名 ↓
*
* com.demo.Test (fullName) / Test (simpleName)
*
* defpackage.a (fullName) / a (simpleName)
*
* 这两个类名都是同一个类 - 但是在有些版本中被混淆有些版本没有
*
* 此时可设置类名为 "com.demo.Test" (fullName) / "Test" (simpleName)
*
* 这样就可在完全匹配类名情况下使用类名而忽略其它查找条件 - 否则忽略此条件继续使用其它查找条件
*/
fun optional() {
name.isOptional = true
}
}
/**
* 设置 [Class] 满足的 [Member] 条件
* @param initiate 条件方法体
* @return [MemberRulesResult]
*/
inline fun member(initiate: MemberRules.() -> Unit = {}) = BaseRules.createMemberRules(this).apply(initiate).build()
/**
* 设置 [Class] 满足的 [Field] 条件
* @param initiate 条件方法体
* @return [MemberRulesResult]
*/
inline fun field(initiate: FieldRules.() -> Unit = {}) = BaseRules.createFieldRules(this).apply(initiate).build()
/**
* 设置 [Class] 满足的 [Method] 条件
* @param initiate 条件方法体
* @return [MemberRulesResult]
*/
inline fun method(initiate: MethodRules.() -> Unit = {}) = BaseRules.createMethodRules(this).apply(initiate).build()
/**
* 设置 [Class] 满足的 [Constructor] 条件
* @param initiate 查找方法体
* @return [MemberRulesResult]
*/
inline fun constructor(initiate: ConstructorRules.() -> Unit = {}) = BaseRules.createConstructorRules(this).apply(initiate).build()
/**
* 得到 [Class] 或一组 [Class]
* @return [HashSet]<[Class]>
* @throws NoClassDefFoundError 如果找不到 [Class]
*/
private val result get() = ReflectionTool.findClasses(loaderSet, rulesData)
/**
* 从本地缓存读取 [Class] 数据
* @return [HashSet]<[Class]>
*/
private fun readFromCache(): HashSet<Class<*>> =
if (async && name.isNotBlank()) currentContext?.let {
hashSetOf<Class<*>>().also { classes ->
it.currentSp().getStringSet(name, emptySet())?.takeIf { it.isNotEmpty() }
?.forEach { className -> if (className.hasClass(loaderSet)) classes.add(className.toClass(loaderSet)) }
}
} ?: let { SystemClock.sleep(1); readFromCache() } else hashSetOf()
/**
* 将当前 [Class] 数组名称保存到本地缓存
* @throws IllegalStateException 如果当前包名为 "android"
*/
private fun HashSet<Class<*>>.saveToCache() {
if (name.isNotBlank() && isNotEmpty()) hashSetOf<String>().also { names ->
takeIf { it.isNotEmpty() }?.forEach { names.add(it.name) }
currentContext?.also {
if (it.packageName == "android") error("Cannot create classes cache for \"android\", please remove \"name\" param")
it.currentSp().edit().apply { putStringSet(name, names) }.apply()
}
}
}
/**
* 设置实例
* @param classes 当前找到的 [Class] 数组
*/
private fun setInstance(classes: HashSet<Class<*>>) {
classInstances.clear()
classes.takeIf { it.isNotEmpty() }?.forEach { classInstances.add(it) }
}
@YukiPrivateApi
override fun build() = runCatching {
if (loaderSet != null) {
/** 开始任务 */
fun startProcess() {
runBlocking {
setInstance(readFromCache().takeIf { it.isNotEmpty() } ?: result)
}.result { ms -> classInstances.takeIf { it.isNotEmpty() }?.forEach { onDebuggingMsg(msg = "Find Class [$it] takes ${ms}ms") } }
}
Result().also { e ->
if (async) e.await {
runCatching {
startProcess()
it.waitResultCallback?.invoke(it.get())
it.waitAllResultCallback?.invoke(it.all())
classInstances.saveToCache()
}.onFailure { e ->
it.isNotFound = true
it.throwable = e
it.noClassDefFoundErrorCallback?.invoke()
onFailureMsg(throwable = e)
}
} else startProcess()
}
} else Result(isNotFound = true, Throwable(LOADERSET_IS_NULL)).await { onFailureMsg() }
}.getOrElse { e -> Result(isNotFound = true, e).await { onFailureMsg(throwable = e) } }
/**
* [Class] 查找结果实现类
* @param isNotFound 是否没有找到 [Class] - 默认否
* @param throwable 错误信息
*/
inner class Result internal constructor(
@PublishedApi internal var isNotFound: Boolean = false,
@PublishedApi internal var throwable: Throwable? = null
) : BaseResult {
/** 异步方法体回调结果 */
internal var waitResultCallback: ((Class<*>?) -> Unit)? = null
/** 异步方法体回调数组结果 */
internal var waitAllResultCallback: ((HashSet<Class<*>>) -> Unit)? = null
/** 异常结果重新回调方法体 */
internal var noClassDefFoundErrorCallback: (() -> Unit)? = null
/**
* 创建监听结果事件方法体
* @param initiate 方法体
* @return [Result] 可继续向下监听
*/
inline fun result(initiate: Result.() -> Unit) = apply(initiate)
/**
* 得到 [Class] 本身
*
* - 若有多个 [Class] 结果只会返回第一个
*
* - 在查找条件找不到任何结果的时候将返回 null
*
* - ❗若你设置了 [async] 请使用 [wait] 方法
* @return [Class] or null
*/
fun get() = all().takeIf { it.isNotEmpty() }?.first()
/**
* 得到 [Class] 本身数组
*
* - 返回全部查找条件匹配的多个 [Class] 实例
*
* - 在查找条件找不到任何结果的时候将返回空的 [HashSet]
*
* - ❗若你设置了 [async] 请使用 [waitAll] 方法
* @return [HashSet]<[Class]>
*/
fun all() = classInstances
/**
* 得到 [Class] 本身数组 (依次遍历)
*
* - 回调全部查找条件匹配的多个 [Class] 实例
*
* - 在查找条件找不到任何结果的时候将不会执行
*
* - ❗若你设置了 [async] 请使用 [waitAll] 方法
* @param result 回调每个结果
* @return [Result] 可继续向下监听
*/
fun all(result: (Class<*>) -> Unit): Result {
all().takeIf { it.isNotEmpty() }?.forEach(result)
return this
}
/**
* 得到 [Class] 本身 (异步)
*
* - 若有多个 [Class] 结果只会回调第一个
*
* - 在查找条件找不到任何结果的时候将回调 null
*
* - ❗你需要设置 [async] 后此方法才会被回调 - 否则请使用 [get] 方法
* @param result 回调 - ([Class] or null)
* @return [Result] 可继续向下监听
*/
fun wait(result: (Class<*>?) -> Unit): Result {
waitResultCallback = result
return this
}
/**
* 得到 [Class] 本身数组 (异步)
*
* - 回调全部查找条件匹配的多个 [Class] 实例
*
* - 在查找条件找不到任何结果的时候将回调空的 [HashSet]
*
* - ❗你需要设置 [async] 后此方法才会被回调 - 否则请使用 [all] 方法
* @param result 回调 - ([HashSet]<[Class]>)
* @return [Result] 可继续向下监听
*/
fun waitAll(result: (HashSet<Class<*>>) -> Unit): Result {
waitAllResultCallback = result
return this
}
/**
* 监听找不到 [Class] 时
* @param result 回调错误
* @return [Result] 可继续向下监听
*/
fun onNoClassDefFoundError(result: (Throwable) -> Unit): Result {
noClassDefFoundErrorCallback = { if (isNotFound) result(throwable ?: Throwable("Initialization Error")) }
noClassDefFoundErrorCallback?.invoke()
return this
}
/**
* 忽略异常并停止打印任何错误日志
*
* - ❗此时若要监听异常结果 - 你需要手动实现 [onNoClassDefFoundError] 方法
* @return [Result] 可继续向下监听
*/
fun ignored(): Result {
isShutErrorPrinting = true
return this
}
}
}

View File

@@ -0,0 +1,181 @@
/*
* 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/5.
*/
@file:Suppress("PropertyName")
package com.highcapable.yukihookapi.hook.core.finder.classes.data
import com.highcapable.yukihookapi.hook.core.finder.base.data.BaseRulesData
import com.highcapable.yukihookapi.hook.core.finder.base.rules.ModifierRules
import com.highcapable.yukihookapi.hook.core.finder.members.data.ConstructorRulesData
import com.highcapable.yukihookapi.hook.core.finder.members.data.FieldRulesData
import com.highcapable.yukihookapi.hook.core.finder.members.data.MemberRulesData
import com.highcapable.yukihookapi.hook.core.finder.members.data.MethodRulesData
import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
/**
* [Class] 规则查找数据类
* @param fromPackages 指定包名范围名称数组
* @param fullName 完整名称
* @param simpleName 简单名称
* @param singleName 独立名称
* @param fullNameConditions 完整名称规则
* @param simpleNameConditions 简单名称规则
* @param singleNameConditions 独立名称规则
* @param isAnonymousClass 匿名类
* @param isNoExtendsClass 无继承的父类
* @param isNoImplementsClass 无继承的实现的接口类
* @param extendsClass 继承的父类名称数组
* @param implementsClass 实现的接口类名称数组
* @param enclosingClass 包含的封闭类 (主类) 名称数组
* @param memberRules [Member] 查找条件数据数组
* @param fieldRules [Field] 查找条件数据数组
* @param methodRules [Method] 查找条件数据数组
* @param constroctorRules [Constructor] 查找条件数据数组
*/
@PublishedApi
internal class ClassRulesData internal constructor(
var fromPackages: ArrayList<PackageRulesData> = arrayListOf(),
var fullName: NameRulesData? = null,
var simpleName: NameRulesData? = null,
var singleName: NameRulesData? = null,
var fullNameConditions: NameConditions? = null,
var simpleNameConditions: NameConditions? = null,
var singleNameConditions: NameConditions? = null,
var isAnonymousClass: Boolean? = null,
var isNoExtendsClass: Boolean? = null,
var isNoImplementsClass: Boolean? = null,
var extendsClass: ArrayList<String> = arrayListOf(),
var implementsClass: ArrayList<String> = arrayListOf(),
var enclosingClass: ArrayList<String> = arrayListOf(),
var memberRules: ArrayList<MemberRulesData> = arrayListOf(),
var fieldRules: ArrayList<FieldRulesData> = arrayListOf(),
var methodRules: ArrayList<MethodRulesData> = arrayListOf(),
var constroctorRules: ArrayList<ConstructorRulesData> = arrayListOf()
) : BaseRulesData() {
/**
* 创建类名匹配条件查找数据类
* @param name 包名
* @return [NameRulesData]
*/
internal fun createNameRulesData(name: String) = NameRulesData(name)
/**
* 创建包名范围名称过滤匹配条件查找数据类
* @param name 包名
* @return [PackageRulesData]
*/
internal fun createPackageRulesData(name: String) = PackageRulesData(name)
/**
* 获取 [Class.getSimpleName] 与 [Class.getName] 的独立名称
* @param instance 当前 [Class] 实例
* @return [String]
*/
internal fun classSingleName(instance: Class<*>) = instance.simpleName.takeIf { it.isNotBlank() }
?: instance.enclosingClass?.let { it.simpleName + instance.name.replace(it.name, newValue = "") } ?: ""
/**
* 类名匹配条件查找数据类
* @param name 包名
* @param isOptional 是否可选 - 默认否
*/
inner class NameRulesData internal constructor(var name: String, var isOptional: Boolean = false) {
/** [Class.getName] */
internal val TYPE_NAME = 0
/** [Class.getSimpleName] */
internal val TYPE_SIMPLE_NAME = 1
/** [Class.getSimpleName] or [Class.getName] */
internal val TYPE_SINGLE_NAME = 2
/**
* 匹配当前 [Class] 实例
* @param instance 当前 [Class] 实例
* @param type 判断类型
* @return [Boolean]
*/
internal fun equals(instance: Class<*>, type: Int) = when (type) {
TYPE_NAME -> instance.name == name
TYPE_SIMPLE_NAME -> instance.simpleName == name
TYPE_SINGLE_NAME -> classSingleName(instance) == name
else -> false
}
override fun toString() = "$name optional($isOptional)"
}
/**
* 包名范围名称过滤匹配条件查找数据类
* @param name 包名
* @param isAbsolute 是否绝对匹配 - 默认否
*/
inner class PackageRulesData internal constructor(var name: String, var isAbsolute: Boolean = false) {
override fun toString() = "$name absolute($isAbsolute)"
}
override val templates
get() = arrayOf(
fromPackages.takeIf { it.isNotEmpty() }?.let { "from:$it" } ?: "",
fullName?.let { "fullName:[$it]" } ?: "",
simpleName?.let { "simpleName:[$it]" } ?: "",
singleName?.let { "singleName:[$it]" } ?: "",
fullNameConditions?.let { "fullNameConditions:[existed]" } ?: "",
simpleNameConditions?.let { "simpleNameConditions:[existed]" } ?: "",
singleNameConditions?.let { "singleNameConditions:[existed]" } ?: "",
modifiers?.let { "modifiers:${ModifierRules.templates(uniqueValue)}" } ?: "",
isAnonymousClass?.let { "isAnonymousClass:[$it]" } ?: "",
isNoExtendsClass?.let { "isNoExtendsClass:[$it]" } ?: "",
isNoImplementsClass?.let { "isNoImplementsClass:[$it]" } ?: "",
extendsClass.takeIf { it.isNotEmpty() }?.let { "extendsClass:$it" } ?: "",
implementsClass.takeIf { it.isNotEmpty() }?.let { "implementsClass:$it" } ?: "",
enclosingClass.takeIf { it.isNotEmpty() }?.let { "enclosingClass:$it" } ?: "",
memberRules.takeIf { it.isNotEmpty() }?.let { "memberRules:[${it.size} existed]" } ?: "",
fieldRules.takeIf { it.isNotEmpty() }?.let { "fieldRules:[${it.size} existed]" } ?: "",
methodRules.takeIf { it.isNotEmpty() }?.let { "methodRules:[${it.size} existed]" } ?: "",
constroctorRules.takeIf { it.isNotEmpty() }?.let { "constroctorRules:[${it.size} existed]" } ?: ""
)
override val objectName get() = "Class"
override val isInitialize
get() = super.isInitialize || fromPackages.isNotEmpty() || fullName != null || simpleName != null || singleName != null ||
fullNameConditions != null || simpleNameConditions != null || singleNameConditions != null || isAnonymousClass != null ||
isNoExtendsClass != null || isNoImplementsClass != null || extendsClass.isNotEmpty() || enclosingClass.isNotEmpty() ||
memberRules.isNotEmpty() || fieldRules.isNotEmpty() || methodRules.isNotEmpty() || constroctorRules.isNotEmpty()
override fun toString() = "[$fromPackages][$fullName][$simpleName][$singleName][$fullNameConditions][$simpleNameConditions]" +
"[$singleNameConditions][$modifiers][$isAnonymousClass][$isNoExtendsClass][$isNoImplementsClass][$extendsClass][$implementsClass]" +
"[$enclosingClass][$memberRules][$fieldRules][$methodRules][$constroctorRules]" + super.toString()
}

View File

@@ -0,0 +1,164 @@
/*
* 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/12.
*/
@file:Suppress("MemberVisibilityCanBePrivate")
package com.highcapable.yukihookapi.hook.core.finder.classes.rules
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.base.BaseRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.result.MemberRulesResult
import com.highcapable.yukihookapi.hook.core.finder.members.data.ConstructorRulesData
import com.highcapable.yukihookapi.hook.core.finder.type.factory.CountConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ObjectsConditions
import com.highcapable.yukihookapi.hook.type.defined.UndefinedType
import com.highcapable.yukihookapi.hook.type.defined.VagueType
import java.lang.reflect.Constructor
/**
* [Constructor] 查找条件实现类
* @param rulesData 当前查找条件规则数据
*/
class ConstructorRules internal constructor(@PublishedApi internal val rulesData: ConstructorRulesData) : BaseRules() {
/**
* 设置 [Constructor] 参数个数
*
* 你可以不使用 [param] 指定参数类型而是仅使用此变量指定参数个数
*
* 若参数个数小于零则忽略并使用 [param]
* @return [Int]
*/
var paramCount
get() = rulesData.paramCount
set(value) {
rulesData.paramCount = value
}
/**
* 设置 [Constructor] 标识符筛选条件
*
* - 可不设置筛选条件
* @param conditions 条件方法体
*/
fun modifiers(conditions: ModifierConditions) {
rulesData.modifiers = conditions
}
/** 设置 [Constructor] 空参数、无参数 */
fun emptyParam() {
rulesData.paramCount = 0
}
/**
* 设置 [Constructor] 参数
*
* 如果同时使用了 [paramCount] 则 [paramType] 的数量必须与 [paramCount] 完全匹配
*
* 如果 [Constructor] 中存在一些无意义又很长的类型 - 你可以使用 [VagueType] 来替代它
*
* 例如下面这个参数结构 ↓
*
* ```java
* Foo(String var1, boolean var2, com.demo.Test var3, int var4)
* ```
*
* 此时就可以简单地写作 ↓
*
* ```kotlin
* param(StringType, BooleanType, VagueType, IntType)
* ```
*
* - ❗无参 [Constructor] 请使用 [emptyParam] 设置查找条件
*
* - ❗有参 [Constructor] 必须使用此方法设定参数或使用 [paramCount] 指定个数
* @param paramType 参数类型数组 - ❗只能是 [Class]、[String]、[VariousClass]
*/
fun param(vararg paramType: Any) {
if (paramType.isEmpty()) error("paramTypes is empty, please use emptyParam() instead")
rulesData.paramTypes =
arrayListOf<Class<*>>().apply { paramType.forEach { add(it.compat(tag = "Constructor") ?: UndefinedType) } }.toTypedArray()
}
/**
* 设置 [Constructor] 参数条件
*
* 使用示例如下 ↓
*
* ```kotlin
* param { it[1] == StringClass || it[2].name == "java.lang.String" }
* ```
*
* - ❗无参 [Constructor] 请使用 [emptyParam] 设置查找条件
*
* - ❗有参 [Constructor] 必须使用此方法设定参数或使用 [paramCount] 指定个数
* @param conditions 条件方法体
*/
fun param(conditions: ObjectsConditions) {
rulesData.paramTypesConditions = conditions
}
/**
* 设置 [Constructor] 参数个数范围
*
* 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数范围
*
* 使用示例如下 ↓
*
* ```kotlin
* paramCount(1..5)
* ```
* @param numRange 个数范围
*/
fun paramCount(numRange: IntRange) {
rulesData.paramCountRange = numRange
}
/**
* 设置 [Constructor] 参数个数条件
*
* 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数条件
*
* 使用示例如下 ↓
*
* ```kotlin
* paramCount { it >= 5 || it.isZero() }
* ```
* @param conditions 条件方法体
*/
fun paramCount(conditions: CountConditions) {
rulesData.paramCountConditions = conditions
}
/**
* 返回结果实现类
* @return [MemberRulesResult]
*/
@PublishedApi
internal fun build() = MemberRulesResult(rulesData)
}

View File

@@ -0,0 +1,109 @@
/*
* 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/12.
*/
package com.highcapable.yukihookapi.hook.core.finder.classes.rules
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.base.BaseRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.result.MemberRulesResult
import com.highcapable.yukihookapi.hook.core.finder.members.data.FieldRulesData
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ObjectConditions
import java.lang.reflect.Field
/**
* [Field] 查找条件实现类
* @param rulesData 当前查找条件规则数据
*/
class FieldRules internal constructor(@PublishedApi internal val rulesData: FieldRulesData) : BaseRules() {
/**
* 设置 [Field] 名称
* @return [String]
*/
var name
get() = rulesData.name
set(value) {
rulesData.name = value
}
/**
* 设置 [Field] 类型
*
* - ❗只能是 [Class]、[String]、[VariousClass]
*
* - 可不填写类型
* @return [Any] or null
*/
var type
get() = rulesData.type
set(value) {
rulesData.type = value?.compat(tag = "Field")
}
/**
* 设置 [Field] 标识符筛选条件
*
* - 可不设置筛选条件
* @param conditions 条件方法体
*/
fun modifiers(conditions: ModifierConditions) {
rulesData.modifiers = conditions
}
/**
* 设置 [Field] 名称条件
* @param conditions 条件方法体
*/
fun name(conditions: NameConditions) {
rulesData.nameConditions = conditions
}
/**
* 设置 [Field] 类型条件
*
* - 可不填写类型
*
* 使用示例如下 ↓
*
* ```kotlin
* type { it == StringClass || it.name == "java.lang.String" }
* ```
* @param conditions 条件方法体
*/
fun type(conditions: ObjectConditions) {
rulesData.typeConditions = conditions
}
/**
* 返回结果实现类
* @return [MemberRulesResult]
*/
@PublishedApi
internal fun build() = MemberRulesResult(rulesData)
}

View File

@@ -0,0 +1,58 @@
/*
* 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/12.
*/
package com.highcapable.yukihookapi.hook.core.finder.classes.rules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.base.BaseRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.result.MemberRulesResult
import com.highcapable.yukihookapi.hook.core.finder.members.data.MemberRulesData
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions
import java.lang.reflect.Member
/**
* [Member] 查找条件实现类
* @param rulesData 当前查找条件规则数据
*/
class MemberRules internal constructor(@PublishedApi internal val rulesData: MemberRulesData) : BaseRules() {
/**
* 设置 [Member] 标识符筛选条件
*
* - 可不设置筛选条件
* @param conditions 条件方法体
*/
fun modifiers(conditions: ModifierConditions) {
rulesData.modifiers = conditions
}
/**
* 返回结果实现类
* @return [MemberRulesResult]
*/
@PublishedApi
internal fun build() = MemberRulesResult(rulesData)
}

View File

@@ -0,0 +1,210 @@
/*
* 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/12.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.highcapable.yukihookapi.hook.core.finder.classes.rules
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.base.BaseRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.result.MemberRulesResult
import com.highcapable.yukihookapi.hook.core.finder.members.data.MethodRulesData
import com.highcapable.yukihookapi.hook.core.finder.type.factory.*
import com.highcapable.yukihookapi.hook.type.defined.UndefinedType
import com.highcapable.yukihookapi.hook.type.defined.VagueType
import java.lang.reflect.Method
/**
* [Method] 查找条件实现类
* @param rulesData 当前查找条件规则数据
*/
class MethodRules internal constructor(@PublishedApi internal val rulesData: MethodRulesData) : BaseRules() {
/**
* 设置 [Method] 名称
* @return [String]
*/
var name
get() = rulesData.name
set(value) {
rulesData.name = value
}
/**
* 设置 [Method] 参数个数
*
* 你可以不使用 [param] 指定参数类型而是仅使用此变量指定参数个数
*
* 若参数个数小于零则忽略并使用 [param]
* @return [Int]
*/
var paramCount
get() = rulesData.paramCount
set(value) {
rulesData.paramCount = value
}
/**
* 设置 [Method] 返回值
*
* - ❗只能是 [Class]、[String]、[VariousClass]
*
* - 可不填写返回值
* @return [Any] or null
*/
var returnType
get() = rulesData.returnType
set(value) {
rulesData.returnType = value.compat(tag = "Method")
}
/**
* 设置 [Method] 标识符筛选条件
*
* - 可不设置筛选条件
* @param conditions 条件方法体
*/
fun modifiers(conditions: ModifierConditions) {
rulesData.modifiers = conditions
}
/** 设置 [Method] 空参数、无参数 */
fun emptyParam() {
rulesData.paramCount = 0
}
/**
* 设置 [Method] 参数
*
* 如果同时使用了 [paramCount] 则 [paramType] 的数量必须与 [paramCount] 完全匹配
*
* 如果 [Method] 中存在一些无意义又很长的类型 - 你可以使用 [VagueType] 来替代它
*
* 例如下面这个参数结构 ↓
*
* ```java
* void foo(String var1, boolean var2, com.demo.Test var3, int var4)
* ```
*
* 此时就可以简单地写作 ↓
*
* ```kotlin
* param(StringType, BooleanType, VagueType, IntType)
* ```
*
* - ❗无参 [Method] 请使用 [emptyParam] 设置查找条件
*
* - ❗有参 [Method] 必须使用此方法设定参数或使用 [paramCount] 指定个数
* @param paramType 参数类型数组 - ❗只能是 [Class]、[String]、[VariousClass]
*/
fun param(vararg paramType: Any) {
if (paramType.isEmpty()) error("paramTypes is empty, please use emptyParam() instead")
rulesData.paramTypes =
arrayListOf<Class<*>>().apply { paramType.forEach { add(it.compat(tag = "Method") ?: UndefinedType) } }.toTypedArray()
}
/**
* 设置 [Method] 参数条件
*
* 使用示例如下 ↓
*
* ```kotlin
* param { it[1] == StringClass || it[2].name == "java.lang.String" }
* ```
*
* - ❗无参 [Method] 请使用 [emptyParam] 设置查找条件
*
* - ❗有参 [Method] 必须使用此方法设定参数或使用 [paramCount] 指定个数
* @param conditions 条件方法体
*/
fun param(conditions: ObjectsConditions) {
rulesData.paramTypesConditions = conditions
}
/**
* 设置 [Method] 名称条件
* @param conditions 条件方法体
*/
fun name(conditions: NameConditions) {
rulesData.nameConditions = conditions
}
/**
* 设置 [Method] 参数个数范围
*
* 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数范围
*
* 使用示例如下 ↓
*
* ```kotlin
* paramCount(1..5)
* ```
* @param numRange 个数范围
*/
fun paramCount(numRange: IntRange) {
rulesData.paramCountRange = numRange
}
/**
* 设置 [Method] 参数个数条件
*
* 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数条件
*
* 使用示例如下 ↓
*
* ```kotlin
* paramCount { it >= 5 || it.isZero() }
* ```
* @param conditions 条件方法体
*/
fun paramCount(conditions: CountConditions) {
rulesData.paramCountConditions = conditions
}
/**
* 设置 [Method] 返回值条件
*
* - 可不填写返回值
*
* 使用示例如下 ↓
*
* ```kotlin
* returnType { it == StringClass || it.name == "java.lang.String" }
* ```
* @param conditions 条件方法体
*/
fun returnType(conditions: ObjectConditions) {
rulesData.returnTypeConditions = conditions
}
/**
* 返回结果实现类
* @return [MemberRulesResult]
*/
@PublishedApi
internal fun build() = MemberRulesResult(rulesData)
}

View File

@@ -0,0 +1,90 @@
/*
* 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/12.
*/
package com.highcapable.yukihookapi.hook.core.finder.classes.rules.base
import com.highcapable.yukihookapi.hook.core.finder.classes.DexClassFinder
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.ConstructorRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.FieldRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.MemberRules
import com.highcapable.yukihookapi.hook.core.finder.classes.rules.MethodRules
import com.highcapable.yukihookapi.hook.core.finder.members.data.ConstructorRulesData
import com.highcapable.yukihookapi.hook.core.finder.members.data.FieldRulesData
import com.highcapable.yukihookapi.hook.core.finder.members.data.MemberRulesData
import com.highcapable.yukihookapi.hook.core.finder.members.data.MethodRulesData
import java.lang.reflect.Member
/**
* [Member] 查找条件实现父类
* @param instance 当前查找类实例
*/
open class BaseRules internal constructor(internal var instance: DexClassFinder? = null) {
@PublishedApi
internal companion object {
/**
* 创建查找条件规则数据
* @param instance 当前查找类实例
* @return [MemberRulesData]
*/
@PublishedApi
internal fun createMemberRules(instance: DexClassFinder) =
MemberRules(MemberRulesData().apply { instance.rulesData.memberRules.add(this) }).apply { this.instance = instance }
/**
* 创建查找条件规则数据
* @return [FieldRulesData]
*/
@PublishedApi
internal fun createFieldRules(instance: DexClassFinder) =
FieldRules(FieldRulesData().apply { instance.rulesData.fieldRules.add(this) }).apply { this.instance = instance }
/**
* 创建查找条件规则数据
* @return [MethodRulesData]
*/
@PublishedApi
internal fun createMethodRules(instance: DexClassFinder) =
MethodRules(MethodRulesData().apply { instance.rulesData.methodRules.add(this) }).apply { this.instance = instance }
/**
* 创建查找条件规则数据
* @return [ConstructorRulesData]
*/
@PublishedApi
internal fun createConstructorRules(instance: DexClassFinder) =
ConstructorRules(ConstructorRulesData().apply { instance.rulesData.constroctorRules.add(this) }).apply { this.instance = instance }
}
/**
* 将目标类型转换为可识别的兼容类型
* @param tag 当前查找类的标识
* @return [Class] or null
*/
internal fun Any?.compat(tag: String) = instance?.compatType(any = this, tag)
}

View File

@@ -0,0 +1,89 @@
/*
* 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/12.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.highcapable.yukihookapi.hook.core.finder.classes.rules.result
import com.highcapable.yukihookapi.hook.core.finder.members.data.MemberRulesData
import com.highcapable.yukihookapi.hook.core.finder.type.factory.CountConditions
import java.lang.reflect.Member
/**
* 当前 [Member] 查找条件结果实现类
* @param rulesData 当前查找条件规则数据
*/
class MemberRulesResult internal constructor(private val rulesData: MemberRulesData) {
/**
* 设置当前 [Member] 在查找条件中个数为 0
* @return [MemberRulesResult] 可继续向下监听
*/
fun none() = count(num = 0)
/**
* 设置当前 [Member] 在查找条件中需要全部匹配的个数
* @param num 个数
* @return [MemberRulesResult] 可继续向下监听
*/
fun count(num: Int): MemberRulesResult {
rulesData.matchCount = num
return this
}
/**
* 设置当前 [Member] 在查找条件中需要全部匹配的个数范围
*
* 使用示例如下 ↓
*
* ```kotlin
* count(1..5)
* ```
* @param numRange 个数范围
* @return [MemberRulesResult] 可继续向下监听
*/
fun count(numRange: IntRange): MemberRulesResult {
rulesData.matchCountRange = numRange
return this
}
/**
* 设置当前 [Member] 在查找条件中需要全部匹配的个数条件
*
* 使用示例如下 ↓
*
* ```kotlin
* count { it >= 5 || it.isZero() }
* ```
* @param conditions 条件方法体
* @return [MemberRulesResult] 可继续向下监听
*/
fun count(conditions: CountConditions): MemberRulesResult {
rulesData.matchCountConditions = conditions
return this
}
}

View File

@@ -0,0 +1,613 @@
/*
* 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/4.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate", "UNCHECKED_CAST", "KotlinConstantConditions")
package com.highcapable.yukihookapi.hook.core.finder.members
import com.highcapable.yukihookapi.annotation.YukiPrivateApi
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreator
import com.highcapable.yukihookapi.hook.core.finder.base.BaseFinder
import com.highcapable.yukihookapi.hook.core.finder.base.MemberBaseFinder
import com.highcapable.yukihookapi.hook.core.finder.members.data.ConstructorRulesData
import com.highcapable.yukihookapi.hook.core.finder.tools.ReflectionTool
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ConstructorConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.CountConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ObjectsConditions
import com.highcapable.yukihookapi.hook.factory.hasExtends
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.type.defined.UndefinedType
import com.highcapable.yukihookapi.hook.type.defined.VagueType
import com.highcapable.yukihookapi.hook.utils.runBlocking
import com.highcapable.yukihookapi.hook.utils.unit
import java.lang.reflect.Constructor
import java.lang.reflect.Member
/**
* [Constructor] 查找类
*
* 可通过指定类型查找指定 [Constructor] 或一组 [Constructor]
* @param classSet 当前需要查找的 [Class] 实例
*/
class ConstructorFinder @PublishedApi internal constructor(@PublishedApi override val classSet: Class<*>? = null) :
MemberBaseFinder(tag = "Constructor", classSet) {
@PublishedApi
internal companion object {
/**
* 通过 [YukiMemberHookCreator.MemberHookCreator] 创建 [Constructor] 查找类
* @param hookInstance 当前 Hooker
* @param classSet 当前需要查找的 [Class] 实例
* @return [ConstructorFinder]
*/
@PublishedApi
internal fun fromHooker(hookInstance: YukiMemberHookCreator.MemberHookCreator, classSet: Class<*>? = null) =
ConstructorFinder(classSet).apply { hookerManager.instance = hookInstance }
}
@PublishedApi
override var rulesData = ConstructorRulesData()
/** 当前使用的 [classSet] */
private var usedClassSet = classSet
/** 当前重查找结果回调 */
private var remedyPlansCallback: (() -> Unit)? = null
/**
* 设置 [Constructor] 参数个数
*
* 你可以不使用 [param] 指定参数类型而是仅使用此变量指定参数个数
*
* 若参数个数小于零则忽略并使用 [param]
* @return [Int]
*/
var paramCount
get() = rulesData.paramCount
set(value) {
rulesData.paramCount = value
}
/**
* 设置 [Constructor] 标识符筛选条件
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun modifiers(conditions: ModifierConditions): IndexTypeCondition {
rulesData.modifiers = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Constructor] 空参数、无参数
*
* @return [BaseFinder.IndexTypeCondition]
*/
fun emptyParam() = paramCount(num = 0)
/**
* 设置 [Constructor] 参数
*
* 如果同时使用了 [paramCount] 则 [paramType] 的数量必须与 [paramCount] 完全匹配
*
* 如果 [Constructor] 中存在一些无意义又很长的类型 - 你可以使用 [VagueType] 来替代它
*
* 例如下面这个参数结构 ↓
*
* ```java
* Foo(String var1, boolean var2, com.demo.Test var3, int var4)
* ```
*
* 此时就可以简单地写作 ↓
*
* ```kotlin
* param(StringType, BooleanType, VagueType, IntType)
* ```
*
* - ❗无参 [Constructor] 请使用 [emptyParam] 设置查找条件
*
* - ❗有参 [Constructor] 必须使用此方法设定参数或使用 [paramCount] 指定个数
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param paramType 参数类型数组 - ❗只能是 [Class]、[String]、[VariousClass]
* @return [BaseFinder.IndexTypeCondition]
*/
fun param(vararg paramType: Any): IndexTypeCondition {
if (paramType.isEmpty()) error("paramTypes is empty, please use emptyParam() instead")
rulesData.paramTypes = arrayListOf<Class<*>>().apply { paramType.forEach { add(it.compat() ?: UndefinedType) } }.toTypedArray()
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Constructor] 参数条件
*
* 使用示例如下 ↓
*
* ```kotlin
* param { it[1] == StringClass || it[2].name == "java.lang.String" }
* ```
*
* - ❗无参 [Constructor] 请使用 [emptyParam] 设置查找条件
*
* - ❗有参 [Constructor] 必须使用此方法设定参数或使用 [paramCount] 指定个数
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun param(conditions: ObjectsConditions): IndexTypeCondition {
rulesData.paramTypesConditions = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 顺序筛选字节码的下标
* @return [BaseFinder.IndexTypeCondition]
*/
fun order() = IndexTypeCondition(IndexConfigType.ORDER)
/**
* 设置 [Constructor] 参数个数
*
* 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数
*
* 若参数个数小于零则忽略并使用 [param]
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param num 个数
* @return [BaseFinder.IndexTypeCondition]
*/
fun paramCount(num: Int): IndexTypeCondition {
rulesData.paramCount = num
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Constructor] 参数个数范围
*
* 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数范围
*
* 使用示例如下 ↓
*
* ```kotlin
* paramCount(1..5)
* ```
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param numRange 个数范围
* @return [BaseFinder.IndexTypeCondition]
*/
fun paramCount(numRange: IntRange): IndexTypeCondition {
rulesData.paramCountRange = numRange
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Constructor] 参数个数条件
*
* 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数条件
*
* 使用示例如下 ↓
*
* ```kotlin
* paramCount { it >= 5 || it.isZero() }
* ```
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun paramCount(conditions: CountConditions): IndexTypeCondition {
rulesData.paramCountConditions = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置在 [classSet] 的所有父类中查找当前 [Constructor]
*
* - ❗若当前 [classSet] 的父类较多可能会耗时 - API 会自动循环到父类继承是 [Any] 前的最后一个类
* @param isOnlySuperClass 是否仅在当前 [classSet] 的父类中查找 - 若父类是 [Any] 则不会生效
*/
fun superClass(isOnlySuperClass: Boolean = false) {
rulesData.isFindInSuper = true
if (isOnlySuperClass && classSet?.hasExtends == true) usedClassSet = classSet.superclass
}
/**
* 得到 [Constructor] 或一组 [Constructor]
* @return [HashSet]<[Constructor]>
* @throws NoSuchMethodError 如果找不到 [Constructor]
*/
private val result by lazy { ReflectionTool.findConstructors(usedClassSet, rulesData) }
/**
* 设置实例
* @param constructors 当前找到的 [Constructor] 数组
*/
private fun setInstance(constructors: HashSet<Constructor<*>>) {
memberInstances.clear()
constructors.takeIf { it.isNotEmpty() }?.onEach { memberInstances.add(it) }
?.first()?.apply { if (hookerManager.isMemberBinded) hookerManager.bindMember(member = this) }
}
/** 得到 [Constructor] 结果 */
private fun internalBuild() {
if (classSet == null) error(CLASSSET_IS_NULL)
runBlocking {
setInstance(result)
}.result { ms ->
memberInstances.takeIf { it.isNotEmpty() }?.forEach { onDebuggingMsg(msg = "Find Constructor [$it] takes ${ms}ms") }
}
}
@YukiPrivateApi
override fun build() = runCatching {
internalBuild()
Result()
}.getOrElse {
onFailureMsg(throwable = it)
Result(isNoSuch = true, it)
}
@YukiPrivateApi
override fun process() = runCatching {
hookerManager.isMemberBinded = true
internalBuild()
Process()
}.getOrElse {
onFailureMsg(throwable = it)
Process(isNoSuch = true, it)
}
@YukiPrivateApi
override fun failure(throwable: Throwable?) = Result(isNoSuch = true, throwable)
@YukiPrivateApi
override fun denied(throwable: Throwable?) = Process(isNoSuch = true, throwable)
/**
* [Constructor] 重查找实现类
*
* 可累计失败次数直到查找成功
*/
inner class RemedyPlan @PublishedApi internal constructor() {
/** 失败尝试次数数组 */
@PublishedApi
internal val remedyPlans = HashSet<Pair<ConstructorFinder, Result>>()
/**
* 创建需要重新查找的 [Constructor]
*
* 你可以添加多个备选 [Constructor] - 直到成功为止
*
* 若最后依然失败 - 将停止查找并输出错误日志
* @param initiate 方法体
*/
inline fun constructor(initiate: ConstructorConditions) = Result().apply {
remedyPlans.add(Pair(ConstructorFinder(classSet).apply {
hookerManager = this@ConstructorFinder.hookerManager
}.apply(initiate), this))
}
/** 开始重查找 */
@PublishedApi
internal fun build() {
if (classSet == null) return
if (remedyPlans.isNotEmpty()) run {
var isFindSuccess = false
var lastError: Throwable? = null
remedyPlans.forEachIndexed { p, it ->
runCatching {
runBlocking {
setInstance(it.first.result)
}.result { ms ->
memberInstances.takeIf { it.isNotEmpty() }?.forEach { onDebuggingMsg(msg = "Find Constructor [$it] takes ${ms}ms") }
}
isFindSuccess = true
it.second.onFindCallback?.invoke(memberInstances.constructors())
remedyPlansCallback?.invoke()
memberInstances.takeIf { it.isNotEmpty() }
?.forEach { onDebuggingMsg(msg = "Constructor [$it] trying ${p + 1} times success by RemedyPlan") }
return@run
}.onFailure {
lastError = it
onFailureMsg(msg = "Trying ${p + 1} times by RemedyPlan --> $it", isAlwaysPrint = true)
}
}
if (isFindSuccess.not()) {
onFailureMsg(
msg = "Trying ${remedyPlans.size} times and all failure by RemedyPlan",
throwable = lastError,
isAlwaysPrint = true
)
remedyPlans.clear()
}
} else yLoggerW(msg = "RemedyPlan is empty, forgot it?${hookerManager.tailTag}")
}
/**
* [RemedyPlan] 结果实现类
*
* 可在这里处理是否成功的回调
*/
inner class Result @PublishedApi internal constructor() {
/** 找到结果时的回调 */
internal var onFindCallback: (HashSet<Constructor<*>>.() -> Unit)? = null
/**
* 当找到结果时
* @param initiate 回调
*/
fun onFind(initiate: HashSet<Constructor<*>>.() -> Unit) {
onFindCallback = initiate
}
}
}
/**
* [Constructor] 查找结果处理类 - 为 [hookerManager] 提供
* @param isNoSuch 是否没有找到 [Constructor] - 默认否
* @param throwable 错误信息
*/
inner class Process internal constructor(
@PublishedApi internal val isNoSuch: Boolean = false,
@PublishedApi internal val throwable: Throwable? = null
) : BaseResult {
/**
* 创建监听结果事件方法体
* @param initiate 方法体
* @return [Process] 可继续向下监听
*/
inline fun result(initiate: Process.() -> Unit) = apply(initiate)
/**
* 设置全部查找条件匹配的多个 [Constructor] 实例结果到 [hookerManager]
* @return [Process] 可继续向下监听
*/
fun all(): Process {
fun HashSet<Member>.bind() = takeIf { it.isNotEmpty() }?.apply { hookerManager.bindMembers(members = this) }.unit()
if (isUsingRemedyPlan)
remedyPlansCallback = { memberInstances.bind() }
else memberInstances.bind()
return this
}
/**
* 创建 [Constructor] 重查找功能
*
* 当你遇到一种 [Constructor] 可能存在不同形式的存在时
*
* 可以使用 [RemedyPlan] 重新查找它 - 而没有必要使用 [onNoSuchConstructor] 捕获异常二次查找 [Constructor]
*
* 若第一次查找失败了 - 你还可以在这里继续添加此方法体直到成功为止
* @param initiate 方法体
* @return [Process] 可继续向下监听
*/
inline fun remedys(initiate: RemedyPlan.() -> Unit): Process {
isUsingRemedyPlan = true
if (isNoSuch) RemedyPlan().apply(initiate).build()
return this
}
/**
* 监听找不到 [Constructor] 时
*
* - 只会返回第一次的错误信息 - 不会返回 [RemedyPlan] 的错误信息
* @param result 回调错误
* @return [Process] 可继续向下监听
*/
inline fun onNoSuchConstructor(result: (Throwable) -> Unit): Process {
if (isNoSuch) result(throwable ?: Throwable("Initialization Error"))
return this
}
}
/**
* [Constructor] 查找结果实现类
* @param isNoSuch 是否没有找到 [Constructor] - 默认否
* @param throwable 错误信息
*/
inner class Result internal constructor(
@PublishedApi internal val isNoSuch: Boolean = false,
@PublishedApi internal val throwable: Throwable? = null
) : BaseResult {
/**
* 创建监听结果事件方法体
* @param initiate 方法体
* @return [Result] 可继续向下监听
*/
inline fun result(initiate: Result.() -> Unit) = apply(initiate)
/**
* 获得 [Constructor] 实例处理类
*
* - 若有多个 [Constructor] 结果只会返回第一个
*
* - ❗在 [memberInstances] 结果为空时使用此方法将无法获得对象
*
* - ❗若你设置了 [remedys] 请使用 [wait] 回调结果方法
* @return [Instance]
*/
fun get() = Instance(give())
/**
* 获得 [Constructor] 实例处理类数组
*
* - 返回全部查找条件匹配的多个 [Constructor] 实例结果
*
* - ❗在 [memberInstances] 结果为空时使用此方法将无法获得对象
*
* - ❗若你设置了 [remedys] 请使用 [waitAll] 回调结果方法
* @return [ArrayList]<[Instance]>
*/
fun all() = arrayListOf<Instance>().apply { giveAll().takeIf { it.isNotEmpty() }?.forEach { add(Instance(it)) } }
/**
* 得到 [Constructor] 本身
*
* - 若有多个 [Constructor] 结果只会返回第一个
*
* - 在查找条件找不到任何结果的时候将返回 null
* @return [Constructor] or null
*/
fun give() = giveAll().takeIf { it.isNotEmpty() }?.first()
/**
* 得到 [Constructor] 本身数组
*
* - 返回全部查找条件匹配的多个 [Constructor] 实例
*
* - 在查找条件找不到任何结果的时候将返回空的 [HashSet]
* @return [HashSet]<[Constructor]>
*/
fun giveAll() = memberInstances.takeIf { it.isNotEmpty() }?.constructors() ?: HashSet()
/**
* 获得 [Constructor] 实例处理类
*
* - 若有多个 [Constructor] 结果只会返回第一个
*
* - ❗若你设置了 [remedys] 必须使用此方法才能获得结果
*
* - ❗若你没有设置 [remedys] 此方法将不会被回调
* @param initiate 回调 [Instance]
*/
fun wait(initiate: Instance.() -> Unit) {
if (memberInstances.isNotEmpty()) initiate(get())
else remedyPlansCallback = { initiate(get()) }
}
/**
* 获得 [Constructor] 实例处理类数组
*
* - 返回全部查找条件匹配的多个 [Constructor] 实例结果
*
* - ❗若你设置了 [remedys] 必须使用此方法才能获得结果
*
* - ❗若你没有设置 [remedys] 此方法将不会被回调
* @param initiate 回调 [ArrayList]<[Instance]>
*/
fun waitAll(initiate: ArrayList<Instance>.() -> Unit) {
if (memberInstances.isNotEmpty()) initiate(all())
else remedyPlansCallback = { initiate(all()) }
}
/**
* 创建 [Constructor] 重查找功能
*
* 当你遇到一种 [Constructor] 可能存在不同形式的存在时
*
* 可以使用 [RemedyPlan] 重新查找它 - 而没有必要使用 [onNoSuchConstructor] 捕获异常二次查找 [Constructor]
* @param initiate 方法体
* @return [Result] 可继续向下监听
*/
inline fun remedys(initiate: RemedyPlan.() -> Unit): Result {
isUsingRemedyPlan = true
if (isNoSuch) RemedyPlan().apply(initiate).build()
return this
}
/**
* 监听找不到 [Constructor] 时
*
* - 只会返回第一次的错误信息 - 不会返回 [RemedyPlan] 的错误信息
* @param result 回调错误
* @return [Result] 可继续向下监听
*/
inline fun onNoSuchConstructor(result: (Throwable) -> Unit): Result {
if (isNoSuch) result(throwable ?: Throwable("Initialization Error"))
return this
}
/**
* 忽略异常并停止打印任何错误日志
*
* - 若 [MemberBaseFinder.MemberHookerManager.isNotIgnoredNoSuchMemberFailure] 为 false 则自动忽略
*
* - ❗此时若要监听异常结果 - 你需要手动实现 [onNoSuchConstructor] 方法
* @return [Result] 可继续向下监听
*/
fun ignored(): Result {
isShutErrorPrinting = true
return this
}
/**
* 忽略异常并停止打印任何错误日志
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [ignored]
* @return [Result] 可继续向下监听
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("ignored()"))
fun ignoredError() = ignored()
/**
* [Constructor] 实例处理类
*
* 调用与创建目标实例类对象
*
* - ❗请使用 [get]、[wait]、[all]、[waitAll] 方法来获取 [Instance]
* @param constructor 当前 [Constructor] 实例对象
*/
inner class Instance internal constructor(private val constructor: Constructor<*>?) {
/**
* 执行 [Constructor] 创建目标实例
* @param args [Constructor] 参数
* @return [Any] or null
*/
private fun baseCall(vararg args: Any?) = constructor?.newInstance(*args)
/**
* 执行 [Constructor] 创建目标实例 - 不指定目标实例类型
* @param args [Constructor] 参数
* @return [Any] or null
*/
fun call(vararg args: Any?) = baseCall(*args)
/**
* 执行 [Constructor] 创建目标实例 - 指定 [T] 目标实例类型
* @param args [Constructor] 参数
* @return [T] or null
*/
fun <T> newInstance(vararg args: Any?) = baseCall(*args) as? T?
override fun toString() = "[${constructor?.name ?: "<empty>"}]"
}
}
}

View File

@@ -0,0 +1,643 @@
/*
* 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/4.
*/
@file:Suppress("unused", "UNCHECKED_CAST", "MemberVisibilityCanBePrivate", "KotlinConstantConditions")
package com.highcapable.yukihookapi.hook.core.finder.members
import com.highcapable.yukihookapi.annotation.YukiPrivateApi
import com.highcapable.yukihookapi.hook.bean.CurrentClass
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreator
import com.highcapable.yukihookapi.hook.core.finder.base.BaseFinder
import com.highcapable.yukihookapi.hook.core.finder.base.MemberBaseFinder
import com.highcapable.yukihookapi.hook.core.finder.members.data.FieldRulesData
import com.highcapable.yukihookapi.hook.core.finder.tools.ReflectionTool
import com.highcapable.yukihookapi.hook.core.finder.type.factory.FieldConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ObjectConditions
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.hasExtends
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.utils.runBlocking
import java.lang.reflect.Field
/**
* [Field] 查找类
*
* 可通过指定类型查找指定 [Field] 或一组 [Field]
* @param classSet 当前需要查找的 [Class] 实例
*/
class FieldFinder @PublishedApi internal constructor(@PublishedApi override val classSet: Class<*>? = null) :
MemberBaseFinder(tag = "Field", classSet) {
@PublishedApi
internal companion object {
/**
* 通过 [YukiMemberHookCreator.MemberHookCreator] 创建 [Field] 查找类
* @param hookInstance 当前 Hooker
* @param classSet 当前需要查找的 [Class] 实例
* @return [FieldFinder]
*/
@PublishedApi
internal fun fromHooker(hookInstance: YukiMemberHookCreator.MemberHookCreator, classSet: Class<*>? = null) =
FieldFinder(classSet).apply { hookerManager.instance = hookInstance }
}
@PublishedApi
override var rulesData = FieldRulesData()
/** 当前使用的 [classSet] */
private var usedClassSet = classSet
/** 当前重查找结果回调 */
private var remedyPlansCallback: (() -> Unit)? = null
/**
* 设置 [Field] 名称
*
* - ❗若不填写名称则必须存在一个其它条件
* @return [String]
*/
var name
get() = rulesData.name
set(value) {
rulesData.name = value
}
/**
* 设置 [Field] 类型
*
* - ❗只能是 [Class]、[String]、[VariousClass]
*
* - 可不填写类型
* @return [Any] or null
*/
var type
get() = rulesData.type
set(value) {
rulesData.type = value.compat()
}
/**
* 设置 [Field] 标识符筛选条件
*
* - 可不设置筛选条件
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun modifiers(conditions: ModifierConditions): IndexTypeCondition {
rulesData.modifiers = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 顺序筛选字节码的下标
* @return [BaseFinder.IndexTypeCondition]
*/
fun order() = IndexTypeCondition(IndexConfigType.ORDER)
/**
* 设置 [Field] 名称
*
* - ❗若不填写名称则必须存在一个其它条件
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param value 名称
* @return [BaseFinder.IndexTypeCondition]
*/
fun name(value: String): IndexTypeCondition {
rulesData.name = value
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Field] 名称条件
*
* - ❗若不填写名称则必须存在一个其它条件
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun name(conditions: NameConditions): IndexTypeCondition {
rulesData.nameConditions = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Field] 类型
*
* - 可不填写类型
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param value 类型 - ❗只能是 [Class]、[String]、[VariousClass]
* @return [BaseFinder.IndexTypeCondition]
*/
fun type(value: Any): IndexTypeCondition {
rulesData.type = value.compat()
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Field] 类型条件
*
* - 可不填写类型
*
* 使用示例如下 ↓
*
* ```kotlin
* type { it == StringClass || it.name == "java.lang.String" }
* ```
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun type(conditions: ObjectConditions): IndexTypeCondition {
rulesData.typeConditions = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置在 [classSet] 的所有父类中查找当前 [Field]
*
* - ❗若当前 [classSet] 的父类较多可能会耗时 - API 会自动循环到父类继承是 [Any] 前的最后一个类
* @param isOnlySuperClass 是否仅在当前 [classSet] 的父类中查找 - 若父类是 [Any] 则不会生效
*/
fun superClass(isOnlySuperClass: Boolean = false) {
rulesData.isFindInSuper = true
if (isOnlySuperClass && classSet?.hasExtends == true) usedClassSet = classSet.superclass
}
/**
* 得到 [Field] 或一组 [Field]
* @return [HashSet]<[Field]>
* @throws NoSuchFieldError 如果找不到 [Field]
*/
private val result get() = ReflectionTool.findFields(usedClassSet, rulesData)
/**
* 设置实例
* @param fields 当前找到的 [Field] 数组
*/
private fun setInstance(fields: HashSet<Field>) {
memberInstances.clear()
fields.takeIf { it.isNotEmpty() }?.forEach { memberInstances.add(it) }
}
/** 得到 [Field] 结果 */
private fun internalBuild() {
if (classSet == null) error(CLASSSET_IS_NULL)
runBlocking {
setInstance(result)
}.result { ms ->
memberInstances.takeIf { it.isNotEmpty() }?.forEach { onDebuggingMsg(msg = "Find Field [$it] takes ${ms}ms") }
}
}
@YukiPrivateApi
override fun build() = runCatching {
internalBuild()
Result()
}.getOrElse {
onFailureMsg(throwable = it)
Result(isNoSuch = true, it)
}
@YukiPrivateApi
override fun process() = error("FieldFinder does not contain this usage")
@YukiPrivateApi
override fun failure(throwable: Throwable?) = Result(isNoSuch = true, throwable)
@YukiPrivateApi
override fun denied(throwable: Throwable?) = error("FieldFinder does not contain this usage")
/**
* [Field] 重查找实现类
*
* 可累计失败次数直到查找成功
*/
inner class RemedyPlan @PublishedApi internal constructor() {
/** 失败尝试次数数组 */
@PublishedApi
internal val remedyPlans = HashSet<Pair<FieldFinder, Result>>()
/**
* 创建需要重新查找的 [Field]
*
* 你可以添加多个备选 [Field] - 直到成功为止
*
* 若最后依然失败 - 将停止查找并输出错误日志
* @param initiate 方法体
* @return [Result] 结果
*/
inline fun field(initiate: FieldConditions) = Result().apply {
remedyPlans.add(Pair(FieldFinder(classSet).apply {
hookerManager = this@FieldFinder.hookerManager
}.apply(initiate), this))
}
/** 开始重查找 */
@PublishedApi
internal fun build() {
if (classSet == null) return
if (remedyPlans.isNotEmpty()) run {
var isFindSuccess = false
var lastError: Throwable? = null
remedyPlans.forEachIndexed { p, it ->
runCatching {
runBlocking {
setInstance(it.first.result)
}.result { ms ->
memberInstances.takeIf { it.isNotEmpty() }
?.forEach { onDebuggingMsg(msg = "Find Field [$it] takes ${ms}ms") }
}
isFindSuccess = true
it.second.onFindCallback?.invoke(memberInstances.fields())
remedyPlansCallback?.invoke()
memberInstances.takeIf { it.isNotEmpty() }
?.forEach { onDebuggingMsg(msg = "Field [$it] trying ${p + 1} times success by RemedyPlan") }
return@run
}.onFailure {
lastError = it
onFailureMsg(msg = "Trying ${p + 1} times by RemedyPlan --> $it", isAlwaysPrint = true)
}
}
if (isFindSuccess.not()) {
onFailureMsg(
msg = "Trying ${remedyPlans.size} times and all failure by RemedyPlan",
throwable = lastError,
isAlwaysPrint = true
)
remedyPlans.clear()
}
} else yLoggerW(msg = "RemedyPlan is empty, forgot it?${hookerManager.tailTag}")
}
/**
* [RemedyPlan] 结果实现类
*
* 可在这里处理是否成功的回调
*/
inner class Result @PublishedApi internal constructor() {
/** 找到结果时的回调 */
internal var onFindCallback: (HashSet<Field>.() -> Unit)? = null
/**
* 当找到结果时
* @param initiate 回调
*/
fun onFind(initiate: HashSet<Field>.() -> Unit) {
onFindCallback = initiate
}
}
}
/**
* [Field] 查找结果实现类
*
* @param isNoSuch 是否没有找到 [Field] - 默认否
* @param throwable 错误信息
*/
inner class Result internal constructor(
@PublishedApi internal val isNoSuch: Boolean = false,
private val throwable: Throwable? = null
) : BaseResult {
/**
* 创建监听结果事件方法体
* @param initiate 方法体
* @return [Result] 可继续向下监听
*/
inline fun result(initiate: Result.() -> Unit) = apply(initiate)
/**
* 获得 [Field] 实例处理类
*
* - 若有多个 [Field] 结果只会返回第一个
*
* - ❗在 [memberInstances] 结果为空时使用此方法将无法获得对象
*
* - ❗如果目标对象不是静态 - 你必须设置 [instance]
*
* - ❗若你设置了 [remedys] 请使用 [wait] 回调结果方法
* @param instance [Field] 所在的实例对象 - 如果是静态可不填 - 默认 null
* @return [Instance]
*/
fun get(instance: Any? = null) = Instance(instance, give())
/**
* 获得 [Field] 实例处理类数组
*
* - 返回全部查找条件匹配的多个 [Field] 实例结果
*
* - ❗在 [memberInstances] 结果为空时使用此方法将无法获得对象
*
* - ❗如果目标对象不是静态 - 你必须设置 [instance]
*
* - ❗若你设置了 [remedys] 请使用 [waitAll] 回调结果方法
* @param instance [Field] 所在的实例对象 - 如果是静态可不填 - 默认 null
* @return [ArrayList]<[Instance]>
*/
fun all(instance: Any? = null) =
arrayListOf<Instance>().apply { giveAll().takeIf { it.isNotEmpty() }?.forEach { add(Instance(instance, it)) } }
/**
* 得到 [Field] 本身
*
* - 若有多个 [Field] 结果只会返回第一个
*
* - 在查找条件找不到任何结果的时候将返回 null
* @return [Field] or null
*/
fun give() = giveAll().takeIf { it.isNotEmpty() }?.first()
/**
* 得到 [Field] 本身数组
*
* - 返回全部查找条件匹配的多个 [Field] 实例
*
* - 在查找条件找不到任何结果的时候将返回空的 [HashSet]
* @return [HashSet]<[Field]>
*/
fun giveAll() = memberInstances.takeIf { it.isNotEmpty() }?.fields() ?: HashSet()
/**
* 获得 [Field] 实例处理类
*
* - 若有多个 [Field] 结果只会返回第一个
*
* - ❗若你设置了 [remedys] 必须使用此方法才能获得结果
*
* - ❗若你没有设置 [remedys] 此方法将不会被回调
* @param instance 所在实例
* @param initiate 回调 [Instance]
*/
fun wait(instance: Any? = null, initiate: Instance.() -> Unit) {
if (memberInstances.isNotEmpty()) initiate(get(instance))
else remedyPlansCallback = { initiate(get(instance)) }
}
/**
* 获得 [Field] 实例处理类数组
*
* - 返回全部查找条件匹配的多个 [Field] 实例结果
*
* - ❗若你设置了 [remedys] 必须使用此方法才能获得结果
*
* - ❗若你没有设置 [remedys] 此方法将不会被回调
* @param instance 所在实例
* @param initiate 回调 [ArrayList]<[Instance]>
*/
fun waitAll(instance: Any? = null, initiate: ArrayList<Instance>.() -> Unit) {
if (memberInstances.isNotEmpty()) initiate(all(instance))
else remedyPlansCallback = { initiate(all(instance)) }
}
/**
* 创建 [Field] 重查找功能
*
* 当你遇到一种方法可能存在不同形式的存在时
*
* 可以使用 [RemedyPlan] 重新查找它 - 而没有必要使用 [onNoSuchField] 捕获异常二次查找 [Field]
*
* 若第一次查找失败了 - 你还可以在这里继续添加此方法体直到成功为止
* @param initiate 方法体
* @return [Result] 可继续向下监听
*/
inline fun remedys(initiate: RemedyPlan.() -> Unit): Result {
isUsingRemedyPlan = true
if (isNoSuch) RemedyPlan().apply(initiate).build()
return this
}
/**
* 监听找不到 [Field] 时
*
* - 只会返回第一次的错误信息 - 不会返回 [RemedyPlan] 的错误信息
* @param result 回调错误
* @return [Result] 可继续向下监听
*/
fun onNoSuchField(result: (Throwable) -> Unit): Result {
if (isNoSuch) result(throwable ?: Throwable("Initialization Error"))
return this
}
/**
* 忽略异常并停止打印任何错误日志
*
* - 若 [MemberBaseFinder.MemberHookerManager.isNotIgnoredNoSuchMemberFailure] 为 false 则自动忽略
*
* - ❗此时若要监听异常结果 - 你需要手动实现 [onNoSuchField] 方法
* @return [Result] 可继续向下监听
*/
fun ignored(): Result {
isShutErrorPrinting = true
return this
}
/**
* 忽略异常并停止打印任何错误日志
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [ignored]
* @return [Result] 可继续向下监听
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("ignored()"))
fun ignoredError() = ignored()
/**
* [Field] 实例处理类
*
* - ❗请使用 [get]、[all] 方法来获取 [Instance]
* @param instance 当前 [Field] 所在类的实例对象
* @param field 当前 [Field] 实例对象
*/
inner class Instance internal constructor(private val instance: Any?, private val field: Field?) {
/**
* 获取当前 [Field] 自身的实例化对象
*
* - 若要直接获取不确定的实例对象 - 请调用 [any] 方法
* @return [Any] or null
*/
@PublishedApi
internal val self
get() = field?.get(instance)
/**
* 获得当前 [Field] 自身 [self] 实例的类操作对象
* @param ignored 是否开启忽略错误警告功能 - 默认否
* @return [CurrentClass] or null
*/
fun current(ignored: Boolean = false) = self?.current(ignored)
/**
* 获得当前 [Field] 自身 [self] 实例的类操作对象
* @param ignored 是否开启忽略错误警告功能 - 默认否
* @param initiate 方法体
* @return [Any] or null
*/
inline fun current(ignored: Boolean = false, initiate: CurrentClass.() -> Unit) = self?.current(ignored, initiate)
/**
* 得到当前 [Field] 实例
* @return [T] or null
*/
fun <T> cast() = self as? T?
/**
* 得到当前 [Field] 的 [Byte] 实例
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回 null
* @return [Byte] or null
*/
fun byte() = cast<Byte?>()
/**
* 得到当前 [Field] 的 [Int] 实例
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回默认值
* @return [Int] 取不到返回 0
*/
fun int() = cast() ?: 0
/**
* 得到当前 [Field] 的 [Long] 实例
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回默认值
* @return [Long] 取不到返回 0L
*/
fun long() = cast() ?: 0L
/**
* 得到当前 [Field] 的 [Short] 实例
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回默认值
* @return [Short] 取不到返回 0
*/
fun short() = cast<Short?>() ?: 0
/**
* 得到当前 [Field] 的 [Double] 实例
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回默认值
* @return [Double] 取不到返回 0.0
*/
fun double() = cast() ?: 0.0
/**
* 得到当前 [Field] 的 [Float] 实例
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回默认值
* @return [Float] 取不到返回 0f
*/
fun float() = cast() ?: 0f
/**
* 得到当前 [Field] 的 [String] 实例
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回默认值
* @return [String] 取不到返回 ""
*/
fun string() = cast() ?: ""
/**
* 得到当前 [Field] 的 [Char] 实例
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回默认值
* @return [Char] 取不到返回 ' '
*/
fun char() = cast() ?: ' '
/**
* 得到当前 [Field] 的 [Boolean] 实例
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回默认值
* @return [Boolean] 取不到返回 false
*/
fun boolean() = cast() ?: false
/**
* 得到当前 [Field] 的 [Any] 实例
* @return [Any] or null
*/
fun any() = self
/**
* 得到当前 [Field] 的 [Array] 实例 - 每项类型 [T]
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回空数组
* @return [Array] 取不到返回空数组
*/
inline fun <reified T> array() = cast() ?: arrayOf<T>()
/**
* 得到当前 [Field] 的 [List] 实例 - 每项类型 [T]
*
* - ❗请确认目标 [Field] 的类型 - 发生错误会返回空数组
* @return [List] 取不到返回空数组
*/
inline fun <reified T> list() = cast() ?: listOf<T>()
/**
* 设置当前 [Field] 实例
* @param any 设置的实例内容
*/
fun set(any: Any?) = field?.set(instance, any)
/**
* 设置当前 [Field] 实例为 true
*
* - ❗请确保示例对象类型为 [Boolean]
*/
fun setTrue() = set(true)
/**
* 设置当前 [Field] 实例为 true
*
* - ❗请确保示例对象类型为 [Boolean]
*/
fun setFalse() = set(false)
/** 设置当前 [Field] 实例为 null */
fun setNull() = set(null)
override fun toString() =
"[${self?.javaClass?.name ?: "<empty>"}] in [${instance?.javaClass?.name ?: "<empty>"}] value \"$self\""
}
}
}

View File

@@ -0,0 +1,825 @@
/*
* 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/4.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate", "UNCHECKED_CAST", "KotlinConstantConditions", "UNCHECKED_CAST")
package com.highcapable.yukihookapi.hook.core.finder.members
import com.highcapable.yukihookapi.annotation.YukiPrivateApi
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreator
import com.highcapable.yukihookapi.hook.core.api.helper.YukiHookHelper
import com.highcapable.yukihookapi.hook.core.finder.base.BaseFinder
import com.highcapable.yukihookapi.hook.core.finder.base.MemberBaseFinder
import com.highcapable.yukihookapi.hook.core.finder.members.data.MethodRulesData
import com.highcapable.yukihookapi.hook.core.finder.tools.ReflectionTool
import com.highcapable.yukihookapi.hook.core.finder.type.factory.CountConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.MethodConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ObjectConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ObjectsConditions
import com.highcapable.yukihookapi.hook.factory.hasExtends
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.type.defined.UndefinedType
import com.highcapable.yukihookapi.hook.type.defined.VagueType
import com.highcapable.yukihookapi.hook.utils.runBlocking
import com.highcapable.yukihookapi.hook.utils.unit
import java.lang.reflect.Member
import java.lang.reflect.Method
/**
* [Method] 查找类
*
* 可通过指定类型查找指定 [Method] 或一组 [Method]
* @param classSet 当前需要查找的 [Class] 实例
*/
class MethodFinder @PublishedApi internal constructor(@PublishedApi override val classSet: Class<*>? = null) :
MemberBaseFinder(tag = "Method", classSet) {
@PublishedApi
internal companion object {
/**
* 通过 [YukiMemberHookCreator.MemberHookCreator] 创建 [Method] 查找类
* @param hookInstance 当前 Hooker
* @param classSet 当前需要查找的 [Class] 实例
* @return [MethodFinder]
*/
@PublishedApi
internal fun fromHooker(hookInstance: YukiMemberHookCreator.MemberHookCreator, classSet: Class<*>? = null) =
MethodFinder(classSet).apply { hookerManager.instance = hookInstance }
}
@PublishedApi
override var rulesData = MethodRulesData()
/** 当前使用的 [classSet] */
private var usedClassSet = classSet
/** 当前重查找结果回调 */
private var remedyPlansCallback: (() -> Unit)? = null
/**
* 设置 [Method] 名称
*
* - ❗若不填写名称则必须存在一个其它条件
* @return [String]
*/
var name
get() = rulesData.name
set(value) {
rulesData.name = value
}
/**
* 设置 [Method] 参数个数
*
* 你可以不使用 [param] 指定参数类型而是仅使用此变量指定参数个数
*
* 若参数个数小于零则忽略并使用 [param]
* @return [Int]
*/
var paramCount
get() = rulesData.paramCount
set(value) {
rulesData.paramCount = value
}
/**
* 设置 [Method] 返回值
*
* - ❗只能是 [Class]、[String]、[VariousClass]
*
* - 可不填写返回值
* @return [Any] or null
*/
var returnType
get() = rulesData.returnType
set(value) {
rulesData.returnType = value.compat()
}
/**
* 设置 [Method] 标识符筛选条件
*
* - 可不设置筛选条件
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun modifiers(conditions: ModifierConditions): IndexTypeCondition {
rulesData.modifiers = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Method] 空参数、无参数
*
* @return [BaseFinder.IndexTypeCondition]
*/
fun emptyParam() = paramCount(num = 0)
/**
* 设置 [Method] 参数
*
* 如果同时使用了 [paramCount] 则 [paramType] 的数量必须与 [paramCount] 完全匹配
*
* 如果 [Method] 中存在一些无意义又很长的类型 - 你可以使用 [VagueType] 来替代它
*
* 例如下面这个参数结构 ↓
*
* ```java
* void foo(String var1, boolean var2, com.demo.Test var3, int var4)
* ```
*
* 此时就可以简单地写作 ↓
*
* ```kotlin
* param(StringType, BooleanType, VagueType, IntType)
* ```
*
* - ❗无参 [Method] 请使用 [emptyParam] 设置查找条件
*
* - ❗有参 [Method] 必须使用此方法设定参数或使用 [paramCount] 指定个数
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param paramType 参数类型数组 - ❗只能是 [Class]、[String]、[VariousClass]
* @return [BaseFinder.IndexTypeCondition]
*/
fun param(vararg paramType: Any): IndexTypeCondition {
if (paramType.isEmpty()) error("paramTypes is empty, please use emptyParam() instead")
rulesData.paramTypes = arrayListOf<Class<*>>().apply { paramType.forEach { add(it.compat() ?: UndefinedType) } }.toTypedArray()
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Method] 参数条件
*
* 使用示例如下 ↓
*
* ```kotlin
* param { it[1] == StringClass || it[2].name == "java.lang.String" }
* ```
*
* - ❗无参 [Method] 请使用 [emptyParam] 设置查找条件
*
* - ❗有参 [Method] 必须使用此方法设定参数或使用 [paramCount] 指定个数
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun param(conditions: ObjectsConditions): IndexTypeCondition {
rulesData.paramTypesConditions = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 顺序筛选字节码的下标
* @return [BaseFinder.IndexTypeCondition]
*/
fun order() = IndexTypeCondition(IndexConfigType.ORDER)
/**
* 设置 [Method] 名称
*
* - ❗若不填写名称则必须存在一个其它条件
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param value 名称
* @return [BaseFinder.IndexTypeCondition]
*/
fun name(value: String): IndexTypeCondition {
rulesData.name = value
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Method] 名称条件
*
* - ❗若不填写名称则必须存在一个其它条件
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun name(conditions: NameConditions): IndexTypeCondition {
rulesData.nameConditions = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Method] 参数个数
*
* 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数
*
* 若参数个数小于零则忽略并使用 [param]
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param num 个数
* @return [BaseFinder.IndexTypeCondition]
*/
fun paramCount(num: Int): IndexTypeCondition {
rulesData.paramCount = num
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Method] 参数个数范围
*
* 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数范围
*
* 使用示例如下 ↓
*
* ```kotlin
* paramCount(1..5)
* ```
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param numRange 个数范围
* @return [BaseFinder.IndexTypeCondition]
*/
fun paramCount(numRange: IntRange): IndexTypeCondition {
rulesData.paramCountRange = numRange
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Method] 参数个数条件
*
* 你可以不使用 [param] 指定参数类型而是仅使用此方法指定参数个数条件
*
* 使用示例如下 ↓
*
* ```kotlin
* paramCount { it >= 5 || it.isZero() }
* ```
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun paramCount(conditions: CountConditions): IndexTypeCondition {
rulesData.paramCountConditions = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Method] 返回值
*
* - 可不填写返回值
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param value 个数
* @return [BaseFinder.IndexTypeCondition]
*/
fun returnType(value: Any): IndexTypeCondition {
rulesData.returnType = value.compat()
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置 [Method] 返回值条件
*
* - 可不填写返回值
*
* 使用示例如下 ↓
*
* ```kotlin
* returnType { it == StringClass || it.name == "java.lang.String" }
* ```
*
* - ❗存在多个 [BaseFinder.IndexTypeCondition] 时除了 [order] 只会生效最后一个
* @param conditions 条件方法体
* @return [BaseFinder.IndexTypeCondition]
*/
fun returnType(conditions: ObjectConditions): IndexTypeCondition {
rulesData.returnTypeConditions = conditions
return IndexTypeCondition(IndexConfigType.MATCH)
}
/**
* 设置在 [classSet] 的所有父类中查找当前 [Method]
*
* - ❗若当前 [classSet] 的父类较多可能会耗时 - API 会自动循环到父类继承是 [Any] 前的最后一个类
* @param isOnlySuperClass 是否仅在当前 [classSet] 的父类中查找 - 若父类是 [Any] 则不会生效
*/
fun superClass(isOnlySuperClass: Boolean = false) {
rulesData.isFindInSuper = true
if (isOnlySuperClass && classSet?.hasExtends == true) usedClassSet = classSet.superclass
}
/**
* 得到 [Method] 或一组 [Method]
* @return [HashSet]<[Method]>
* @throws NoSuchMethodError 如果找不到 [Method]
*/
private val result get() = ReflectionTool.findMethods(usedClassSet, rulesData)
/**
* 设置实例
* @param methods 当前找到的 [Method] 数组
*/
private fun setInstance(methods: HashSet<Method>) {
memberInstances.clear()
methods.takeIf { it.isNotEmpty() }?.onEach { memberInstances.add(it) }
?.first()?.apply { if (hookerManager.isMemberBinded) hookerManager.bindMember(member = this) }
}
/** 得到 [Method] 结果 */
private fun internalBuild() {
if (classSet == null) error(CLASSSET_IS_NULL)
runBlocking {
setInstance(result)
}.result { ms ->
memberInstances.takeIf { it.isNotEmpty() }?.forEach { onDebuggingMsg(msg = "Find Method [$it] takes ${ms}ms") }
}
}
@YukiPrivateApi
override fun build() = runCatching {
internalBuild()
Result()
}.getOrElse {
onFailureMsg(throwable = it)
Result(isNoSuch = true, it)
}
@YukiPrivateApi
override fun process() = runCatching {
hookerManager.isMemberBinded = true
internalBuild()
Process()
}.getOrElse {
onFailureMsg(throwable = it)
Process(isNoSuch = true, it)
}
@YukiPrivateApi
override fun failure(throwable: Throwable?) = Result(isNoSuch = true, throwable)
@YukiPrivateApi
override fun denied(throwable: Throwable?) = Process(isNoSuch = true, throwable)
/**
* [Method] 重查找实现类
*
* 可累计失败次数直到查找成功
*/
inner class RemedyPlan @PublishedApi internal constructor() {
/** 失败尝试次数数组 */
@PublishedApi
internal val remedyPlans = HashSet<Pair<MethodFinder, Result>>()
/**
* 创建需要重新查找的 [Method]
*
* 你可以添加多个备选 [Method] - 直到成功为止
*
* 若最后依然失败 - 将停止查找并输出错误日志
* @param initiate 方法体
* @return [Result] 结果
*/
inline fun method(initiate: MethodConditions) = Result().apply {
remedyPlans.add(Pair(MethodFinder(classSet).apply {
hookerManager = this@MethodFinder.hookerManager
}.apply(initiate), this))
}
/** 开始重查找 */
@PublishedApi
internal fun build() {
if (classSet == null) return
if (remedyPlans.isNotEmpty()) run {
var isFindSuccess = false
var lastError: Throwable? = null
remedyPlans.forEachIndexed { p, it ->
runCatching {
runBlocking {
setInstance(it.first.result)
}.result { ms ->
memberInstances.takeIf { it.isNotEmpty() }?.forEach { onDebuggingMsg(msg = "Find Method [$it] takes ${ms}ms") }
}
isFindSuccess = true
it.second.onFindCallback?.invoke(memberInstances.methods())
remedyPlansCallback?.invoke()
memberInstances.takeIf { it.isNotEmpty() }
?.forEach { onDebuggingMsg(msg = "Method [$it] trying ${p + 1} times success by RemedyPlan") }
return@run
}.onFailure {
lastError = it
onFailureMsg(msg = "Trying ${p + 1} times by RemedyPlan --> $it", isAlwaysPrint = true)
}
}
if (isFindSuccess.not()) {
onFailureMsg(
msg = "Trying ${remedyPlans.size} times and all failure by RemedyPlan",
throwable = lastError,
isAlwaysPrint = true
)
remedyPlans.clear()
}
} else yLoggerW(msg = "RemedyPlan is empty, forgot it?${hookerManager.tailTag}")
}
/**
* [RemedyPlan] 结果实现类
*
* 可在这里处理是否成功的回调
*/
inner class Result @PublishedApi internal constructor() {
/** 找到结果时的回调 */
internal var onFindCallback: (HashSet<Method>.() -> Unit)? = null
/**
* 当找到结果时
* @param initiate 回调
*/
fun onFind(initiate: HashSet<Method>.() -> Unit) {
onFindCallback = initiate
}
}
}
/**
* [Method] 查找结果处理类 - 为 [hookerManager] 提供
* @param isNoSuch 是否没有找 [Method] - 默认否
* @param throwable 错误信息
*/
inner class Process internal constructor(
@PublishedApi internal val isNoSuch: Boolean = false,
@PublishedApi internal val throwable: Throwable? = null
) : BaseResult {
/**
* 创建监听结果事件方法体
* @param initiate 方法体
* @return [Process] 可继续向下监听
*/
inline fun result(initiate: Process.() -> Unit) = apply(initiate)
/**
* 设置全部查找条件匹配的多个 [Method] 实例结果到 [hookerManager]
* @return [Process] 可继续向下监听
*/
fun all(): Process {
fun HashSet<Member>.bind() = takeIf { it.isNotEmpty() }?.apply { hookerManager.bindMembers(members = this) }.unit()
if (isUsingRemedyPlan)
remedyPlansCallback = { memberInstances.bind() }
else memberInstances.bind()
return this
}
/**
* 创建 [Method] 重查找功能
*
* 当你遇到一种 [Method] 可能存在不同形式的存在时
*
* 可以使用 [RemedyPlan] 重新查找它 - 而没有必要使用 [onNoSuchMethod] 捕获异常二次查找 [Method]
*
* 若第一次查找失败了 - 你还可以在这里继续添加此方法体直到成功为止
* @param initiate 方法体
* @return [Process] 可继续向下监听
*/
inline fun remedys(initiate: RemedyPlan.() -> Unit): Process {
isUsingRemedyPlan = true
if (isNoSuch) RemedyPlan().apply(initiate).build()
return this
}
/**
* 监听找不到 [Method] 时
*
* - 只会返回第一次的错误信息 - 不会返回 [RemedyPlan] 的错误信息
* @param result 回调错误
* @return [Process] 可继续向下监听
*/
inline fun onNoSuchMethod(result: (Throwable) -> Unit): Process {
if (isNoSuch) result(throwable ?: Throwable("Initialization Error"))
return this
}
}
/**
* [Method] 查找结果实现类
* @param isNoSuch 是否没有找到 [Method] - 默认否
* @param throwable 错误信息
*/
inner class Result internal constructor(
@PublishedApi internal val isNoSuch: Boolean = false,
@PublishedApi internal val throwable: Throwable? = null
) : BaseResult {
/**
* 创建监听结果事件方法体
* @param initiate 方法体
* @return [Result] 可继续向下监听
*/
inline fun result(initiate: Result.() -> Unit) = apply(initiate)
/**
* 获得 [Method] 实例处理类
*
* - 若有多个 [Method] 结果只会返回第一个
*
* - ❗在 [memberInstances] 结果为空时使用此方法将无法获得对象
*
* - ❗若你设置了 [remedys] 请使用 [wait] 回调结果方法
* @param instance 所在实例
* @return [Instance]
*/
fun get(instance: Any? = null) = Instance(instance, give())
/**
* 获得 [Method] 实例处理类数组
*
* - 返回全部查找条件匹配的多个 [Method] 实例结果
*
* - ❗在 [memberInstances] 结果为空时使用此方法将无法获得对象
*
* - ❗若你设置了 [remedys] 请使用 [waitAll] 回调结果方法
* @param instance 所在实例
* @return [ArrayList]<[Instance]>
*/
fun all(instance: Any? = null) =
arrayListOf<Instance>().apply { giveAll().takeIf { it.isNotEmpty() }?.forEach { add(Instance(instance, it)) } }
/**
* 得到 [Method] 本身
*
* - 若有多个 [Method] 结果只会返回第一个
*
* - 在查找条件找不到任何结果的时候将返回 null
* @return [Method] or null
*/
fun give() = giveAll().takeIf { it.isNotEmpty() }?.first()
/**
* 得到 [Method] 本身数组
*
* - 返回全部查找条件匹配的多个 [Method] 实例
*
* - 在查找条件找不到任何结果的时候将返回空的 [HashSet]
* @return [HashSet]<[Method]>
*/
fun giveAll() = memberInstances.takeIf { it.isNotEmpty() }?.methods() ?: HashSet()
/**
* 获得 [Method] 实例处理类
*
* - 若有多个 [Method] 结果只会返回第一个
*
* - ❗若你设置了 [remedys] 必须使用此方法才能获得结果
*
* - ❗若你没有设置 [remedys] 此方法将不会被回调
* @param instance 所在实例
* @param initiate 回调 [Instance]
*/
fun wait(instance: Any? = null, initiate: Instance.() -> Unit) {
if (memberInstances.isNotEmpty()) initiate(get(instance))
else remedyPlansCallback = { initiate(get(instance)) }
}
/**
* 获得 [Method] 实例处理类数组
*
* - 返回全部查找条件匹配的多个 [Method] 实例结果
*
* - ❗若你设置了 [remedys] 必须使用此方法才能获得结果
*
* - ❗若你没有设置 [remedys] 此方法将不会被回调
* @param instance 所在实例
* @param initiate 回调 [ArrayList]<[Instance]>
*/
fun waitAll(instance: Any? = null, initiate: ArrayList<Instance>.() -> Unit) {
if (memberInstances.isNotEmpty()) initiate(all(instance))
else remedyPlansCallback = { initiate(all(instance)) }
}
/**
* 创建 [Method] 重查找功能
*
* 当你遇到一种 [Method] 可能存在不同形式的存在时
*
* 可以使用 [RemedyPlan] 重新查找它 - 而没有必要使用 [onNoSuchMethod] 捕获异常二次查找 [Method]
*
* 若第一次查找失败了 - 你还可以在这里继续添加此方法体直到成功为止
* @param initiate 方法体
* @return [Result] 可继续向下监听
*/
inline fun remedys(initiate: RemedyPlan.() -> Unit): Result {
isUsingRemedyPlan = true
if (isNoSuch) RemedyPlan().apply(initiate).build()
return this
}
/**
* 监听找不到 [Method] 时
*
* - 只会返回第一次的错误信息 - 不会返回 [RemedyPlan] 的错误信息
* @param result 回调错误
* @return [Result] 可继续向下监听
*/
inline fun onNoSuchMethod(result: (Throwable) -> Unit): Result {
if (isNoSuch) result(throwable ?: Throwable("Initialization Error"))
return this
}
/**
* 忽略异常并停止打印任何错误日志
*
* - 若 [MemberBaseFinder.MemberHookerManager.isNotIgnoredNoSuchMemberFailure] 为 false 则自动忽略
*
* - ❗此时若要监听异常结果 - 你需要手动实现 [onNoSuchMethod] 方法
* @return [Result] 可继续向下监听
*/
fun ignored(): Result {
isShutErrorPrinting = true
return this
}
/**
* 忽略异常并停止打印任何错误日志
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [ignored]
* @return [Result] 可继续向下监听
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("ignored()"))
fun ignoredError() = ignored()
/**
* [Method] 实例处理类
*
* - ❗请使用 [get]、[wait]、[all]、[waitAll] 方法来获取 [Instance]
* @param instance 当前 [Method] 所在类的实例对象
* @param method 当前 [Method] 实例对象
*/
inner class Instance internal constructor(private val instance: Any?, private val method: Method?) {
/** 标识需要调用当前 [Method] 未经 Hook 的原始方法 */
private var isCallOriginal = false
/**
* 标识需要调用当前 [Method] 未经 Hook 的原始 [Method]
*
* 若当前 [Method] 并未 Hook 则会使用原始的 [Method.invoke] 方法调用
*
* - ❗你只能在 (Xposed) 宿主环境中使用此功能
* @return [Instance] 可继续向下监听
*/
fun original(): Instance {
isCallOriginal = true
return this
}
/**
* 执行 [Method]
* @param args 方法参数
* @return [Any] or null
*/
private fun baseCall(vararg args: Any?) =
if (isCallOriginal && YukiHookHelper.isMemberHooked(method))
YukiHookHelper.invokeOriginalMember(method, instance, args)
else method?.invoke(instance, *args)
/**
* 执行 [Method] - 不指定返回值类型
* @param args 方法参数
* @return [Any] or null
*/
fun call(vararg args: Any?) = baseCall(*args)
/**
* 执行 [Method] - 指定 [T] 返回值类型
* @param args 方法参数
* @return [T] or null
*/
fun <T> invoke(vararg args: Any?) = baseCall(*args) as? T?
/**
* 执行 [Method] - 指定 [Byte] 返回值类型
*
* - ❗请确认目标变量的类型 - 发生错误会返回 null
* @param args 方法参数
* @return [Byte] or null
*/
fun byte(vararg args: Any?) = invoke<Byte?>(*args)
/**
* 执行 [Method] - 指定 [Int] 返回值类型
*
* - ❗请确认目标 [Method] 的返回值 - 发生错误会返回默认值
* @param args 方法参数
* @return [Int] 取不到返回 0
*/
fun int(vararg args: Any?) = invoke(*args) ?: 0
/**
* 执行 [Method] - 指定 [Long] 返回值类型
*
* - ❗请确认目标 [Method] 的返回值 - 发生错误会返回默认值
* @param args 方法参数
* @return [Long] 取不到返回 0L
*/
fun long(vararg args: Any?) = invoke(*args) ?: 0L
/**
* 执行 [Method] - 指定 [Short] 返回值类型
*
* - ❗请确认目标 [Method] 的返回值 - 发生错误会返回默认值
* @param args 方法参数
* @return [Short] 取不到返回 0
*/
fun short(vararg args: Any?) = invoke<Short?>(*args) ?: 0
/**
* 执行 [Method] - 指定 [Double] 返回值类型
*
* - ❗请确认目标 [Method] 的返回值 - 发生错误会返回默认值
* @param args 方法参数
* @return [Double] 取不到返回 0.0
*/
fun double(vararg args: Any?) = invoke(*args) ?: 0.0
/**
* 执行 [Method] - 指定 [Float] 返回值类型
*
* - ❗请确认目标 [Method] 的返回值 - 发生错误会返回默认值
* @param args 方法参数
* @return [Float] 取不到返回 0f
*/
fun float(vararg args: Any?) = invoke(*args) ?: 0f
/**
* 执行 [Method] - 指定 [String] 返回值类型
* @param args 方法参数
* @return [String] 取不到返回 ""
*/
fun string(vararg args: Any?) = invoke(*args) ?: ""
/**
* 执行 [Method] - 指定 [Char] 返回值类型
* @param args 方法参数
* @return [Char] 取不到返回 ' '
*/
fun char(vararg args: Any?) = invoke(*args) ?: ' '
/**
* 执行 [Method] - 指定 [Boolean] 返回值类型
*
* - ❗请确认目标 [Method] 的返回值 - 发生错误会返回默认值
* @param args 方法参数
* @return [Boolean] 取不到返回 false
*/
fun boolean(vararg args: Any?) = invoke(*args) ?: false
/**
* 执行 [Method] - 指定 [Array] 返回值类型 - 每项类型 [T]
*
* - ❗请确认目标 [Method] 的返回值 - 发生错误会返回空数组
* @return [Array] 取不到返回空数组
*/
inline fun <reified T> array(vararg args: Any?) = invoke(*args) ?: arrayOf<T>()
/**
* 执行 [Method] - 指定 [List] 返回值类型 - 每项类型 [T]
*
* - ❗请确认目标 [Method] 的返回值 - 发生错误会返回空数组
* @return [List] 取不到返回空数组
*/
inline fun <reified T> list(vararg args: Any?) = invoke(*args) ?: listOf<T>()
override fun toString() = "[${method?.name ?: "<empty>"}] in [${instance?.javaClass?.name ?: "<empty>"}]"
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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/4.
*/
package com.highcapable.yukihookapi.hook.core.finder.members.data
import com.highcapable.yukihookapi.hook.core.finder.type.factory.CountConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ObjectsConditions
import java.lang.reflect.Constructor
/**
* [Constructor] 规则查找数据类
* @param paramTypes 参数类型数组
* @param paramTypesConditions 参数类型条件
* @param paramCount 参数个数
* @param paramCountRange 参数个数范围
* @param paramCountConditions 参数个数条件
*/
@PublishedApi
internal class ConstructorRulesData internal constructor(
var paramTypes: Array<out Class<*>>? = null,
var paramTypesConditions: ObjectsConditions? = null,
var paramCount: Int = -1,
var paramCountRange: IntRange = IntRange.EMPTY,
var paramCountConditions: CountConditions? = null
) : MemberRulesData() {
override val templates
get() = arrayOf(
paramCount.takeIf { it >= 0 }?.let { "paramCount:[$it]" } ?: "",
paramCountRange.takeIf { it.isEmpty().not() }?.let { "paramCountRange:[$it]" } ?: "",
paramCountConditions?.let { "paramCountConditions:[existed]" } ?: "",
paramTypes?.typeOfString()?.let { "paramTypes:[$it]" } ?: "",
paramTypesConditions?.let { "paramTypesConditions:[existed]" } ?: "", *super.templates
)
override val objectName get() = "Constructor"
override val isInitialize
get() = super.isInitializeOfSuper || paramTypes != null || paramTypesConditions != null || paramCount >= 0 ||
paramCountRange.isEmpty().not() || paramCountConditions != null
override fun toString() = "[$paramTypes][$paramTypesConditions][$paramCount][$paramCountRange]" + super.toString()
}

View File

@@ -0,0 +1,63 @@
/*
* 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/4.
*/
package com.highcapable.yukihookapi.hook.core.finder.members.data
import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ObjectConditions
import java.lang.reflect.Field
/**
* [Field] 规则查找数据类
* @param name 名称
* @param nameConditions 名称规则
* @param type 类型
* @param typeConditions 类型条件
*/
@PublishedApi
internal class FieldRulesData internal constructor(
var name: String = "",
var nameConditions: NameConditions? = null,
var type: Any? = null,
var typeConditions: ObjectConditions? = null
) : MemberRulesData() {
override val templates
get() = arrayOf(
name.takeIf { it.isNotBlank() }?.let { "name:[$it]" } ?: "",
nameConditions?.let { "nameConditions:[existed]" } ?: "",
type?.let { "type:[$it]" } ?: "",
typeConditions?.let { "typeConditions:[existed]" } ?: "", *super.templates
)
override val objectName get() = "Field"
override val isInitialize
get() = super.isInitializeOfSuper || name.isNotBlank() || nameConditions != null || type != null || typeConditions != null
override fun toString() = "[$name][$nameConditions][$type][$typeConditions]" + super.toString()
}

View File

@@ -0,0 +1,74 @@
/*
* 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/4.
*/
package com.highcapable.yukihookapi.hook.core.finder.members.data
import com.highcapable.yukihookapi.hook.core.finder.base.data.BaseRulesData
import com.highcapable.yukihookapi.hook.core.finder.base.rules.ModifierRules
import com.highcapable.yukihookapi.hook.core.finder.type.factory.CountConditions
import java.lang.reflect.Member
/**
* [Member] 规则查找数据类
* @param isFindInSuper 是否在未找到后继续在父类中查找
* @param matchCount 匹配的字节码个数
* @param matchCountRange 匹配的字节码个数范围
* @param matchCountConditions 匹配的字节码个数条件
*/
@PublishedApi
internal open class MemberRulesData internal constructor(
var isFindInSuper: Boolean = false,
var matchCount: Int = -1,
var matchCountRange: IntRange = IntRange.EMPTY,
var matchCountConditions: CountConditions? = null
) : BaseRulesData() {
override val templates
get() = arrayOf(
modifiers?.let { "modifiers:${ModifierRules.templates(uniqueValue)}" } ?: "",
orderIndex?.let { it.takeIf { it.second }?.let { e -> "orderIndex:[${e.first}]" } ?: "orderIndex:[last]" } ?: "",
matchIndex?.let { it.takeIf { it.second }?.let { e -> "matchIndex:[${e.first}]" } ?: "matchIndex:[last]" } ?: ""
)
override val objectName get() = "Member"
/**
* 判断 [matchCount]、[matchCountRange] 规则是否已经初始化 (设置了任意一个参数)
* @return [Boolean]
*/
internal val isInitializeOfMatch get() = matchCount >= 0 || matchCountRange.isEmpty().not() || matchCountConditions != null
/**
* 判断 [BaseRulesData] 规则是否已经初始化 (设置了任意一个参数)
* @return [Boolean]
*/
internal val isInitializeOfSuper get() = super.isInitialize
override val isInitialize get() = isInitializeOfSuper || isInitializeOfMatch
override fun toString() = "[$isFindInSuper][$matchIndex][$matchCountRange][$matchCountConditions]" + super.toString()
}

View File

@@ -0,0 +1,83 @@
/*
* 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/4.
*/
package com.highcapable.yukihookapi.hook.core.finder.members.data
import com.highcapable.yukihookapi.hook.core.finder.type.factory.CountConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.NameConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ObjectConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ObjectsConditions
import java.lang.reflect.Method
/**
* [Method] 规则查找数据类
* @param name 名称
* @param nameConditions 名称规则
* @param paramTypes 参数类型数组
* @param paramTypesConditions 参数类型条件
* @param paramCount 参数个数
* @param paramCountRange 参数个数范围
* @param paramCountConditions 参数个数条件
* @param returnType 返回值类型
* @param returnTypeConditions 返回值类型条件
*/
@PublishedApi
internal class MethodRulesData internal constructor(
var name: String = "",
var nameConditions: NameConditions? = null,
var paramTypes: Array<out Class<*>>? = null,
var paramTypesConditions: ObjectsConditions? = null,
var paramCount: Int = -1,
var paramCountRange: IntRange = IntRange.EMPTY,
var paramCountConditions: CountConditions? = null,
var returnType: Any? = null,
var returnTypeConditions: ObjectConditions? = null
) : MemberRulesData() {
override val templates
get() = arrayOf(
name.takeIf { it.isNotBlank() }?.let { "name:[$it]" } ?: "",
nameConditions?.let { "nameConditions:[existed]" } ?: "",
paramCount.takeIf { it >= 0 }?.let { "paramCount:[$it]" } ?: "",
paramCountRange.takeIf { it.isEmpty().not() }?.let { "paramCountRange:[$it]" } ?: "",
paramCountConditions?.let { "paramCountConditions:[existed]" } ?: "",
paramTypes?.typeOfString()?.let { "paramTypes:[$it]" } ?: "",
paramTypesConditions?.let { "paramTypesConditions:[existed]" } ?: "",
returnType?.let { "returnType:[$it]" } ?: "",
returnTypeConditions?.let { "returnTypeConditions:[existed]" } ?: "", *super.templates
)
override val objectName get() = "Method"
override val isInitialize
get() = super.isInitializeOfSuper || name.isNotBlank() || nameConditions != null || paramTypes != null || paramTypesConditions != null ||
paramCount >= 0 || paramCountRange.isEmpty().not() || paramCountConditions != null ||
returnType != null || returnTypeConditions != null
override fun toString() = "[$name][$nameConditions][$paramTypes][$paramTypesConditions][$paramCount]" +
"[$paramCountRange][$returnType][$returnTypeConditions]" + super.toString()
}

View File

@@ -0,0 +1,711 @@
/*
* 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/3/27.
*/
@file:Suppress("KotlinConstantConditions", "KDocUnresolvedReference")
package com.highcapable.yukihookapi.hook.core.finder.tools
import android.util.ArrayMap
import com.highcapable.yukihookapi.hook.core.finder.base.data.BaseRulesData
import com.highcapable.yukihookapi.hook.core.finder.classes.data.ClassRulesData
import com.highcapable.yukihookapi.hook.core.finder.members.data.ConstructorRulesData
import com.highcapable.yukihookapi.hook.core.finder.members.data.FieldRulesData
import com.highcapable.yukihookapi.hook.core.finder.members.data.MemberRulesData
import com.highcapable.yukihookapi.hook.core.finder.members.data.MethodRulesData
import com.highcapable.yukihookapi.hook.factory.*
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.type.defined.UndefinedType
import com.highcapable.yukihookapi.hook.type.defined.VagueType
import com.highcapable.yukihookapi.hook.type.java.DalvikBaseDexClassLoader
import com.highcapable.yukihookapi.hook.type.java.NoClassDefFoundErrorClass
import com.highcapable.yukihookapi.hook.type.java.NoSuchFieldErrorClass
import com.highcapable.yukihookapi.hook.type.java.NoSuchMethodErrorClass
import com.highcapable.yukihookapi.hook.utils.*
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import dalvik.system.BaseDexClassLoader
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
import java.util.*
import kotlin.math.abs
/**
* 这是一个对 [Class]、[Member] 查找的工具实现类
*/
@PublishedApi
internal object ReflectionTool {
/** 当前工具类的标签 */
private const val TAG = "YukiHookAPI#ReflectionTool"
/**
* 当前工具类的 [ClassLoader]
* @return [ClassLoader]
*/
private val currentClassLoader get() = AppParasitics.baseClassLoader
/**
* 内存缓存实例实现
*/
private object MemoryCache {
/** 缓存的 [Class] 列表数组 */
val dexClassListData = ArrayMap<String, List<String>>()
/** 缓存的 [Class] 对象数组 */
val classData = ArrayMap<String, Class<*>?>()
}
/**
* 写出当前 [ClassLoader] 下所有 [Class] 名称数组
* @param loader 当前使用的 [ClassLoader]
* @return [List]<[String]>
* @throws IllegalStateException 如果 [loader] 不是 [BaseDexClassLoader]
*/
internal fun findDexClassList(loader: ClassLoader?) = MemoryCache.dexClassListData[loader.toString()]
?: DalvikBaseDexClassLoader.field { name = "pathList" }.ignored().get(loader.value().let {
while (it.value !is BaseDexClassLoader) {
if (it.value?.parent != null) it.value = it.value?.parent
else error("ClassLoader [$loader] is not a DexClassLoader")
}; it.value ?: error("ClassLoader [$loader] load failed")
}).current(ignored = true)?.field { name = "dexElements" }?.array<Any>()?.flatMap { element ->
element.current(ignored = true).field { name = "dexFile" }.current(ignored = true)
?.method { name = "entries" }?.invoke<Enumeration<String>>()?.toList().orEmpty()
}.orEmpty().also { if (it.isNotEmpty()) MemoryCache.dexClassListData[loader.toString()] = it }
/**
* 使用字符串类名查找 [Class] 是否存在
* @param name [Class] 完整名称
* @param loader [Class] 所在的 [ClassLoader]
* @return [Boolean]
*/
internal fun hasClassByName(name: String, loader: ClassLoader?) = runCatching { findClassByName(name, loader); true }.getOrNull() ?: false
/**
* 使用字符串类名获取 [Class]
* @param name [Class] 完整名称
* @param loader [Class] 所在的 [ClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class]
* @throws NoClassDefFoundError 如果找不到 [Class] 或设置了错误的 [ClassLoader]
*/
@PublishedApi
internal fun findClassByName(name: String, loader: ClassLoader?, initialize: Boolean = false): Class<*> {
val uniqueCode = "[$name][$loader]"
/**
* 获取 [Class.forName] 的 [Class] 对象
* @param name [Class] 完整名称
* @param initialize 是否初始化 [Class] 的静态方法块
* @param loader [Class] 所在的 [ClassLoader] - 默认为 [currentClassLoader]
* @return [Class]
*/
fun classForName(name: String, initialize: Boolean, loader: ClassLoader? = currentClassLoader) =
Class.forName(name, initialize, loader)
/**
* 使用默认方式和 [ClassLoader] 装载 [Class]
* @return [Class] or null
*/
fun loadWithDefaultClassLoader() = if (initialize.not()) loader?.loadClass(name) else classForName(name, initialize, loader)
return MemoryCache.classData[uniqueCode] ?: runCatching {
(loadWithDefaultClassLoader() ?: classForName(name, initialize)).also { MemoryCache.classData[uniqueCode] = it }
}.getOrNull() ?: throw createException(loader ?: currentClassLoader, name = "Class", "name:[$name]")
}
/**
* 查找任意 [Class] 或一组 [Class]
* @param loaderSet 类所在 [ClassLoader]
* @param rulesData 规则查找数据
* @return [HashSet]<[Class]>
* @throws IllegalStateException 如果 [loaderSet] 为 null 或未设置任何条件
* @throws NoClassDefFoundError 如果找不到 [Class]
*/
internal fun findClasses(loaderSet: ClassLoader?, rulesData: ClassRulesData) = rulesData.createResult {
hashSetOf<Class<*>>().also { classes ->
/**
* 开始查找作业
* @param instance 当前 [Class] 实例
*/
fun startProcess(instance: Class<*>) {
conditions {
fromPackages.takeIf { it.isNotEmpty() }?.also { and(true) }
fullName?.also { it.equals(instance, it.TYPE_NAME).also { e -> if (it.isOptional) opt(e) else and(e) } }
simpleName?.also { it.equals(instance, it.TYPE_SIMPLE_NAME).also { e -> if (it.isOptional) opt(e) else and(e) } }
singleName?.also { it.equals(instance, it.TYPE_SINGLE_NAME).also { e -> if (it.isOptional) opt(e) else and(e) } }
fullNameConditions?.also { instance.name.also { n -> runCatching { and(it(n.cast(), n)) } } }
simpleNameConditions?.also { instance.simpleName.also { n -> runCatching { and(it(n.cast(), n)) } } }
singleNameConditions?.also { classSingleName(instance).also { n -> runCatching { and(it(n.cast(), n)) } } }
modifiers?.also { runCatching { and(it(instance.cast())) } }
extendsClass.takeIf { it.isNotEmpty() }?.also { and(instance.hasExtends && it.contains(instance.superclass.name)) }
implementsClass.takeIf { it.isNotEmpty() }
?.also { and(instance.interfaces.isNotEmpty() && instance.interfaces.any { e -> it.contains(e.name) }) }
enclosingClass.takeIf { it.isNotEmpty() }
?.also { and(instance.enclosingClass != null && it.contains(instance.enclosingClass.name)) }
isAnonymousClass?.also { and(instance.isAnonymousClass && it) }
isNoExtendsClass?.also { and(instance.hasExtends.not() && it) }
isNoImplementsClass?.also { and(instance.interfaces.isEmpty() && it) }
/**
* 匹配 [MemberRulesData]
* @param size [Member] 个数
* @param result 回调是否匹配
*/
fun MemberRulesData.matchCount(size: Int, result: (Boolean) -> Unit) {
takeIf { it.isInitializeOfMatch }?.also { rule ->
rule.conditions {
value.matchCount.takeIf { it >= 0 }?.also { and(it == size) }
value.matchCountRange.takeIf { it.isEmpty().not() }?.also { and(size in it) }
value.matchCountConditions?.also { runCatching { and(it(size.cast(), size)) } }
}.finally { result(true) }.without { result(false) }
} ?: result(true)
}
/**
* 检查类型中的 [Class] 是否存在 - 即不存在 [UndefinedType]
* @param type 类型
* @return [Boolean]
*/
fun MemberRulesData.exists(vararg type: Any?): Boolean {
if (type.isEmpty()) return true
for (i in type.indices) if (type[i] == UndefinedType) {
yLoggerW(msg = "$objectName type[$i] mistake, it will be ignored in current conditions")
return false
}
return true
}
memberRules.takeIf { it.isNotEmpty() }?.forEach { rule ->
instance.existMembers?.apply {
var numberOfFound = 0
if (rule.isInitializeOfSuper) forEach { member ->
rule.conditions {
value.modifiers?.also { runCatching { and(it(member.cast())) } }
}.finally { numberOfFound++ }
}.run { rule.matchCount(numberOfFound) { and(it && numberOfFound > 0) } }
else rule.matchCount(count()) { and(it) }
}
}
fieldRules.takeIf { it.isNotEmpty() }?.forEach { rule ->
instance.existFields?.apply {
var numberOfFound = 0
if (rule.isInitialize) forEach { field ->
rule.conditions {
value.type?.takeIf { value.exists(it) }?.also { and(it == field.type) }
value.name.takeIf { it.isNotBlank() }?.also { and(it == field.name) }
value.modifiers?.also { runCatching { and(it(field.cast())) } }
value.nameConditions?.also { field.name.also { n -> runCatching { and(it(n.cast(), n)) } } }
value.typeConditions?.also { field.also { t -> runCatching { and(it(t.type(), t.type)) } } }
}.finally { numberOfFound++ }
}.run { rule.matchCount(numberOfFound) { and(it && numberOfFound > 0) } }
else rule.matchCount(count()) { and(it) }
}
}
methodRules.takeIf { it.isNotEmpty() }?.forEach { rule ->
instance.existMethods?.apply {
var numberOfFound = 0
if (rule.isInitialize) forEach { method ->
rule.conditions {
value.name.takeIf { it.isNotBlank() }?.also { and(it == method.name) }
value.returnType?.takeIf { value.exists(it) }?.also { and(it == method.returnType) }
value.returnTypeConditions
?.also { method.also { r -> runCatching { and(it(r.returnType(), r.returnType)) } } }
value.paramCount.takeIf { it >= 0 }?.also { and(method.parameterTypes.size == it) }
value.paramCountRange.takeIf { it.isEmpty().not() }?.also { and(method.parameterTypes.size in it) }
value.paramCountConditions
?.also { method.parameterTypes.size.also { s -> runCatching { and(it(s.cast(), s)) } } }
value.paramTypes?.takeIf { value.exists(*it) }?.also { and(paramTypesEq(it, method.parameterTypes)) }
value.paramTypesConditions
?.also { method.also { t -> runCatching { and(it(t.paramTypes(), t.parameterTypes)) } } }
value.modifiers?.also { runCatching { and(it(method.cast())) } }
value.nameConditions?.also { method.name.also { n -> runCatching { and(it(n.cast(), n)) } } }
}.finally { numberOfFound++ }
}.run { rule.matchCount(numberOfFound) { and(it && numberOfFound > 0) } }
else rule.matchCount(count()) { and(it) }
}
}
constroctorRules.takeIf { it.isNotEmpty() }?.forEach { rule ->
instance.existConstructors?.apply {
var numberOfFound = 0
if (rule.isInitialize) forEach { constructor ->
rule.conditions {
value.paramCount.takeIf { it >= 0 }?.also { and(constructor.parameterTypes.size == it) }
value.paramCountRange.takeIf { it.isEmpty().not() }?.also { and(constructor.parameterTypes.size in it) }
value.paramCountConditions
?.also { constructor.parameterTypes.size.also { s -> runCatching { and(it(s.cast(), s)) } } }
value.paramTypes?.takeIf { value.exists(*it) }?.also { and(paramTypesEq(it, constructor.parameterTypes)) }
value.paramTypesConditions
?.also { constructor.also { t -> runCatching { and(it(t.paramTypes(), t.parameterTypes)) } } }
value.modifiers?.also { runCatching { and(it(constructor.cast())) } }
}.finally { numberOfFound++ }
}.run { rule.matchCount(numberOfFound) { and(it && numberOfFound > 0) } }
else rule.matchCount(count()) { and(it) }
}
}
}.finally { classes.add(instance) }
}
findDexClassList(loaderSet).takeIf { it.isNotEmpty() }?.forEach { className ->
/** 分离包名 → com.demo.Test → com.demo (获取最后一个 "." + 简单类名的长度) → 由于末位存在 "." 最后要去掉 1 个长度 */
(if (className.contains("."))
className.substring(0, className.length - className.split(".").let { it[it.lastIndex] }.length - 1)
else className).also { packageName ->
if ((fromPackages.isEmpty() || fromPackages.any {
if (it.isAbsolute) packageName == it.name else packageName.startsWith(it.name)
}) && className.hasClass(loaderSet)
) startProcess(className.toClass(loaderSet))
}
}
}.takeIf { it.isNotEmpty() } ?: throwNotFoundError(loaderSet)
}
/**
* 查找任意 [Field] 或一组 [Field]
* @param classSet [Field] 所在类
* @param rulesData 规则查找数据
* @return [HashSet]<[Field]>
* @throws IllegalStateException 如果未设置任何条件或 [FieldRulesData.type] 目标类不存在
* @throws NoSuchFieldError 如果找不到 [Field]
*/
internal fun findFields(classSet: Class<*>?, rulesData: FieldRulesData) = rulesData.createResult {
if (type == UndefinedType) error("Field match type class is not found")
if (classSet == null) return@createResult hashSetOf()
hashSetOf<Field>().also { fields ->
classSet.existFields?.also { declares ->
var iType = -1
var iName = -1
var iModify = -1
var iNameCds = -1
var iTypeCds = -1
val iLType = type?.let(matchIndex) { e -> declares.findLastIndex { e == it.type } } ?: -1
val iLName = name.takeIf(matchIndex) { it.isNotBlank() }?.let { e -> declares.findLastIndex { e == it.name } } ?: -1
val iLModify = modifiers?.let(matchIndex) { e -> declares.findLastIndex { runOrFalse { e(it.cast()) } } } ?: -1
val iLNameCds = nameConditions
?.let(matchIndex) { e -> declares.findLastIndex { it.name.let { n -> runOrFalse { e(n.cast(), n) } } } } ?: -1
val iLTypeCds = typeConditions?.let(matchIndex) { e -> declares.findLastIndex { runOrFalse { e(it.type(), it.type) } } } ?: -1
declares.forEachIndexed { index, instance ->
conditions {
type?.also {
and((it == instance.type).let { hold ->
if (hold) iType++
hold && matchIndex.compare(iType, iLType)
})
}
name.takeIf { it.isNotBlank() }?.also {
and((it == instance.name).let { hold ->
if (hold) iName++
hold && matchIndex.compare(iName, iLName)
})
}
modifiers?.also {
and(runOrFalse { it(instance.cast()) }.let { hold ->
if (hold) iModify++
hold && matchIndex.compare(iModify, iLModify)
})
}
nameConditions?.also {
and(instance.name.let { n -> runOrFalse { it(n.cast(), n) } }.let { hold ->
if (hold) iNameCds++
hold && matchIndex.compare(iNameCds, iLNameCds)
})
}
typeConditions?.also {
and(instance.let { t -> runOrFalse { it(t.type(), t.type) } }.let { hold ->
if (hold) iTypeCds++
hold && matchIndex.compare(iTypeCds, iLTypeCds)
})
}
orderIndex.compare(index, declares.lastIndex()) { and(it) }
}.finally { fields.add(instance.apply { isAccessible = true }) }
}
}
}.takeIf { it.isNotEmpty() } ?: findSuperOrThrow(classSet)
}
/**
* 查找任意 [Method] 或一组 [Method]
* @param classSet [Method] 所在类
* @param rulesData 规则查找数据
* @return [HashSet]<[Method]>
* @throws IllegalStateException 如果未设置任何条件或 [MethodRulesData.paramTypes] 以及 [MethodRulesData.returnType] 目标类不存在
* @throws NoSuchMethodError 如果找不到 [Method]
*/
internal fun findMethods(classSet: Class<*>?, rulesData: MethodRulesData) = rulesData.createResult {
if (returnType == UndefinedType) error("Method match returnType class is not found")
if (classSet == null) return@createResult hashSetOf()
paramTypes?.takeIf { it.isNotEmpty() }
?.forEachIndexed { p, it -> if (it == UndefinedType) error("Method match paramType[$p] class is not found") }
hashSetOf<Method>().also { methods ->
classSet.existMethods?.also { declares ->
var iReturnType = -1
var iReturnTypeCds = -1
var iParamTypes = -1
var iParamTypesCds = -1
var iParamCount = -1
var iParamCountRange = -1
var iParamCountCds = -1
var iName = -1
var iModify = -1
var iNameCds = -1
val iLReturnType = returnType?.let(matchIndex) { e -> declares.findLastIndex { e == it.returnType } } ?: -1
val iLReturnTypeCds = returnTypeConditions
?.let(matchIndex) { e -> declares.findLastIndex { runOrFalse { e(it.returnType(), it.returnType) } } } ?: -1
val iLParamCount = paramCount.takeIf(matchIndex) { it >= 0 }
?.let { e -> declares.findLastIndex { e == it.parameterTypes.size } } ?: -1
val iLParamCountRange = paramCountRange.takeIf(matchIndex) { it.isEmpty().not() }
?.let { e -> declares.findLastIndex { it.parameterTypes.size in e } } ?: -1
val iLParamCountCds = paramCountConditions?.let(matchIndex) { e ->
declares.findLastIndex { it.parameterTypes.size.let { s -> runOrFalse { e(s.cast(), s) } } }
} ?: -1
val iLParamTypes = paramTypes?.let(matchIndex) { e -> declares.findLastIndex { paramTypesEq(e, it.parameterTypes) } } ?: -1
val iLParamTypesCds = paramTypesConditions
?.let(matchIndex) { e -> declares.findLastIndex { runOrFalse { e(it.paramTypes(), it.parameterTypes) } } } ?: -1
val iLName = name.takeIf(matchIndex) { it.isNotBlank() }?.let { e -> declares.findLastIndex { e == it.name } } ?: -1
val iLModify = modifiers?.let(matchIndex) { e -> declares.findLastIndex { runOrFalse { e(it.cast()) } } } ?: -1
val iLNameCds = nameConditions
?.let(matchIndex) { e -> declares.findLastIndex { it.name.let { n -> runOrFalse { e(n.cast(), n) } } } } ?: -1
declares.forEachIndexed { index, instance ->
conditions {
name.takeIf { it.isNotBlank() }?.also {
and((it == instance.name).let { hold ->
if (hold) iName++
hold && matchIndex.compare(iName, iLName)
})
}
returnType?.also {
and((it == instance.returnType).let { hold ->
if (hold) iReturnType++
hold && matchIndex.compare(iReturnType, iLReturnType)
})
}
returnTypeConditions?.also {
and(instance.let { r -> runOrFalse { it(r.returnType(), r.returnType) } }.let { hold ->
if (hold) iReturnTypeCds++
hold && matchIndex.compare(iReturnTypeCds, iLReturnTypeCds)
})
}
paramCount.takeIf { it >= 0 }?.also {
and((instance.parameterTypes.size == it).let { hold ->
if (hold) iParamCount++
hold && matchIndex.compare(iParamCount, iLParamCount)
})
}
paramCountRange.takeIf { it.isEmpty().not() }?.also {
and((instance.parameterTypes.size in it).let { hold ->
if (hold) iParamCountRange++
hold && matchIndex.compare(iParamCountRange, iLParamCountRange)
})
}
paramCountConditions?.also {
and(instance.parameterTypes.size.let { s -> runOrFalse { it(s.cast(), s) } }.let { hold ->
if (hold) iParamCountCds++
hold && matchIndex.compare(iParamCountCds, iLParamCountCds)
})
}
paramTypes?.also {
and(paramTypesEq(it, instance.parameterTypes).let { hold ->
if (hold) iParamTypes++
hold && matchIndex.compare(iParamTypes, iLParamTypes)
})
}
paramTypesConditions?.also {
and(instance.let { t -> runOrFalse { it(t.paramTypes(), t.parameterTypes) } }.let { hold ->
if (hold) iParamTypesCds++
hold && matchIndex.compare(iParamTypesCds, iLParamTypesCds)
})
}
modifiers?.also {
and(runOrFalse { it(instance.cast()) }.let { hold ->
if (hold) iModify++
hold && matchIndex.compare(iModify, iLModify)
})
}
nameConditions?.also {
and(instance.name.let { n -> runOrFalse { it(n.cast(), n) } }.let { hold ->
if (hold) iNameCds++
hold && matchIndex.compare(iNameCds, iLNameCds)
})
}
orderIndex.compare(index, declares.lastIndex()) { and(it) }
}.finally { methods.add(instance.apply { isAccessible = true }) }
}
}
}.takeIf { it.isNotEmpty() } ?: findSuperOrThrow(classSet)
}
/**
* 查找任意 [Constructor] 或一组 [Constructor]
* @param classSet [Constructor] 所在类
* @param rulesData 规则查找数据
* @return [HashSet]<[Constructor]>
* @throws IllegalStateException 如果未设置任何条件或 [ConstructorRulesData.paramTypes] 目标类不存在
* @throws NoSuchMethodError 如果找不到 [Constructor]
*/
internal fun findConstructors(classSet: Class<*>?, rulesData: ConstructorRulesData) = rulesData.createResult {
if (classSet == null) return@createResult hashSetOf()
paramTypes?.takeIf { it.isNotEmpty() }
?.forEachIndexed { p, it -> if (it == UndefinedType) error("Constructor match paramType[$p] class is not found") }
hashSetOf<Constructor<*>>().also { constructors ->
classSet.existConstructors?.also { declares ->
var iParamTypes = -1
var iParamTypesCds = -1
var iParamCount = -1
var iParamCountRange = -1
var iParamCountCds = -1
var iModify = -1
val iLParamCount = paramCount.takeIf(matchIndex) { it >= 0 }
?.let { e -> declares.findLastIndex { e == it.parameterTypes.size } } ?: -1
val iLParamCountRange = paramCountRange.takeIf(matchIndex) { it.isEmpty().not() }
?.let { e -> declares.findLastIndex { it.parameterTypes.size in e } } ?: -1
val iLParamCountCds = paramCountConditions?.let(matchIndex) { e ->
declares.findLastIndex { it.parameterTypes.size.let { s -> runOrFalse { e(s.cast(), s) } } }
} ?: -1
val iLParamTypes = paramTypes?.let(matchIndex) { e -> declares.findLastIndex { paramTypesEq(e, it.parameterTypes) } } ?: -1
val iLParamTypesCds = paramTypesConditions
?.let(matchIndex) { e -> declares.findLastIndex { runOrFalse { e(it.paramTypes(), it.parameterTypes) } } } ?: -1
val iLModify = modifiers?.let(matchIndex) { e -> declares.findLastIndex { runOrFalse { e(it.cast()) } } } ?: -1
declares.forEachIndexed { index, instance ->
conditions {
paramCount.takeIf { it >= 0 }?.also {
and((instance.parameterTypes.size == it).let { hold ->
if (hold) iParamCount++
hold && matchIndex.compare(iParamCount, iLParamCount)
})
}
paramCountRange.takeIf { it.isEmpty().not() }?.also {
and((instance.parameterTypes.size in it).let { hold ->
if (hold) iParamCountRange++
hold && matchIndex.compare(iParamCountRange, iLParamCountRange)
})
}
paramCountConditions?.also {
and(instance.parameterTypes.size.let { s -> runOrFalse { it(s.cast(), s) } }.let { hold ->
if (hold) iParamCountCds++
hold && matchIndex.compare(iParamCountCds, iLParamCountCds)
})
}
paramTypes?.also {
and(paramTypesEq(it, instance.parameterTypes).let { hold ->
if (hold) iParamTypes++
hold && matchIndex.compare(iParamTypes, iLParamTypes)
})
}
paramTypesConditions?.also {
and(instance.let { t -> runOrFalse { it(t.paramTypes(), t.parameterTypes) } }.let { hold ->
if (hold) iParamTypesCds++
hold && matchIndex.compare(iParamTypesCds, iLParamTypesCds)
})
}
modifiers?.also {
and(runOrFalse { it(instance.cast()) }.let { hold ->
if (hold) iModify++
hold && matchIndex.compare(iModify, iLModify)
})
}
orderIndex.compare(index, declares.lastIndex()) { and(it) }
}.finally { constructors.add(instance.apply { isAccessible = true }) }
}
}
}.takeIf { it.isNotEmpty() } ?: findSuperOrThrow(classSet)
}
/**
* 比较位置下标的前后顺序
* @param need 当前位置
* @param last 最后位置
* @return [Boolean] 返回是否成立
*/
private fun Pair<Int, Boolean>?.compare(need: Int, last: Int) = this == null || ((first >= 0 && first == need && second) ||
(first < 0 && abs(first) == (last - need) && second) || (last == need && second.not()))
/**
* 比较位置下标的前后顺序
* @param need 当前位置
* @param last 最后位置
* @param result 回调是否成立
*/
private fun Pair<Int, Boolean>?.compare(need: Int, last: Int, result: (Boolean) -> Unit) {
if (this == null) return
((first >= 0 && first == need && second) ||
(first < 0 && abs(first) == (last - need) && second) ||
(last == need && second.not())).also(result)
}
/**
* 创建查找结果方法体
* @param result 回调方法体
* @return [T]
* @throws IllegalStateException 如果没有 [BaseRulesData.isInitialize]
*/
private inline fun <reified T, R : BaseRulesData> R.createResult(result: R.() -> T): T {
when (this) {
is FieldRulesData -> isInitialize.not()
is MethodRulesData -> isInitialize.not()
is ConstructorRulesData -> isInitialize.not()
is ClassRulesData -> isInitialize.not()
else -> true
}.takeIf { it }?.also { error("You must set a condition when finding a $objectName") }
return result(this)
}
/**
* 在 [Class.getSuperclass] 中查找或抛出异常
* @param classSet 所在类
* @return [T]
* @throws NoSuchFieldError 继承于方法 [throwNotFoundError] 的异常
* @throws NoSuchMethodError 继承于方法 [throwNotFoundError] 的异常
* @throws IllegalStateException 如果 [R] 的类型错误
*/
private inline fun <reified T, R : MemberRulesData> R.findSuperOrThrow(classSet: Class<*>): T = when (this) {
is FieldRulesData ->
if (isFindInSuper && classSet.hasExtends)
findFields(classSet.superclass, rulesData = this) as T
else throwNotFoundError(classSet)
is MethodRulesData ->
if (isFindInSuper && classSet.hasExtends)
findMethods(classSet.superclass, rulesData = this) as T
else throwNotFoundError(classSet)
is ConstructorRulesData ->
if (isFindInSuper && classSet.hasExtends)
findConstructors(classSet.superclass, rulesData = this) as T
else throwNotFoundError(classSet)
else -> error("Type [$this] not allowed")
}
/**
* 抛出找不到 [Class]、[Member] 的异常
* @param instanceSet 所在 [ClassLoader] or [Class]
* @throws NoClassDefFoundError 如果找不到 [Class]
* @throws NoSuchFieldError 如果找不到 [Field]
* @throws NoSuchMethodError 如果找不到 [Method] or [Constructor]
* @throws IllegalStateException 如果 [BaseRulesData] 的类型错误
*/
private fun BaseRulesData.throwNotFoundError(instanceSet: Any?): Nothing = when (this) {
is FieldRulesData -> throw createException(instanceSet, objectName, *templates)
is MethodRulesData -> throw createException(instanceSet, objectName, *templates)
is ConstructorRulesData -> throw createException(instanceSet, objectName, *templates)
is ClassRulesData -> throw createException(instanceSet ?: currentClassLoader, objectName, *templates)
else -> error("Type [$this] not allowed")
}
/**
* 创建一个异常
* @param instanceSet 所在 [ClassLoader] or [Class]
* @param name 实例名称
* @param content 异常内容
* @return [Throwable]
*/
private fun createException(instanceSet: Any?, name: String, vararg content: String): Throwable {
/**
* 获取 [Class.getName] 长度的空格数量并使用 "->" 拼接
* @return [String]
*/
fun Class<*>.space(): String {
var space = ""
for (i in 0..this.name.length) space += " "
return "$space -> "
}
if (content.isEmpty()) return IllegalStateException("Exception content is null")
val space = when (name) {
"Class" -> NoClassDefFoundErrorClass.space()
"Field" -> NoSuchFieldErrorClass.space()
"Method", "Constructor" -> NoSuchMethodErrorClass.space()
else -> error("Invalid Exception type")
}
var splicing = ""
content.forEach { if (it.isNotBlank()) splicing += "$space$it\n" }
val template = "Can't find this $name in [$instanceSet]:\n${splicing}Generated by $TAG"
return when (name) {
"Class" -> NoClassDefFoundError(template)
"Field" -> NoSuchFieldError(template)
"Method", "Constructor" -> NoSuchMethodError(template)
else -> error("Invalid Exception type")
}
}
/**
* 获取当前 [Class] 中存在的 [Member] 数组
* @return [Sequence]<[Member]> or null
*/
private val Class<*>.existMembers
get() = runCatching {
arrayListOf<Member>().apply {
addAll(declaredFields.toList())
addAll(declaredMethods.toList())
addAll(declaredConstructors.toList())
}.asSequence()
}.onFailure {
yLoggerW(msg = "Failed to get the declared Members in [$this] because got an exception\n$it")
}.getOrNull()
/**
* 获取当前 [Class] 中存在的 [Field] 数组
* @return [Sequence]<[Field]> or null
*/
private val Class<*>.existFields
get() = runCatching { declaredFields.asSequence() }.onFailure {
yLoggerW(msg = "Failed to get the declared Fields in [$this] because got an exception\n$it")
}.getOrNull()
/**
* 获取当前 [Class] 中存在的 [Method] 数组
* @return [Sequence]<[Method]> or null
*/
private val Class<*>.existMethods
get() = runCatching { declaredMethods.asSequence() }.onFailure {
yLoggerW(msg = "Failed to get the declared Methods in [$this] because got an exception\n$it")
}.getOrNull()
/**
* 获取当前 [Class] 中存在的 [Constructor] 数组
* @return [Sequence]<[Constructor]> or null
*/
private val Class<*>.existConstructors
get() = runCatching { declaredConstructors.asSequence() }.onFailure {
yLoggerW(msg = "Failed to get the declared Constructors in [$this] because got an exception\n$it")
}.getOrNull()
/**
* 判断两个方法、构造方法类型数组是否相等
*
* 复制自 [Class] 中的 [Class.arrayContentsEq]
* @param compare 用于比较的数组
* @param original 方法、构造方法原始数组
* @return [Boolean] 是否相等
* @throws IllegalStateException 如果 [VagueType] 配置不正确
*/
private fun paramTypesEq(compare: Array<out Any>?, original: Array<out Any>?): Boolean {
return when {
(compare == null && original == null) || (compare?.isEmpty() == true && original?.isEmpty() == true) -> true
(compare == null && original != null) || (compare != null && original == null) || (compare?.size != original?.size) -> false
else -> {
if (compare == null || original == null) return false
if (compare.all { it == VagueType }) error("The number of VagueType must be at least less than the count of paramTypes")
for (i in compare.indices) if ((compare[i] !== VagueType) && (compare[i] !== original[i])) return false
true
}
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* 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/14.
*/
package com.highcapable.yukihookapi.hook.core.finder.type.factory
import com.highcapable.yukihookapi.hook.core.finder.base.rules.CountRules
import com.highcapable.yukihookapi.hook.core.finder.base.rules.ModifierRules
import com.highcapable.yukihookapi.hook.core.finder.base.rules.NameRules
import com.highcapable.yukihookapi.hook.core.finder.base.rules.ObjectRules
import com.highcapable.yukihookapi.hook.core.finder.classes.DexClassFinder
import com.highcapable.yukihookapi.hook.core.finder.members.ConstructorFinder
import com.highcapable.yukihookapi.hook.core.finder.members.FieldFinder
import com.highcapable.yukihookapi.hook.core.finder.members.MethodFinder
/** 定义 [DexClassFinder] 方法体类型 */
internal typealias ClassConditions = DexClassFinder.() -> Unit
/** 定义 [FieldFinder] 方法体类型 */
internal typealias FieldConditions = FieldFinder.() -> Unit
/** 定义 [MethodFinder] 方法体类型 */
internal typealias MethodConditions = MethodFinder.() -> Unit
/** 定义 [ConstructorFinder] 方法体类型 */
internal typealias ConstructorConditions = ConstructorFinder.() -> Unit
/** 定义 [NameRules] 方法体类型 */
internal typealias NameConditions = NameRules.(String) -> Boolean
/** 定义 [CountRules] 方法体类型 */
internal typealias CountConditions = CountRules.(Int) -> Boolean
/** 定义 [ModifierRules] 方法体类型 */
internal typealias ModifierConditions = ModifierRules.() -> Boolean
/** 定义 [ObjectRules] 方法体类型 */
internal typealias ObjectConditions = ObjectRules.(Class<*>) -> Boolean
/** 定义 [ObjectRules] 方法体类型 */
internal typealias ObjectsConditions = ObjectRules.(Array<Class<*>>) -> Boolean

View File

@@ -0,0 +1,58 @@
/*
* 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/3.
*/
package com.highcapable.yukihookapi.hook.entity
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.param.PackageParam
/**
* [YukiHookAPI] 的子类 Hooker 实现
*
* 也许你的模块中存在多个功能模块 (Hooker) - 继承并使用此类可以方便帮你管理每个功能模块 (Hooker)
*
* 更多请参考 [InjectYukiHookWithXposed] 中的注解内容
*
* 详情请参考 [通过自定义 Hooker 创建](https://fankes.github.io/YukiHookAPI/zh-cn/config/api-example#%E9%80%9A%E8%BF%87%E8%87%AA%E5%AE%9A%E4%B9%89-hooker-%E5%88%9B%E5%BB%BA)
*
* For English version, see [Created by Custom Hooker](https://fankes.github.io/YukiHookAPI/en/config/api-example#created-by-custom-hooker)
*/
abstract class YukiBaseHooker : PackageParam() {
/**
* 赋值并克隆一个 [PackageParam]
* @param packageParam 需要使用的 [PackageParam]
*/
internal fun assignInstance(packageParam: PackageParam) {
assign(packageParam.wrapper)
onHook()
}
/** 子类 Hook 开始 */
abstract fun onHook()
}

View File

@@ -0,0 +1,440 @@
/*
* 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/2.
*/
@file:Suppress("unused", "UNCHECKED_CAST")
package com.highcapable.yukihookapi.hook.factory
import com.highcapable.yukihookapi.hook.bean.CurrentClass
import com.highcapable.yukihookapi.hook.bean.GenericClass
import com.highcapable.yukihookapi.hook.core.finder.base.rules.ModifierRules
import com.highcapable.yukihookapi.hook.core.finder.classes.DexClassFinder
import com.highcapable.yukihookapi.hook.core.finder.members.ConstructorFinder
import com.highcapable.yukihookapi.hook.core.finder.members.FieldFinder
import com.highcapable.yukihookapi.hook.core.finder.members.MethodFinder
import com.highcapable.yukihookapi.hook.core.finder.tools.ReflectionTool
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ClassConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ConstructorConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.FieldConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.MethodConditions
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ModifierConditions
import com.highcapable.yukihookapi.hook.type.java.AnyClass
import com.highcapable.yukihookapi.hook.type.java.BooleanClass
import com.highcapable.yukihookapi.hook.type.java.BooleanType
import com.highcapable.yukihookapi.hook.type.java.ByteClass
import com.highcapable.yukihookapi.hook.type.java.ByteType
import com.highcapable.yukihookapi.hook.type.java.CharClass
import com.highcapable.yukihookapi.hook.type.java.CharType
import com.highcapable.yukihookapi.hook.type.java.DoubleClass
import com.highcapable.yukihookapi.hook.type.java.DoubleType
import com.highcapable.yukihookapi.hook.type.java.FloatClass
import com.highcapable.yukihookapi.hook.type.java.FloatType
import com.highcapable.yukihookapi.hook.type.java.IntClass
import com.highcapable.yukihookapi.hook.type.java.IntType
import com.highcapable.yukihookapi.hook.type.java.LongClass
import com.highcapable.yukihookapi.hook.type.java.LongType
import com.highcapable.yukihookapi.hook.type.java.ShortClass
import com.highcapable.yukihookapi.hook.type.java.ShortType
import com.highcapable.yukihookapi.hook.type.java.UnitClass
import com.highcapable.yukihookapi.hook.type.java.UnitType
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import dalvik.system.BaseDexClassLoader
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
import java.lang.reflect.ParameterizedType
/**
* 定义一个 [Class] 中的 [Member] 类型
*/
enum class MembersType {
/** 全部 [Method] 与 [Constructor] */
ALL,
/** 全部 [Method] */
METHOD,
/** 全部 [Constructor] */
CONSTRUCTOR
}
/**
* 写出当前 [ClassLoader] 下所有 [Class] 名称数组
*
* - ❗此方法在 [Class] 数量过多时会非常耗时
*
* - ❗若要按指定规则查找一个 [Class] - 请使用 [searchClass] 方法
* @return [List]<[String]>
* @throws IllegalStateException 如果当前 [ClassLoader] 不是 [BaseDexClassLoader]
*/
fun ClassLoader.listOfClasses() = ReflectionTool.findDexClassList(loader = this)
/**
* 通过当前 [ClassLoader] 按指定条件查找并得到 Dex 中的 [Class]
*
* - ❗此方法在 [Class] 数量过多及查找条件复杂时会非常耗时
*
* - ❗建议启用 [async] 或设置 [name] 参数 - [name] 参数将在 Hook APP (宿主) 不同版本中自动进行本地缓存以提升效率
*
* - ❗此功能尚在试验阶段 - 性能与稳定性可能仍然存在问题 - 使用过程遇到问题请向我们报告并帮助我们改进
* @param name 标识当前 [Class] 缓存的名称 - 不设置将不启用缓存 - 启用缓存自动启用 [async]
* @param async 是否启用异步 - 默认否
* @param initiate 方法体
* @return [DexClassFinder.Result]
*/
inline fun ClassLoader.searchClass(name: String = "", async: Boolean = false, initiate: ClassConditions) =
DexClassFinder(name, async = async || name.isNotBlank(), loaderSet = this).apply(initiate).build()
/**
* 监听当前 [ClassLoader] 的 [ClassLoader.loadClass] 方法装载
*
* - ❗请注意只有当前 [ClassLoader] 有主动使用 [ClassLoader.loadClass] 事件时才能被捕获
*
* - ❗这是一个实验性功能 - 一般情况下不会用到此方法 - 不保证不会发生错误
*
* - ❗只能在 (Xposed) 宿主环境使用此功能 - 其它环境下使用将不生效且会打印警告信息
* @param result 回调 - ([Class] 实例对象)
*/
fun ClassLoader.onLoadClass(result: (Class<*>) -> Unit) = AppParasitics.hookClassLoader(loader = this, result)
/**
* 当前 [Class] 是否有继承关系 - 父类是 [Any] 将被认为没有继承关系
* @return [Boolean]
*/
val Class<*>.hasExtends get() = superclass != null && superclass != AnyClass
/**
* 当前 [Class] 是否继承于 [other]
*
* 如果当前 [Class] 就是 [other] 也会返回 true
*
* 如果当前 [Class] 为 null 或 [other] 为 null 会返回 false
* @param other 需要判断的 [Class]
* @return [Boolean]
*/
infix fun Class<*>?.extends(other: Class<*>?): Boolean {
if (this == null || other == null) return false
var isMatched = false
/**
* 查找是否存在父类
* @param current 当前 [Class]
*/
fun findSuperClass(current: Class<*>) {
if (current == other)
isMatched = true
else if (current != AnyClass && current.superclass != null) findSuperClass(current.superclass)
}
findSuperClass(current = this)
return isMatched
}
/**
* 当前 [Class] 是否不继承于 [other]
*
* 此方法相当于 [extends] 的反向判断
* @param other 需要判断的 [Class]
* @return [Boolean]
*/
infix fun Class<*>?.notExtends(other: Class<*>?) = extends(other).not()
/**
* 当前 [Class] 是否实现了 [other] 接口类
*
* 如果当前 [Class] 为 null 或 [other] 为 null 会返回 false
* @param other 需要判断的 [Class]
* @return [Boolean]
*/
infix fun Class<*>?.implements(other: Class<*>?): Boolean {
if (this == null || other == null) return false
/**
* 获取当前 [Class] 实现的所有接口类
* @return [Set]<[Class]>
*/
fun Class<*>.findAllInterfaces(): Set<Class<*>> = mutableSetOf(*interfaces).apply { superclass?.also { addAll(it.findAllInterfaces()) } }
return findAllInterfaces().takeIf { it.isNotEmpty() }?.any { it.name == other.name } ?: false
}
/**
* 当前 [Class] 是否未实现 [other] 接口类
*
* 此方法相当于 [implements] 的反向判断
* @param other 需要判断的 [Class]
* @return [Boolean]
*/
infix fun Class<*>?.notImplements(other: Class<*>?) = implements(other).not()
/**
* 自动转换当前 [Class] 为 Java 原始类型 (Primitive Type)
*
* 如果当前 [Class] 为 Java 或 Kotlin 基本类型将自动执行类型转换
*
* 当前能够自动转换的基本类型如下 ↓
*
* - [kotlin.Unit]
* - [java.lang.Void]
* - [java.lang.Boolean]
* - [java.lang.Integer]
* - [java.lang.Float]
* - [java.lang.Double]
* - [java.lang.Long]
* - [java.lang.Short]
* - [java.lang.Character]
* - [java.lang.Byte]
* @return [Class]
*/
fun Class<*>.toJavaPrimitiveType() = when (this) {
classOf<Unit>(), UnitClass, UnitType -> UnitType
BooleanClass, BooleanType -> BooleanType
IntClass, IntType -> IntType
FloatClass, FloatType -> FloatType
DoubleClass, DoubleType -> DoubleType
LongClass, LongType -> LongType
ShortClass, ShortType -> ShortType
CharClass, CharType -> CharType
ByteClass, ByteType -> ByteType
else -> this
}
/**
* 通过字符串类名转换为 [loader] 中的实体类
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [toClass]
* @return [Class]
* @throws NoClassDefFoundError 如果找不到 [Class] 或设置了错误的 [ClassLoader]
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("name.toClass(loader)"))
fun classOf(name: String, loader: ClassLoader? = null) = name.toClass(loader)
/**
* 通过字符串类名转换为 [loader] 中的实体类
* @param loader [Class] 所在的 [ClassLoader] - 默认空 - 不填使用默认 [ClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class]
* @throws NoClassDefFoundError 如果找不到 [Class] 或设置了错误的 [ClassLoader]
*/
fun String.toClass(loader: ClassLoader? = null, initialize: Boolean = false) = ReflectionTool.findClassByName(name = this, loader, initialize)
/**
* 通过字符串类名转换为 [loader] 中的实体类
* @param loader [Class] 所在的 [ClassLoader] - 默认空 - 不填使用默认 [ClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class]<[T]>
* @throws NoClassDefFoundError 如果找不到 [Class] 或设置了错误的 [ClassLoader]
* @throws IllegalStateException 如果 [Class] 的类型不为 [T]
*/
@JvmName("toClass_Generics")
inline fun <reified T> String.toClass(loader: ClassLoader? = null, initialize: Boolean = false) =
ReflectionTool.findClassByName(name = this, loader, initialize) as? Class<T>? ?: error("Target Class type cannot cast to ${T::class.java}")
/**
* 通过字符串类名转换为 [loader] 中的实体类
*
* 找不到 [Class] 会返回 null - 不会抛出异常
* @param loader [Class] 所在的 [ClassLoader] - 默认空 - 不填使用默认 [ClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class] or null
*/
fun String.toClassOrNull(loader: ClassLoader? = null, initialize: Boolean = false) = runCatching { toClass(loader, initialize) }.getOrNull()
/**
* 通过字符串类名转换为 [loader] 中的实体类
*
* 找不到 [Class] 会返回 null - 不会抛出异常
* @param loader [Class] 所在的 [ClassLoader] - 默认空 - 不填使用默认 [ClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class]<[T]> or null
*/
@JvmName("toClassOrNull_Generics")
inline fun <reified T> String.toClassOrNull(loader: ClassLoader? = null, initialize: Boolean = false) =
runCatching { toClass<T>(loader, initialize) }.getOrNull()
/**
* 通过 [T] 得到其 [Class] 实例并转换为实体类
* @param loader [Class] 所在的 [ClassLoader] - 默认空 - 可不填
* @param initialize 是否初始化 [Class] 的静态方法块 - 如果未设置 [loader] (为 null) 时将不会生效 - 默认否
* @return [Class]<[T]>
* @throws NoClassDefFoundError 如果找不到 [Class] 或设置了错误的 [ClassLoader]
*/
inline fun <reified T> classOf(loader: ClassLoader? = null, initialize: Boolean = false) =
loader?.let { T::class.java.name.toClass(loader, initialize) as Class<T> } ?: T::class.java
/**
* 通过字符串类名使用指定的 [ClassLoader] 查找是否存在
* @param loader [Class] 所在的 [ClassLoader] - 不填使用默认 [ClassLoader]
* @return [Boolean] 是否存在
*/
fun String.hasClass(loader: ClassLoader? = null) = ReflectionTool.hasClassByName(name = this, loader)
/**
* 查找变量是否存在
* @param initiate 方法体
* @return [Boolean] 是否存在
*/
inline fun Class<*>.hasField(initiate: FieldConditions) = field(initiate).ignored().isNoSuch.not()
/**
* 查找方法是否存在
* @param initiate 方法体
* @return [Boolean] 是否存在
*/
inline fun Class<*>.hasMethod(initiate: MethodConditions) = method(initiate).ignored().isNoSuch.not()
/**
* 查找构造方法是否存在
* @param initiate 方法体
* @return [Boolean] 是否存在
*/
inline fun Class<*>.hasConstructor(initiate: ConstructorConditions = { emptyParam() }) = constructor(initiate).ignored().isNoSuch.not()
/**
* 查找 [Member] 中匹配的描述符
* @param conditions 条件方法体
* @return [Boolean] 是否存在
*/
inline fun Member.hasModifiers(conditions: ModifierConditions) = conditions(ModifierRules.with(instance = this))
/**
* 查找 [Class] 中匹配的描述符
* @param conditions 条件方法体
* @return [Boolean] 是否存在
*/
inline fun Class<*>.hasModifiers(conditions: ModifierConditions) = conditions(ModifierRules.with(instance = this))
/**
* 查找并得到变量
* @param initiate 查找方法体
* @return [FieldFinder.Result]
*/
inline fun Class<*>.field(initiate: FieldConditions) = FieldFinder(classSet = this).apply(initiate).build()
/**
* 查找并得到方法
* @param initiate 查找方法体
* @return [MethodFinder.Result]
*/
inline fun Class<*>.method(initiate: MethodConditions) = MethodFinder(classSet = this).apply(initiate).build()
/**
* 查找并得到构造方法
* @param initiate 查找方法体
* @return [ConstructorFinder.Result]
*/
inline fun Class<*>.constructor(initiate: ConstructorConditions = { emptyParam() }) = ConstructorFinder(classSet = this).apply(initiate).build()
/**
* 获得当前 [Class] 的泛型父类
*
* 如果当前实例不存在泛型将返回 null
* @return [GenericClass] or null
*/
fun Class<*>.generic() = genericSuperclass?.let { (it as? ParameterizedType?)?.let { e -> GenericClass(e) } }
/**
* 获得当前 [Class] 的泛型父类
*
* 如果当前实例不存在泛型将返回 null
* @param initiate 实例方法体
* @return [GenericClass] or null
*/
inline fun Class<*>.generic(initiate: GenericClass.() -> Unit) = generic()?.apply(initiate)
/**
* 获得当前实例的类操作对象
* @param ignored 是否开启忽略错误警告功能 - 默认否
* @return [CurrentClass]
*/
inline fun <reified T : Any> T.current(ignored: Boolean = false) =
CurrentClass(javaClass, instance = this).apply { isShutErrorPrinting = ignored }
/**
* 获得当前实例的类操作对象
* @param ignored 是否开启忽略错误警告功能 - 默认否
* @param initiate 方法体
* @return [T]
*/
inline fun <reified T : Any> T.current(ignored: Boolean = false, initiate: CurrentClass.() -> Unit): T {
current(ignored).apply(initiate)
return this
}
/**
* 通过构造方法创建新实例 - 任意类型 [Any]
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [buildOf]
* @return [Any] or null
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("buildOf(*param, initiate)"))
fun Class<*>.buildOfAny(vararg args: Any?, initiate: ConstructorConditions = { emptyParam() }) = buildOf(*args, initiate)
/**
* 通过构造方法创建新实例 - 任意类型 [Any]
* @param args 方法参数
* @param initiate 查找方法体
* @return [Any] or null
*/
inline fun Class<*>.buildOf(vararg args: Any?, initiate: ConstructorConditions = { emptyParam() }) =
constructor(initiate).get().call(*args)
/**
* 通过构造方法创建新实例 - 指定类型 [T]
* @param args 方法参数
* @param initiate 查找方法体
* @return [T] or null
*/
@JvmName(name = "buildOf_Generics")
inline fun <T> Class<*>.buildOf(vararg args: Any?, initiate: ConstructorConditions = { emptyParam() }) =
constructor(initiate).get().newInstance<T>(*args)
/**
* 遍历当前类中的所有方法
* @param isAccessible 是否强制设置成员为可访问类型 - 默认是
* @param result 回调 - ([Int] 下标,[Method] 实例)
*/
inline fun Class<*>.allMethods(isAccessible: Boolean = true, result: (index: Int, method: Method) -> Unit) =
declaredMethods.forEachIndexed { p, it -> result(p, it.also { e -> e.isAccessible = isAccessible }) }
/**
* 遍历当前类中的所有构造方法
* @param isAccessible 是否强制设置成员为可访问类型 - 默认是
* @param result 回调 - ([Int] 下标,[Constructor] 实例)
*/
inline fun Class<*>.allConstructors(isAccessible: Boolean = true, result: (index: Int, constructor: Constructor<*>) -> Unit) =
declaredConstructors.forEachIndexed { p, it -> result(p, it.also { e -> e.isAccessible = isAccessible }) }
/**
* 遍历当前类中的所有变量
* @param isAccessible 是否强制设置成员为可访问类型 - 默认是
* @param result 回调 - ([Int] 下标,[Field] 实例)
*/
inline fun Class<*>.allFields(isAccessible: Boolean = true, result: (index: Int, field: Field) -> Unit) =
declaredFields.forEachIndexed { p, it -> result(p, it.also { e -> e.isAccessible = isAccessible }) }

View File

@@ -0,0 +1,223 @@
/*
* 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/2.
*/
@file:Suppress("unused", "UnusedReceiverParameter", "DeprecatedCallableAddReplaceWith")
package com.highcapable.yukihookapi.hook.factory
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.content.res.Resources
import android.net.Uri
import android.os.Build
import android.os.Process
import android.view.ContextThemeWrapper
import android.widget.ImageView
import androidx.annotation.RequiresApi
import androidx.annotation.StyleRes
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppActivity
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppCompatActivity
import com.highcapable.yukihookapi.hook.xposed.parasitic.context.wrapper.ModuleContextThemeWrapper
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookPrefsBridge
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
import java.io.BufferedReader
import java.io.File
import java.io.FileReader
/**
* 在 [IYukiHookXposedInit] 中调用 [YukiHookAPI.configs]
* @param initiate 配置方法体
*/
inline fun IYukiHookXposedInit.configs(initiate: YukiHookAPI.Configs.() -> Unit) = YukiHookAPI.configs(initiate)
/**
* 在 [IYukiHookXposedInit] 中调用 [YukiHookAPI.encase]
* @param initiate Hook 方法体
*/
fun IYukiHookXposedInit.encase(initiate: PackageParam.() -> Unit) = YukiHookAPI.encase(initiate)
/**
* 在 [IYukiHookXposedInit] 中装载 [YukiHookAPI]
* @param hooker Hook 子类数组 - 必填不能为空
* @throws IllegalStateException 如果 [hooker] 是空的
*/
fun IYukiHookXposedInit.encase(vararg hooker: YukiBaseHooker) = YukiHookAPI.encase(hooker = hooker)
/**
* 获取模块的存取对象
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [Context.prefs] 方法
* @return [YukiHookPrefsBridge]
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("prefs()"))
val Context.modulePrefs get() = prefs()
/**
* 获取模块的存取对象
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [Context.prefs] 方法
* @return [YukiHookPrefsBridge]
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("prefs(name)"))
fun Context.modulePrefs(name: String) = prefs(name)
/**
* 创建 [YukiHookPrefsBridge] 对象
*
* 可以同时在模块与 (Xposed) 宿主环境中使用
*
* 如果你想在 (Xposed) 宿主环境将数据存入当前宿主的私有空间 - 请使用 [YukiHookPrefsBridge.native] 方法
*
* 在未声明任何条件的情况下 (Xposed) 宿主环境默认读取模块中的数据
* @param name 自定义 Sp 存储名称 - 默认空
* @return [YukiHookPrefsBridge]
*/
fun Context.prefs(name: String = "") = YukiHookPrefsBridge.from(context = this).let { if (name.isNotBlank()) it.name(name) else it }
/**
* 获取 [YukiHookDataChannel] 对象
*
* - ❗只能在模块环境使用此功能 - 其它环境下使用将不起作用
* @param packageName 目标 Hook APP (宿主) 包名
* @return [YukiHookDataChannel.NameSpace]
*/
fun Context.dataChannel(packageName: String) = YukiHookDataChannel.instance().nameSpace(context = this, packageName)
/**
* 获取当前进程名称
* @return [String]
*/
val Context.processName
get() = runCatching {
BufferedReader(FileReader(File("/proc/${Process.myPid()}/cmdline"))).let { buff ->
buff.readLine().trim { it <= ' ' }.let {
buff.close()
it
}
}
}.getOrNull() ?: packageName ?: ""
/**
* 向 Hook APP (宿主) [Context] 注入当前 Xposed 模块的资源
*
* 注入成功后 - 你就可以直接使用例如 [ImageView.setImageResource] or [Resources.getString] 装载当前 Xposed 模块的资源 ID
*
* 注入的资源作用域仅限当前 [Context] - 你需要在每个用到宿主 [Context] 的地方重复调用此方法进行注入才能使用
*
* 详情请参考 [注入模块资源 (Resources)](https://fankes.github.io/YukiHookAPI/zh-cn/api/special-features/host-inject#%E6%B3%A8%E5%85%A5%E6%A8%A1%E5%9D%97%E8%B5%84%E6%BA%90-resources)
*
* For English version, see [Inject Module App's Resources](https://fankes.github.io/YukiHookAPI/en/api/special-features/host-inject#inject-module-app-s-resources)
*
* - ❗只能在 (Xposed) 宿主环境使用此功能 - 其它环境下使用将不生效且会打印警告信息
*/
fun Context.injectModuleAppResources() = resources?.injectModuleAppResources()
/**
* 向 Hook APP (宿主) 指定 [Resources] 直接注入当前 Xposed 模块的资源
*
* 注入成功后 - 你就可以直接使用例如 [ImageView.setImageResource] or [Resources.getString] 装载当前 Xposed 模块的资源 ID
*
* 注入的资源作用域仅限当前 [Resources] - 你需要在每个用到宿主 [Resources] 的地方重复调用此方法进行注入才能使用
*
* 详情请参考 [注入模块资源 (Resources)](https://fankes.github.io/YukiHookAPI/zh-cn/api/special-features/host-inject#%E6%B3%A8%E5%85%A5%E6%A8%A1%E5%9D%97%E8%B5%84%E6%BA%90-resources)
*
* For English version, see [Inject Module App's Resources](https://fankes.github.io/YukiHookAPI/en/api/special-features/host-inject#inject-module-app-s-resources)
*
* - ❗只能在 (Xposed) 宿主环境使用此功能 - 其它环境下使用将不生效且会打印警告信息
*/
fun Resources.injectModuleAppResources() = AppParasitics.injectModuleAppResources(hostResources = this)
/**
* 向 Hook APP (宿主) 注册当前 Xposed 模块的 [Activity]
*
* 注册成功后 - 你就可以直接使用 [Context.startActivity] 来启动未在宿主中注册的 [Activity]
*
* 使用此方法会在未注册的 [Activity] 在 Hook APP (宿主) 中启动时自动调用 [injectModuleAppResources] 注入当前 Xposed 模块的资源
*
* - 你要将需要在宿主启动的 [Activity] 继承于 [ModuleAppActivity] or [ModuleAppCompatActivity]
*
* 详情请参考 [注册模块 Activity](https://fankes.github.io/YukiHookAPI/zh-cn/api/special-features/host-inject#%E6%B3%A8%E5%86%8C%E6%A8%A1%E5%9D%97-activity)
*
* For English version, see [Register Module App's Activity](https://fankes.github.io/YukiHookAPI/en/api/special-features/host-inject#register-module-app-s-activity)
*
* - ❗只能在 (Xposed) 宿主环境使用此功能 - 其它环境下使用将不生效且会打印警告信息
*
* - ❗最低支持 Android 7.0 (API 24)
* @param proxy 代理的 [Activity] - 必须存在于宿主的 AndroidMainifest 清单中 - 不填使用默认 [Activity]
*/
@RequiresApi(Build.VERSION_CODES.N)
fun Context.registerModuleAppActivities(proxy: Any? = null) = AppParasitics.registerModuleAppActivities(context = this, proxy)
/**
* 生成一个 [ContextThemeWrapper] 代理以应用当前 Xposed 模块的主题资源
*
* 在 Hook APP (宿主) 中使用此方法会自动调用 [injectModuleAppResources] 注入当前 Xposed 模块的资源
*
* - 如果在 Hook APP (宿主) 中使用此方法发生 [ClassCastException] - 请手动设置新的 [configuration]
*
* 详情请参考 [创建 ContextThemeWrapper 代理](https://fankes.github.io/YukiHookAPI/zh-cn/api/special-features/host-inject#%E5%88%9B%E5%BB%BA-contextthemewrapper-%E4%BB%A3%E7%90%86)
*
* For English version, see [Create ContextThemeWrapper Proxy](https://fankes.github.io/YukiHookAPI/en/api/special-features/host-inject#create-contextthemewrapper-proxy)
* @param theme 主题资源 ID
* @param configuration 使用的 [Configuration] - 默认空
* @return [ModuleContextThemeWrapper]
*/
fun Context.applyModuleTheme(@StyleRes theme: Int, configuration: Configuration? = null) =
ModuleContextThemeWrapper.wrapper(baseContext = this, theme, configuration)
/**
* 仅判断模块是否在太极、无极中激活
*
* 此处的实现代码来自太极官方文档中示例代码的封装与改进
*
* 详情请参考太极开发指南中的 [如何判断模块是否激活了?](https://taichi.cool/zh/doc/for-xposed-dev.html#%E5%A6%82%E4%BD%95%E5%88%A4%E6%96%AD%E6%A8%A1%E5%9D%97%E6%98%AF%E5%90%A6%E6%BF%80%E6%B4%BB%E4%BA%86%EF%BC%9F)
* @return [Boolean] 是否激活
*/
internal val Context.isTaiChiModuleActive: Boolean
get() {
/**
* 获取模块是否激活
* @return [Boolean] or null
*/
fun isModuleActive() =
contentResolver?.call(Uri.parse("content://me.weishu.exposed.CP/"), "active", null, null)?.getBoolean("active", false)
return runCatching { isModuleActive() }.getOrNull() ?: runCatching {
startActivity(Intent("me.weishu.exp.ACTION_ACTIVE").apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) })
isModuleActive()
}.getOrNull() ?: false
}

View File

@@ -0,0 +1,448 @@
/*
* 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/3.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.highcapable.yukihookapi.hook.log
import android.system.ErrnoException
import android.util.Log
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.core.api.helper.YukiHookHelper
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.utils.toStackTrace
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import java.io.File
import java.io.Serializable
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* 需要打印的日志类型
*
* 决定于模块与 (Xposed) 宿主环境使用的打印方式
*/
enum class LoggerType {
/** 仅使用 [Log] */
LOGD,
/**
* 仅在 (Xposed) 宿主环境使用
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [XPOSED_ENVIRONMENT]
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("XPOSED_ENVIRONMENT"))
XPOSEDBRIDGE,
/**
* 仅在 (Xposed) 宿主环境使用
*
* - ❗只能在 (Xposed) 宿主环境中使用 - 模块环境将不生效
*/
XPOSED_ENVIRONMENT,
/**
* 分区使用
*
* (Xposed) 宿主环境仅使用 [XPOSED_ENVIRONMENT]
*
* 模块环境仅使用 [LOGD]
*/
SCOPE,
/**
* 同时使用
*
* (Xposed) 宿主环境使用 [LOGD] 与 [XPOSED_ENVIRONMENT]
*
* 模块环境仅使用 [LOGD]
*/
BOTH
}
/**
* 调试日志数据实现类
* @param timestamp 当前时间戳
* @param time 当前 UTC 时间
* @param tag 当前标签
* @param priority 当前优先级 - D、I、W、E
* @param packageName 当前包名
* @param userId 当前用户 ID
* @param msg 当前日志内容
* @param throwable 当前异常堆栈
*/
data class YukiLoggerData internal constructor(
var timestamp: Long = 0L,
var time: String = "",
var tag: String = YukiHookLogger.Configs.tag,
var priority: String = "",
var packageName: String = "",
var userId: Int = 0,
var msg: String = "",
var throwable: Throwable? = null
) : Serializable {
/** 是否隐式打印 */
internal var isImplicit = false
init {
timestamp = System.currentTimeMillis()
time = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ROOT).format(Date(timestamp))
packageName = if (YukiXposedModule.isXposedEnvironment) YukiXposedModule.hostProcessName else AppParasitics.currentPackageName
userId = AppParasitics.findUserId(AppParasitics.currentPackageName)
}
/**
* 获取头部时间字符串
* @return [String]
*/
internal val head get() = "$time ------ "
override fun toString(): String {
var content = ""
YukiHookLogger.Configs.elements.takeIf { it.isNotEmpty() }?.forEach {
if (it == YukiHookLogger.Configs.TAG) content += "[$tag]"
if (it == YukiHookLogger.Configs.PRIORITY) content += "[$priority]"
if (it == YukiHookLogger.Configs.PACKAGE_NAME && isImplicit.not() && packageName.isNotBlank()) content += "[$packageName]"
if (it == YukiHookLogger.Configs.USER_ID && isImplicit.not() && userId != 0) content += "[$userId]"
}
return content.takeIf { it.isNotBlank() }?.let { "$content--> $msg" } ?: msg
}
}
/**
* 调试日志实现类
*/
object YukiHookLogger {
/**
* 当前全部已记录的日志数据
*
* - ❗获取到的日志数据在 Hook APP (宿主) 及模块进程中是相互隔离的
*/
val inMemoryData = ArrayList<YukiLoggerData>()
/**
* 获取当前日志文件内容
*
* 如果当前没有已记录的日志会返回空字符串
*
* - ❗获取到的日志数据在 Hook APP (宿主) 及模块进程中是相互隔离的
* @return [String]
*/
val contents get() = contents()
/**
* 获取、格式化当前日志文件内容
*
* 如果当前没有已记录的日志 ([data] 为空) 会返回空字符串
*
* - ❗获取到的日志数据在 Hook APP (宿主) 及模块进程中是相互隔离的
* @param data 日志数据 - 默认为 [inMemoryData]
* @return [String]
*/
fun contents(data: ArrayList<YukiLoggerData> = inMemoryData): String {
var content = ""
data.takeIf { it.isNotEmpty() }?.forEach {
content += "${it.head}$it\n"
it.throwable?.also { e ->
content += "${it.head}Dump stack trace for \"${e.current().name}\":\n"
content += e.toStackTrace()
}
}
return content
}
/**
* 清除全部已记录的日志
*
* 你也可以直接获取 [inMemoryData] 来清除
*
* - ❗获取到的日志数据在 Hook APP (宿主) 及模块进程中是相互隔离的
*/
fun clear() = inMemoryData.clear()
/**
* 保存当前日志到文件
*
* 若当前未开启 [Configs.isRecord] 或记录为空则不会进行任何操作
*
* 日志文件会追加到 [fileName] 的文件结尾 - 若文件不存在会自动创建
*
* - ❗文件读写权限取决于当前宿主、模块已获取的权限
* @param fileName 完整文件名 - 例如 /data/data/.../files/xxx.log
* @param data 日志数据 - 默认为 [inMemoryData]
* @throws ErrnoException 如果目标路径不可写
*/
fun saveToFile(fileName: String, data: ArrayList<YukiLoggerData> = inMemoryData) {
if (data.isNotEmpty()) File(fileName).appendText(contents(data))
}
/**
* 配置 [YukiHookLogger]
*/
object Configs {
/**
* 标签
*
* 显示效果如下 ↓
*
* ```
* [YukiHookAPI][...][...]--> ...
* ```
*/
const val TAG = 1000
/**
* 优先级
*
* 显示效果如下 ↓
*
* ```
* [...][E][...]--> ...
* ```
*/
const val PRIORITY = 1001
/**
* 当前宿主的包名
*
* 显示效果如下 ↓
*
* ```
* [...][com.demo.test][...]--> ...
* ```
*/
const val PACKAGE_NAME = 1002
/**
* 当前宿主的用户 ID (主用户不显示)
*
* 显示效果如下 ↓
*
* ```
* [...][...][999]--> ...
* ```
*/
const val USER_ID = 1003
/** 当前已添加的元素顺序列表数组 */
internal var elements = arrayOf(TAG, PRIORITY, PACKAGE_NAME, USER_ID)
/**
* 是否启用调试日志的输出功能 - 默认启用
*
* - ❗关闭后将会停用 [YukiHookAPI] 对全部日志的输出
*
* 但是不影响当你手动调用下面这些方法输出日志
*
* [loggerD]、[loggerI]、[loggerW]、[loggerE]
*
* 当 [isEnable] 关闭后 [YukiHookAPI.Configs.isDebug] 也将同时关闭
*/
var isEnable = true
/**
* 是否启用调试日志的记录功能 - 默认不启用
*
* 开启后将会在内存中记录全部可用的日志和异常堆栈
*
* 需要同时启用 [isEnable] 才能有效
*
* - ❗过量的日志可能会导致宿主运行缓慢或造成频繁 GC
*
* 开启后你可以调用 [YukiHookLogger.saveToFile] 实时保存日志到文件或使用 [YukiHookLogger.contents] 获取实时日志文件
*/
var isRecord = false
/**
* 这是一个调试日志的全局标识
*
* 默认文案为 YukiHookAPI
*
* 你可以修改为你自己的文案
*/
var tag = "YukiHookAPI"
/**
* 自定义调试日志对外显示的元素
*
* 只对日志记录和 (Xposed) 宿主环境的日志生效
*
* 日志元素的排列将按照你在 [item] 中设置的顺序进行显示
*
* 你还可以留空 [item] 以不显示除日志内容外的全部元素
*
* 可用的元素有:[TAG]、[PRIORITY]、[PACKAGE_NAME]、[USER_ID]
*
* 默认排列方式如下 ↓
*
* ```
* [TAG][PRIORITY][PACKAGE_NAME][USER_ID]--> Message
* ```
* @param item 自定义的元素数组
*/
fun elements(vararg item: Int) {
elements = arrayOf(*item.toTypedArray())
}
/** 结束方法体 */
@PublishedApi
internal fun build() = Unit
}
}
/**
* 向控制台和 (Xposed) 宿主环境打印日志 - 最终实现方法
* @param type 日志打印的类型
* @param data 日志数据
* @param isImplicit 是否隐式打印 - 不会记录 - 也不会显示包名和用户 ID
*/
private fun baseLogger(type: LoggerType, data: YukiLoggerData, isImplicit: Boolean = false) {
/** 是否为有效日志 */
val isNotBlankLog = data.msg.isNotBlank() || (data.msg.isBlank() && data.throwable != null)
/** 打印到 [Log] */
fun logByLogd() = when (data.priority) {
"D" -> Log.d(data.tag, data.msg)
"I" -> Log.i(data.tag, data.msg)
"W" -> Log.w(data.tag, data.msg)
"E" -> Log.e(data.tag, data.msg, data.throwable)
else -> Log.wtf(data.tag, data.msg, data.throwable)
}
/** 打印到 (Xposed) 宿主环境 */
fun logByHooker() {
if (isNotBlankLog) YukiHookHelper.logByHooker(data.also { it.isImplicit = isImplicit }.toString(), data.throwable)
}
@Suppress("DEPRECATION")
when (type) {
LoggerType.LOGD -> logByLogd()
LoggerType.XPOSEDBRIDGE, LoggerType.XPOSED_ENVIRONMENT -> logByHooker()
LoggerType.SCOPE -> if (YukiXposedModule.isXposedEnvironment) logByHooker() else logByLogd()
LoggerType.BOTH -> {
logByLogd()
if (YukiXposedModule.isXposedEnvironment) logByHooker()
}
}
if (isImplicit.not() && YukiHookLogger.Configs.isRecord && isNotBlankLog) YukiHookLogger.inMemoryData.add(data)
}
/**
* [YukiHookAPI] 向控制台和 (Xposed) 宿主环境打印日志 - D
* @param msg 日志打印的内容
* @param isImplicit 是否隐式打印 - 不会记录 - 也不会显示包名和用户 ID
* @param isDisableLog 禁止打印日志 - 标识后将什么也不做 - 默认为 false
*/
internal fun yLoggerD(msg: String, isImplicit: Boolean = false, isDisableLog: Boolean = false) {
if (YukiHookLogger.Configs.isEnable.not() || isDisableLog) return
baseLogger(LoggerType.BOTH, YukiLoggerData(priority = "D", msg = msg), isImplicit)
}
/**
* [YukiHookAPI] 向控制台和 (Xposed) 宿主环境打印日志 - I
* @param msg 日志打印的内容
* @param isImplicit 是否隐式打印 - 不会记录 - 也不会显示包名和用户 ID
* @param isDisableLog 禁止打印日志 - 标识后将什么也不做 - 默认为 false
*/
internal fun yLoggerI(msg: String, isImplicit: Boolean = false, isDisableLog: Boolean = false) {
if (YukiHookLogger.Configs.isEnable.not() || isDisableLog) return
baseLogger(LoggerType.BOTH, YukiLoggerData(priority = "I", msg = msg), isImplicit)
}
/**
* [YukiHookAPI] 向控制台和 (Xposed) 宿主环境打印日志 - W
* @param msg 日志打印的内容
* @param isImplicit 是否隐式打印 - 不会记录 - 也不会显示包名和用户 ID
* @param isDisableLog 禁止打印日志 - 标识后将什么也不做 - 默认为 false
*/
internal fun yLoggerW(msg: String, isImplicit: Boolean = false, isDisableLog: Boolean = false) {
if (YukiHookLogger.Configs.isEnable.not() || isDisableLog) return
baseLogger(LoggerType.BOTH, YukiLoggerData(priority = "W", msg = msg), isImplicit)
}
/**
* [YukiHookAPI] 向控制台和 (Xposed) 宿主环境打印日志 - E
* @param msg 日志打印的内容 - 默认空 - 如果你仅想打印异常堆栈可只设置 [e]
* @param e 可填入异常堆栈信息 - 将自动完整打印到控制台
* @param isImplicit 是否隐式打印 - 不会记录 - 也不会显示包名和用户 ID
* @param isDisableLog 禁止打印日志 - 标识后将什么也不做 - 默认为 false
*/
internal fun yLoggerE(msg: String = "", e: Throwable? = null, isImplicit: Boolean = false, isDisableLog: Boolean = false) {
if (YukiHookLogger.Configs.isEnable.not() || isDisableLog) return
baseLogger(LoggerType.BOTH, YukiLoggerData(priority = "E", msg = msg, throwable = e), isImplicit)
}
/**
* 向控制台和 (Xposed) 宿主环境打印日志 - D
*
* (Xposed) 宿主环境中的日志打印风格为 [[tag]]「类型」--> [msg]
* @param tag 日志打印的标签 - 建议和自己的模块名称设置成一样的 - 默认为 [YukiHookLogger.Configs.tag]
* @param msg 日志打印的内容
* @param type 日志打印的类型 - 默认为 [LoggerType.BOTH]
*/
fun loggerD(tag: String = YukiHookLogger.Configs.tag, msg: String, type: LoggerType = LoggerType.BOTH) =
baseLogger(type, YukiLoggerData(priority = "D", tag = tag, msg = msg))
/**
* 向控制台和 (Xposed) 宿主环境打印日志 - I
*
* (Xposed) 宿主环境中的日志打印风格为 [[tag]]「类型」--> [msg]
* @param tag 日志打印的标签 - 建议和自己的模块名称设置成一样的 - 默认为 [YukiHookLogger.Configs.tag]
* @param msg 日志打印的内容
* @param type 日志打印的类型 - 默认为 [LoggerType.BOTH]
*/
fun loggerI(tag: String = YukiHookLogger.Configs.tag, msg: String, type: LoggerType = LoggerType.BOTH) =
baseLogger(type, YukiLoggerData(priority = "I", tag = tag, msg = msg))
/**
* 向控制台和 (Xposed) 宿主环境打印日志 - W
*
* (Xposed) 宿主环境中的日志打印风格为 [[tag]]「类型」--> [msg]
* @param tag 日志打印的标签 - 建议和自己的模块名称设置成一样的 - 默认为 [YukiHookLogger.Configs.tag]
* @param msg 日志打印的内容
* @param type 日志打印的类型 - 默认为 [LoggerType.BOTH]
*/
fun loggerW(tag: String = YukiHookLogger.Configs.tag, msg: String, type: LoggerType = LoggerType.BOTH) =
baseLogger(type, YukiLoggerData(priority = "W", tag = tag, msg = msg))
/**
* 向控制台和 (Xposed) 宿主环境打印日志 - E
*
* (Xposed) 宿主环境中的日志打印风格为 [[tag]]「类型」--> [msg]
* @param tag 日志打印的标签 - 建议和自己的模块名称设置成一样的 - 默认为 [YukiHookLogger.Configs.tag]
* @param msg 日志打印的内容 - 默认空 - 如果你仅想打印异常堆栈可只设置 [e]
* @param e 可填入异常堆栈信息 - 将自动完整打印到控制台
* @param type 日志打印的类型 - 默认为 [LoggerType.BOTH]
*/
fun loggerE(tag: String = YukiHookLogger.Configs.tag, msg: String = "", e: Throwable? = null, type: LoggerType = LoggerType.BOTH) =
baseLogger(type, YukiLoggerData(priority = "E", tag = tag, msg = msg, throwable = e))

View File

@@ -0,0 +1,445 @@
/*
* 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/2.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate", "UNCHECKED_CAST")
package com.highcapable.yukihookapi.hook.param
import android.os.Bundle
import android.util.ArrayMap
import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreator
import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreator.MemberHookCreator
import com.highcapable.yukihookapi.hook.core.api.helper.YukiHookHelper
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiHookCallback
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.log.yLoggerE
import java.lang.reflect.Constructor
import java.lang.reflect.Member
import java.lang.reflect.Method
/**
* Hook 方法、构造方法的目标对象实现类
* @param creatorInstance [YukiMemberHookCreator] 的实例对象
* @param paramId 当前回调方法体 ID
* @param param Hook 结果回调接口
*/
class HookParam internal constructor(
private val creatorInstance: YukiMemberHookCreator,
private var paramId: String = "",
private var param: YukiHookCallback.Param? = null
) {
internal companion object {
/** 每个回调方法体的数据存储实例数据 */
private val dataExtras = ArrayMap<String, Bundle>()
/** [HookParam] 是否已经执行首次回调事件 */
internal var isCallbackCalled = false
/** 设置 [HookParam] 执行首次回调事件 */
internal fun invoke() {
isCallbackCalled = true
}
}
/**
* 在回调中设置 [HookParam] 使用的 [YukiHookCallback.Param]
* @param paramId 当前回调方法体 ID
* @param param Hook 结果回调接口
* @return [HookParam]
*/
internal fun assign(paramId: String, param: YukiHookCallback.Param): HookParam {
this.paramId = paramId
this.param = param
return this
}
/**
* 获取当前 Hook 对象 [method] or [constructor] 的参数对象数组
*
* 这里的数组每项类型默认为 [Any] - 你可以使用 [args] 方法来实现 [ArgsModifyer.cast] 功能
* @return [Array]
* @throws IllegalStateException 如果对象为空
*/
val args get() = param?.args ?: error("Current hooked Member args is null")
/**
* 获取当前 Hook 实例的对象
*
* - ❗如果你当前 Hook 的对象是一个静态 - 那么它将不存在实例的对象
*
* - 如果你不确定当前实例的对象是否为 null - 你可以使用 [instanceOrNull]
* @return [Any]
* @throws IllegalStateException 如果对象为空
*/
val instance get() = param?.instance ?: error("HookParam instance got null! Is this a static member?")
/**
* 获取当前 Hook 实例的对象
*
* - ❗如果你当前 Hook 的对象是一个静态 - 那么它将不存在实例的对象
* @return [Any] or null
*/
val instanceOrNull get() = param?.instance
/**
* 获取当前 Hook 实例的类对象
* @return [Class]
*/
val instanceClass get() = param?.instance?.javaClass ?: creatorInstance.instanceClass
/**
* 获取当前 Hook 对象的 [Member]
*
* 在不确定 [Member] 类型为 [Method] or [Constructor] 时可以使用此方法
* @return [Member]
* @throws IllegalStateException 如果 [member] 为空
*/
val member get() = param?.member ?: error("Current hooked Member is null")
/**
* 获取当前 Hook 对象的方法
* @return [Method]
* @throws IllegalStateException 如果 [member] 类型不是 [Method]
*/
val method get() = member as? Method? ?: error("Current hooked Member is not a Method")
/**
* 获取当前 Hook 对象的构造方法
* @return [Constructor]
* @throws IllegalStateException 如果 [member] 类型不是 [Constructor]
*/
val constructor get() = member as? Constructor<*>? ?: error("Current hooked Member is not a Constructor")
/**
* 获取、设置当前 Hook 对象的 [method] or [constructor] 的返回值
* @return [Any] or null
*/
var result: Any?
get() = param?.result
set(value) {
param?.result = value
}
/**
* 获取当前回调方法体范围内的数据存储实例
* @return [Bundle]
*/
val dataExtra get() = dataExtras[paramId] ?: Bundle().apply { dataExtras[paramId] = this }
/**
* 判断是否存在设置过的方法调用抛出异常
* @return [Boolean]
*/
val hasThrowable get() = param?.hasThrowable
/**
* 获取设置的方法调用抛出异常
* @return [Throwable] or null
*/
val throwable get() = param?.throwable
/**
* 向 Hook APP 抛出异常
*
* 使用 [hasThrowable] 判断当前是否存在被抛出的异常
*
* 使用 [throwable] 获取当前设置的方法调用抛出异常
*
* - 仅会在回调方法的 [MemberHookCreator.beforeHook] or [MemberHookCreator.afterHook] 中生效
*
* - ❗设置后会同时执行 [resultNull] 方法并将异常抛出给当前 Hook APP
* @return [Throwable] or null
* @throws Throwable
*/
fun Throwable.throwToApp() {
param?.throwable = this
yLoggerE(msg = message ?: "", e = this)
}
/**
* 获取当前 Hook 对象的 [method] or [constructor] 的返回值 [T]
* @return [T] or null
*/
inline fun <reified T> result() = result as? T?
/**
* 获取当前 Hook 实例的对象 [T]
* @return [T]
* @throws IllegalStateException 如果对象为空或对象类型不是 [T]
*/
inline fun <reified T> instance() = instance as? T? ?: error("HookParam instance cannot cast to ${classOf<T>().name}")
/**
* 获取当前 Hook 实例的对象 [T]
* @return [T] or null
*/
inline fun <reified T> instanceOrNull() = instanceOrNull as? T?
/**
* 获取当前 Hook 对象的 [method] or [constructor] 的参数数组下标实例化类
* @return [ArgsIndexCondition]
*/
fun args() = ArgsIndexCondition()
/**
* 获取当前 Hook 对象的 [method] or [constructor] 的参数实例化对象类
* @param index 参数对象数组下标
* @return [ArgsModifyer]
*/
fun args(index: Int) = ArgsModifyer(index)
/**
* 执行原始 [Member]
*
* 调用自身未进行 Hook 的原始 [Member] 并调用原始参数执行
* @return [Any] or null
*/
fun callOriginal() = callOriginal<Any>()
/**
* 执行原始 [Member]
*
* 调用自身未进行 Hook 的原始 [Member] 并调用原始参数执行
* @return [T] or null
*/
@JvmName(name = "callOriginal_Generics")
fun <T> callOriginal() = invokeOriginal<T>(*args)
/**
* 执行原始 [Member]
*
* 调用自身未进行 Hook 的原始 [Member] 并自定义 [args] 执行
* @param args 参数实例
* @return [Any] or null
*/
fun invokeOriginal(vararg args: Any?) = invokeOriginal<Any>(*args)
/**
* 执行原始 [Member]
*
* 调用自身未进行 Hook 的原始 [Member] 并自定义 [args] 执行
* @param args 参数实例
* @return [T] or null
*/
@JvmName(name = "invokeOriginal_Generics")
fun <T> invokeOriginal(vararg args: Any?) = YukiHookHelper.invokeOriginalMember(member, param?.instance, args) as T?
/**
* 设置当前 Hook 对象方法的 [result] 返回值为 true
*
* - ❗请确保 [result] 类型为 [Boolean]
*/
fun resultTrue() {
result = true
}
/**
* 设置当前 Hook 对象方法的 [result] 返回值为 false
*
* - ❗请确保 [result] 类型为 [Boolean]
*/
fun resultFalse() {
result = false
}
/**
* 设置当前 Hook 对象方法的 [result] 为 null
*
* - ❗此方法将强制设置方法体的 [result] 为 null
*/
fun resultNull() {
result = null
}
/**
* 对方法参数的数组下标进行实例化类
*
* - ❗请使用第一个 [args] 方法来获取 [ArgsIndexCondition]
*/
inner class ArgsIndexCondition internal constructor() {
/**
* 获取当前 Hook 对象的 [method] or [constructor] 的参数数组第一位
* @return [ArgsModifyer]
*/
fun first() = args(index = 0)
/**
* 获取当前 Hook 对象的 [method] or [constructor] 的参数数组最后一位
* @return [ArgsModifyer]
*/
fun last() = args(index = args.lastIndex)
}
/**
* 对方法参数的修改进行实例化类
*
* - ❗请使用第二个 [args] 方法来获取 [ArgsModifyer]
* @param index 参数对象数组下标
*/
inner class ArgsModifyer internal constructor(private val index: Int) {
/**
* 得到方法参数的实例对象 [T]
* @return [T] or null
*/
fun <T> cast() = runCatching { args[index] as? T? }.getOrNull()
/**
* 得到方法参数的实例对象 [Byte]
*
* - ❗请确认目标参数的类型 - 发生错误会返回 null
* @return [Byte] or null
*/
fun byte() = cast<Byte?>()
/**
* 得到方法参数的实例对象 [Int]
*
* - ❗请确认目标参数的类型 - 发生错误会返回默认值
* @return [Int] 取不到返回 0
*/
fun int() = cast() ?: 0
/**
* 得到方法参数的实例对象 [Long]
*
* - ❗请确认目标参数的类型 - 发生错误会返回默认值
* @return [Long] 取不到返回 0L
*/
fun long() = cast() ?: 0L
/**
* 得到方法参数的实例对象 [Short]
*
* - ❗请确认目标参数的类型 - 发生错误会返回默认值
* @return [Short] 取不到返回 0
*/
fun short() = cast<Short?>() ?: 0
/**
* 得到方法参数的实例对象 [Double]
*
* - ❗请确认目标参数的类型 - 发生错误会返回默认值
* @return [Double] 取不到返回 0.0
*/
fun double() = cast() ?: 0.0
/**
* 得到方法参数的实例对象 [Float]
*
* - ❗请确认目标参数的类型 - 发生错误会返回默认值
* @return [Float] 取不到返回 0f
*/
fun float() = cast() ?: 0f
/**
* 得到方法参数的实例对象 [String]
*
* - ❗请确认目标参数的类型 - 发生错误会返回默认值
* @return [String] 取不到返回 ""
*/
fun string() = cast() ?: ""
/**
* 得到方法参数的实例对象 [Char]
*
* - ❗请确认目标参数的类型 - 发生错误会返回默认值
* @return [Char] 取不到返回 ' '
*/
fun char() = cast() ?: ' '
/**
* 得到方法参数的实例对象 [Boolean]
*
* - ❗请确认目标参数的类型 - 发生错误会返回默认值
* @return [Boolean] 取不到返回 false
*/
fun boolean() = cast() ?: false
/**
* 得到方法参数的实例对象 [Any]
* @return [Any] or null
*/
fun any() = cast<Any?>()
/**
* 得到方法参数的实例对象 [Array] - 每项类型 [T]
*
* - ❗请确认目标参数的类型 - 发生错误会返回空数组
* @return [Array] 取不到返回空数组
*/
inline fun <reified T> array() = cast() ?: arrayOf<T>()
/**
* 得到方法参数的实例对象 [List] - 每项类型 [T]
*
* - ❗请确认目标参数的类型 - 发生错误会返回空数组
* @return [List] 取不到返回空数组
*/
inline fun <reified T> list() = cast() ?: listOf<T>()
/**
* 设置方法参数的实例对象
* @param any 实例对象
* @throws IllegalStateException 如果目标方法参数对象数组为空或 [index] 下标不存在
*/
fun <T> set(any: T?) {
if (index < 0) error("HookParam Method args index must be >= 0")
if (args.isEmpty()) error("HookParam Method args is empty, mabe not has args")
if (index > args.lastIndex) error("HookParam Method args index out of bounds, max is ${args.lastIndex}")
param?.args?.set(index, any)
}
/**
* 设置方法参数的实例对象为 null
*
* 此方法可以将任何被 Hook 的目标对象设置为空
*/
fun setNull() = set(null)
/**
* 设置方法参数的实例对象为 true
*
* - ❗请确保目标对象的类型是 [Boolean] 不然会发生意想不到的问题
*/
fun setTrue() = set(true)
/**
* 设置方法参数的实例对象为 false
*
* - ❗请确保目标对象的类型是 [Boolean] 不然会发生意想不到的问题
*/
fun setFalse() = set(false)
override fun toString() = "Args of index $index"
}
override fun toString() = "HookParam by $param"
}

View File

@@ -0,0 +1,758 @@
/*
* 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/2.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate", "UNCHECKED_CAST")
package com.highcapable.yukihookapi.hook.param
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.res.Configuration
import android.content.res.Resources
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.bean.HookClass
import com.highcapable.yukihookapi.hook.bean.HookResources
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.core.YukiMemberHookCreator
import com.highcapable.yukihookapi.hook.core.YukiResourcesHookCreator
import com.highcapable.yukihookapi.hook.core.finder.classes.DexClassFinder
import com.highcapable.yukihookapi.hook.core.finder.tools.ReflectionTool
import com.highcapable.yukihookapi.hook.core.finder.type.factory.ClassConditions
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper
import com.highcapable.yukihookapi.hook.utils.value
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiModuleResources
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiResources
import com.highcapable.yukihookapi.hook.xposed.bridge.type.HookEntryType
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookPrefsBridge
/**
* 装载 Hook 的目标 APP 入口对象实现类
* @param wrapper [PackageParam] 的参数包装类实例 - 默认是空的
*/
open class PackageParam internal constructor(@PublishedApi internal var wrapper: PackageParamWrapper? = null) {
@PublishedApi
internal companion object {
/** 获取当前 Xposed 模块包名 */
@PublishedApi
internal val modulePackageName get() = YukiXposedModule.modulePackageName
/** Android 系统框架名称 */
@PublishedApi
internal const val SYSTEM_FRAMEWORK_NAME = AppParasitics.SYSTEM_FRAMEWORK_NAME
}
/** 当前设置的 [ClassLoader] */
private var currentClassLoader: ClassLoader? = null
/**
* 获取、设置当前 Hook APP 的 [ClassLoader]
*
* 你可以在这里手动设置当前 Hook APP 的 [ClassLoader] - 默认情况下会自动获取
*
* - ❗如果设置了错误或无效的 [ClassLoader] 会造成功能异常 - 请谨慎操作
* @return [ClassLoader]
*/
var appClassLoader
get() = currentClassLoader ?: wrapper?.appClassLoader ?: AppParasitics.currentApplication?.classLoader ?: AppParasitics.baseClassLoader
set(value) {
currentClassLoader = value
}
/**
* 获取当前 Hook APP 的 [ApplicationInfo]
* @return [ApplicationInfo]
*/
val appInfo get() = wrapper?.appInfo ?: AppParasitics.currentApplicationInfo ?: ApplicationInfo()
/**
* 获取当前 Hook APP 的用户 ID
*
* 机主为 0 - 应用双开 (分身) 或工作资料因系统环境不同 ID 也各不相同
* @return [Int]
*/
val appUserId get() = AppParasitics.findUserId(packageName)
/**
* 获取当前 Hook APP 的 [Application] 实例
*
* - ❗首次装载可能是空的 - 请延迟一段时间再获取或通过设置 [onAppLifecycle] 监听来完成
* @return [Application] or null
*/
val appContext get() = AppParasitics.hostApplication ?: AppParasitics.currentApplication
/**
* 获取当前 Hook APP 的 Resources
*
* - ❗你只能在 [HookResources.hook] 方法体内或 [appContext] 装载完毕时进行调用
* @return [Resources] or null
*/
val appResources get() = wrapper?.appResources ?: appContext?.resources
/**
* 获取当前系统框架的 [Context]
* @return [Context] ContextImpl 实例对象
* @throws IllegalStateException 如果获取不到系统框架的 [Context]
*/
val systemContext get() = AppParasitics.systemContext
/**
* 获取当前 Hook APP 的进程名称
*
* 默认的进程名称是 [packageName]
* @return [String]
*/
val processName get() = wrapper?.processName ?: AppParasitics.currentProcessName
/**
* 获取当前 Hook APP 的包名
* @return [String]
*/
val packageName get() = wrapper?.packageName ?: AppParasitics.currentPackageName
/**
* 获取当前 Hook APP 是否为第一个 [Application]
* @return [Boolean]
*/
val isFirstApplication get() = packageName.trim() == processName.trim()
/**
* 获取当前 Hook APP 的主进程名称
*
* 其对应的就是 [packageName]
* @return [String]
*/
val mainProcessName get() = packageName.trim()
/**
* 获取当前 Xposed 模块自身 APK 文件路径
*
* - ❗作为 Hook API 装载时无法使用 - 会获取到空字符串
* @return [String]
*/
val moduleAppFilePath get() = YukiXposedModule.moduleAppFilePath
/**
* 获取当前 Xposed 模块自身 [Resources]
*
* - ❗作为 Hook API 或不支持的 Hook Framework 装载时无法使用 - 会抛出异常
* @return [YukiModuleResources]
* @throws IllegalStateException 如果当前 Hook Framework 不支持此功能
*/
val moduleAppResources
get() = (if (YukiHookAPI.Configs.isEnableModuleAppResourcesCache) YukiXposedModule.moduleAppResources
else YukiXposedModule.dynamicModuleAppResources) ?: error("Current Hook Framework not support moduleAppResources")
/**
* 创建 [YukiHookPrefsBridge] 对象
*
* - ❗作为 Hook API 装载时无法使用 - 会抛出异常
* @return [YukiHookPrefsBridge]
*/
val prefs get() = YukiHookPrefsBridge.from()
/**
* 创建 [YukiHookPrefsBridge] 对象
*
* - ❗作为 Hook API 装载时无法使用 - 会抛出异常
* @param name 自定义 Sp 存储名称
* @return [YukiHookPrefsBridge]
*/
fun prefs(name: String) = prefs.name(name)
/**
* 获取 [YukiHookDataChannel] 对象
*
* - ❗作为 Hook API 装载时无法使用 - 会抛出异常
* @return [YukiHookDataChannel.NameSpace]
* @throws IllegalStateException 如果在 [HookEntryType.ZYGOTE] 装载
*/
val dataChannel
get() = if (wrapper?.type != HookEntryType.ZYGOTE)
YukiHookDataChannel.instance().nameSpace(packageName = packageName)
else error("YukiHookDataChannel cannot used in zygote")
/**
* 设置 [PackageParam] 使用的 [PackageParamWrapper]
* @param wrapper [PackageParam] 的参数包装类实例
* @return [PackageParam]
*/
internal fun assign(wrapper: PackageParamWrapper?): PackageParam {
this.wrapper = wrapper
return this
}
/**
* 获得当前 Hook APP 的 [YukiResources] 对象
*
* 请调用 [HookResources.hook] 方法开始 Hook
* @return [HookResources]
*/
fun resources() = HookResources(wrapper?.appResources)
/** 刷新当前 Xposed 模块自身 [Resources] */
fun refreshModuleAppResources() = YukiXposedModule.refreshModuleAppResources()
/**
* 监听当前 Hook APP 生命周期装载事件
*
* - ❗在 [loadZygote] 中不会被装载 - 仅会在 [loadSystem]、[loadApp] 中装载
*
* - ❗作为 Hook API 装载时请使用原生的 [Application] 实现生命周期监听
* @param isOnFailureThrowToApp 是否在发生异常时将异常抛出给宿主 - 默认是
* @param initiate 方法体
*/
inline fun onAppLifecycle(isOnFailureThrowToApp: Boolean = true, initiate: AppLifecycle.() -> Unit) =
AppLifecycle(isOnFailureThrowToApp).apply(initiate).build()
/**
* 装载并 Hook 指定包名的 APP
*
* 若要装载 APP Zygote 事件 - 请使用 [loadZygote]
*
* 若要 Hook 系统框架 - 请使用 [loadSystem]
* @param name 包名
* @param initiate 方法体
*/
inline fun loadApp(name: String, initiate: PackageParam.() -> Unit) {
if (wrapper?.type != HookEntryType.ZYGOTE && (packageName == name || name.isBlank())) initiate(this)
}
/**
* 装载并 Hook 指定包名的 APP
*
* 若要装载 APP Zygote 事件 - 请使用 [loadZygote]
*
* 若要 Hook 系统框架 - 请使用 [loadSystem]
* @param name 包名数组
* @param initiate 方法体
*/
inline fun loadApp(vararg name: String, initiate: PackageParam.() -> Unit) {
if (name.isEmpty()) return loadApp(initiate = initiate)
if (wrapper?.type != HookEntryType.ZYGOTE && name.any { it == packageName }) initiate(this)
}
/**
* 装载并 Hook 指定包名的 APP
*
* 若要装载 APP Zygote 事件 - 请使用 [loadZygote]
*
* 若要 Hook 系统框架 - 请使用 [loadSystem]
* @param name 包名
* @param hooker Hook 子类
*/
fun loadApp(name: String, hooker: YukiBaseHooker) {
if (wrapper?.type != HookEntryType.ZYGOTE && (packageName == name || name.isBlank())) loadHooker(hooker)
}
/**
* 装载并 Hook 指定包名的 APP
*
* 若要装载 APP Zygote 事件 - 请使用 [loadZygote]
*
* 若要 Hook 系统框架 - 请使用 [loadSystem]
* @param name 包名 - 不填将过滤除了 [loadZygote] 事件外的全部 APP
* @param hooker Hook 子类数组
*/
fun loadApp(name: String, vararg hooker: YukiBaseHooker) {
if (hooker.isEmpty()) error("loadApp method need a \"hooker\" param")
if (wrapper?.type != HookEntryType.ZYGOTE && (packageName == name || name.isBlank())) hooker.forEach { loadHooker(it) }
}
/**
* 装载并 Hook 全部 APP
*
* 若要装载 APP Zygote 事件 - 请使用 [loadZygote]
*
* 若要 Hook 系统框架 - 请使用 [loadSystem]
* @param isExcludeSelf 是否排除模块自身 - 默认否 - 启用后被 Hook 的 APP 将不包含当前模块自身
* @param initiate 方法体
*/
inline fun loadApp(isExcludeSelf: Boolean = false, initiate: PackageParam.() -> Unit) {
if (wrapper?.type != HookEntryType.ZYGOTE &&
(isExcludeSelf.not() || isExcludeSelf && packageName != modulePackageName)
) initiate(this)
}
/**
* 装载并 Hook 全部 APP
*
* 若要装载 APP Zygote 事件 - 请使用 [loadZygote]
*
* 若要 Hook 系统框架 - 请使用 [loadSystem]
* @param isExcludeSelf 是否排除模块自身 - 默认否 - 启用后被 Hook 的 APP 将不包含当前模块自身
* @param hooker Hook 子类
*/
fun loadApp(isExcludeSelf: Boolean = false, hooker: YukiBaseHooker) {
if (wrapper?.type != HookEntryType.ZYGOTE &&
(isExcludeSelf.not() || isExcludeSelf && packageName != modulePackageName)
) loadHooker(hooker)
}
/**
* 装载并 Hook 全部 APP
*
* 若要装载 APP Zygote 事件 - 请使用 [loadZygote]
*
* 若要 Hook 系统框架 - 请使用 [loadSystem]
* @param isExcludeSelf 是否排除模块自身 - 默认否 - 启用后被 Hook 的 APP 将不包含当前模块自身
* @param hooker Hook 子类数组
*/
fun loadApp(isExcludeSelf: Boolean = false, vararg hooker: YukiBaseHooker) {
if (hooker.isEmpty()) error("loadApp method need a \"hooker\" param")
if (wrapper?.type != HookEntryType.ZYGOTE &&
(isExcludeSelf.not() || isExcludeSelf && packageName != modulePackageName)
) hooker.forEach { loadHooker(it) }
}
/**
* 装载并 Hook 系统框架
* @param initiate 方法体
*/
inline fun loadSystem(initiate: PackageParam.() -> Unit) = loadApp(SYSTEM_FRAMEWORK_NAME, initiate)
/**
* 装载并 Hook 系统框架
* @param hooker Hook 子类
*/
fun loadSystem(hooker: YukiBaseHooker) = loadApp(SYSTEM_FRAMEWORK_NAME, hooker)
/**
* 装载并 Hook 系统框架
* @param hooker Hook 子类数组
*/
fun loadSystem(vararg hooker: YukiBaseHooker) {
if (hooker.isEmpty()) error("loadSystem method need a \"hooker\" param")
loadApp(SYSTEM_FRAMEWORK_NAME, *hooker)
}
/**
* 装载 APP Zygote 事件
* @param initiate 方法体
*/
inline fun loadZygote(initiate: PackageParam.() -> Unit) {
if (wrapper?.type == HookEntryType.ZYGOTE) initiate(this)
}
/**
* 装载 APP Zygote 事件
* @param hooker Hook 子类
*/
fun loadZygote(hooker: YukiBaseHooker) {
if (wrapper?.type == HookEntryType.ZYGOTE) loadHooker(hooker)
}
/**
* 装载 APP Zygote 事件
* @param hooker Hook 子类数组
*/
fun loadZygote(vararg hooker: YukiBaseHooker) {
if (hooker.isEmpty()) error("loadZygote method need a \"hooker\" param")
if (wrapper?.type == HookEntryType.ZYGOTE) hooker.forEach { loadHooker(it) }
}
/**
* 装载并 Hook APP 的指定进程
* @param name 进程名 - 若要指定主进程可填写 [mainProcessName] - 效果与 [isFirstApplication] 一致
* @param initiate 方法体
*/
inline fun withProcess(name: String, initiate: PackageParam.() -> Unit) {
if (processName == name) initiate(this)
}
/**
* 装载并 Hook APP 的指定进程
* @param name 进程名数组 - 若要指定主进程可填写 [mainProcessName] - 效果与 [isFirstApplication] 一致
* @param initiate 方法体
*/
inline fun withProcess(vararg name: String, initiate: PackageParam.() -> Unit) {
if (name.isEmpty()) error("withProcess method need a \"name\" param")
if (name.any { it == processName }) initiate(this)
}
/**
* 装载并 Hook APP 的指定进程
* @param name 进程名 - 若要指定主进程可填写 [mainProcessName] - 效果与 [isFirstApplication] 一致
* @param hooker Hook 子类
*/
fun withProcess(name: String, hooker: YukiBaseHooker) {
if (processName == name) loadHooker(hooker)
}
/**
* 装载并 Hook APP 的指定进程
* @param name 进程名 - 若要指定主进程可填写 [mainProcessName] - 效果与 [isFirstApplication] 一致
* @param hooker Hook 子类数组
*/
fun withProcess(name: String, vararg hooker: YukiBaseHooker) {
if (name.isEmpty()) error("withProcess method need a \"hooker\" param")
if (processName == name) hooker.forEach { loadHooker(it) }
}
/**
* 装载 Hook 子类
*
* 你可以在 Hooker 中继续装载 Hooker
* @param hooker Hook 子类
*/
fun loadHooker(hooker: YukiBaseHooker) {
hooker.wrapper?.also {
if (it.packageName.isNotBlank() && it.type != HookEntryType.ZYGOTE)
if (it.packageName == wrapper?.packageName)
hooker.assignInstance(packageParam = this)
else yLoggerW(
msg = "This Hooker \"${hooker::class.java.name}\" is singleton or reused, " +
"but the current process has multiple package name \"${wrapper?.packageName}\", " +
"the original is \"${it.packageName}\"\n" +
"Make sure your Hooker supports multiple instances for this situation\n" +
"The process with package name \"${wrapper?.packageName}\" will be ignored"
)
else hooker.assignInstance(packageParam = this)
} ?: hooker.assignInstance(packageParam = this)
}
/**
* 通过 [appClassLoader] 按指定条件查找并得到当前 Hook APP Dex 中的 [Class]
*
* - ❗此方法在 [Class] 数量过多及查找条件复杂时会非常耗时
*
* - ❗建议启用 [async] 或设置 [name] 参数 - [name] 参数将在 Hook APP (宿主) 不同版本中自动进行本地缓存以提升效率
*
* - ❗此功能尚在试验阶段 - 性能与稳定性可能仍然存在问题 - 使用过程遇到问题请向我们报告并帮助我们改进
* @param name 标识当前 [Class] 缓存的名称 - 不设置将不启用缓存 - 启用缓存自动启用 [async]
* @param async 是否启用异步 - 默认否
* @param initiate 方法体
* @return [DexClassFinder.Result]
*/
inline fun searchClass(name: String = "", async: Boolean = false, initiate: ClassConditions) =
DexClassFinder(name, async = async || name.isNotBlank(), appClassLoader).apply(initiate).build()
/**
* 通过字符串类名转换为当前 Hook APP 的实体类
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [toClass]
* @return [Class]
* @throws NoClassDefFoundError 如果找不到 [Class]
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("toClass()"))
val String.clazz
get() = toClass()
/**
* [VariousClass] 转换为当前 Hook APP 的实体类
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [toClass]
* @return [Class]
* @throws IllegalStateException 如果任何 [Class] 都没有匹配到
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("toClass()"))
val VariousClass.clazz
get() = toClass()
/**
* 通过字符串类名查找是否存在
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [hasClass]
* @return [Boolean] 是否存在
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("hasClass()"))
val String.hasClass
get() = hasClass()
/**
* 通过字符串类名转换为 [loader] 中的实体类
* @param loader [Class] 所在的 [ClassLoader] - 不填使用 [appClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class]
* @throws NoClassDefFoundError 如果找不到 [Class]
*/
fun String.toClass(loader: ClassLoader? = appClassLoader, initialize: Boolean = false) =
ReflectionTool.findClassByName(name = this, loader, initialize)
/**
* 通过字符串类名转换为 [loader] 中的实体类
* @param loader [Class] 所在的 [ClassLoader] - 不填使用 [appClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class]<[T]>
* @throws NoClassDefFoundError 如果找不到 [Class]
* @throws IllegalStateException 如果 [Class] 的类型不为 [T]
*/
@JvmName("toClass_Generics")
inline fun <reified T> String.toClass(loader: ClassLoader? = appClassLoader, initialize: Boolean = false) =
ReflectionTool.findClassByName(name = this, loader, initialize) as? Class<T>?
?: error("Target Class type cannot cast to ${T::class.java}")
/**
* 通过字符串类名转换为 [loader] 中的实体类
*
* 找不到 [Class] 会返回 null - 不会抛出异常
* @param loader [Class] 所在的 [ClassLoader] - 不填使用 [appClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class] or null
*/
fun String.toClassOrNull(loader: ClassLoader? = appClassLoader, initialize: Boolean = false) =
runCatching { toClass(loader, initialize) }.getOrNull()
/**
* 通过字符串类名转换为 [loader] 中的实体类
*
* 找不到 [Class] 会返回 null - 不会抛出异常
* @param loader [Class] 所在的 [ClassLoader] - 不填使用 [appClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class]<[T]> or null
*/
@JvmName("toClassOrNull_Generics")
inline fun <reified T> String.toClassOrNull(loader: ClassLoader? = appClassLoader, initialize: Boolean = false) =
runCatching { toClass<T>(loader, initialize) }.getOrNull()
/**
* [VariousClass] 转换为 [loader] 中的实体类
* @param loader [Class] 所在的 [ClassLoader] - 不填使用 [appClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class]
* @throws IllegalStateException 如果任何 [Class] 都没有匹配到
*/
fun VariousClass.toClass(loader: ClassLoader? = appClassLoader, initialize: Boolean = false) = get(loader, initialize)
/**
* [VariousClass] 转换为 [loader] 中的实体类
*
* 匹配不到 [Class] 会返回 null - 不会抛出异常
* @param loader [Class] 所在的 [ClassLoader] - 不填使用 [appClassLoader]
* @param initialize 是否初始化 [Class] 的静态方法块 - 默认否
* @return [Class] or null
*/
fun VariousClass.toClassOrNull(loader: ClassLoader? = appClassLoader, initialize: Boolean = false) = getOrNull(loader, initialize)
/**
* 通过字符串类名查找是否存在
* @param loader [Class] 所在的 [ClassLoader] - 不填使用 [appClassLoader]
* @return [Boolean] 是否存在
*/
fun String.hasClass(loader: ClassLoader? = appClassLoader) = ReflectionTool.hasClassByName(name = this, loader)
/**
* 查找并装载 [HookClass]
*
* - ❗使用此方法会得到一个 [HookClass] 仅用于 Hook - 若想查找 [Class] 请使用 [toClass] 功能
* @param name 类名
* @param loader 当前 [ClassLoader] - 默认使用 [appClassLoader] - 设为 null 使用默认 [ClassLoader]
* @return [HookClass]
*/
fun findClass(name: String, loader: ClassLoader? = appClassLoader) =
runCatching { name.toClass(loader).toHookClass() }.getOrElse { HookClass(name = name, throwable = it) }
/**
* 查找并装载 [HookClass]
*
* 使用此方法查找将会取 [name] 其中命中存在的第一个 [Class] 作为结果
*
* - ❗使用此方法会得到一个 [HookClass] 仅用于 Hook - 若想查找 [Class] 请使用 [toClass] 功能
* @param name 可填入多个类名 - 自动匹配
* @param loader 当前 [ClassLoader] - 默认使用 [appClassLoader] - 设为 null 使用默认 [ClassLoader]
* @return [HookClass]
*/
fun findClass(vararg name: String, loader: ClassLoader? = appClassLoader) = VariousClass(*name).toHookClass(loader)
/**
* Hook 方法、构造方法
*
* - 使用当前 [appClassLoader] 装载目标 [Class]
*
* - ❗为防止任何字符串都被当做 [Class] 进行 Hook - 推荐优先使用 [findClass]
* @param initiate 方法体
* @return [YukiMemberHookCreator.Result]
*/
inline fun String.hook(initiate: YukiMemberHookCreator.() -> Unit) = findClass(name = this).hook(initiate)
/**
* Hook 方法、构造方法
*
* - 自动选择与当前 [Class] 相匹配的 [ClassLoader] - 优先使用 [appClassLoader]
*
* - ❗若当前 [Class] 不在 [appClassLoader] 且自动匹配无法找到该 [Class] - 请启用 [isForceUseAbsolute]
* @param isForceUseAbsolute 是否强制使用绝对实例对象 - 默认否
* @param initiate 方法体
* @return [YukiMemberHookCreator.Result]
*/
inline fun Class<*>.hook(isForceUseAbsolute: Boolean = false, initiate: YukiMemberHookCreator.() -> Unit) = when {
isForceUseAbsolute -> toHookClass()
name.hasClass() -> findClass(name)
else -> toHookClass()
}.hook(initiate)
/**
* Hook 方法、构造方法
*
* - 使用当前 [appClassLoader] 装载目标 [Class]
* @param initiate 方法体
* @return [YukiMemberHookCreator.Result]
*/
inline fun VariousClass.hook(initiate: YukiMemberHookCreator.() -> Unit) = toHookClass(appClassLoader).hook(initiate)
/**
* Hook 方法、构造方法
* @param initiate 方法体
* @return [YukiMemberHookCreator.Result]
*/
inline fun HookClass.hook(initiate: YukiMemberHookCreator.() -> Unit) =
YukiMemberHookCreator(packageParam = this@PackageParam, hookClass = this).apply(initiate).hook()
/**
* Hook APP 的 Resources
*
* - ❗请注意你需要确保当前 Hook Framework 支持且 [InjectYukiHookWithXposed.isUsingResourcesHook] 已启用
* @param initiate 方法体
*/
inline fun HookResources.hook(initiate: YukiResourcesHookCreator.() -> Unit) =
YukiResourcesHookCreator(packageParam = this@PackageParam, hookResources = this).apply(initiate).hook()
/**
* [VariousClass] 转换为 [HookClass]
* @param loader 当前 [ClassLoader] - 若留空使用默认 [ClassLoader]
* @return [HookClass]
*/
@PublishedApi
internal fun VariousClass.toHookClass(loader: ClassLoader? = null) =
runCatching { get(loader).toHookClass() }.getOrElse { HookClass(name = "VariousClass", throwable = Throwable(it.message)) }
/**
* [Class] 转换为 [HookClass]
* @return [HookClass]
*/
@PublishedApi
internal fun Class<*>.toHookClass() = HookClass(instance = this, name)
/**
* 当前 Hook APP 的生命周期实例处理类
*
* - ❗请使用 [onAppLifecycle] 方法来获取 [AppLifecycle]
* @param isOnFailureThrowToApp 是否在发生异常时将异常抛出给宿主
*/
inner class AppLifecycle @PublishedApi internal constructor(private val isOnFailureThrowToApp: Boolean) {
/**
* 是否为当前操作 [HookEntryType.PACKAGE] 的调用域
*
* 为避免多次设置回调事件 - 回调事件仅在 Hook 开始后生效
* @return [Boolean]
*/
private val isCurrentScope get() = wrapper?.type == HookEntryType.PACKAGE
/**
* 监听当前 Hook APP 装载 [Application.attachBaseContext]
* @param result 回调 - ([Context] baseContext,[Boolean] 是否已执行 super)
*/
fun attachBaseContext(result: (baseContext: Context, hasCalledSuper: Boolean) -> Unit) {
if (isCurrentScope) AppParasitics.AppLifecycleCallback.attachBaseContextCallback = result
}
/**
* 监听当前 Hook APP 装载 [Application.onCreate]
* @param initiate 方法体
*/
fun onCreate(initiate: Application.() -> Unit) {
if (isCurrentScope) AppParasitics.AppLifecycleCallback.onCreateCallback = initiate
}
/**
* 监听当前 Hook APP 装载 [Application.onTerminate]
* @param initiate 方法体
*/
fun onTerminate(initiate: Application.() -> Unit) {
if (isCurrentScope) AppParasitics.AppLifecycleCallback.onTerminateCallback = initiate
}
/**
* 监听当前 Hook APP 装载 [Application.onLowMemory]
* @param initiate 方法体
*/
fun onLowMemory(initiate: Application.() -> Unit) {
if (isCurrentScope) AppParasitics.AppLifecycleCallback.onLowMemoryCallback = initiate
}
/**
* 监听当前 Hook APP 装载 [Application.onTrimMemory]
* @param result 回调 - ([Application] 当前实例,[Int] 类型)
*/
fun onTrimMemory(result: (self: Application, level: Int) -> Unit) {
if (isCurrentScope) AppParasitics.AppLifecycleCallback.onTrimMemoryCallback = result
}
/**
* 监听当前 Hook APP 装载 [Application.onConfigurationChanged]
* @param result 回调 - ([Application] 当前实例,[Configuration] 配置实例)
*/
fun onConfigurationChanged(result: (self: Application, config: Configuration) -> Unit) {
if (isCurrentScope) AppParasitics.AppLifecycleCallback.onConfigurationChangedCallback = result
}
/**
* 注册系统广播监听
* @param action 系统广播 Action
* @param result 回调 - ([Context] 当前上下文,[Intent] 当前 Intent)
*/
fun registerReceiver(vararg action: String, result: (context: Context, intent: Intent) -> Unit) {
if (isCurrentScope && action.isNotEmpty())
AppParasitics.AppLifecycleCallback.onReceiverActionsCallbacks[action.value()] = Pair(action, result)
}
/**
* 注册系统广播监听
* @param filter 广播意图过滤器
* @param result 回调 - ([Context] 当前上下文,[Intent] 当前 Intent)
*/
fun registerReceiver(filter: IntentFilter, result: (context: Context, intent: Intent) -> Unit) {
if (isCurrentScope) AppParasitics.AppLifecycleCallback.onReceiverFiltersCallbacks[filter.toString()] = Pair(filter, result)
}
/** 设置创建生命周期监听回调 */
@PublishedApi
internal fun build() {
AppParasitics.AppLifecycleCallback.isOnFailureThrowToApp = isOnFailureThrowToApp
AppParasitics.AppLifecycleCallback.isCallbackSetUp = true
}
}
override fun toString() = "PackageParam by $wrapper"
}

View File

@@ -0,0 +1,75 @@
/*
* 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/7.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate")
package com.highcapable.yukihookapi.hook.param.wrapper
import android.content.pm.ApplicationInfo
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiResources
import com.highcapable.yukihookapi.hook.xposed.bridge.type.HookEntryType
import dalvik.system.PathClassLoader
/**
* 用于包装 [PackageParam]
* @param type 当前正在进行的 Hook 类型
* @param packageName 包名
* @param processName 当前进程名
* @param appClassLoader APP [ClassLoader]
* @param appInfo APP [ApplicationInfo]
* @param appResources APP [YukiResources]
*/
@PublishedApi
internal class PackageParamWrapper internal constructor(
var type: HookEntryType,
var packageName: String,
var processName: String,
var appClassLoader: ClassLoader,
var appInfo: ApplicationInfo? = null,
var appResources: YukiResources? = null
) {
/**
* 获取当前包装实例的名称 ID
* @return [String]
*/
internal val wrapperNameId get() = if (type == HookEntryType.ZYGOTE) "android-zygote" else packageName
/**
* 获取当前正在进行的 Hook 进程是否正确
*
* 此功能为修复在 Hook 系统框架、系统 APP 等情况时会出现 [ClassLoader] 不匹配的问题
*
* 如果 [type] 不是 [HookEntryType.ZYGOTE] 那么 [appClassLoader] 就应该得到 [PathClassLoader]
* @return [Boolean] 是否正确
*/
internal val isCorrectProcess get() = type == HookEntryType.ZYGOTE || (type != HookEntryType.ZYGOTE && appClassLoader is PathClassLoader)
override fun toString() =
"[type] $type [packageName] $packageName [processName] $processName [appClassLoader] $appClassLoader [appInfo] $appInfo [appResources] $appResources"
}

View File

@@ -0,0 +1,823 @@
/*
* 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/2.
*/
@file:Suppress("unused", "KDocUnresolvedReference", "DEPRECATION")
package com.highcapable.yukihookapi.hook.type.android
import android.app.* // ktlint-disable no-wildcard-imports
import android.appwidget.AppWidgetHost
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.appwidget.AppWidgetProviderInfo
import android.content.*
import android.content.Intent.ShortcutIconResource
import android.content.pm.*
import android.content.pm.LauncherApps.ShortcutQuery
import android.content.res.*
import android.database.sqlite.SQLiteDatabase
import android.graphics.drawable.*
import android.icu.text.SimpleDateFormat
import android.media.MediaPlayer
import android.os.*
import android.provider.Settings
import android.service.notification.StatusBarNotification
import android.util.*
import android.view.*
import android.view.accessibility.AccessibilityEvent
import android.view.accessibility.AccessibilityManager
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.Toast
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.toClass
import com.highcapable.yukihookapi.hook.factory.toClassOrNull
/**
* 获得 [android.R] 类型
* @return [Class]<[android.R]>
*/
val AndroidRClass get() = classOf<android.R>()
/**
* 获得 [Context] 类型
* @return [Class]<[Context]>
*/
val ContextClass get() = classOf<Context>()
/**
* 获得 [ContextImpl] 类型
* @return [Class]
*/
val ContextImplClass get() = "android.app.ContextImpl".toClass()
/**
* 获得 [ContextWrapper] 类型
* @return [Class]<[ContextWrapper]>
*/
val ContextWrapperClass get() = classOf<ContextWrapper>()
/**
* 获得 [Application] 类型
* @return [Class]<[Application]>
*/
val ApplicationClass get() = classOf<Application>()
/**
* 获得 [ApplicationInfo] 类型
* @return [Class]<[ApplicationInfo]>
*/
val ApplicationInfoClass get() = classOf<ApplicationInfo>()
/**
* 获得 [Instrumentation] 类型
* @return [Class]<[Instrumentation]>
*/
val InstrumentationClass get() = classOf<Instrumentation>()
/**
* 获得 [PackageInfo] 类型
* @return [Class]<[PackageInfo]>
*/
val PackageInfoClass get() = classOf<PackageInfo>()
/**
* 获得 [ApplicationPackageManager] 类型
* @return [Class]
*/
val ApplicationPackageManagerClass get() = "android.app.ApplicationPackageManager".toClass()
/**
* 获得 [ActivityThread] 类型
* @return [Class]
*/
val ActivityThreadClass get() = "android.app.ActivityThread".toClass()
/**
* 获得 [ActivityManager] 类型
* @return [Class]<[ActivityManager]>
*/
val ActivityManagerClass get() = classOf<ActivityManager>()
/**
* 获得 [IActivityManager] 类型
* @return [Class]
*/
val IActivityManagerClass get() = "android.app.IActivityManager".toClass()
/**
* 获得 [ActivityManagerNative] 类型
* @return [Class]
*/
val ActivityManagerNativeClass get() = "android.app.ActivityManagerNative".toClass()
/**
* 获得 [IActivityTaskManager] 类型
*
* - ❗在 Android O (26) 及以上系统加入
* @return [Class] or null
*/
val IActivityTaskManagerClass get() = "android.app.IActivityTaskManager".toClassOrNull()
/**
* 获得 [ActivityTaskManager] 类型
*
* - ❗在 Android O (26) 及以上系统加入
* @return [Class] or null
*/
val ActivityTaskManagerClass get() = "android.app.ActivityTaskManager".toClassOrNull()
/**
* 获得 [IPackageManager] 类型
* @return [Class]
*/
val IPackageManagerClass get() = "android.content.pm.IPackageManager".toClass()
/**
* 获得 [ClientTransaction] 类型
* @return [Class]
*/
val ClientTransactionClass get() = "android.app.servertransaction.ClientTransaction".toClass()
/**
* 获得 [LoadedApk] 类型
* @return [Class]
*/
val LoadedApkClass get() = "android.app.LoadedApk".toClass()
/**
* 获得 [Singleton] 类型
* @return [Class]
*/
val SingletonClass get() = "android.util.Singleton".toClass()
/**
* 获得 [Activity] 类型
* @return [Class]<[Activity]>
*/
val ActivityClass get() = classOf<Activity>()
/**
* 获得 [Looper] 类型
* @return [Class]<[Looper]>
*/
val LooperClass get() = classOf<Looper>()
/**
* 获得 [Fragment] 类型 - Support
* @return [Class]
*/
val FragmentClass_AndroidSupport get() = "android.support.v4.app.Fragment".toClass()
/**
* 获得 [Fragment] 类型 - AndroidX
* @return [Class]
*/
val FragmentClass_AndroidX get() = "androidx.fragment.app.Fragment".toClass()
/**
* 获得 [FragmentActivity] 类型 - Support
* @return [Class]
*/
val FragmentActivityClass_AndroidSupport get() = "android.support.v4.app.FragmentActivity".toClass()
/**
* 获得 [FragmentActivity] 类型 - AndroidX
* @return [Class]
*/
val FragmentActivityClass_AndroidX get() = "androidx.fragment.app.FragmentActivity".toClass()
/**
* 获得 [DocumentFile] 类型 - AndroidX
* @return [Class]
*/
val DocumentFileClass get() = "androidx.documentfile.provider.DocumentFile".toClass()
/**
* 获得 [Service] 类型
* @return [Class]<[Service]>
*/
val ServiceClass get() = classOf<Service>()
/**
* 获得 [Binder] 类型
* @return [Class]<[Binder]>
*/
val BinderClass get() = classOf<Binder>()
/**
* 获得 [IBinder] 类型
* @return [Class]<[IBinder]>
*/
val IBinderClass get() = classOf<IBinder>()
/**
* 获得 [BroadcastReceiver] 类型
* @return [Class]<[BroadcastReceiver]>
*/
val BroadcastReceiverClass get() = classOf<BroadcastReceiver>()
/**
* 获得 [Bundle] 类型
* @return [Class]<[Bundle]>
*/
val BundleClass get() = classOf<Bundle>()
/**
* 获得 [BaseBundle] 类型
* @return [Class]<[BaseBundle]>
*/
val BaseBundleClass get() = classOf<BaseBundle>()
/**
* 获得 [Resources] 类型
* @return [Class]<[Resources]>
*/
val ResourcesClass get() = classOf<Resources>()
/**
* 获得 [Configuration] 类型
* @return [Class]<[Configuration]>
*/
val ConfigurationClass get() = classOf<Configuration>()
/**
* 获得 [ConfigurationInfo] 类型
* @return [Class]<[ConfigurationInfo]>
*/
val ConfigurationInfoClass get() = classOf<ConfigurationInfo>()
/**
* 获得 [ContentResolver] 类型
* @return [Class]<[ContentResolver]>
*/
val ContentResolverClass get() = classOf<ContentResolver>()
/**
* 获得 [ContentProvider] 类型
* @return [Class]<[ContentProvider]>
*/
val ContentProviderClass get() = classOf<ContentProvider>()
/**
* 获得 [Settings] 类型
* @return [Class]<[Settings]>
*/
val SettingsClass get() = classOf<Settings>()
/**
* 获得 [Settings.System] 类型
* @return [Class]<[Settings.System]>
*/
val Settings_SystemClass get() = classOf<Settings.System>()
/**
* 获得 [Settings.Secure] 类型
* @return [Class]<[Settings.Secure]>
*/
val Settings_SecureClass get() = classOf<Settings.Secure>()
/**
* 获得 [TypedArray] 类型
* @return [Class]<[TypedArray]>
*/
val TypedArrayClass get() = classOf<TypedArray>()
/**
* 获得 [TypedValue] 类型
* @return [Class]<[TypedValue]>
*/
val TypedValueClass get() = classOf<TypedValue>()
/**
* 获得 [SparseArray] 类型
* @return [Class]<[SparseArray]>
*/
val SparseArrayClass get() = classOf<SparseArray<*>>()
/**
* 获得 [SparseIntArray] 类型
* @return [Class]<[SparseIntArray]>
*/
val SparseIntArrayClass get() = classOf<SparseIntArray>()
/**
* 获得 [SparseBooleanArray] 类型
* @return [Class]<[SparseBooleanArray]>
*/
val SparseBooleanArrayClass get() = classOf<SparseBooleanArray>()
/**
* 获得 [SparseLongArray] 类型
* @return [Class]<[SparseLongArray]>
*/
val SparseLongArrayClass get() = classOf<SparseLongArray>()
/**
* 获得 [LongSparseArray] 类型
* @return [Class]<[LongSparseArray]>
*/
val LongSparseArrayClass get() = classOf<LongSparseArray<*>>()
/**
* 获得 [ArrayMap] 类型
* @return [Class]<[ArrayMap]>
*/
val ArrayMapClass get() = classOf<ArrayMap<*, *>>()
/**
* 获得 [ArraySet] 类型
*
* - ❗在 Android M (23) 及以上系统加入
* @return [Class]<[ArraySet]> or null
*/
val ArraySetClass get() = if (Build.VERSION.SDK_INT >= 23) classOf<ArraySet<*>>() else null
/**
* 获得 [Handler] 类型
* @return [Class]<[Handler]>
*/
val HandlerClass get() = classOf<Handler>()
/**
* 获得 [Handler.Callback] 类型
* @return [Class]<[Handler.Callback]>
*/
val Handler_CallbackClass get() = classOf<Handler.Callback>()
/**
* 获得 [Message] 类型
* @return [Class]<[Message]>
*/
val MessageClass get() = classOf<Message>()
/**
* 获得 [MessageQueue] 类型
* @return [Class]<[MessageQueue]>
*/
val MessageQueueClass get() = classOf<MessageQueue>()
/**
* 获得 [Messenger] 类型
* @return [Class]<[Messenger]>
*/
val MessengerClass get() = classOf<Messenger>()
/**
* 获得 [AsyncTask] 类型
* @return [Class]<[AsyncTask]>
*/
val AsyncTaskClass get() = classOf<AsyncTask<*, *, *>>()
/**
* 获得 [SimpleDateFormat] 类型
*
* - ❗在 Android N (24) 及以上系统加入
* @return [Class]<[SimpleDateFormat]> or null
*/
val SimpleDateFormatClass_Android get() = if (Build.VERSION.SDK_INT >= 24) classOf<SimpleDateFormat>() else null
/**
* 获得 [Base64] 类型
* @return [Class]<[Base64]>
*/
val Base64Class_Android get() = classOf<Base64>()
/**
* 获得 [Window] 类型
* @return [Class]<[Window]>
*/
val WindowClass get() = classOf<Window>()
/**
* 获得 [WindowMetrics] 类型
*
* - ❗在 Android R (30) 及以上系统加入
* @return [Class]<[WindowMetrics]> or null
*/
val WindowMetricsClass get() = if (Build.VERSION.SDK_INT >= 30) classOf<WindowMetrics>() else null
/**
* 获得 [WindowInsets] 类型
* @return [Class]<[WindowInsets]>
*/
val WindowInsetsClass get() = classOf<WindowInsets>()
/**
* 获得 [WindowInsets.Type] 类型
*
* - ❗在 Android R (30) 及以上系统加入
* @return [Class]<[WindowInsets.Type]> or null
*/
val WindowInsets_TypeClass get() = if (Build.VERSION.SDK_INT >= 30) classOf<WindowInsets.Type>() else null
/**
* 获得 [WindowManager] 类型
* @return [Class]<[WindowManager]>
*/
val WindowManagerClass get() = classOf<WindowManager>()
/**
* 获得 [WindowManager.LayoutParams] 类型
* @return [Class]<[WindowManager.LayoutParams]>
*/
val WindowManager_LayoutParamsClass get() = classOf<WindowManager.LayoutParams>()
/**
* 获得 [ViewManager] 类型
* @return [Class]<[ViewManager]>
*/
val ViewManagerClass get() = classOf<ViewManager>()
/**
* 获得 [Parcel] 类型
* @return [Class]<[Parcel]>
*/
val ParcelClass get() = classOf<Parcel>()
/**
* 获得 [Parcelable] 类型
* @return [Class]<[Parcelable]>
*/
val ParcelableClass get() = classOf<Parcelable>()
/**
* 获得 [Parcelable.Creator] 类型
* @return [Class]<[Parcelable.Creator]>
*/
val Parcelable_CreatorClass get() = classOf<Parcelable.Creator<*>>()
/**
* 获得 [Dialog] 类型
* @return [Class]<[Dialog]>
*/
val DialogClass get() = classOf<Dialog>()
/**
* 获得 [AlertDialog] 类型
* @return [Class]<[AlertDialog]>
*/
val AlertDialogClass get() = classOf<AlertDialog>()
/**
* 获得 [DisplayMetrics] 类型
* @return [Class]<[DisplayMetrics]>
*/
val DisplayMetricsClass get() = classOf<DisplayMetrics>()
/**
* 获得 [Display] 类型
* @return [Class]<[Display]>
*/
val DisplayClass get() = classOf<Display>()
/**
* 获得 [Toast] 类型
* @return [Class]<[Toast]>
*/
val ToastClass get() = classOf<Toast>()
/**
* 获得 [Intent] 类型
* @return [Class]<[Intent]>
*/
val IntentClass get() = classOf<Intent>()
/**
* 获得 [ComponentInfo] 类型
* @return [Class]<[ComponentInfo]>
*/
val ComponentInfoClass get() = classOf<ComponentInfo>()
/**
* 获得 [ComponentName] 类型
* @return [Class]<[ComponentName]>
*/
val ComponentNameClass get() = classOf<ComponentName>()
/**
* 获得 [PendingIntent] 类型
* @return [Class]<[PendingIntent]>
*/
val PendingIntentClass get() = classOf<PendingIntent>()
/**
* 获得 [ColorStateList] 类型
* @return [Class]<[ColorStateList]>
*/
val ColorStateListClass get() = classOf<ColorStateList>()
/**
* 获得 [ContentValues] 类型
* @return [Class]<[ContentValues]>
*/
val ContentValuesClass get() = classOf<ContentValues>()
/**
* 获得 [SharedPreferences] 类型
* @return [Class]<[SharedPreferences]>
*/
val SharedPreferencesClass get() = classOf<SharedPreferences>()
/**
* 获得 [MediaPlayer] 类型
* @return [Class]<[MediaPlayer]>
*/
val MediaPlayerClass get() = classOf<MediaPlayer>()
/**
* 获得 [ProgressDialog] 类型
* @return [Class]<[ProgressDialog]>
*/
val ProgressDialogClass get() = classOf<ProgressDialog>()
/**
* 获得 [Log] 类型
* @return [Class]<[Log]>
*/
val LogClass get() = classOf<Log>()
/**
* 获得 [Build] 类型
* @return [Class]<[Build]>
*/
val BuildClass get() = classOf<Build>()
/**
* 获得 [Xml] 类型
* @return [Class]<[Xml]>
*/
val XmlClass get() = classOf<Xml>()
/**
* 获得 [ContrastColorUtil] 类型
* @return [Class]
*/
val ContrastColorUtilClass get() = "com.android.internal.util.ContrastColorUtil".toClass()
/**
* 获得 [StatusBarNotification] 类型
* @return [Class]<[StatusBarNotification]>
*/
val StatusBarNotificationClass get() = classOf<StatusBarNotification>()
/**
* 获得 [Notification] 类型
* @return [Class]<[Notification]>
*/
val NotificationClass get() = classOf<Notification>()
/**
* 获得 [Notification.Builder] 类型
* @return [Class]<[Notification.Builder]>
*/
val Notification_BuilderClass get() = classOf<Notification.Builder>()
/**
* 获得 [Notification.Action] 类型
* @return [Class]<[Notification.Action]>
*/
val Notification_ActionClass get() = classOf<Notification.Action>()
/**
* 获得 [DialogInterface] 类型
* @return [Class]<[DialogInterface]>
*/
val DialogInterfaceClass get() = classOf<DialogInterface>()
/**
* 获得 [DialogInterface.OnClickListener] 类型
* @return [Class]<[DialogInterface.OnClickListener]>
*/
val DialogInterface_OnClickListenerClass get() = classOf<DialogInterface.OnClickListener>()
/**
* 获得 [DialogInterface.OnCancelListener] 类型
* @return [Class]<[DialogInterface.OnCancelListener]>
*/
val DialogInterface_OnCancelListenerClass get() = classOf<DialogInterface.OnCancelListener>()
/**
* 获得 [DialogInterface.OnDismissListener] 类型
* @return [Class]<[DialogInterface.OnDismissListener]>
*/
val DialogInterface_OnDismissListenerClass get() = classOf<DialogInterface.OnDismissListener>()
/**
* 获得 [Environment] 类型
* @return [Class]<[Environment]>
*/
val EnvironmentClass get() = classOf<Environment>()
/**
* 获得 [Process] 类型
* @return [Class]<[Process]>
*/
val ProcessClass get() = classOf<Process>()
/**
* 获得 [Vibrator] 类型
* @return [Class]<[Vibrator]>
*/
val VibratorClass get() = classOf<Vibrator>()
/**
* 获得 [VibrationEffect] 类型
*
* - ❗在 Android O (26) 及以上系统加入
* @return [Class]<[VibrationEffect]> or null
*/
val VibrationEffectClass get() = if (Build.VERSION.SDK_INT >= 26) classOf<VibrationEffect>() else null
/**
* 获得 [VibrationAttributes] 类型
*
* - ❗在 Android R (30) 及以上系统加入
* @return [Class]<[VibrationAttributes]> or null
*/
val VibrationAttributesClass get() = if (Build.VERSION.SDK_INT >= 30) classOf<VibrationAttributes>() else null
/**
* 获得 [SystemClock] 类型
* @return [Class]<[SystemClock]>
*/
val SystemClockClass get() = classOf<SystemClock>()
/**
* 获得 [PowerManager] 类型
* @return [Class]<[PowerManager]>
*/
val PowerManagerClass get() = classOf<PowerManager>()
/**
* 获得 [PowerManager.WakeLock] 类型
* @return [Class]<[PowerManager.WakeLock]>
*/
val PowerManager_WakeLockClass get() = classOf<PowerManager.WakeLock>()
/**
* 获得 [UserHandle] 类型
* @return [Class]<[UserHandle]>
*/
val UserHandleClass get() = classOf<UserHandle>()
/**
* 获得 [ShortcutInfo] 类型
*
* - ❗在 Android N_MR1 (25) 及以上系统加入
* @return [Class]<[ShortcutInfo]> or null
*/
val ShortcutInfoClass get() = if (Build.VERSION.SDK_INT >= 25) classOf<ShortcutInfo>() else null
/**
* 获得 [ShortcutManager] 类型
*
* - ❗在 Android R (30) 及以上系统加入
* @return [Class]<[ShortcutManager]> or null
*/
val ShortcutManagerClass get() = if (Build.VERSION.SDK_INT >= 30) classOf<ShortcutManager>() else null
/**
* 获得 [ShortcutQuery] 类型
*
* - ❗在 Android N_MR1 (25) 及以上系统加入
* @return [Class]<[ShortcutQuery]> or null
*/
val ShortcutQueryClass get() = if (Build.VERSION.SDK_INT >= 25) classOf<ShortcutQuery>() else null
/**
* 获得 [KeyboardShortcutInfo] 类型
* @return [Class]<[KeyboardShortcutInfo]>
*/
val KeyboardShortcutInfoClass get() = classOf<KeyboardShortcutInfo>()
/**
* 获得 [KeyboardShortcutGroup] 类型
* @return [Class]<[KeyboardShortcutGroup]>
*/
val KeyboardShortcutGroupClass get() = classOf<KeyboardShortcutGroup>()
/**
* 获得 [ShortcutIconResource] 类型
* @return [Class]<[ShortcutIconResource]>
*/
val ShortcutIconResourceClass get() = classOf<ShortcutIconResource>()
/**
* 获得 [AssetManager] 类型
* @return [Class]<[AssetManager]>
*/
val AssetManagerClass get() = classOf<AssetManager>()
/**
* 获得 [AppWidgetManager] 类型
* @return [Class]<[AppWidgetManager]>
*/
val AppWidgetManagerClass get() = classOf<AppWidgetManager>()
/**
* 获得 [AppWidgetProvider] 类型
* @return [Class]<[AppWidgetProvider]>
*/
val AppWidgetProviderClass get() = classOf<AppWidgetProvider>()
/**
* 获得 [AppWidgetProviderInfo] 类型
* @return [Class]<[AppWidgetProviderInfo]>
*/
val AppWidgetProviderInfoClass get() = classOf<AppWidgetProviderInfo>()
/**
* 获得 [AppWidgetHost] 类型
* @return [Class]<[AppWidgetHost]>
*/
val AppWidgetHostClass get() = classOf<AppWidgetHost>()
/**
* 获得 [ActivityInfo] 类型
* @return [Class]<[ActivityInfo]>
*/
val ActivityInfoClass get() = classOf<ActivityInfo>()
/**
* 获得 [ResolveInfo] 类型
* @return [Class]<[ResolveInfo]>
*/
val ResolveInfoClass get() = classOf<ResolveInfo>()
/**
* 获得 [Property] 类型
* @return [Class]<[Property]>
*/
val PropertyClass get() = classOf<Property<*, *>>()
/**
* 获得 [IntProperty] 类型
* @return [Class]<[IntProperty]>
*/
val IntPropertyClass get() = classOf<IntProperty<*>>()
/**
* 获得 [FloatProperty] 类型
* @return [Class]<[FloatProperty]>
*/
val FloatPropertyClass get() = classOf<FloatProperty<*>>()
/**
* 获得 [SQLiteDatabase] 类型
* @return [Class]<[SQLiteDatabase]>
*/
val SQLiteDatabaseClass get() = classOf<SQLiteDatabase>()
/**
* 获得 [StrictMode] 类型
* @return [Class]<[StrictMode]>
*/
val StrictModeClass get() = classOf<StrictMode>()
/**
* 获得 [AccessibilityManager] 类型
* @return [Class]<[AccessibilityManager]>
*/
val AccessibilityManagerClass get() = classOf<AccessibilityManager>()
/**
* 获得 [AccessibilityEvent] 类型
* @return [Class]<[AccessibilityEvent]>
*/
val AccessibilityEventClass get() = classOf<AccessibilityEvent>()
/**
* 获得 [AccessibilityNodeInfo] 类型
* @return [Class]<[AccessibilityNodeInfo]>
*/
val AccessibilityNodeInfoClass get() = classOf<AccessibilityNodeInfo>()
/**
* 获得 [IInterface] 类型
* @return [Class]<[IInterface]>
*/
val IInterfaceClass get() = classOf<IInterface>()

View File

@@ -0,0 +1,243 @@
/*
* 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/13.
*/
@file:Suppress("unused", "KDocUnresolvedReference")
package com.highcapable.yukihookapi.hook.type.android
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Matrix
import android.graphics.NinePatch
import android.graphics.Outline
import android.graphics.Paint
import android.graphics.Point
import android.graphics.PointF
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Typeface
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.Icon
import android.os.Build
import android.text.Editable
import android.text.GetChars
import android.text.Spannable
import android.text.SpannableStringBuilder
import android.text.TextPaint
import android.text.TextUtils
import android.text.TextWatcher
import android.util.Size
import android.util.SizeF
import com.highcapable.yukihookapi.hook.factory.classOf
/**
* 获得 [Typeface] 类型
* @return [Class]<[Typeface]>
*/
val TypefaceClass get() = classOf<Typeface>()
/**
* 获得 [Bitmap] 类型
* @return [Class]<[Bitmap]>
*/
val BitmapClass get() = classOf<Bitmap>()
/**
* 获得 [Icon] 类型
*
* - ❗在 Android M (23) 及以上系统加入
* @return [Class]<[Icon]> or null
*/
val IconClass get() = if (Build.VERSION.SDK_INT >= 23) classOf<Icon>() else null
/**
* 获得 [Outline] 类型
* @return [Class]<[Outline]>
*/
val OutlineClass get() = classOf<Outline>()
/**
* 获得 [Drawable] 类型
* @return [Class]<[Drawable]>
*/
val DrawableClass get() = classOf<Drawable>()
/**
* 获得 [GradientDrawable] 类型
* @return [Class]<[GradientDrawable]>
*/
val GradientDrawableClass get() = classOf<GradientDrawable>()
/**
* 获得 [ColorDrawable] 类型
* @return [Class]<[ColorDrawable]>
*/
val ColorDrawableClass get() = classOf<ColorDrawable>()
/**
* 获得 [BitmapDrawable] 类型
* @return [Class]<[BitmapDrawable]>
*/
val BitmapDrawableClass get() = classOf<BitmapDrawable>()
/**
* 获得 [Size] 类型
* @return [Class]<[Size]>
*/
val SizeClass get() = classOf<Size>()
/**
* 获得 [SizeF] 类型
* @return [Class]<[SizeF]>
*/
val SizeFClass get() = classOf<SizeF>()
/**
* 获得 [Rect] 类型
* @return [Class]<[Rect]>
*/
val RectClass get() = classOf<Rect>()
/**
* 获得 [RectF] 类型
* @return [Class]<[RectF]>
*/
val RectFClass get() = classOf<RectF>()
/**
* 获得 [NinePatch] 类型
* @return [Class]<[NinePatch]>
*/
val NinePatchClass get() = classOf<NinePatch>()
/**
* 获得 [Paint] 类型
* @return [Class]<[Paint]>
*/
val PaintClass get() = classOf<Paint>()
/**
* 获得 [TextPaint] 类型
* @return [Class]<[TextPaint]>
*/
val TextPaintClass get() = classOf<TextPaint>()
/**
* 获得 [Canvas] 类型
* @return [Class]<[Canvas]>
*/
val CanvasClass get() = classOf<Canvas>()
/**
* 获得 [Point] 类型
* @return [Class]<[Point]>
*/
val PointClass get() = classOf<Point>()
/**
* 获得 [PointF] 类型
* @return [Class]<[PointF]>
*/
val PointFClass get() = classOf<PointF>()
/**
* 获得 [Matrix] 类型
* @return [Class]<[Matrix]>
*/
val MatrixClass get() = classOf<Matrix>()
/**
* 获得 [ColorMatrix] 类型
* @return [Class]<[ColorMatrix]>
*/
val ColorMatrixClass get() = classOf<ColorMatrix>()
/**
* 获得 [ColorMatrixColorFilter] 类型
* @return [Class]<[ColorMatrixColorFilter]>
*/
val ColorMatrixColorFilterClass get() = classOf<ColorMatrixColorFilter>()
/**
* 获得 [TextUtils] 类型
* @return [Class]<[TextUtils]>
*/
val TextUtilsClass get() = classOf<TextUtils>()
/**
* 获得 [Editable] 类型
* @return [Class]<[Editable]>
*/
val EditableClass get() = classOf<Editable>()
/**
* 获得 [TextWatcher] 类型
* @return [Class]<[TextWatcher]>
*/
val TextWatcherClass get() = classOf<TextWatcher>()
/**
* 获得 [Editable.Factory] 类型
* @return [Class]<[Editable.Factory]>
*/
val Editable_FactoryClass get() = classOf<Editable.Factory>()
/**
* 获得 [GetChars] 类型
* @return [Class]<[GetChars]>
*/
val GetCharsClass get() = classOf<GetChars>()
/**
* 获得 [Spannable] 类型
* @return [Class]<[Spannable]>
*/
val SpannableClass get() = classOf<Spannable>()
/**
* 获得 [SpannableStringBuilder] 类型
* @return [Class]<[SpannableStringBuilder]>
*/
val SpannableStringBuilderClass get() = classOf<SpannableStringBuilder>()
/**
* 获得 [BitmapFactory] 类型
* @return [Class]<[BitmapFactory]>
*/
val BitmapFactoryClass get() = classOf<BitmapFactory>()
/**
* 获得 [BitmapFactory.Options] 类型
* @return [Class]<[BitmapFactory.Options]>
*/
val BitmapFactory_OptionsClass get() = classOf<BitmapFactory.Options>()

View File

@@ -0,0 +1,449 @@
/*
* 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/2.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.type.android
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.animation.PropertyValuesHolder
import android.animation.ValueAnimator
import android.appwidget.AppWidgetHostView
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.Surface
import android.view.SurfaceView
import android.view.TextureView
import android.view.View
import android.view.ViewGroup
import android.view.ViewParent
import android.view.ViewPropertyAnimator
import android.view.ViewStructure
import android.view.ViewStub
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.animation.TranslateAnimation
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.BaseAdapter
import android.widget.Button
import android.widget.CheckBox
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ListAdapter
import android.widget.ListView
import android.widget.ProgressBar
import android.widget.RelativeLayout
import android.widget.RemoteViews
import android.widget.RemoteViews.RemoteView
import android.widget.TextClock
import android.widget.TextView
import android.widget.VideoView
import android.widget.ViewAnimator
import com.highcapable.yukihookapi.hook.factory.classOf
/**
* 获得 [View] 类型
* @return [Class]<[View]>
*/
val ViewClass get() = classOf<View>()
/**
* 获得 [Surface] 类型
* @return [Class]<[Surface]>
*/
val SurfaceClass get() = classOf<Surface>()
/**
* 获得 [SurfaceView] 类型
* @return [Class]<[SurfaceView]>
*/
val SurfaceViewClass get() = classOf<SurfaceView>()
/**
* 获得 [TextureView] 类型
* @return [Class]<[TextureView]>
*/
val TextureViewClass get() = classOf<TextureView>()
/**
* 获得 [WebView] 类型
* @return [Class]<[WebView]>
*/
val WebViewClass get() = classOf<WebView>()
/**
* 获得 [WebViewClient] 类型
* @return [Class]<[WebViewClient]>
*/
val WebViewClientClass get() = classOf<WebViewClient>()
/**
* 获得 [ViewStructure] 类型
* @return [Class]<[ViewStructure]>
*/
val ViewStructureClass get() = classOf<ViewStructure>()
/**
* 获得 [ViewGroup] 类型
* @return [Class]<[ViewGroup]>
*/
val ViewGroupClass get() = classOf<ViewGroup>()
/**
* 获得 [ViewParent] 类型
* @return [Class]<[ViewParent]>
*/
val ViewParentClass get() = classOf<ViewParent>()
/**
* 获得 [AppWidgetHostView] 类型
* @return [Class]<[AppWidgetHostView]>
*/
val AppWidgetHostViewClass get() = classOf<AppWidgetHostView>()
/**
* 获得 [RemoteViews] 类型
* @return [Class]<[RemoteViews]>
*/
val RemoteViewsClass get() = classOf<RemoteViews>()
/**
* 获得 [RemoteView] 类型
* @return [Class]<[RemoteView]>
*/
val RemoteViewClass get() = classOf<RemoteView>()
/**
* 获得 [TextView] 类型
* @return [Class]<[TextView]>
*/
val TextViewClass get() = classOf<TextView>()
/**
* 获得 [ImageView] 类型
* @return [Class]<[ImageView]>
*/
val ImageViewClass get() = classOf<ImageView>()
/**
* 获得 [ImageButton] 类型
* @return [Class]<[ImageButton]>
*/
val ImageButtonClass get() = classOf<ImageButton>()
/**
* 获得 [EditText] 类型
* @return [Class]<[EditText]>
*/
val EditTextClass get() = classOf<EditText>()
/**
* 获得 [Button] 类型
* @return [Class]<[Button]>
*/
val ButtonClass get() = classOf<Button>()
/**
* 获得 [CheckBox] 类型
* @return [Class]<[CheckBox]>
*/
val CheckBoxClass get() = classOf<CheckBox>()
/**
* 获得 [CompoundButton] 类型
* @return [Class]<[CompoundButton]>
*/
val CompoundButtonClass get() = classOf<CompoundButton>()
/**
* 获得 [VideoView] 类型
* @return [Class]<[VideoView]>
*/
val VideoViewClass get() = classOf<VideoView>()
/**
* 获得 [ListView] 类型
* @return [Class]<[ListView]>
*/
val ListViewClass get() = classOf<ListView>()
/**
* 获得 [LayoutInflater] 类型
* @return [Class]<[LayoutInflater]>
*/
val LayoutInflaterClass get() = classOf<LayoutInflater>()
/**
* 获得 [LayoutInflater.Filter] 类型
* @return [Class]<[LayoutInflater.Filter]>
*/
val LayoutInflater_FilterClass get() = classOf<LayoutInflater.Filter>()
/**
* 获得 [LayoutInflater.Factory] 类型
* @return [Class]<[LayoutInflater.Factory]>
*/
val LayoutInflater_FactoryClass get() = classOf<LayoutInflater.Factory>()
/**
* 获得 [LayoutInflater.Factory2] 类型
* @return [Class]<[LayoutInflater.Factory2]>
*/
val LayoutInflater_Factory2Class get() = classOf<LayoutInflater.Factory2>()
/**
* 获得 [ListAdapter] 类型
* @return [Class]<[ListAdapter]>
*/
val ListAdapterClass get() = classOf<ListAdapter>()
/**
* 获得 [ArrayAdapter] 类型
* @return [Class]<[ArrayAdapter]>
*/
val ArrayAdapterClass get() = classOf<ArrayAdapter<*>>()
/**
* 获得 [BaseAdapter] 类型
* @return [Class]<[BaseAdapter]>
*/
val BaseAdapterClass get() = classOf<BaseAdapter>()
/**
* 获得 [RelativeLayout] 类型
* @return [Class]<[RelativeLayout]>
*/
val RelativeLayoutClass get() = classOf<RelativeLayout>()
/**
* 获得 [FrameLayout] 类型
* @return [Class]<[FrameLayout]>
*/
val FrameLayoutClass get() = classOf<FrameLayout>()
/**
* 获得 [LinearLayout] 类型
* @return [Class]<[LinearLayout]>
*/
val LinearLayoutClass get() = classOf<LinearLayout>()
/**
* 获得 [ViewGroup.LayoutParams] 类型
* @return [Class]<[ViewGroup.LayoutParams]>
*/
val ViewGroup_LayoutParamsClass get() = classOf<ViewGroup.LayoutParams>()
/**
* 获得 [RelativeLayout.LayoutParams] 类型
* @return [Class]<[RelativeLayout.LayoutParams]>
*/
val RelativeLayout_LayoutParamsClass get() = classOf<RelativeLayout.LayoutParams>()
/**
* 获得 [LinearLayout.LayoutParams] 类型
* @return [Class]<[LinearLayout.LayoutParams]>
*/
val LinearLayout_LayoutParamsClass get() = classOf<LinearLayout.LayoutParams>()
/**
* 获得 [FrameLayout.LayoutParams] 类型
* @return [Class]<[FrameLayout.LayoutParams]>
*/
val FrameLayout_LayoutParamsClass get() = classOf<FrameLayout.LayoutParams>()
/**
* 获得 [TextClock] 类型
* @return [Class]<[TextClock]>
*/
val TextClockClass get() = classOf<TextClock>()
/**
* 获得 [MotionEvent] 类型
* @return [Class]<[MotionEvent]>
*/
val MotionEventClass get() = classOf<MotionEvent>()
/**
* 获得 [View.OnClickListener] 类型
* @return [Class]<[View.OnClickListener]>
*/
val View_OnClickListenerClass get() = classOf<View.OnClickListener>()
/**
* 获得 [View.OnLongClickListener] 类型
* @return [Class]<[View.OnLongClickListener]>
*/
val View_OnLongClickListenerClass get() = classOf<View.OnLongClickListener>()
/**
* 获得 [View.OnTouchListener] 类型
* @return [Class]<[View.OnTouchListener]>
*/
val View_OnTouchListenerClass get() = classOf<View.OnTouchListener>()
/**
* 获得 [AutoCompleteTextView] 类型
* @return [Class]<[AutoCompleteTextView]>
*/
val AutoCompleteTextViewClass get() = classOf<AutoCompleteTextView>()
/**
* 获得 [ViewStub] 类型
* @return [Class]<[ViewStub]>
*/
val ViewStubClass get() = classOf<ViewStub>()
/**
* 获得 [ViewStub.OnInflateListener] 类型
* @return [Class]<[ViewStub.OnInflateListener]>
*/
val ViewStub_OnInflateListenerClass get() = classOf<ViewStub.OnInflateListener>()
/**
* 获得 [GestureDetector] 类型
* @return [Class]<[GestureDetector]>
*/
val GestureDetectorClass get() = classOf<GestureDetector>()
/**
* 获得 [GestureDetector.SimpleOnGestureListener] 类型
* @return [Class]<[GestureDetector.SimpleOnGestureListener]>
*/
val GestureDetector_SimpleOnGestureListenerClass get() = classOf<GestureDetector.SimpleOnGestureListener>()
/**
* 获得 [ProgressBar] 类型
* @return [Class]<[ProgressBar]>
*/
val ProgressBarClass get() = classOf<ProgressBar>()
/**
* 获得 [AttributeSet] 类型
* @return [Class]<[AttributeSet]>
*/
val AttributeSetClass get() = classOf<AttributeSet>()
/**
* 获得 [Animation] 类型
* @return [Class]<[Animation]>
*/
val AnimationClass get() = classOf<Animation>()
/**
* 获得 [Animation.AnimationListener] 类型
* @return [Class]<[Animation.AnimationListener]>
*/
val Animation_AnimationListenerClass get() = classOf<Animation.AnimationListener>()
/**
* 获得 [TranslateAnimation] 类型
* @return [Class]<[TranslateAnimation]>
*/
val TranslateAnimationClass get() = classOf<TranslateAnimation>()
/**
* 获得 [AlphaAnimation] 类型
* @return [Class]<[AlphaAnimation]>
*/
val AlphaAnimationClass get() = classOf<AlphaAnimation>()
/**
* 获得 [Animator] 类型
* @return [Class]<[Animator]>
*/
val AnimatorClass get() = classOf<Animator>()
/**
* 获得 [Animator.AnimatorListener] 类型
* @return [Class]<[Animator.AnimatorListener]>
*/
val Animator_AnimatorListenerClass get() = classOf<Animator.AnimatorListener>()
/**
* 获得 [ObjectAnimator] 类型
* @return [Class]<[ObjectAnimator]>
*/
val ObjectAnimatorClass get() = classOf<ObjectAnimator>()
/**
* 获得 [ValueAnimator] 类型
* @return [Class]<[ValueAnimator]>
*/
val ValueAnimatorClass get() = classOf<ValueAnimator>()
/**
* 获得 [ValueAnimator.AnimatorUpdateListener] 类型
* @return [Class]<[ValueAnimator.AnimatorUpdateListener]>
*/
val ValueAnimator_AnimatorUpdateListenerClass get() = classOf<ValueAnimator.AnimatorUpdateListener>()
/**
* 获得 [ViewAnimator] 类型
* @return [Class]<[ViewAnimator]>
*/
val ViewAnimatorClass get() = classOf<ViewAnimator>()
/**
* 获得 [AnimatorSet] 类型
* @return [Class]<[AnimatorSet]>
*/
val AnimatorSetClass get() = classOf<AnimatorSet>()
/**
* 获得 [AnimatorSet.Builder] 类型
* @return [Class]<[AnimatorSet.Builder]>
*/
val AnimatorSet_BuilderClass get() = classOf<AnimatorSet.Builder>()
/**
* 获得 [PropertyValuesHolder] 类型
* @return [Class]<[PropertyValuesHolder]>
*/
val PropertyValuesHolderClass get() = classOf<PropertyValuesHolder>()
/**
* 获得 [ViewPropertyAnimator] 类型
* @return [Class]<[ViewPropertyAnimator]>
*/
val ViewPropertyAnimatorClass get() = classOf<ViewPropertyAnimator>()
/**
* 获得 [View.MeasureSpec] 类型
* @return [Class]<[View.MeasureSpec]>
*/
val View_MeasureSpecClass get() = classOf<View.MeasureSpec>()

View File

@@ -0,0 +1,56 @@
/*
* 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/4/3.
*/
package com.highcapable.yukihookapi.hook.type.defined
import com.highcapable.yukihookapi.hook.factory.classOf
/**
* 未定义类型实例
*
* 请使用 [UndefinedType] 来调用它
*/
internal class UndefinedClass private constructor()
/**
* 模糊类型实例
*
* 请使用 [VagueType] 来调用它
*/
class VagueClass private constructor()
/**
* 得到未定义类型
* @return [Class]<[UndefinedClass]>
*/
internal val UndefinedType get() = classOf<UndefinedClass>()
/**
* 得到模糊类型
* @return [Class]<[VagueClass]>
*/
val VagueType get() = classOf<VagueClass>()

View File

@@ -0,0 +1,867 @@
/*
* 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/2.
*/
@file:Suppress("unused", "KDocUnresolvedReference", "DEPRECATION", "FunctionName", "UNCHECKED_CAST")
package com.highcapable.yukihookapi.hook.type.java
import android.os.Build
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.toClass
import com.highcapable.yukihookapi.hook.factory.toClassOrNull
import dalvik.system.BaseDexClassLoader
import dalvik.system.DexClassLoader
import dalvik.system.InMemoryDexClassLoader
import dalvik.system.PathClassLoader
import org.json.JSONArray
import org.json.JSONObject
import java.io.* // ktlint-disable no-wildcard-imports
import java.lang.ref.Reference
import java.lang.ref.WeakReference
import java.lang.reflect.Constructor
import java.lang.reflect.Field
import java.lang.reflect.Member
import java.lang.reflect.Method
import java.net.HttpCookie
import java.net.HttpURLConnection
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import java.util.function.Supplier
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream
import java.lang.reflect.Array as JavaArray
import java.util.function.Function as JavaFunction
/**
* 获得任意类型的数组
*
* 它在 Java 中表示为:([type])[]
* @param type 类型
* @return [Class]<[JavaArray]>
*/
fun ArrayClass(type: Class<*>) = JavaArray.newInstance(type, 0).javaClass as Class<JavaArray>
/**
* 获得 [Any] 类型
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [AnyClass]
* @return [Class]<[Any]>
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("AnyClass"))
val AnyType get() = AnyClass
/**
* 获得 [Boolean] 类型
*
* 这是 Java 原始类型 (Primitive Type) - 它在字节码中的关键字为 "boolean"
* @return [Class]
*/
val BooleanType get() = Boolean::class.javaPrimitiveType ?: "boolean".toClass()
/**
* 获得 [Char] 类型
*
* 这是 Java 原始类型 (Primitive Type) - 它在字节码中的关键字为 "char"
* @return [Class]
*/
val CharType get() = Char::class.javaPrimitiveType ?: "char".toClass()
/**
* 获得 [Byte] 类型
*
* 这是 Java 原始类型 (Primitive Type) - 它在字节码中的关键字为 "byte"
* @return [Class]
*/
val ByteType get() = Byte::class.javaPrimitiveType ?: "byte".toClass()
/**
* 获得 [Short] 类型
*
* 这是 Java 原始类型 (Primitive Type) - 它在字节码中的关键字为 "short"
* @return [Class]
*/
val ShortType get() = Short::class.javaPrimitiveType ?: "short".toClass()
/**
* 获得 [Int] 类型
*
* 这是 Java 原始类型 (Primitive Type) - 它在字节码中的关键字为 "int"
* @return [Class]
*/
val IntType get() = Int::class.javaPrimitiveType ?: "int".toClass()
/**
* 获得 [Float] 类型
*
* 这是 Java 原始类型 (Primitive Type) - 它在字节码中的关键字为 "float"
* @return [Class]
*/
val FloatType get() = Float::class.javaPrimitiveType ?: "float".toClass()
/**
* 获得 [Long] 类型
*
* 这是 Java 原始类型 (Primitive Type) - 它在字节码中的关键字为 "long"
* @return [Class]
*/
val LongType get() = Long::class.javaPrimitiveType ?: "long".toClass()
/**
* 获得 [Double] 类型
*
* 这是 Java 原始类型 (Primitive Type) - 它在字节码中的关键字为 "double"
* @return [Class]
*/
val DoubleType get() = Double::class.javaPrimitiveType ?: "double".toClass()
/**
* 获得 [Unit] 类型
*
* 这是 Java 原始类型 (Primitive Type) - 它在字节码中的关键字为 "void"
* @return [Class]
*/
val UnitType get() = Void.TYPE ?: "void".toClass()
/**
* 获得 [Any] 类型
*
* 它等价于 Java 中的 [java.lang.Object]
* @return [Class]<[Any]>
*/
val AnyClass get() = classOf<Any>()
/**
* 获得 [Boolean] 类型
*
* 它等价于 Java 中的 [java.lang.Boolean]
* @return [Class]<[Boolean]>
*/
val BooleanClass get() = classOf<Boolean>()
/**
* 获得 [Char] 类型
*
* 它等价于 Java 中的 [java.lang.Character]
* @return [Class]<[Char]>
*/
val CharClass get() = classOf<Char>()
/**
* 获得 [Byte] 类型
*
* 它等价于 Java 中的 [java.lang.Byte]
* @return [Class]<[Byte]>
*/
val ByteClass get() = classOf<Byte>()
/**
* 获得 [Short] 类型
*
* 它等价于 Java 中的 [java.lang.Short]
* @return [Class]<[Short]>
*/
val ShortClass get() = classOf<Short>()
/**
* 获得 [Int] 类型
*
* 它等价于 Java 中的 [java.lang.Integer]
* @return [Class]<[Int]>
*/
val IntClass get() = classOf<Int>()
/**
* 获得 [Float] 类型
*
* 它等价于 Java 中的 [java.lang.Float]
* @return [Class]<[Float]>
*/
val FloatClass get() = classOf<Float>()
/**
* 获得 [Long] 类型
*
* 它等价于 Java 中的 [java.lang.Long]
* @return [Class]<[Long]>
*/
val LongClass get() = classOf<Long>()
/**
* 获得 [Double] 类型
*
* 它等价于 Java 中的 [java.lang.Double]
* @return [Class]<[Double]>
*/
val DoubleClass get() = classOf<Double>()
/**
* 获得 [Number] 类型
*
* 它等价于 Java 中的 [java.lang.Number]
* @return [Class]<[Number]>
*/
val NumberClass get() = classOf<Number>()
/**
* 获得 [Unit] 类型
*
* 它等价于 Java 中的 [java.lang.Void]
* @return [Class]<[Void]>
*/
val UnitClass get() = classOf<Void>()
/**
* 获得 [String] 类型
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [StringClass]
* @return [Class]<[String]>
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("StringClass"))
val StringType get() = StringClass
/**
* 获得 [CharSequence] 类型
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [CharSequenceClass]
* @return [Class]<[CharSequence]>
*/
@Deprecated(message = "请使用新的命名方法", ReplaceWith("CharSequenceClass"))
val CharSequenceType get() = CharSequenceClass
/**
* 获得 [String] 类型
*
* 它等价于 Java 中的 [java.lang.String]
* @return [Class]<[String]>
*/
val StringClass get() = classOf<String>()
/**
* 获得 [CharSequence] 类型
*
* 它等价于 Java 中的 [java.lang.CharSequence]
* @return [Class]<[CharSequence]>
*/
val CharSequenceClass get() = classOf<CharSequence>()
/**
* 获得 [Serializable] 类型
* @return [Class]<[Serializable]>
*/
val SerializableClass get() = classOf<Serializable>()
/**
* 获得 [Array] 类型
*
* 它等价于 Java 中的 [java.lang.reflect.Array]
* @return [Class]<[JavaArray]>
*/
val ArrayClass get() = classOf<JavaArray>()
/**
* 获得 [Boolean] - [Array] 类型
*
* 这是 Java 原始类型 (Primitive Type) 数组 - 它在字节码中的关键字为 "boolean[]"
* @return [Class]<[JavaArray]>
*/
val BooleanArrayType get() = ArrayClass(BooleanType)
/**
* 获得 [Char] - [Array] 类型
*
* 这是 Java 原始类型 (Primitive Type) 数组 - 它在字节码中的关键字为 "char[]"
* @return [Class]<[JavaArray]>
*/
val CharArrayType get() = ArrayClass(CharType)
/**
* 获得 [Byte] - [Array] 类型
*
* 这是 Java 原始类型 (Primitive Type) 数组 - 它在字节码中的关键字为 "byte[]"
* @return [Class]<[JavaArray]>
*/
val ByteArrayType get() = ArrayClass(ByteType)
/**
* 获得 [Short] - [Array] 类型
*
* 这是 Java 原始类型 (Primitive Type) 数组 - 它在字节码中的关键字为 "short[]"
* @return [Class]<[JavaArray]>
*/
val ShortArrayType get() = ArrayClass(ShortType)
/**
* 获得 [Short] - [Array] 类型
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [ShortArrayType]
* @return [Class]<[JavaArray]>
*/
@Deprecated(message = "请使用修复后的命名方法", ReplaceWith("ShortArrayType"))
val ShortArraytType get() = ShortArrayType
/**
* 获得 [Int] - [Array] 类型
*
* 这是 Java 原始类型 (Primitive Type) 数组 - 它在字节码中的关键字为 "int[]"
* @return [Class]<[JavaArray]>
*/
val IntArrayType get() = ArrayClass(IntType)
/**
* 获得 [Float] - [Array] 类型
*
* 这是 Java 原始类型 (Primitive Type) 数组 - 它在字节码中的关键字为 "float[]"
* @return [Class]<[JavaArray]>
*/
val FloatArrayType get() = ArrayClass(FloatType)
/**
* 获得 [Long] - [Array] 类型
*
* 这是 Java 原始类型 (Primitive Type) 数组 - 它在字节码中的关键字为 "long[]"
* @return [Class]<[JavaArray]>
*/
val LongArrayType get() = ArrayClass(LongType)
/**
* 获得 [Double] - [Array] 类型
*
* 这是 Java 原始类型 (Primitive Type) 数组 - 它在字节码中的关键字为 "double[]"
* @return [Class]<[JavaArray]>
*/
val DoubleArrayType get() = ArrayClass(DoubleType)
/**
* 获得 [Any] - [Array] 类型
*
* 它在 Java 中表示为Object[]
* @return [Class]<[JavaArray]>
*/
val AnyArrayClass get() = ArrayClass(AnyClass)
/**
* 获得 [Boolean] - [Array] 类型
*
* 它在 Java 中表示为Boolean[]
* @return [Class]<[JavaArray]>
*/
val BooleanArrayClass get() = ArrayClass(BooleanClass)
/**
* 获得 [Char] - [Array] 类型
*
* 它在 Java 中表示为Character[]
* @return [Class]<[JavaArray]>
*/
val CharArrayClass get() = ArrayClass(CharClass)
/**
* 获得 [Byte] - [Array] 类型
*
* 它在 Java 中表示为Byte[]
* @return [Class]<[JavaArray]>
*/
val ByteArrayClass get() = ArrayClass(ByteClass)
/**
* 获得 [Short] - [Array] 类型
*
* 它在 Java 中表示为Short[]
* @return [Class]<[JavaArray]>
*/
val ShortArrayClass get() = ArrayClass(ShortClass)
/**
* 获得 [Int] - [Array] 类型
*
* 它在 Java 中表示为Integer[]
* @return [Class]<[JavaArray]>
*/
val IntArrayClass get() = ArrayClass(IntClass)
/**
* 获得 [Float] - [Array] 类型
*
* 它在 Java 中表示为Float[]
* @return [Class]<[JavaArray]>
*/
val FloatArrayClass get() = ArrayClass(FloatClass)
/**
* 获得 [Long] - [Array] 类型
*
* 它在 Java 中表示为Long[]
* @return [Class]<[JavaArray]>
*/
val LongArrayClass get() = ArrayClass(LongClass)
/**
* 获得 [Double] - [Array] 类型
*
* 它在 Java 中表示为Double[]
* @return [Class]<[JavaArray]>
*/
val DoubleArrayClass get() = ArrayClass(DoubleClass)
/**
* 获得 [Number] - [Array] 类型
*
* 它在 Java 中表示为Number[]
* @return [Class]<[JavaArray]>
*/
val NumberArrayClass get() = ArrayClass(NumberClass)
/**
* 获得 [String] - [Array] 类型
*
* 它在 Java 中表示为String[]
* @return [Class]<[JavaArray]>
*/
val StringArrayClass get() = ArrayClass(StringClass)
/**
* 获得 [CharSequence] - [Array] 类型
*
* 它在 Java 中表示为CharSequence[]
* @return [Class]<[JavaArray]>
*/
val CharSequenceArrayClass get() = ArrayClass(CharSequenceClass)
/**
* 获得 [Cloneable] 类型
* @return [Class]<[Cloneable]>
*/
val CloneableClass get() = classOf<Cloneable>()
/**
* 获得 [List] 类型
* @return [Class]<[List]>
*/
val ListClass get() = classOf<List<*>>()
/**
* 获得 [ArrayList] 类型
* @return [Class]<[ArrayList]>
*/
val ArrayListClass get() = classOf<ArrayList<*>>()
/**
* 获得 [HashMap] 类型
* @return [Class]<[HashMap]>
*/
val HashMapClass get() = classOf<HashMap<*, *>>()
/**
* 获得 [HashSet] 类型
* @return [Class]<[HashSet]>
*/
val HashSetClass get() = classOf<HashSet<*>>()
/**
* 获得 [WeakHashMap] 类型
* @return [Class]<[WeakHashMap]>
*/
val WeakHashMapClass get() = classOf<WeakHashMap<*, *>>()
/**
* 获得 [WeakReference] 类型
* @return [Class]<[WeakReference]>
*/
val WeakReferenceClass get() = classOf<WeakReference<*>>()
/**
* 获得 [Enum] 类型
* @return [Class]<[Enum]>
*/
val EnumClass get() = classOf<Enum<*>>()
/**
* 获得 [Map] 类型
* @return [Class]<[Map]>
*/
val MapClass get() = classOf<Map<*, *>>()
/**
* 获得 [Map.Entry] 类型
* @return [Class]<[Map.Entry]>
*/
val Map_EntryClass get() = classOf<Map.Entry<*, *>>()
/**
* 获得 [Reference] 类型
* @return [Class]<[Reference]>
*/
val ReferenceClass get() = classOf<Reference<*>>()
/**
* 获得 [Vector] 类型
* @return [Class]<[Vector]>
*/
val VectorClass get() = classOf<Vector<*>>()
/**
* 获得 [File] 类型
* @return [Class]<[File]>
*/
val FileClass get() = classOf<File>()
/**
* 获得 [InputStream] 类型
* @return [Class]<[InputStream]>
*/
val InputStreamClass get() = classOf<InputStream>()
/**
* 获得 [OutputStream] 类型
* @return [Class]<[OutputStream]>
*/
val OutputStreamClass get() = classOf<OutputStream>()
/**
* 获得 [BufferedReader] 类型
* @return [Class]<[BufferedReader]>
*/
val BufferedReaderClass get() = classOf<BufferedReader>()
/**
* 获得 [Date] 类型
* @return [Class]<[Date]>
*/
val DateClass get() = classOf<Date>()
/**
* 获得 [TimeZone] 类型
* @return [Class]<[TimeZone]>
*/
val TimeZoneClass get() = classOf<TimeZone>()
/**
* 获得 [SimpleDateFormat] 类型
* @return [Class]<[SimpleDateFormat]>
*/
val SimpleDateFormatClass_Java get() = classOf<SimpleDateFormat>()
/**
* 获得 [Timer] 类型
* @return [Class]<[Timer]>
*/
val TimerClass get() = classOf<Timer>()
/**
* 获得 [TimerTask] 类型
* @return [Class]<[TimerTask]>
*/
val TimerTaskClass get() = classOf<TimerTask>()
/**
* 获得 [Thread] 类型
* @return [Class]<[Thread]>
*/
val ThreadClass get() = classOf<Thread>()
/**
* 获得 [Base64] 类型
*
* - ❗在 Android O (26) 及以上系统加入
* @return [Class]<[Base64]> or null
*/
val Base64Class_Java get() = if (Build.VERSION.SDK_INT >= 26) classOf<Base64>() else null
/**
* 获得 [Observer] 类型
* @return [Class]<[Observer]>
*/
val ObserverClass get() = classOf<Observer>()
/**
* 获得 [Set] 类型
* @return [Class]<[Set]>
*/
val SetClass get() = classOf<Set<*>>()
/**
* 获得 [JSONObject] 类型
* @return [Class]<[JSONObject]>
*/
val JSONObjectClass get() = classOf<JSONObject>()
/**
* 获得 [JSONArray] 类型
* @return [Class]<[JSONArray]>
*/
val JSONArrayClass get() = classOf<JSONArray>()
/**
* 获得 [StringBuilder] 类型
* @return [Class]<[StringBuilder]>
*/
val StringBuilderClass get() = classOf<StringBuilder>()
/**
* 获得 [StringBuffer] 类型
* @return [Class]<[StringBuffer]>
*/
val StringBufferClass get() = classOf<StringBuffer>()
/**
* 获得 [ZipEntry] 类型
* @return [Class]<[ZipEntry]>
*/
val ZipEntryClass get() = classOf<ZipEntry>()
/**
* 获得 [ZipFile] 类型
* @return [Class]<[ZipFile]>
*/
val ZipFileClass get() = classOf<ZipFile>()
/**
* 获得 [ZipInputStream] 类型
* @return [Class]<[ZipInputStream]>
*/
val ZipInputStreamClass get() = classOf<ZipInputStream>()
/**
* 获得 [ZipOutputStream] 类型
* @return [Class]<[ZipOutputStream]>
*/
val ZipOutputStreamClass get() = classOf<ZipOutputStream>()
/**
* 获得 [HttpURLConnection] 类型
* @return [Class]<[HttpURLConnection]>
*/
val HttpURLConnectionClass get() = classOf<HttpURLConnection>()
/**
* 获得 [HttpCookie] 类型
* @return [Class]<[HttpCookie]>
*/
val HttpCookieClass get() = classOf<HttpCookie>()
/**
* 获得 [HttpClient] 类型
* @return [Class] or null
*/
val HttpClientClass get() = "java.net.http.HttpClient".toClassOrNull()
/**
* 获得 [AtomicBoolean] 类型
* @return [Class]<[AtomicBoolean]>
*/
val AtomicBooleanClass get() = classOf<AtomicBoolean>()
/**
* 获得 [Supplier] 类型
* @return [Class]<[Supplier]>
*/
val SupplierClass get() = classOf<Supplier<*>>()
/**
* 获得 [Class] 类型
* @return [Class]<[Class]>
*/
val JavaClass get() = classOf<Class<*>>()
/**
* 获得 [ClassLoader] 类型
* @return [Class]<[ClassLoader]>
*/
val JavaClassLoader get() = classOf<ClassLoader>()
/**
* 获得 [BaseDexClassLoader] 类型
* @return [Class]<[BaseDexClassLoader]>
*/
val DalvikBaseDexClassLoader get() = classOf<BaseDexClassLoader>()
/**
* 获得 [DexClassLoader] 类型
* @return [Class]<[DexClassLoader]>
*/
val DalvikDexClassLoader get() = classOf<DexClassLoader>()
/**
* 获得 [PathClassLoader] 类型
* @return [Class]<[PathClassLoader]>
*/
val DalvikPathClassLoader get() = classOf<PathClassLoader>()
/**
* 获得 [InMemoryDexClassLoader] 类型
* @return [Class]<[InMemoryDexClassLoader]>
*/
val DalvikInMemoryDexClassLoader get() = classOf<InMemoryDexClassLoader>()
/**
* 获得 [Method] 类型
* @return [Class]<[Method]>
*/
val JavaMethodClass get() = classOf<Method>()
/**
* 获得 [Field] 类型
* @return [Class]<[Field]>
*/
val JavaFieldClass get() = classOf<Field>()
/**
* 获得 [Constructor] 类型
* @return [Class]<[Constructor]>
*/
val JavaConstructorClass get() = classOf<Constructor<*>>()
/**
* 获得 [Member] 类型
* @return [Class]<[Member]>
*/
val JavaMemberClass get() = classOf<Member>()
/**
* 获得 [Annotation] 类型
* @return [Class]<[Annotation]>
*/
val JavaAnnotationClass get() = classOf<Annotation>()
/**
* 获得 [java.util.function.Function] 类型
* @return [Class]<[JavaFunction]>
*/
val FunctionClass get() = classOf<JavaFunction<*, *>>()
/**
* 获得 [Optional] 类型
* @return [Class]<[Optional]>
*/
val OptionalClass get() = classOf<Optional<*>>()
/**
* 获得 [OptionalInt] 类型
* @return [Class]<[OptionalInt]>
*/
val OptionalIntClass get() = classOf<OptionalInt>()
/**
* 获得 [OptionalLong] 类型
* @return [Class]<[OptionalLong]>
*/
val OptionalLongClass get() = classOf<OptionalLong>()
/**
* 获得 [OptionalDouble] 类型
* @return [Class]<[OptionalDouble]>
*/
val OptionalDoubleClass get() = classOf<OptionalDouble>()
/**
* 获得 [Objects] 类型
* @return [Class]<[Objects]>
*/
val ObjectsClass get() = classOf<Objects>()
/**
* 获得 [Runtime] 类型
* @return [Class]<[Runtime]>
*/
val RuntimeClass get() = classOf<Runtime>()
/**
* 获得 [NullPointerException] 类型
* @return [Class]<[NullPointerException]>
*/
val NullPointerExceptionClass get() = classOf<NullPointerException>()
/**
* 获得 [NumberFormatException] 类型
* @return [Class]<[NumberFormatException]>
*/
val NumberFormatExceptionClass get() = classOf<NumberFormatException>()
/**
* 获得 [IllegalStateException] 类型
* @return [Class]<[IllegalStateException]>
*/
val IllegalStateExceptionClass get() = classOf<IllegalStateException>()
/**
* 获得 [RuntimeException] 类型
* @return [Class]<[RuntimeException]>
*/
val RuntimeExceptionClass get() = classOf<RuntimeException>()
/**
* 获得 [ClassNotFoundException] 类型
* @return [Class]<[ClassNotFoundException]>
*/
val ClassNotFoundExceptionClass get() = classOf<ClassNotFoundException>()
/**
* 获得 [NoClassDefFoundError] 类型
* @return [Class]<[NoClassDefFoundError]>
*/
val NoClassDefFoundErrorClass get() = classOf<NoClassDefFoundError>()
/**
* 获得 [NoSuchMethodError] 类型
* @return [Class]<[NoSuchMethodError]>
*/
val NoSuchMethodErrorClass get() = classOf<NoSuchMethodError>()
/**
* 获得 [NoSuchFieldError] 类型
* @return [Class]<[NoSuchFieldError]>
*/
val NoSuchFieldErrorClass get() = classOf<NoSuchFieldError>()
/**
* 获得 [Error] 类型
* @return [Class]<[Error]>
*/
val ErrorClass get() = classOf<Error>()
/**
* 获得 [Exception] 类型
* @return [Class]<[Exception]>
*/
val ExceptionClass get() = classOf<Exception>()
/**
* 获得 [Throwable] 类型
* @return [Class]<[Throwable]>
*/
val ThrowableClass get() = classOf<Throwable>()

View File

@@ -0,0 +1,252 @@
/*
* 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")
package com.highcapable.yukihookapi.hook.utils
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
/**
* 创建当前线程池服务
* @return [ExecutorService]
*/
private val currentThreadPool get() = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
/**
* 对 [T] 返回无返回值的 [Unit]
* @return [Unit]
*/
internal fun <T> T?.unit() = let {}
/**
* 启动 [Thread] 延迟等待 [block] 的结果 [T]
* @param delayMs 延迟毫秒 - 默认 1 ms
* @param block 方法块
* @return [T]
*/
internal inline fun <T> T.await(delayMs: Long = 1, crossinline block: (T) -> Unit): T {
currentThreadPool.apply {
execute {
if (delayMs > 0) Thread.sleep(delayMs)
block(this@await)
shutdown()
}
}
return this
}
/**
* 获取数组内容依次列出的字符串表示
* @return [String]
*/
internal inline fun <reified T> Array<out T>.value() = if (isNotEmpty()) {
var value = ""
forEach { value += it.toString() + ", " }
"[${value.trim().let { it.substring(0, it.lastIndex) }}]"
} else "[]"
/**
* 通过 [conditions] 查找符合条件的最后一个元素的下标
* @return [Int] 没有找到符合条件的下标将返回 -1
*/
internal inline fun <reified T> Sequence<T>.findLastIndex(conditions: (T) -> Boolean) =
withIndex().findLast { conditions(it.value) }?.index ?: -1
/**
* 返回最后一个元素的下标
* @return [Int] 如果 [Sequence] 为空将返回 -1
*/
internal inline fun <reified T> Sequence<T>.lastIndex() = foldIndexed(-1) { index, _, _ -> index }.takeIf { it >= 0 } ?: -1
/**
* 满足条件判断方法体 - 对 [kotlin.takeIf] 进行封装
* @param other 需要满足不为空的对象 - 仅用于判断是否为 null
* @param predicate 原始方法体
* @return [T] or null
*/
internal inline fun <T> T.takeIf(other: Any?, predicate: (T) -> Boolean) = if (other != null) takeIf(predicate) else null
/**
* 满足条件返回值 - 对 [kotlin.let] 进行封装
* @param other 需要满足不为空的对象 - 仅用于判断是否为 null
* @param block 原始方法体
* @return [R] or null
*/
internal inline fun <T, R> T.let(other: Any?, block: (T) -> R) = if (other != null) let(block) else null
/**
* 条件判断方法体捕获异常返回 true
* @param block 原始方法体
* @return [Boolean]
*/
internal inline fun runOrTrue(block: () -> Boolean) = runCatching { block() }.getOrNull() ?: true
/**
* 条件判断方法体捕获异常返回 false
* @param block 原始方法体
* @return [Boolean]
*/
internal inline fun runOrFalse(block: () -> Boolean) = runCatching { block() }.getOrNull() ?: false
/**
* 获取完整的异常堆栈内容
* @return [String]
*/
internal fun Throwable.toStackTrace() = ByteArrayOutputStream().also { printStackTrace(PrintStream(it)) }.toString()
/**
* 计算方法执行耗时
* @param block 方法块
* @return [RunBlockResult]
*/
internal inline fun <R> runBlocking(block: () -> R): RunBlockResult {
val start = System.currentTimeMillis()
block()
return RunBlockResult(afterMs = System.currentTimeMillis() - start)
}
/**
* 构造耗时计算结果类
* @param afterMs 耗时
*/
internal class RunBlockResult(internal val afterMs: Long) {
/**
* 获取耗时计算结果
* @param result 回调结果 - ([Long] 耗时)
*/
internal inline fun result(result: (Long) -> Unit) = result(afterMs)
}
/**
* 创建多项条件判断 - 条件对象 [T]
* @param initiate 方法体
* @return [Conditions.Result]
*/
internal inline fun <T> T.conditions(initiate: Conditions<T>.() -> Unit) = Conditions(value = this).apply(initiate).build()
/**
* 构造条件判断类
* @param value 当前条件对象
*/
internal class Conditions<T>(internal var value: T) {
/** 全部判断条件数组 (与) */
private val andConditions = ArrayList<Boolean>()
/** 全部判断条件数组 (或) */
private val optConditions = ArrayList<Boolean>()
/**
* 添加与 (and) 条件
* @param value 条件值
*/
internal fun and(value: Boolean) {
andConditions.add(value)
}
/**
* 添加或 (or) 条件
* @param value 条件值
*/
internal fun opt(value: Boolean) {
optConditions.add(value)
}
/**
* 结束方法体
* @return [Result]
*/
internal fun build() = Result()
/**
* 构造条件判断结果类
*/
inner class Result internal constructor() {
/**
* 获取条件判断结果
* @return [Boolean]
*/
private val result by lazy {
optConditions.takeIf { it.isNotEmpty() }?.any { it } == true ||
andConditions.takeIf { it.isNotEmpty() }?.any { it.not() }?.not() == true
}
/**
* 当条件成立
* @param callback 回调
*/
internal inline fun finally(callback: () -> Unit): Result {
if (result) callback()
return this
}
/**
* 当条件不成立
* @param callback 回调
*/
internal inline fun without(callback: () -> Unit): Result {
if (result.not()) callback()
return this
}
}
}
/**
* 获取 [ModifyValue] 对象
* @return [ModifyValue]
*/
internal fun <T> T.value() = ModifyValue(value = this)
/**
* 可修改变量实现类
* @param value 变量自身实例
*/
internal data class ModifyValue<T>(var value: T)
/**
* 随机种子工具类
*/
internal object RandomSeed {
/** 随机字母和数字定义 */
private const val RANDOM_LETTERS_NUMBERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
/**
* 生成随机字符串
* @param length 生成长度 - 默认 15
* @return [String]
*/
internal fun createString(length: Int = 15) = StringBuilder().apply {
for (i in 1..length) append(RANDOM_LETTERS_NUMBERS[(0..RANDOM_LETTERS_NUMBERS.lastIndex).random()])
}.toString()
}

View File

@@ -0,0 +1,88 @@
/*
* 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/4/15.
*/
package com.highcapable.yukihookapi.hook.xposed.application
import android.app.Application
import android.content.Context
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication.Companion.appContext
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit
import com.highcapable.yukihookapi.thirdparty.me.weishu.reflection.Reflection
/**
* 这是对使用 [YukiHookAPI] Xposed 模块实现中的一个扩展功能
*
* 在你的 Xposed 模块的 [Application] 中继承此类
*
* 或在 AndroidManifest.xml 的 application 标签中指定此类
*
* 目前可实现功能如下
*
* - 全局共享模块中静态的 [appContext]
*
* - 在模块与宿主中装载 [YukiHookAPI.Configs] 以确保 [YukiHookAPI.Configs.debugTag] 不需要重复定义
*
* - 在模块与宿主中使用 [YukiHookDataChannel] 进行通讯
*
* - 在模块中使用系统隐藏 API - 核心技术引用了开源项目 [FreeReflection](https://github.com/tiann/FreeReflection)
*
* - 在模块中使用 [YukiHookAPI.Status.isTaiChiModuleActive] 判断太极、无极激活状态
*
* 详情请参考 [API 文档 - ModuleApplication](https://fankes.github.io/YukiHookAPI/zh-cn/api/public/com/highcapable/yukihookapi/hook/xposed/application/ModuleApplication)
*
* For English version, see [API Document - ModuleApplication](https://fankes.github.io/YukiHookAPI/en/api/public/com/highcapable/yukihookapi/hook/xposed/application/ModuleApplication)
*/
open class ModuleApplication : Application() {
companion object {
/** 全局静态 [Application] 实例 */
internal var currentContext: ModuleApplication? = null
/**
* 获取全局静态 [Application] 实例
* @return [ModuleApplication]
* @throws IllegalStateException 如果 [Application] 没有正确装载完成
*/
val appContext get() = currentContext ?: error("App is dead, You cannot call to appContext")
}
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
runCatching { Reflection.unseal(base) }
}
override fun onCreate() {
super.onCreate()
currentContext = this
/** 调用 Hook 入口类的 [IYukiHookXposedInit.onInit] 方法 */
runCatching { ModuleApplication_Impl.callHookEntryInit() }
YukiHookDataChannel.instance().register(context = this)
}
}

View File

@@ -0,0 +1,226 @@
/*
* 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/4/3.
* This file is Modified by fankes on 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.xposed.bridge
import android.content.pm.ApplicationInfo
import android.content.res.Resources
import android.util.ArrayMap
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.core.api.compat.HookApiCategoryHelper
import com.highcapable.yukihookapi.hook.factory.hasClass
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper
import com.highcapable.yukihookapi.hook.xposed.bridge.proxy.IYukiXposedModuleLifecycle
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiModuleResources
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiResources
import com.highcapable.yukihookapi.hook.xposed.bridge.type.HookEntryType
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import dalvik.system.PathClassLoader
/**
* Xposed 模块核心功能实现类
*/
internal object YukiXposedModule : IYukiXposedModuleLifecycle {
/** Xposed 模块是否已被装载 */
private var isModuleLoaded = false
/** Xposed 模块是否装载完成 */
private var isModuleLoadFinished = false
/** 当前 Hook 进程是否正处于 Zygote */
private var isInitializingZygote = false
/** 当前 [PackageParam] 实例数组 */
private val packageParams = ArrayMap<String, PackageParam>()
/** 已在 [PackageParam] 中被装载的 APP 包名 */
private val loadedPackageNames = HashSet<String>()
/** 当前 [PackageParamWrapper] 实例数组 */
private val packageParamWrappers = ArrayMap<String, PackageParamWrapper>()
/** 当前 [PackageParam] 方法体回调 */
internal var packageParamCallback: (PackageParam.() -> Unit)? = null
/** 当前 Hook Framework 是否支持 Resources Hook */
internal var isSupportResourcesHook = false
/** 预设的 Xposed 模块包名 */
internal var modulePackageName = ""
/** 当前 Xposed 模块自身 APK 路径 */
internal var moduleAppFilePath = ""
/** 当前 Xposed 模块自身 [Resources] */
internal var moduleAppResources: YukiModuleResources? = null
/**
* 获取当前 Xposed 模块自身动态 [Resources]
* @return [YukiModuleResources] or null
*/
internal val dynamicModuleAppResources get() = runCatching { YukiModuleResources.wrapper(moduleAppFilePath) }.getOrNull()
/**
* 模块是否装载了 Xposed 回调方法
* @return [Boolean]
*/
internal val isXposedCallbackSetUp get() = isModuleLoadFinished.not() && packageParamCallback != null
/**
* 当前宿主正在进行的 Hook 进程标识名称
* @return [String]
*/
internal val hostProcessName get() = if (isInitializingZygote) "android-zygote" else AppParasitics.currentPackageName
/**
* 获取当前是否为 (Xposed) 宿主环境
* @return [Boolean]
*/
internal val isXposedEnvironment get() = HookApiCategoryHelper.hasAvailableHookApi && isModuleLoaded
/**
* 自动忽略 MIUI 系统可能出现的日志收集注入实例
* @param packageName 当前包名
* @return [Boolean] 是否存在
*/
private fun isMiuiCatcherPatch(packageName: String?) =
(packageName == "com.miui.contentcatcher" || packageName == "com.miui.catcherpatch") && "android.miui.R".hasClass()
/**
* 当前包名是否已在指定的 [HookEntryType] 被装载
* @param packageName 包名
* @param type 当前 Hook 类型
* @return [Boolean] 是否已被装载
*/
private fun isPackageLoaded(packageName: String?, type: HookEntryType): Boolean {
if (packageName == null) return false
if (loadedPackageNames.contains("$packageName:$type")) return true
loadedPackageNames.add("$packageName:$type")
return false
}
/**
* 实例化当前 [PackageParamWrapper] 到 [PackageParam]
*
* 如果实例不存在将会自动创建一个新实例
* @return [PackageParam]
*/
private fun PackageParamWrapper.instantiate() = packageParams[wrapperNameId] ?: PackageParam().apply { packageParams[wrapperNameId] = this }
/**
* 创建、修改 [PackageParamWrapper]
*
* 忽略在 [type] 不为 [HookEntryType.ZYGOTE] 时 [appClassLoader] 为空导致首次使用 [ClassLoader.getSystemClassLoader] 装载的问题
* @param type 当前正在进行的 Hook 类型
* @param packageName 包名
* @param processName 当前进程名
* @param appClassLoader APP [ClassLoader]
* @param appInfo APP [ApplicationInfo]
* @param appResources APP [YukiResources]
* @return [PackageParamWrapper] or null
*/
private fun assignWrapper(
type: HookEntryType,
packageName: String?,
processName: String? = "",
appClassLoader: ClassLoader? = null,
appInfo: ApplicationInfo? = null,
appResources: YukiResources? = null
) = run {
isInitializingZygote = type == HookEntryType.ZYGOTE
if (packageParamWrappers[packageName] == null)
if (type == HookEntryType.ZYGOTE || appClassLoader != null)
PackageParamWrapper(
type = type,
packageName = packageName ?: AppParasitics.SYSTEM_FRAMEWORK_NAME,
processName = processName ?: AppParasitics.SYSTEM_FRAMEWORK_NAME,
appClassLoader = appClassLoader ?: ClassLoader.getSystemClassLoader(),
appInfo = appInfo,
appResources = appResources
).also { packageParamWrappers[packageName ?: AppParasitics.SYSTEM_FRAMEWORK_NAME] = it }
else null
else packageParamWrappers[packageName]?.also { wrapper ->
wrapper.type = type
packageName?.takeIf { it.isNotBlank() }?.also { wrapper.packageName = it }
processName?.takeIf { it.isNotBlank() }?.also { wrapper.processName = it }
appClassLoader?.takeIf { type == HookEntryType.ZYGOTE || it is PathClassLoader }?.also { wrapper.appClassLoader = it }
appInfo?.also { wrapper.appInfo = it }
appResources?.also { wrapper.appResources = it }
}
}
/** 刷新当前 Xposed 模块自身 [Resources] */
internal fun refreshModuleAppResources() {
dynamicModuleAppResources?.let { moduleAppResources = it }
}
override fun onStartLoadModule(packageName: String, appFilePath: String) {
isModuleLoaded = true
modulePackageName = packageName
moduleAppFilePath = appFilePath
refreshModuleAppResources()
}
override fun onFinishLoadModule() {
isModuleLoadFinished = true
}
override fun onPackageLoaded(
type: HookEntryType,
packageName: String?,
processName: String?,
appClassLoader: ClassLoader?,
appInfo: ApplicationInfo?,
appResources: YukiResources?
) {
if (isMiuiCatcherPatch(packageName).not()) when (type) {
HookEntryType.ZYGOTE ->
assignWrapper(HookEntryType.ZYGOTE, AppParasitics.SYSTEM_FRAMEWORK_NAME, AppParasitics.SYSTEM_FRAMEWORK_NAME, appClassLoader)
HookEntryType.PACKAGE ->
if (isPackageLoaded(packageName, HookEntryType.PACKAGE).not())
assignWrapper(HookEntryType.PACKAGE, packageName, processName, appClassLoader, appInfo)
else null
HookEntryType.RESOURCES ->
/** 这里可能会出现 [packageName] 获取到非实际宿主的问题 - 如果包名与 [AppParasitics.currentPackageName] 不相同这里做忽略处理 */
if (isPackageLoaded(packageName, HookEntryType.RESOURCES).not() && packageName == AppParasitics.currentPackageName)
assignWrapper(HookEntryType.RESOURCES, packageName, appResources = appResources)
else null
}?.also {
runCatching {
if (it.isCorrectProcess) packageParamCallback?.invoke(it.instantiate().assign(it).apply { YukiHookAPI.printSplashInfo() })
if (it.type != HookEntryType.ZYGOTE && it.packageName == modulePackageName)
AppParasitics.hookModuleAppRelated(it.appClassLoader, it.type)
if (it.type == HookEntryType.PACKAGE) AppParasitics.registerToAppLifecycle(it.packageName)
if (it.type == HookEntryType.RESOURCES) isSupportResourcesHook = true
}.onFailure { yLoggerE(msg = "An exception occurred in the Hooking Process of YukiHookAPI", e = it) }
}
}
}

View File

@@ -0,0 +1,104 @@
/*
* 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 2023/1/9.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.xposed.bridge.caller
import android.content.pm.ApplicationInfo
import com.highcapable.yukihookapi.annotation.YukiGenerateApi
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiResources
import com.highcapable.yukihookapi.hook.xposed.bridge.type.HookEntryType
/**
* Xposed 模块核心功能调用类
*
* - ❗装载代码将自动生成 - 请勿手动调用
*/
@YukiGenerateApi
object YukiXposedModuleCaller {
/**
* 模块是否装载了 Xposed 回调方法
*
* - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件
* @return [Boolean]
*/
@YukiGenerateApi
val isXposedCallbackSetUp get() = YukiXposedModule.isXposedCallbackSetUp
/**
* 标识 Xposed 模块开始装载
*
* - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件
* @param packageName 当前 Xposed 模块包名
* @param appFilePath 当前 Xposed 模块自身 APK 路径
*/
@YukiGenerateApi
fun callOnStartLoadModule(packageName: String, appFilePath: String) = YukiXposedModule.onStartLoadModule(packageName, appFilePath)
/**
* 标识 Xposed 模块装载完成
*
* - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件
*/
@YukiGenerateApi
fun callOnFinishLoadModule() = YukiXposedModule.onFinishLoadModule()
/**
* 标识可用的 Hook APP (宿主) 开始装载
*
* - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件
* @param type 当前正在进行的 Hook 类型
* @param packageName 宿主包名
* @param processName 宿主进程名
* @param appClassLoader 宿主 [ClassLoader]
* @param appInfo 宿主 [ApplicationInfo]
* @param appResources 宿主 [YukiResources]
*/
@YukiGenerateApi
fun callOnPackageLoaded(
type: HookEntryType,
packageName: String?,
processName: String? = "",
appClassLoader: ClassLoader? = null,
appInfo: ApplicationInfo? = null,
appResources: YukiResources? = null
) = YukiXposedModule.onPackageLoaded(type, packageName, processName, appClassLoader, appInfo, appResources)
/**
* 打印内部 E 级别的日志
*
* - ❗装载代码将自动生成 - 请勿手动调用
* @param msg 日志打印的内容
* @param e 异常堆栈信息 - 默认空
*/
@YukiGenerateApi
fun internalLoggerE(msg: String, e: Throwable? = null) = yLoggerE(msg = msg, e = e)
}

View File

@@ -0,0 +1,55 @@
/*
* 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 2023/4/16.
*/
package com.highcapable.yukihookapi.hook.xposed.bridge.delegate
import de.robv.android.xposed.XSharedPreferences
/**
* [XSharedPreferences] 代理类
* @param packageName APP 包名
* @param prefFileName 存储文件名
*/
internal class XSharedPreferencesDelegate private constructor(private val packageName: String, private val prefFileName: String) {
internal companion object {
/**
* 创建代理类
* @param packageName APP 包名
* @param prefFileName 存储文件名
* @return [XSharedPreferencesDelegate]
*/
fun from(packageName: String, prefFileName: String) = XSharedPreferencesDelegate(packageName, prefFileName)
}
/**
* 获取实例
* @return [XSharedPreferences]
*/
internal val instance by lazy { XSharedPreferences(packageName, prefFileName) }
}

View File

@@ -0,0 +1,82 @@
/*
* 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/4/30.
* This file is Modified by fankes on 2022/1/10.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.xposed.bridge.event
import de.robv.android.xposed.IXposedHookZygoteInit.StartupParam
import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam
/**
* 实现对原生 Xposed API 的装载事件监听
*/
object YukiXposedEvent {
/** 监听 initZygote 开始的回调方法 */
internal var initZygoteCallback: ((StartupParam) -> Unit)? = null
/** 监听 handleLoadPackage 开始的回调方法 */
internal var handleLoadPackageCallback: ((LoadPackageParam) -> Unit)? = null
/** 监听 handleInitPackageResources 开始的回调方法 */
internal var handleInitPackageResourcesCallback: ((InitPackageResourcesParam) -> Unit)? = null
/**
* 对 [YukiXposedEvent] 创建一个方法体
* @param initiate 方法体
*/
inline fun events(initiate: YukiXposedEvent.() -> Unit) {
YukiXposedEvent.apply(initiate)
}
/**
* 设置 initZygote 事件监听
* @param result 回调方法体
*/
fun onInitZygote(result: (StartupParam) -> Unit) {
initZygoteCallback = result
}
/**
* 设置 handleLoadPackage 事件监听
* @param result 回调方法体
*/
fun onHandleLoadPackage(result: (LoadPackageParam) -> Unit) {
handleLoadPackageCallback = result
}
/**
* 设置 handleInitPackageResources 事件监听
* @param result 回调方法体
*/
fun onHandleInitPackageResources(result: (InitPackageResourcesParam) -> Unit) {
handleInitPackageResourcesCallback = result
}
}

View File

@@ -0,0 +1,81 @@
/*
* 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/1/10.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.xposed.bridge.event.caller
import com.highcapable.yukihookapi.annotation.YukiGenerateApi
import com.highcapable.yukihookapi.hook.xposed.bridge.event.YukiXposedEvent
import de.robv.android.xposed.IXposedHookZygoteInit
import de.robv.android.xposed.callbacks.XC_InitPackageResources
import de.robv.android.xposed.callbacks.XC_LoadPackage
/**
* 实现对原生 Xposed API 装载事件监听的回调监听事件处理类
*
* - ❗装载代码将自动生成 - 请勿手动调用
*/
@YukiGenerateApi
object YukiXposedEventCaller {
/**
* 回调 initZygote 事件监听
*
* - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件
* @param sparam Xposed API 实例
*/
@YukiGenerateApi
fun callInitZygote(sparam: IXposedHookZygoteInit.StartupParam?) {
if (sparam == null) return
YukiXposedEvent.initZygoteCallback?.invoke(sparam)
}
/**
* 回调 handleLoadPackage 事件监听
*
* - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件
* @param lpparam Xposed API 实例
*/
@YukiGenerateApi
fun callHandleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
if (lpparam == null) return
YukiXposedEvent.handleLoadPackageCallback?.invoke(lpparam)
}
/**
* 回调 handleInitPackageResources 事件监听
*
* - ❗装载代码将自动生成 - 你不应该手动使用此方法装载 Xposed 模块事件
* @param resparam Xposed API 实例
*/
@YukiGenerateApi
fun callHandleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam?) {
if (resparam == null) return
YukiXposedEvent.handleInitPackageResourcesCallback?.invoke(resparam)
}
}

View File

@@ -0,0 +1,66 @@
/*
* 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 2023/1/11.
*/
package com.highcapable.yukihookapi.hook.xposed.bridge.proxy
import android.content.pm.ApplicationInfo
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiResources
import com.highcapable.yukihookapi.hook.xposed.bridge.type.HookEntryType
/**
* Xposed 模块生命周期实现接口
*/
internal interface IYukiXposedModuleLifecycle {
/**
* 当 Xposed 模块开始装载
* @param packageName 当前 Xposed 模块包名
* @param appFilePath 当前 Xposed 模块自身 APK 路径
*/
fun onStartLoadModule(packageName: String, appFilePath: String)
/** 当 Xposed 模块装载完成 */
fun onFinishLoadModule()
/**
* 当可用的 Hook APP (宿主) 开始装载
* @param type 当前正在进行的 Hook 类型
* @param packageName 宿主包名
* @param processName 宿主进程名
* @param appClassLoader 宿主 [ClassLoader]
* @param appInfo 宿主 [ApplicationInfo]
* @param appResources 宿主 [YukiResources]
*/
fun onPackageLoaded(
type: HookEntryType,
packageName: String?,
processName: String? = "",
appClassLoader: ClassLoader? = null,
appInfo: ApplicationInfo? = null,
appResources: YukiResources? = null
)
}

View File

@@ -0,0 +1,69 @@
/*
* 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/4/29.
*/
@file:Suppress("DEPRECATION")
package com.highcapable.yukihookapi.hook.xposed.bridge.resources
import android.content.res.Resources
import android.content.res.XModuleResources
import android.content.res.XResForwarder
/**
* 对接 [XModuleResources] 的中间层实例
* @param baseInstance 原始实例
*/
class YukiModuleResources private constructor(private val baseInstance: XModuleResources) :
Resources(
runCatching { baseInstance.assets }.getOrNull(),
runCatching { baseInstance.displayMetrics }.getOrNull(),
runCatching { baseInstance.configuration }.getOrNull()
) {
internal companion object {
/**
* 对接 [XModuleResources.createInstance] 方法
*
* 创建 [YukiModuleResources] 与 [XModuleResources] 实例
* @param path Xposed 模块 APK 路径
* @return [YukiModuleResources]
*/
internal fun wrapper(path: String) = YukiModuleResources(XModuleResources.createInstance(path, null))
}
/**
* 对接 [XModuleResources.fwd] 方法
*
* 创建 [YukiResForwarder] 与 [XResForwarder] 实例
* @param resId Resources Id
* @return [YukiResForwarder]
*/
fun fwd(resId: Int) = YukiResForwarder.wrapper(baseInstance.fwd(resId))
override fun toString() = "YukiModuleResources by $baseInstance"
}

View File

@@ -0,0 +1,71 @@
/*
* 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/4/29.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.xposed.bridge.resources
import android.content.res.Resources
import android.content.res.XResForwarder
/**
* 对接 [XResForwarder] 的中间层实例
* @param baseInstance 原始实例
*/
class YukiResForwarder private constructor(private val baseInstance: XResForwarder) {
internal companion object {
/**
* 从 [XResForwarder] 创建 [YukiResForwarder] 实例
* @param baseInstance [XResForwarder] 实例
* @return [YukiResForwarder]
*/
internal fun wrapper(baseInstance: XResForwarder) = YukiResForwarder(baseInstance)
}
/**
* 获得 [XResForwarder] 实例
* @return [XResForwarder]
*/
internal val instance get() = baseInstance
/**
* 获得当前 Resources Id
* @return [Int]
*/
val id get() = baseInstance.id
/**
* 获得当前 Resources
* @return [Resources]
* @throws IllegalStateException 如果 [XResForwarder] 出现问题
*/
val resources get() = baseInstance.resources ?: error("XResForwarder is invalid")
override fun toString() = "YukiResForwarder by $baseInstance"
}

View File

@@ -0,0 +1,274 @@
/*
* 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/4/29.
*/
@file:Suppress("unused", "DEPRECATION", "DiscouragedApi")
package com.highcapable.yukihookapi.hook.xposed.bridge.resources
import android.content.res.Resources
import android.content.res.XResources
import android.graphics.drawable.Drawable
import android.view.View
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiResources.LayoutInflatedParam
import de.robv.android.xposed.callbacks.XC_LayoutInflated
/**
* 对接 [XResources] 的中间层实例
* @param baseInstance 原始实例
*/
class YukiResources private constructor(private val baseInstance: XResources) :
Resources(
runCatching { baseInstance.assets }.getOrNull(),
runCatching { baseInstance.displayMetrics }.getOrNull(),
runCatching { baseInstance.configuration }.getOrNull()
) {
internal companion object {
/**
* 从 [XResources] 创建 [YukiResources] 实例
* @param baseInstance [XResources] 实例
* @return [YukiResources]
*/
internal fun wrapper(baseInstance: XResources) = YukiResources(baseInstance)
/**
* 兼容对接替换 Resources
* @param replacement 替换 Resources
* @return [Any] 兼容后的替换 Resources
*/
private fun compat(replacement: Any?) = when (replacement) {
is YukiResForwarder -> replacement.instance
is Drawable -> object : XResources.DrawableLoader() {
override fun newDrawable(res: XResources?, id: Int): Drawable = replacement
}
else -> replacement
}
/**
* 在 Zygote 中替换 Resources
*
* 对接 [XResources.setSystemWideReplacement]
* @param resId Resources Id
* @param replacement 替换 Resources
* @param callback 是否成功执行回调
*/
internal fun setSystemWideReplacement(resId: Int, replacement: Any?, callback: () -> Unit = {}) =
runIfAnyErrors(name = "setSystemWideReplacement") {
XResources.setSystemWideReplacement(resId, compat(replacement))
callback()
}
/**
* 在 Zygote 中替换 Resources
*
* 对接 [XResources.setSystemWideReplacement]
* @param packageName 包名
* @param type Resources 类型
* @param name Resources 名称
* @param replacement 替换 Resources
* @param callback 是否成功执行回调
*/
internal fun setSystemWideReplacement(packageName: String, type: String, name: String, replacement: Any?, callback: () -> Unit = {}) =
runIfAnyErrors(name = "setSystemWideReplacement") {
XResources.setSystemWideReplacement(packageName, type, name, compat(replacement))
callback()
}
/**
* 在 Zygote 中注入布局 Resources
*
* 对接 [XResources.hookSystemWideLayout]
* @param resId Resources Id
* @param initiate 注入方法体
* @param callback 是否成功执行回调
*/
internal fun hookSystemWideLayout(resId: Int, initiate: LayoutInflatedParam.() -> Unit, callback: () -> Unit = {}) =
runIfAnyErrors(name = "hookSystemWideLayout") {
XResources.hookSystemWideLayout(resId, object : XC_LayoutInflated() {
override fun handleLayoutInflated(liparam: LayoutInflatedParam?) {
if (liparam == null) return
initiate(LayoutInflatedParam(liparam))
}
})
callback()
}
/**
* 在 Zygote 中注入布局 Resources
*
* 对接 [XResources.hookSystemWideLayout]
* @param packageName 包名
* @param type Resources 类型
* @param name Resources 名称
* @param initiate 注入方法体
* @param callback 是否成功执行回调
*/
internal fun hookSystemWideLayout(
packageName: String,
type: String,
name: String,
initiate: LayoutInflatedParam.() -> Unit,
callback: () -> Unit = {}
) = runIfAnyErrors(name = "hookSystemWideLayout") {
XResources.hookSystemWideLayout(packageName, type, name, object : XC_LayoutInflated() {
override fun handleLayoutInflated(liparam: LayoutInflatedParam?) {
if (liparam == null) return
initiate(LayoutInflatedParam(liparam))
}
})
callback()
}
/**
* 忽略异常执行
* @param name 方法名称
* @param initiate 方法体
*/
private inline fun runIfAnyErrors(name: String, initiate: () -> Unit) {
runCatching {
initiate()
}.onFailure { yLoggerE(msg = "Failed to execute method \"$name\", maybe your Hook Framework not support Resources Hook", it) }
}
}
/**
* 执行替换 Resources
*
* 对接 [XResources.setReplacement]
* @param resId Resources Id
* @param replacement 替换 Resources
* @param callback 是否成功执行回调
*/
internal fun setReplacement(resId: Int, replacement: Any?, callback: () -> Unit = {}) =
runIfAnyErrors(name = "setReplacement") {
baseInstance.setReplacement(resId, compat(replacement))
callback()
}
/**
* 执行替换 Resources
*
* 对接 [XResources.setReplacement]
* @param packageName 包名
* @param type Resources 类型
* @param name Resources 名称
* @param replacement 替换 Resources
* @param callback 是否成功执行回调
*/
internal fun setReplacement(packageName: String, type: String, name: String, replacement: Any?, callback: () -> Unit = {}) =
runIfAnyErrors(name = "setReplacement") {
baseInstance.setReplacement(packageName, type, name, compat(replacement))
callback()
}
/**
* 执行注入布局 Resources
*
* 对接 [XResources.hookLayout]
* @param resId Resources Id
* @param initiate 注入方法体
* @param callback 是否成功执行回调
*/
internal fun hookLayout(resId: Int, initiate: LayoutInflatedParam.() -> Unit, callback: () -> Unit = {}) =
runIfAnyErrors(name = "hookLayout") {
baseInstance.hookLayout(resId, object : XC_LayoutInflated() {
override fun handleLayoutInflated(liparam: LayoutInflatedParam?) {
if (liparam == null) return
initiate(LayoutInflatedParam(liparam))
}
})
callback()
}
/**
* 执行注入布局 Resources
*
* 对接 [XResources.hookLayout]
* @param packageName 包名
* @param type Resources 类型
* @param name Resources 名称
* @param initiate 注入方法体
* @param callback 是否成功执行回调
*/
internal fun hookLayout(
packageName: String,
type: String,
name: String,
initiate: LayoutInflatedParam.() -> Unit,
callback: () -> Unit = {}
) = runIfAnyErrors(name = "hookLayout") {
baseInstance.hookLayout(packageName, type, name, object : XC_LayoutInflated() {
override fun handleLayoutInflated(liparam: LayoutInflatedParam?) {
if (liparam == null) return
initiate(LayoutInflatedParam(liparam))
}
})
callback()
}
/**
* 装载 Hook APP 的目标布局 Resources 实现类
* @param baseParam 对接 [XC_LayoutInflated.LayoutInflatedParam]
*/
class LayoutInflatedParam(@PublishedApi internal val baseParam: XC_LayoutInflated.LayoutInflatedParam) {
/**
* 获取当前被 Hook 的布局装载目录名称
*
* 例如layout、layout-land、layout-sw600dp
* @return [String]
*/
val variantName get() = baseParam.variant ?: ""
/**
* 获取当前被 Hook 的布局实例
* @return [View]
*/
val currentView get() = baseParam.view ?: error("LayoutInflatedParam View instance got null")
/**
* 使用 Identifier 查找 Hook APP 指定 Id 的 [View]
* @param name Id 的 Identifier 名称
* @return [T] or null
*/
inline fun <reified T : View> View.findViewByIdentifier(name: String): T? =
findViewById(baseParam.res.getIdentifier(name, "id", baseParam.resNames.pkg))
/**
* 使用 Identifier 查找 Hook APP 当前装载布局中指定 Id 的 [View]
* @param name Id 的 Identifier 名称
* @return [T] or null
*/
inline fun <reified T : View> findViewByIdentifier(name: String) = currentView.findViewByIdentifier<T>(name)
override fun toString() = "LayoutInflatedParam by $baseParam"
}
override fun toString() = "YukiResources by $baseInstance"
}

View File

@@ -0,0 +1,53 @@
/*
* 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 2023/1/10.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.xposed.bridge.resources.caller
import android.content.res.XResources
import com.highcapable.yukihookapi.annotation.YukiGenerateApi
import com.highcapable.yukihookapi.hook.xposed.bridge.resources.YukiResources
/**
* Xposed 模块资源钩子 (Resources Hook) 调用类
*
* - ❗装载代码将自动生成 - 请勿手动调用
*/
@YukiGenerateApi
object YukiXposedResourcesCaller {
/**
* 从 [XResources] 创建 [YukiResources]
*
* - ❗装载代码将自动生成 - 请勿手动调用
* @param xResources [XResources] 实例
* @return [YukiResources] or null
*/
@YukiGenerateApi
fun createYukiResourcesFromXResources(xResources: XResources?) = xResources?.let { YukiResources.wrapper(it) }
}

View File

@@ -0,0 +1,107 @@
/*
* 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/3.
* This file is Modified by fankes on 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.xposed.bridge.status
import com.highcapable.yukihookapi.YukiHookAPI
/**
* 这是一个 Xposed 模块 Hook 状态类
*
* 我们需要监听自己的模块是否被激活 - 可使用以下方法调用
*
* 调用 [YukiHookAPI.Status.isModuleActive] or [YukiHookAPI.Status.isTaiChiModuleActive]
*
* 调用 [YukiHookAPI.Status.isXposedModuleActive]
*
* 你还可以通过调用 [YukiHookAPI.Status.Executor] 获取当前 Hook Framework 的详细信息
*
* 详情请参考 [Xposed 模块判断自身激活状态](https://fankes.github.io/YukiHookAPI/zh-cn/guide/example#xposed-%E6%A8%A1%E5%9D%97%E5%88%A4%E6%96%AD%E8%87%AA%E8%BA%AB%E6%BF%80%E6%B4%BB%E7%8A%B6%E6%80%81)
*
* For English version, see [Xposed Module own Active State](https://fankes.github.io/YukiHookAPI/en/guide/example#xposed-module-own-active-state)
*/
internal object YukiXposedModuleStatus {
internal const val IS_ACTIVE_METHOD_NAME = "__--"
internal const val IS_SUPPORT_RESOURCES_HOOK_METHOD_NAME = "_--_"
internal const val GET_EXECUTOR_NAME_METHOD_NAME = "_-_-"
internal const val GET_EXECUTOR_API_LEVEL_METHOD_NAME = "-__-"
internal const val GET_EXECUTOR_VERSION_NAME_METHOD_NAME = "-_-_"
internal const val GET_EXECUTOR_VERSION_CODE_METHOD_NAME = "___-"
/** [YukiXposedModuleStatus_Impl] 完整类名 */
internal const val IMPL_CLASS_NAME = "com.highcapable.yukihookapi.hook.xposed.bridge.status.YukiXposedModuleStatus_Impl"
/**
* 获取当前模块的激活状态
*
* 请使用 [YukiHookAPI.Status.isModuleActive]、[YukiHookAPI.Status.isXposedModuleActive]、[YukiHookAPI.Status.isTaiChiModuleActive] 判断模块激活状态
* @return [Boolean]
*/
internal val isActive get() = runCatching { YukiXposedModuleStatus_Impl.isActive() }.getOrNull() ?: false
/**
* 获取当前 Hook Framework 是否支持资源钩子 (Resources Hook)
*
* 请使用 [YukiHookAPI.Status.isSupportResourcesHook] 判断支持状态
* @return [Boolean]
*/
internal val isSupportResourcesHook get() = runCatching { YukiXposedModuleStatus_Impl.isSupportResourcesHook() }.getOrNull() ?: false
/**
* 获取当前 Hook Framework 名称
*
* 请使用 [YukiHookAPI.Status.Executor.name] 获取
* @return [String] 模块未激活会返回 unknown
*/
internal val executorName get() = runCatching { YukiXposedModuleStatus_Impl.getExecutorName() }.getOrNull() ?: "unknown"
/**
* 获取当前 Hook Framework 的 API 版本
*
* 请使用 [YukiHookAPI.Status.Executor.apiLevel] 获取
* @return [Int] 模块未激活会返回 -1
*/
internal val executorApiLevel get() = runCatching { YukiXposedModuleStatus_Impl.getExecutorApiLevel() }.getOrNull() ?: -1
/**
* 获取当前 Hook Framework 版本名称
*
* 请使用 [YukiHookAPI.Status.Executor.versionName] 获取
* @return [Int] 模块未激活会返回 unknown
*/
internal val executorVersionName get() = runCatching { YukiXposedModuleStatus_Impl.getExecutorVersionName() }.getOrNull() ?: "unknown"
/**
* 获取当前 Hook Framework 版本号
*
* 请使用 [YukiHookAPI.Status.Executor.versionCode] 获取
* @return [Int] 模块未激活会返回 -1
*/
internal val executorVersionCode get() = runCatching { YukiXposedModuleStatus_Impl.getExecutorVersionCode() }.getOrNull() ?: -1
}

View File

@@ -0,0 +1,49 @@
/*
* 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/4/26.
* This file is Modified by fankes on 2023/1/9.
*/
package com.highcapable.yukihookapi.hook.xposed.bridge.type
import com.highcapable.yukihookapi.annotation.YukiGenerateApi
/**
* 当前正在进行的 Hook 类型
*
* - ❗装载代码将自动生成 - 请勿手动调用
*/
@YukiGenerateApi
enum class HookEntryType {
/** 装载 Zygote */
ZYGOTE,
/** 装载 APP */
PACKAGE,
/** 装载 Resources Hook */
RESOURCES
}

View File

@@ -0,0 +1,716 @@
/*
* 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/5/16.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate", "UNCHECKED_CAST", "StaticFieldLeak", "KotlinConstantConditions")
package com.highcapable.yukihookapi.hook.xposed.channel
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.app.Application
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Context.ACTIVITY_SERVICE
import android.content.Intent
import android.content.IntentFilter
import android.os.Build
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import android.os.TransactionTooLargeException
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.CauseProblemsApi
import com.highcapable.yukihookapi.hook.log.YukiHookLogger
import com.highcapable.yukihookapi.hook.log.YukiLoggerData
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.utils.RandomSeed
import com.highcapable.yukihookapi.hook.xposed.application.ModuleApplication
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
import com.highcapable.yukihookapi.hook.xposed.channel.data.wrapper.ChannelDataWrapper
import com.highcapable.yukihookapi.hook.xposed.channel.priority.ChannelPriority
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import java.io.Serializable
import java.util.concurrent.ConcurrentHashMap
/**
* 实现 Xposed 模块的数据通讯桥
*
* 通过模块与宿主相互注册 [BroadcastReceiver] 来实现数据的交互
*
* 模块需要将 [Application] 继承于 [ModuleApplication] 来实现此功能
*
* - ❗模块与宿主需要保持存活状态 - 否则无法建立通讯
*
* 详情请参考 [API 文档 - YukiHookDataChannel](https://fankes.github.io/YukiHookAPI/zh-cn/api/public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel)
*
* For English version, see [API Document - YukiHookDataChannel](https://fankes.github.io/YukiHookAPI/en/api/public/com/highcapable/yukihookapi/hook/xposed/channel/YukiHookDataChannel)
*/
class YukiHookDataChannel private constructor() {
internal companion object {
/** 是否为 (Xposed) 宿主环境 */
private val isXposedEnvironment = YukiXposedModule.isXposedEnvironment
/** 自动生成的 Xposed 模块构建版本号 */
private val moduleGeneratedVersion = YukiHookAPI.Status.compiledTimestamp.toString()
/** 模块构建版本号获取标签 */
private const val GET_MODULE_GENERATED_VERSION = "module_generated_version_get"
/** 模块构建版本号结果标签 */
private const val RESULT_MODULE_GENERATED_VERSION = "module_generated_version_result"
/** 调试日志数据获取标签 */
private const val GET_YUKI_LOGGER_INMEMORY_DATA = "yuki_logger_inmemory_data_get"
/** 调试日志数据结果标签 */
private val RESULT_YUKI_LOGGER_INMEMORY_DATA = ChannelData<ArrayList<YukiLoggerData>>("yuki_logger_inmemory_data_result")
/** 仅监听结果键值 */
private const val VALUE_WAIT_FOR_LISTENER = "wait_for_listener_value"
/**
* 系统广播允许发送的最大数据字节大小
*
* 标准为 1 MB - 实测不同系统目前已知能得到的数据分别有 1、2、3 MB
*
* 经过测试分段发送 900 KB 数据在 1 台 Android 13 系统的设备上依然会发生异常
*
* 综上所述 - 为了防止系统不同限制不同 - 最终决定默认设置为 500 KB - 超出后以此大小分段发送数据
*/
private var receiverDataMaxByteSize = 500 * 1024
/**
* 系统广播允许发送的最大数据字节大小倍数 (分段数据)
*
* 分段后的数据每次也不能超出 [receiverDataMaxByteSize] 的大小
*
* 此倍数被作用于分配 [receiverDataMaxByteSize] 的大小
*
* 倍数计算公式为 [receiverDataMaxByteSize] / [receiverDataMaxByteCompressionFactor] = [receiverDataSegmentMaxByteSize]
*/
private var receiverDataMaxByteCompressionFactor = 3
/**
* 获取当前系统广播允许的最大单个分段数据字节大小
* @return [Int]
*/
private val receiverDataSegmentMaxByteSize get() = receiverDataMaxByteSize / receiverDataMaxByteCompressionFactor
/** 当前 [YukiHookDataChannel] 单例 */
private var instance: YukiHookDataChannel? = null
/**
* 获取 [YukiHookDataChannel] 单例
* @return [YukiHookDataChannel]
*/
internal fun instance() = instance ?: YukiHookDataChannel().apply { instance = this }
}
/**
* 键值回调的监听类型定义
*/
private enum class CallbackKeyType { SINGLE, CDATA, VMFL }
/** 注册广播回调数组 */
private var receiverCallbacks = ConcurrentHashMap<String, Pair<Context?, (String, Intent) -> Unit>>()
/** 当前注册广播的 [Context] */
private var receiverContext: Context? = null
/** 是否允许发送超出 [receiverDataMaxByteSize] 大小的数据 */
private var isAllowSendTooLargeData = false
/** 广播接收器 */
private val handlerReceiver by lazy {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent == null) return
intent.action?.also { action ->
runCatching {
receiverCallbacks.takeIf { it.isNotEmpty() }?.apply {
arrayListOf<String>().also { destroyedCallbacks ->
forEach { (key, it) ->
when {
(it.first as? Activity?)?.isDestroyed == true -> destroyedCallbacks.add(key)
isCurrentBroadcast(it.first) -> it.second(action, intent)
}
}
destroyedCallbacks.takeIf { it.isNotEmpty() }?.forEach { remove(it) }
}
}
}.onFailure { yLoggerE(msg = "Received action \"$action\" failed", e = it) }
}
}
}
}
/** 检查 API 装载状态 */
private fun checkApi() {
if (YukiHookAPI.isLoadedFromBaseContext) error("YukiHookDataChannel not allowed in Custom Hook API")
if (isXposedEnvironment && YukiXposedModule.modulePackageName.isBlank())
error("Xposed modulePackageName load failed, please reset and rebuild it")
isAllowSendTooLargeData = false
}
/**
* 是否为当前正在使用的广播回调事件
* @param context 当前实例
* @return [Boolean]
*/
private fun isCurrentBroadcast(context: Context?) = runCatching {
@Suppress("DEPRECATION")
context is Application || isXposedEnvironment || (((context ?: receiverContext)
?.getSystemService(ACTIVITY_SERVICE) as? ActivityManager?)
?.getRunningTasks(9999)?.filter { context?.javaClass?.name == it?.topActivity?.className }?.size ?: 0) > 0
}.getOrNull() ?: yLoggerW(msg = "Couldn't got current Activity status because a SecurityException blocked it").let { false }
/**
* 获取宿主广播 Action 名称
* @param packageName 包名
* @return [String]
*/
private fun hostActionName(packageName: String) = "yuki_hook_host_data_channel_${packageName.trim().hashCode()}"
/**
* 获取模块广播 Action 名称
* @param context 实例 - 默认空
* @return [String]
*/
private fun moduleActionName(context: Context? = null) =
"yuki_hook_module_data_channel_${YukiXposedModule.modulePackageName.ifBlank { context?.packageName ?: "" }.trim().hashCode()}"
/**
* 注册广播
* @param context 目标 Hook APP (宿主) 或模块全局上下文实例 - 为空停止注册
* @param packageName 包名 - 为空获取 [context] 的 [Context.getPackageName]
*/
internal fun register(context: Context?, packageName: String = context?.packageName ?: "") {
if (YukiHookAPI.Configs.isEnableDataChannel.not() || context == null) return
receiverContext = context
IntentFilter().apply {
addAction(if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context))
}.also { filter ->
/**
* 从 Android 14 (及预览版) 开始
* 外部广播必须声明 [Context.RECEIVER_EXPORTED]
*/
@SuppressLint("UnspecifiedRegisterReceiverFlag")
if (Build.VERSION.SDK_INT >= 33)
context.registerReceiver(handlerReceiver, filter, Context.RECEIVER_EXPORTED)
else context.registerReceiver(handlerReceiver, filter)
}
/** 排除模块环境下模块注册自身广播 */
if (isXposedEnvironment.not()) return
nameSpace(context, packageName).with {
/** 注册监听模块与宿主的版本是否匹配 */
wait<String>(GET_MODULE_GENERATED_VERSION) { fromPackageName ->
nameSpace(context, fromPackageName).put(RESULT_MODULE_GENERATED_VERSION, moduleGeneratedVersion)
}
/** 注册监听模块与宿主之间的调试日志数据 */
wait<String>(GET_YUKI_LOGGER_INMEMORY_DATA) { fromPackageName ->
nameSpace(context, fromPackageName).put(RESULT_YUKI_LOGGER_INMEMORY_DATA, YukiHookLogger.inMemoryData)
}
}
}
/**
* 获取命名空间
* @param context 上下文实例
* @param packageName 目标 Hook APP (宿主) 的包名
* @return [NameSpace]
*/
internal fun nameSpace(context: Context? = null, packageName: String): NameSpace {
checkApi()
return NameSpace(context = context ?: receiverContext, packageName)
}
/**
* 分段数据临时集合实例
* @param listData [List] 数据数组
* @param mapData [Map] 数据数组
* @param setData [Set] 数据数组
* @param stringData [String] 数据数组
*/
internal inner class SegmentsTempData(
var listData: ArrayList<List<*>> = arrayListOf(),
var mapData: ArrayList<Map<*, *>> = arrayListOf(),
var setData: ArrayList<Set<*>> = arrayListOf(),
var stringData: ArrayList<String> = arrayListOf()
)
/**
* [YukiHookDataChannel] 命名空间
*
* - ❗请使用 [nameSpace] 方法来获取 [NameSpace]
* @param context 上下文实例
* @param packageName 目标 Hook APP (宿主) 的包名
*/
inner class NameSpace internal constructor(private val context: Context?, private val packageName: String) {
/** 当前分段数据临时集合数据 */
private val segmentsTempData = ConcurrentHashMap<String, SegmentsTempData>()
/**
* 键值尾部名称
* @param type 类型
* @return [String]
*/
private fun keyShortName(type: CallbackKeyType) =
"${keyNonRepeatName}_${if (isXposedEnvironment) "X" else context?.javaClass?.name ?: "M"}_${type.ordinal}"
/**
* 键值不重复名称 - 确保每个宿主使用的键值名称互不干扰
* @return [String]
*/
private val keyNonRepeatName get() = "_${packageName.hashCode()}"
/**
* 创建一个调用空间
* @param initiate 方法体
* @return [NameSpace] 可继续向下监听
*/
inline fun with(initiate: NameSpace.() -> Unit) = apply(initiate)
/**
* [YukiHookDataChannel] 允许发送的最大数据字节大小
*
* 默认为 500 KB (500 * 1024) - 详情请参考 [receiverDataMaxByteSize] 的注释
*
* 最小不能低于 100 KB (100 * 1024) - 否则会被重新设置为 100 KB (100 * 1024)
*
* 设置后将在全局生效 - 直到当前进程结束
*
* - 超出最大数据字节大小后的数据将被自动分段发送
*
* - ❗警告:请谨慎调整此参数 - 如果超出了系统能够允许的大小会引发 [TransactionTooLargeException] 异常
* @return [Int]
*/
var dataMaxByteSize
get() = receiverDataMaxByteSize
set(value) {
receiverDataMaxByteSize = if (value < 100 * 1024) 100 * 1024 else value
}
/**
* [YukiHookDataChannel] 允许发送的最大数据字节大小倍数 (分段数据)
*
* 默认为 3 - 详情请参考 [receiverDataMaxByteCompressionFactor] 的注释
*
* 最小不能低于 2 - 否则会被重新设置为 2
*
* 设置后将在全局生效 - 直到当前进程结束
*
* - 超出最大数据字节大小后的数据将按照此倍数自动划分 [receiverDataMaxByteSize] 的大小
*
* - ❗警告:请谨慎调整此参数 - 如果超出了系统能够允许的大小会引发 [TransactionTooLargeException] 异常
* @return [Int]
*/
var dataMaxByteCompressionFactor
get() = receiverDataMaxByteCompressionFactor
set(value) {
receiverDataMaxByteCompressionFactor = if (value < 2) 2 else value
}
/**
* 解除发送数据的大小限制并禁止开启分段发送功能
*
* 仅会在每次调用时生效 - 下一次没有调用此方法则此功能将被自动关闭
*
* 你还需要在整个调用域中声明注解 [CauseProblemsApi] 以消除警告
*
* - ❗若你不知道允许此功能会带来何种后果 - 请勿使用
* @return [NameSpace]
*/
@CauseProblemsApi
fun allowSendTooLargeData(): NameSpace {
isAllowSendTooLargeData = true
return this
}
/**
* 发送键值数据
* @param key 键值名称
* @param value 键值数据
*/
fun <T> put(key: String, value: T) = parseSendingData(ChannelData(key, value).toWrapper())
/**
* 发送键值数据
* @param data 键值实例
* @param value 键值数据 - 未指定为 [ChannelData.value]
*/
fun <T> put(data: ChannelData<T>, value: T? = data.value) = parseSendingData(ChannelData(data.key, value).toWrapper())
/**
* 发送键值数据
* @param data 键值实例
*/
fun put(vararg data: ChannelData<*>) = data.takeIf { it.isNotEmpty() }?.forEach { parseSendingData(it.toWrapper()) }
/**
* 仅发送键值监听 - 使用默认值 [VALUE_WAIT_FOR_LISTENER] 发送键值数据
* @param key 键值名称
*/
fun put(key: String) = put(key, VALUE_WAIT_FOR_LISTENER)
/**
* 获取键值数据
* @param key 键值名称
* @param priority 响应优先级 - 默认不设置
* @param result 回调结果数据
*/
fun <T> wait(key: String, priority: ChannelPriority? = null, result: (value: T) -> Unit) {
receiverCallbacks[key + keyShortName(CallbackKeyType.SINGLE)] = Pair(context) { action, intent ->
if (priority == null || priority.result)
if (action == if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context))
parseReceivedData(intent.getDataWrapper(key), result)
}
}
/**
* 获取键值数据
* @param data 键值实例
* @param priority 响应优先级 - 默认不设置
* @param result 回调结果数据
*/
fun <T> wait(data: ChannelData<T>, priority: ChannelPriority? = null, result: (value: T) -> Unit) {
receiverCallbacks[data.key + keyShortName(CallbackKeyType.CDATA)] = Pair(context) { action, intent ->
if (priority == null || priority.result)
if (action == if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context))
parseReceivedData(intent.getDataWrapper(data.key), result)
}
}
/**
* 仅获取监听结果 - 不获取键值数据
*
* - ❗仅限使用 [VALUE_WAIT_FOR_LISTENER] 发送的监听才能被接收
* @param key 键值名称
* @param priority 响应优先级 - 默认不设置
* @param callback 回调结果
*/
fun wait(key: String, priority: ChannelPriority? = null, callback: () -> Unit) {
receiverCallbacks[key + keyShortName(CallbackKeyType.VMFL)] = Pair(context) { action, intent ->
if (priority == null || priority.result)
if (action == if (isXposedEnvironment) hostActionName(packageName) else moduleActionName(context))
intent.getDataWrapper<String>(key)?.let { if (it.instance.value == VALUE_WAIT_FOR_LISTENER) callback() }
}
}
/**
* 获取模块与宿主的版本是否匹配
*
* 通过此方法可原生判断 Xposed 模块更新后宿主并未重新装载造成两者不匹配的情况
* @param priority 响应优先级 - 默认不设置
* @param result 回调是否匹配
*/
fun checkingVersionEquals(priority: ChannelPriority? = null, result: (Boolean) -> Unit) {
wait<String>(RESULT_MODULE_GENERATED_VERSION, priority) { result(it == moduleGeneratedVersion) }
put(GET_MODULE_GENERATED_VERSION, packageName)
}
/**
* 获取模块与宿主之间的 [ArrayList]<[YukiLoggerData]> 数据
*
* 由于模块与宿主处于不同的进程 - 我们可以使用数据通讯桥访问各自的调试日志数据
*
* - ❗模块与宿主必须启用 [YukiHookLogger.Configs.isRecord] 才能获取到调试日志数据
*
* - ❗由于 Android 限制了数据传输大小的最大值 - 如果调试日志过多将会自动进行分段发送 - 数据越大速度越慢
* @param priority 响应优先级 - 默认不设置
* @param result 回调 [ArrayList]<[YukiLoggerData]>
*/
fun obtainLoggerInMemoryData(priority: ChannelPriority? = null, result: (ArrayList<YukiLoggerData>) -> Unit) {
wait(RESULT_YUKI_LOGGER_INMEMORY_DATA, priority) { result(it) }
put(GET_YUKI_LOGGER_INMEMORY_DATA, packageName)
}
/**
* 从 [Intent] 获取接收到的任意类型数据转换为 [ChannelDataWrapper]<[T]> 实例
* @param key 键值名称
* @return [ChannelDataWrapper]<[T]> or null
*/
private fun <T> Intent.getDataWrapper(key: String) = runCatching {
@Suppress("DEPRECATION")
extras?.getSerializable(key + keyNonRepeatName) as? ChannelDataWrapper<T>
}.getOrNull()
/**
* [ChannelData]<[T]> 转换为 [ChannelDataWrapper]<[T]> 实例
* @param id 包装实例 ID - 默认为 [RandomSeed.createString]
* @param size 分段数据总大小 (长度) - 默认为 -1
* @param index 分段数据当前接收到的下标 - 默认为 -1
* @return [ChannelDataWrapper]<[T]>
*/
private fun <T> ChannelData<T>.toWrapper(id: String = RandomSeed.createString(), size: Int = -1, index: Int = -1) =
ChannelDataWrapper(id, size > 0, size, index, this)
/**
* 计算任意类型所占空间的字节大小
* @return [Int] 字节大小
*/
private fun Any.calDataByteSize(): Int {
val key = if (this is ChannelData<*>) key else "placeholder"
val value = if (this is ChannelData<*>) value else this
val bundle = Bundle().apply {
when (value) {
null -> Unit
is Boolean -> putBoolean(key, value)
is BooleanArray -> putBooleanArray(key, value)
is Byte -> putByte(key, value)
is ByteArray -> putByteArray(key, value)
is Char -> putChar(key, value)
is CharArray -> putCharArray(key, value)
is Double -> putDouble(key, value)
is DoubleArray -> putDoubleArray(key, value)
is Float -> putFloat(key, value)
is FloatArray -> putFloatArray(key, value)
is Int -> putInt(key, value)
is IntArray -> putIntArray(key, value)
is Long -> putLong(key, value)
is LongArray -> putLongArray(key, value)
is Short -> putShort(key, value)
is ShortArray -> putShortArray(key, value)
is String -> putString(key, value)
is Array<*> -> putSerializable(key, value)
is CharSequence -> putCharSequence(key, value)
is Parcelable -> putParcelable(key, value)
is Serializable -> putSerializable(key, value)
else -> error("Key-Value type ${value.javaClass.name} is not allowed")
}
}
return runCatching {
Parcel.obtain().let { parcel ->
parcel.writeBundle(bundle)
val size = parcel.dataSize()
parcel.recycle()
size
}
}.getOrNull() ?: -1
}
/**
* 处理收到的广播数据
* @param wrapper 键值数据包装类
* @param result 回调结果数据
*/
private fun <T> parseReceivedData(wrapper: ChannelDataWrapper<T>?, result: (value: T) -> Unit) {
if (YukiHookAPI.Configs.isEnableDataChannel.not()) return
if (wrapper == null) return
if (wrapper.isSegmentsType) runCatching {
val tempData = segmentsTempData[wrapper.wrapperId] ?: SegmentsTempData().apply { segmentsTempData[wrapper.wrapperId] = this }
when (wrapper.instance.value) {
is List<*> -> (wrapper.instance.value as List<*>).also { value ->
if (tempData.listData.isEmpty() && wrapper.segmentsIndex > 0) return
tempData.listData.add(wrapper.segmentsIndex, value)
if (tempData.listData.size == wrapper.segmentsSize) {
result(arrayListOf<Any?>().also { list -> tempData.listData.forEach { list.addAll(it) } } as T)
tempData.listData.clear()
segmentsTempData.remove(wrapper.wrapperId)
}
}
is Map<*, *> -> (wrapper.instance.value as Map<*, *>).also { value ->
if (tempData.mapData.isEmpty() && wrapper.segmentsIndex > 0) return
tempData.mapData.add(wrapper.segmentsIndex, value)
if (tempData.mapData.size == wrapper.segmentsSize) {
result(hashMapOf<Any?, Any?>().also { map -> tempData.mapData.forEach { it.forEach { (k, v) -> map[k] = v } } } as T)
tempData.mapData.clear()
segmentsTempData.remove(wrapper.wrapperId)
}
}
is Set<*> -> (wrapper.instance.value as Set<*>).also { value ->
if (tempData.setData.isEmpty() && wrapper.segmentsIndex > 0) return
tempData.setData.add(wrapper.segmentsIndex, value)
if (tempData.setData.size == wrapper.segmentsSize) {
result(hashSetOf<Any?>().also { set -> tempData.setData.forEach { set.addAll(it) } } as T)
tempData.setData.clear()
segmentsTempData.remove(wrapper.wrapperId)
}
}
is String -> (wrapper.instance.value as String).also { value ->
if (tempData.stringData.isEmpty() && wrapper.segmentsIndex > 0) return
tempData.stringData.add(wrapper.segmentsIndex, value)
if (tempData.stringData.size == wrapper.segmentsSize) {
result(StringBuilder().apply { tempData.stringData.forEach { append(it) } }.toString() as T)
tempData.stringData.clear()
segmentsTempData.remove(wrapper.wrapperId)
}
}
else -> yLoggerE(msg = "Unsupported segments data key of \"${wrapper.instance.key}\"'s type")
}
}.onFailure {
yLoggerE(msg = "YukiHookDataChannel cannot merge this segments data key of \"${wrapper.instance.key}\"", e = it)
} else wrapper.instance.value?.let { e -> result(e) }
}
/**
* 处理需要发送的广播数据
* @param wrapper 键值数据包装类
*/
private fun parseSendingData(wrapper: ChannelDataWrapper<*>) {
if (YukiHookAPI.Configs.isEnableDataChannel.not()) return
/** 当前包装实例 ID */
val wrapperId = RandomSeed.createString()
/** 当前需要发送的数据字节大小 */
val dataByteSize = wrapper.instance.calDataByteSize()
if (dataByteSize < 0 && isAllowSendTooLargeData.not()) return yLoggerE(
msg = "YukiHookDataChannel cannot calculate the byte size of the data key of \"${wrapper.instance.key}\" to be sent, " +
"so this data cannot be sent\n" +
"If you want to lift this restriction, use the allowSendTooLargeData function when calling, " +
"but this may cause the app crash"
)
/**
* 如果数据过大打印警告信息 - 仅限 [YukiHookAPI.Configs.isDebug] 启用时生效
* @param name 数据类型名称
* @param size 分段总大小 (长度)
*/
fun loggerForTooLargeData(name: String, size: Int) {
if (YukiHookAPI.Configs.isDebug) yLoggerW(
msg = "This data key of \"${wrapper.instance.key}\" type $name is too large (total ${dataByteSize / 1024f} KB, " +
"limit ${receiverDataMaxByteSize / 1024f} KB), will be segmented to $size piece to send"
)
}
/**
* 如果数据过大且无法分段打印错误信息
* @param suggestionMessage 建议内容 - 默认空
*/
fun loggerForUnprocessableData(suggestionMessage: String = "") = yLoggerE(
msg = "YukiHookDataChannel cannot send this data key of \"${wrapper.instance.key}\" type ${wrapper.instance.value?.javaClass}, " +
"because it is too large (total ${dataByteSize / 1024f} KB, " +
"limit ${receiverDataMaxByteSize / 1024f} KB) and cannot be segmented\n" +
(if (suggestionMessage.isNotBlank()) "$suggestionMessage\n" else "") +
"If you want to lift this restriction, use the allowSendTooLargeData function when calling, " +
"but this may cause the app crash"
)
/**
* 如果数据过大且无法分段打印错误信息 (首元素超出 - 分段数组内容为空)
* @param name 数据类型名称
*/
fun loggerForUnprocessableDataByFirstElement(name: String) = loggerForUnprocessableData(
suggestionMessage = "Failed to segment $name type because the size of its first element has exceeded the maximum limit"
)
when {
wrapper.isSegmentsType || isAllowSendTooLargeData -> pushReceiver(wrapper)
dataByteSize >= receiverDataMaxByteSize -> when (wrapper.instance.value) {
is List<*> -> (wrapper.instance.value as List<*>).also { value ->
val segments = arrayListOf<List<*>>()
var segment = arrayListOf<Any?>()
value.forEach {
segment.add(it)
if (segment.calDataByteSize() >= receiverDataSegmentMaxByteSize) {
segments.add(segment)
segment = arrayListOf()
}
}
if (segment.isNotEmpty()) segments.add(segment)
loggerForTooLargeData(name = "List", segments.size)
segments.takeIf { it.isNotEmpty() }?.forEachIndexed { p, it ->
pushReceiver(ChannelData(wrapper.instance.key, it).toWrapper(wrapperId, segments.size, p))
} ?: loggerForUnprocessableDataByFirstElement(name = "List")
}
is Map<*, *> -> (wrapper.instance.value as Map<*, *>).also { value ->
val segments = arrayListOf<Map<*, *>>()
var segment = hashMapOf<Any?, Any?>()
value.forEach { (k, v) ->
segment[k] = v
if (segment.calDataByteSize() >= receiverDataSegmentMaxByteSize) {
segments.add(segment)
segment = hashMapOf()
}
}
if (segment.isNotEmpty()) segments.add(segment)
loggerForTooLargeData(name = "Map", segments.size)
segments.takeIf { it.isNotEmpty() }?.forEachIndexed { p, it ->
pushReceiver(ChannelData(wrapper.instance.key, it).toWrapper(wrapperId, segments.size, p))
} ?: loggerForUnprocessableDataByFirstElement(name = "Map")
}
is Set<*> -> (wrapper.instance.value as Set<*>).also { value ->
val segments = arrayListOf<Set<*>>()
var segment = hashSetOf<Any?>()
value.forEach {
segment.add(it)
if (segment.calDataByteSize() >= receiverDataSegmentMaxByteSize) {
segments.add(segment)
segment = hashSetOf()
}
}
if (segment.isNotEmpty()) segments.add(segment)
loggerForTooLargeData(name = "Set", segments.size)
segments.takeIf { it.isNotEmpty() }?.forEachIndexed { p, it ->
pushReceiver(ChannelData(wrapper.instance.key, it).toWrapper(wrapperId, segments.size, p))
} ?: loggerForUnprocessableDataByFirstElement(name = "Set")
}
is String -> (wrapper.instance.value as String).also { value ->
/** 由于字符会被按照双字节计算 - 所以这里将限制字节大小除以 2 */
val twoByteMaxSize = receiverDataMaxByteSize / 2
val segments = arrayListOf<String>()
for (i in 0..value.length step twoByteMaxSize)
if (i + twoByteMaxSize <= value.length)
segments.add(value.substring(i, i + twoByteMaxSize))
else segments.add(value.substring(i, value.length))
if (segments.size == 1) return pushReceiver(wrapper)
loggerForTooLargeData(name = "String", segments.size)
segments.takeIf { it.isNotEmpty() }?.forEachIndexed { p, it ->
pushReceiver(ChannelData(wrapper.instance.key, it).toWrapper(wrapperId, segments.size, p))
} ?: loggerForUnprocessableDataByFirstElement(name = "String")
}
is ByteArray, is CharArray, is ShortArray,
is IntArray, is LongArray, is FloatArray,
is DoubleArray, is BooleanArray, is Array<*> -> loggerForUnprocessableData(
suggestionMessage = "Primitive Array type like String[], int[] ... cannot be segmented, " +
"the suggestion is send those data using List type"
)
else -> loggerForUnprocessableData()
}
else -> pushReceiver(wrapper)
}
}
/**
* 发送广播
* @param wrapper 键值数据包装类
*/
private fun pushReceiver(wrapper: ChannelDataWrapper<*>) {
/** 发送广播 */
(context ?: AppParasitics.currentApplication)?.sendBroadcast(Intent().apply {
action = if (isXposedEnvironment) moduleActionName() else hostActionName(packageName)
/** 由于系统框架的包名可能不唯一 - 为防止发生问题不再对系统框架的广播设置接收者包名 */
if (packageName != AppParasitics.SYSTEM_FRAMEWORK_NAME)
setPackage(if (isXposedEnvironment) YukiXposedModule.modulePackageName else packageName)
putExtra(wrapper.instance.key + keyNonRepeatName, wrapper)
}) ?: yLoggerE(msg = "Failed to sendBroadcast like \"${wrapper.instance.key}\", because got null context in \"$packageName\"")
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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/5/16.
*/
package com.highcapable.yukihookapi.hook.xposed.channel.data
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
import java.io.Serializable
/**
* 数据通讯桥键值构造类
*
* 这个类是对 [YukiHookDataChannel] 的一个扩展用法
*
* 详情请参考 [API 文档 - ChannelData](https://fankes.github.io/YukiHookAPI/zh-cn/api/public/com/highcapable/yukihookapi/hook/xposed/channel/data/ChannelData)
*
* For English version, see [API Document - ChannelData](https://fankes.github.io/YukiHookAPI/en/api/public/com/highcapable/yukihookapi/hook/xposed/channel/data/ChannelData)
* @param key 键值
* @param value 键值数据 - 作为接收数据时可空
*/
data class ChannelData<T>(var key: String, var value: T? = null) : Serializable

View File

@@ -0,0 +1,47 @@
/*
* 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 2023/1/6.
*/
package com.highcapable.yukihookapi.hook.xposed.channel.data.wrapper
import com.highcapable.yukihookapi.hook.xposed.channel.data.ChannelData
import java.io.Serializable
/**
* 数据通讯桥键值数据包装类
* @param wrapperId 包装实例 ID
* @param isSegmentsType 是否为分段数据
* @param segmentsSize 分段数据总大小 (长度)
* @param segmentsIndex 分段数据当前接收到的下标
* @param instance 原始数据实例
*/
internal data class ChannelDataWrapper<T>(
val wrapperId: String,
val isSegmentsType: Boolean,
val segmentsSize: Int,
val segmentsIndex: Int,
val instance: ChannelData<T>
) : Serializable

View File

@@ -0,0 +1,45 @@
/*
* 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 2023/1/3.
*/
package com.highcapable.yukihookapi.hook.xposed.channel.priority
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
/**
* 数据通讯桥响应优先级构造类
*
* 这个类是对 [YukiHookDataChannel] 的一个扩展用法
* @param conditions 条件方法体
*/
class ChannelPriority(private val conditions: () -> Boolean) {
/**
* 获取条件方法体结果
* @return [Boolean]
*/
internal val result get() = conditions()
}

View File

@@ -0,0 +1,485 @@
/*
* 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/8/14.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
@file:Suppress("QueryPermissionsNeeded")
package com.highcapable.yukihookapi.hook.xposed.parasitic
import android.annotation.SuppressLint
import android.app.Activity
import android.app.ActivityManager
import android.app.AndroidAppHelper
import android.app.Application
import android.app.Instrumentation
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import android.os.Handler
import android.util.ArrayMap
import androidx.annotation.RequiresApi
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.core.api.compat.HookApiProperty
import com.highcapable.yukihookapi.hook.core.api.helper.YukiHookHelper
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiHookCallback
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiMemberHook
import com.highcapable.yukihookapi.hook.core.api.proxy.YukiMemberReplacement
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.hasClass
import com.highcapable.yukihookapi.hook.factory.hasMethod
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.factory.toClass
import com.highcapable.yukihookapi.hook.factory.toClassOrNull
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.type.android.ActivityManagerClass
import com.highcapable.yukihookapi.hook.type.android.ActivityManagerNativeClass
import com.highcapable.yukihookapi.hook.type.android.ActivityTaskManagerClass
import com.highcapable.yukihookapi.hook.type.android.ActivityThreadClass
import com.highcapable.yukihookapi.hook.type.android.ApplicationClass
import com.highcapable.yukihookapi.hook.type.android.ContextClass
import com.highcapable.yukihookapi.hook.type.android.ContextImplClass
import com.highcapable.yukihookapi.hook.type.android.HandlerClass
import com.highcapable.yukihookapi.hook.type.android.IActivityManagerClass
import com.highcapable.yukihookapi.hook.type.android.IActivityTaskManagerClass
import com.highcapable.yukihookapi.hook.type.android.InstrumentationClass
import com.highcapable.yukihookapi.hook.type.android.IntentClass
import com.highcapable.yukihookapi.hook.type.android.SingletonClass
import com.highcapable.yukihookapi.hook.type.android.UserHandleClass
import com.highcapable.yukihookapi.hook.type.java.BooleanType
import com.highcapable.yukihookapi.hook.type.java.IntType
import com.highcapable.yukihookapi.hook.type.java.JavaClassLoader
import com.highcapable.yukihookapi.hook.type.java.StringClass
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.bridge.status.YukiXposedModuleStatus
import com.highcapable.yukihookapi.hook.xposed.bridge.type.HookEntryType
import com.highcapable.yukihookapi.hook.xposed.channel.YukiHookDataChannel
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config.ActivityProxyConfig
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.InstrumentationDelegate
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.impl.HandlerDelegateImpl
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.impl.IActivityManagerProxyImpl
/**
* 这是一个管理 APP 寄生功能的控制类
*
* 通过这些功能即可轻松实现对 (Xposed) 宿主环境的 [Resources] 注入以及 [Activity] 代理
*/
internal object AppParasitics {
/** Android 系统框架名称 */
internal const val SYSTEM_FRAMEWORK_NAME = "android"
/** [YukiHookDataChannel] 是否已经注册 */
private var isDataChannelRegistered = false
/** [Activity] 代理是否已经注册 */
private var isActivityProxyRegistered = false
/** [ClassLoader] 是否已被 Hook */
private var isClassLoaderHooked = false
/** [ClassLoader] 监听回调数组 */
private var classLoaderCallbacks = ArrayMap<Int, (Class<*>) -> Unit>()
/**
* 当前 Hook APP (宿主) 的全局生命周期 [Application]
*
* 需要 [YukiHookAPI.Configs.isEnableDataChannel] or [AppLifecycleCallback.isCallbackSetUp] 才会生效
*/
internal var hostApplication: Application? = null
/**
* 当前环境中使用的 [ClassLoader]
*
* 装载位于 (Xposed) 宿主环境与模块环境时均使用当前 DEX 内的 [ClassLoader]
* @return [ClassLoader]
* @throws IllegalStateException 如果 [ClassLoader] 为空
*/
internal val baseClassLoader get() = classOf<YukiHookAPI>().classLoader ?: error("Operating system not supported")
/**
* 获取当前系统框架的 [Context]
* @return [Context] ContextImpl 实例对象
* @throws IllegalStateException 如果获取不到系统框架的 [Context]
*/
internal val systemContext
get() = ActivityThreadClass.method { name = "currentActivityThread" }.ignored().get().call()?.let {
ActivityThreadClass.method { name = "getSystemContext" }.ignored().get(it).invoke<Context?>()
} ?: error("Failed to got SystemContext")
/**
* 获取当前宿主的 [Application]
* @return [Application] or null
*/
internal val currentApplication
get() = runCatching { AndroidAppHelper.currentApplication() }.getOrNull() ?: runCatching {
ActivityThreadClass.method { name = "currentApplication" }.ignored().get().invoke<Application>()
}.getOrNull()
/**
* 获取当前宿主的 [ApplicationInfo]
* @return [ApplicationInfo] or null
*/
internal val currentApplicationInfo
get() = runCatching { AndroidAppHelper.currentApplicationInfo() }.getOrNull() ?: runCatching {
ActivityThreadClass.method { name = "currentActivityThread" }.ignored().get().call()
?.current(ignored = true)?.field { name = "mBoundApplication" }
?.current(ignored = true)?.field { name = "appInfo" }
?.cast<ApplicationInfo>()
}.getOrNull()
/**
* 获取当前宿主的包名
* @return [String]
*/
internal val currentPackageName get() = currentApplicationInfo?.packageName ?: SYSTEM_FRAMEWORK_NAME
/**
* 获取当前宿主的进程名
* @return [String]
*/
internal val currentProcessName
get() = runCatching { AndroidAppHelper.currentProcessName() }.getOrNull() ?: runCatching {
ActivityThreadClass.method { name = "currentPackageName" }.ignored().get().string()
.takeIf { it.isNotBlank() } ?: SYSTEM_FRAMEWORK_NAME
}.getOrNull() ?: SYSTEM_FRAMEWORK_NAME
/**
* 获取指定 [packageName] 的用户 ID
*
* 机主为 0 - 应用双开 (分身) 或工作资料因系统环境不同 ID 也各不相同
* @param packageName 当前包名
* @return [Int]
*/
internal fun findUserId(packageName: String) = runCatching {
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
UserHandleClass.method {
name = "getUserId"
param(IntType)
}.ignored().get().int(systemContext.packageManager.getApplicationInfo(packageName, PackageManager.GET_ACTIVITIES).uid)
}.getOrNull() ?: 0
/**
* 监听并 Hook 当前 [ClassLoader] 的 [ClassLoader.loadClass] 方法
* @param loader 当前 [ClassLoader]
* @param result 回调 - ([Class] 实例对象)
*/
internal fun hookClassLoader(loader: ClassLoader?, result: (Class<*>) -> Unit) {
if (loader == null) return
if (YukiXposedModule.isXposedEnvironment.not()) return yLoggerW(msg = "You can only use hook ClassLoader method in Xposed Environment")
classLoaderCallbacks[loader.hashCode()] = result
if (isClassLoaderHooked) return
runCatching {
YukiHookHelper.hook(JavaClassLoader.method { name = "loadClass"; param(StringClass, BooleanType) }, object : YukiMemberHook() {
override fun afterHookedMember(param: Param) {
param.instance?.also { loader ->
(param.result as? Class<*>?)?.also { classLoaderCallbacks[loader.hashCode()]?.invoke(it) }
}
}
})
isClassLoaderHooked = true
}.onFailure { yLoggerW(msg = "Try to hook ClassLoader failed: $it") }
}
/**
* Hook 模块 APP 相关功能 - 包括自身激活状态、Resources Hook 支持状态以及 [SharedPreferences]
* @param loader 模块的 [ClassLoader]
* @param type 当前正在进行的 Hook 类型
*/
internal fun hookModuleAppRelated(loader: ClassLoader?, type: HookEntryType) {
if (YukiHookAPI.Configs.isEnableHookSharedPreferences && type == HookEntryType.PACKAGE)
YukiHookHelper.hook(ContextImplClass.method { name = "setFilePermissionsFromMode" }, object : YukiMemberHook() {
override fun beforeHookedMember(param: Param) {
if ((param.args?.get(0) as? String?)?.endsWith("preferences.xml") == true) param.args?.set(1, 1)
}
})
if (YukiHookAPI.Configs.isEnableHookModuleStatus.not()) return
YukiXposedModuleStatus.IMPL_CLASS_NAME.toClassOrNull(loader)?.apply {
if (type != HookEntryType.RESOURCES) {
YukiHookHelper.hook(method { name = YukiXposedModuleStatus.IS_ACTIVE_METHOD_NAME },
object : YukiMemberReplacement() {
override fun replaceHookedMember(param: Param) = true
})
YukiHookHelper.hook(method { name = YukiXposedModuleStatus.GET_EXECUTOR_NAME_METHOD_NAME },
object : YukiMemberReplacement() {
override fun replaceHookedMember(param: Param) = HookApiProperty.name
})
YukiHookHelper.hook(
method { name = YukiXposedModuleStatus.GET_EXECUTOR_API_LEVEL_METHOD_NAME },
object : YukiMemberReplacement() {
override fun replaceHookedMember(param: Param) = HookApiProperty.apiLevel
})
YukiHookHelper.hook(
method { name = YukiXposedModuleStatus.GET_EXECUTOR_VERSION_NAME_METHOD_NAME },
object : YukiMemberReplacement() {
override fun replaceHookedMember(param: Param) = HookApiProperty.versionName
})
YukiHookHelper.hook(
method { name = YukiXposedModuleStatus.GET_EXECUTOR_VERSION_CODE_METHOD_NAME },
object : YukiMemberReplacement() {
override fun replaceHookedMember(param: Param) = HookApiProperty.versionCode
})
} else YukiHookHelper.hook(method { name = YukiXposedModuleStatus.IS_SUPPORT_RESOURCES_HOOK_METHOD_NAME },
object : YukiMemberReplacement() {
override fun replaceHookedMember(param: Param) = true
})
}
}
/**
* 注入当前 Hook APP (宿主) 全局生命周期
* @param packageName 包名
*/
internal fun registerToAppLifecycle(packageName: String) {
/**
* 向当前 Hook APP (宿主) 抛出异常或打印错误日志
* @param throwable 当前异常
*/
fun YukiHookCallback.Param.throwToAppOrLogger(throwable: Throwable) {
if (AppLifecycleCallback.isOnFailureThrowToApp) this.throwable = throwable
else yLoggerE(msg = "An exception occurred during AppLifecycle event", e = throwable)
}
/** Hook [Application] 装载方法 */
runCatching {
if (AppLifecycleCallback.isCallbackSetUp) {
YukiHookHelper.hook(ApplicationClass.method { name = "attach"; param(ContextClass) }, object : YukiMemberHook() {
override fun beforeHookedMember(param: Param) {
runCatching {
(param.args?.get(0) as? Context?)?.also { AppLifecycleCallback.attachBaseContextCallback?.invoke(it, false) }
}.onFailure { param.throwToAppOrLogger(it) }
}
override fun afterHookedMember(param: Param) {
runCatching {
(param.args?.get(0) as? Context?)?.also { AppLifecycleCallback.attachBaseContextCallback?.invoke(it, true) }
}.onFailure { param.throwToAppOrLogger(it) }
}
})
YukiHookHelper.hook(ApplicationClass.method { name = "onTerminate" }, object : YukiMemberHook() {
override fun afterHookedMember(param: Param) {
runCatching {
(param.instance as? Application?)?.also { AppLifecycleCallback.onTerminateCallback?.invoke(it) }
}.onFailure { param.throwToAppOrLogger(it) }
}
})
YukiHookHelper.hook(ApplicationClass.method { name = "onLowMemory" }, object : YukiMemberHook() {
override fun afterHookedMember(param: Param) {
runCatching {
(param.instance as? Application?)?.also { AppLifecycleCallback.onLowMemoryCallback?.invoke(it) }
}.onFailure { param.throwToAppOrLogger(it) }
}
})
YukiHookHelper.hook(ApplicationClass.method { name = "onTrimMemory"; param(IntType) }, object : YukiMemberHook() {
override fun afterHookedMember(param: Param) {
runCatching {
val self = param.instance as? Application? ?: return
val type = param.args?.get(0) as? Int? ?: return
AppLifecycleCallback.onTrimMemoryCallback?.invoke(self, type)
}.onFailure { param.throwToAppOrLogger(it) }
}
})
YukiHookHelper.hook(ApplicationClass.method { name = "onConfigurationChanged" }, object : YukiMemberHook() {
override fun afterHookedMember(param: Param) {
runCatching {
val self = param.instance as? Application? ?: return
val config = param.args?.get(0) as? Configuration? ?: return
AppLifecycleCallback.onConfigurationChangedCallback?.invoke(self, config)
}.onFailure { param.throwToAppOrLogger(it) }
}
})
}
if (YukiHookAPI.Configs.isEnableDataChannel || AppLifecycleCallback.isCallbackSetUp)
YukiHookHelper.hook(InstrumentationClass.method { name = "callApplicationOnCreate" }, object : YukiMemberHook() {
override fun afterHookedMember(param: Param) {
runCatching {
(param.args?.get(0) as? Application?)?.also {
/**
* 注册广播
* @param result 回调 - ([Context] 当前实例, [Intent] 当前对象)
*/
fun IntentFilter.registerReceiver(result: (Context, Intent) -> Unit) {
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (context == null || intent == null) return
result(context, intent)
}
}.also { receiver ->
/**
* 从 Android 14 (及预览版) 开始
* 外部广播必须声明 [Context.RECEIVER_EXPORTED]
*/
@SuppressLint("UnspecifiedRegisterReceiverFlag")
if (Build.VERSION.SDK_INT >= 33)
it.registerReceiver(receiver, this, Context.RECEIVER_EXPORTED)
else it.registerReceiver(receiver, this)
}
}
hostApplication = it
AppLifecycleCallback.onCreateCallback?.invoke(it)
AppLifecycleCallback.onReceiverActionsCallbacks.takeIf { e -> e.isNotEmpty() }?.forEach { (_, e) ->
if (e.first.isNotEmpty()) IntentFilter().apply {
e.first.forEach { action -> addAction(action) }
}.registerReceiver(e.second)
}
AppLifecycleCallback.onReceiverFiltersCallbacks.takeIf { e -> e.isNotEmpty() }
?.forEach { (_, e) -> e.first.registerReceiver(e.second) }
runCatching {
/** 过滤系统框架与一系列服务组件包名不唯一的情况 */
if (isDataChannelRegistered ||
(currentPackageName == SYSTEM_FRAMEWORK_NAME && packageName != SYSTEM_FRAMEWORK_NAME)
) return
YukiHookDataChannel.instance().register(it, packageName)
isDataChannelRegistered = true
}
}
}.onFailure { param.throwToAppOrLogger(it) }
}
})
}
}
/**
* 向 Hook APP (宿主) 注入当前 Xposed 模块的资源
* @param hostResources 需要注入的宿主 [Resources]
*/
internal fun injectModuleAppResources(hostResources: Resources) {
if (YukiXposedModule.isXposedEnvironment) runCatching {
if (currentPackageName == YukiXposedModule.modulePackageName)
return yLoggerE(msg = "You cannot inject module resources into yourself")
hostResources.assets.current(ignored = true).method {
name = "addAssetPath"
param(StringClass)
}.call(YukiXposedModule.moduleAppFilePath)
}.onFailure {
yLoggerE(msg = "Failed to inject module resources into [$hostResources]", e = it)
} else yLoggerW(msg = "You can only inject module resources in Xposed Environment")
}
/**
* 向 Hook APP (宿主) 注册当前 Xposed 模块的 [Activity]
* @param context 当前 [Context]
* @param proxy 代理的 [Activity]
*/
@RequiresApi(Build.VERSION_CODES.N)
internal fun registerModuleAppActivities(context: Context, proxy: Any?) {
if (isActivityProxyRegistered) return
if (YukiXposedModule.isXposedEnvironment.not()) return yLoggerW(msg = "You can only register Activity Proxy in Xposed Environment")
if (context.packageName == YukiXposedModule.modulePackageName) return yLoggerE(msg = "You cannot register Activity Proxy into yourself")
if (Build.VERSION.SDK_INT < 24) return yLoggerE(msg = "Activity Proxy only support for Android 7.0 (API 24) or higher")
runCatching {
ActivityProxyConfig.apply {
proxyIntentName = "${YukiXposedModule.modulePackageName}.ACTIVITY_PROXY_INTENT"
proxyClassName = proxy?.let {
when (it) {
is String, is CharSequence -> it.toString()
is Class<*> -> it.name
else -> error("This proxy [$it] type is not allowed")
}
}?.takeIf { it.isNotBlank() } ?: context.packageManager?.runCatching {
@Suppress("DEPRECATION", "KotlinRedundantDiagnosticSuppress")
queryIntentActivities(getLaunchIntentForPackage(context.packageName)!!, 0).first().activityInfo.name
}?.getOrNull() ?: ""
if ((proxyClassName.hasClass(context.classLoader) && proxyClassName.toClass(context.classLoader).hasMethod {
name = "setIntent"; param(IntentClass); superClass()
}).not()
) (if (proxyClassName.isBlank()) error("Cound not got launch intent for package \"${context.packageName}\"")
else error("Could not found \"$proxyClassName\" or Class is not a type of Activity"))
}
/** Patched [Instrumentation] */
ActivityThreadClass.field { name = "sCurrentActivityThread" }.ignored().get().any()?.current(ignored = true) {
method { name = "getInstrumentation" }
.invoke<Instrumentation>()
?.also { field { name = "mInstrumentation" }.set(InstrumentationDelegate.wrapper(it)) }
HandlerClass.field { name = "mCallback" }.get(field { name = "mH" }.any()).apply {
cast<Handler.Callback?>()?.apply {
if (current().name != HandlerDelegateImpl.wrapperClassName) set(HandlerDelegateImpl.createWrapper(baseInstance = this))
} ?: set(HandlerDelegateImpl.createWrapper())
}
}
/** Patched [ActivityManager] */
runCatching {
runCatching {
ActivityManagerNativeClass.field { name = "gDefault" }.ignored().get().any()
}.getOrNull() ?: ActivityManagerClass.field { name = "IActivityManagerSingleton" }.ignored().get().any()
}.getOrNull()?.also { default ->
SingletonClass.field { name = "mInstance" }.ignored().result {
get(default).apply { any()?.also { set(IActivityManagerProxyImpl.createWrapper(IActivityManagerClass, it)) } }
ActivityTaskManagerClass?.field { name = "IActivityTaskManagerSingleton" }?.ignored()?.get()?.any()?.also { singleton ->
SingletonClass.method { name = "get" }.ignored().get(singleton).call()
get(singleton).apply { any()?.also { set(IActivityManagerProxyImpl.createWrapper(IActivityTaskManagerClass, it)) } }
}
}
}
isActivityProxyRegistered = true
}.onFailure { yLoggerE(msg = "Activity Proxy initialization failed because got an Exception", e = it) }
}
/**
* 当前 Hook APP (宿主) 的生命周期回调处理类
*/
internal object AppLifecycleCallback {
/** 是否已设置回调 */
internal var isCallbackSetUp = false
/** 是否在发生异常时将异常抛出给宿主 */
internal var isOnFailureThrowToApp = true
/** [Application.attachBaseContext] 回调 */
internal var attachBaseContextCallback: ((Context, Boolean) -> Unit)? = null
/** [Application.onCreate] 回调 */
internal var onCreateCallback: (Application.() -> Unit)? = null
/** [Application.onTerminate] 回调 */
internal var onTerminateCallback: (Application.() -> Unit)? = null
/** [Application.onLowMemory] 回调 */
internal var onLowMemoryCallback: (Application.() -> Unit)? = null
/** [Application.onTrimMemory] 回调 */
internal var onTrimMemoryCallback: ((Application, Int) -> Unit)? = null
/** [Application.onConfigurationChanged] 回调 */
internal var onConfigurationChangedCallback: ((Application, Configuration) -> Unit)? = null
/** 系统广播监听回调 */
internal val onReceiverActionsCallbacks = ArrayMap<String, Pair<Array<out String>, (Context, Intent) -> Unit>>()
/** 系统广播监听回调 */
internal val onReceiverFiltersCallbacks = ArrayMap<String, Pair<IntentFilter, (Context, Intent) -> Unit>>()
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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/8/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.annotation.CallSuper
import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources
import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.parasitic.reference.ModuleClassLoader
/**
* 代理 [Activity]
*
* 继承于此类的 [Activity] 可以同时在宿主与模块中启动
*
* - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册
*/
open class ModuleAppActivity : Activity() {
/**
* 设置当前代理的 [Activity] 类名
*
* 留空则使用 [Context.registerModuleAppActivities] 时设置的类名
*
* - ❗代理的 [Activity] 类名必须存在于宿主的 AndroidMainifest 清单中
* @return [String]
*/
open val proxyClassName get() = ""
override fun getClassLoader(): ClassLoader? = ModuleClassLoader.instance()
@CallSuper
override fun onConfigurationChanged(newConfig: Configuration) {
if (YukiXposedModule.isXposedEnvironment) injectModuleAppResources()
super.onConfigurationChanged(newConfig)
}
@CallSuper
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = classLoader
super.onRestoreInstanceState(savedInstanceState)
}
}

View File

@@ -0,0 +1,88 @@
/*
* 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/8/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base
import android.app.Activity
import android.content.Context
import android.content.res.Configuration
import android.os.Bundle
import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity
import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources
import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.parasitic.reference.ModuleClassLoader
/**
* 代理 [AppCompatActivity]
*
* 继承于此类的 [Activity] 可以同时在宿主与模块中启动
*
* - 在 (Xposed) 宿主环境需要在宿主启动时调用 [Context.registerModuleAppActivities] 进行注册
*
* - 在 (Xposed) 宿主环境需要重写 [moduleTheme] 设置 AppCompat 主题 - 否则会无法启动
*/
open class ModuleAppCompatActivity : AppCompatActivity() {
/**
* 设置当前代理的 [Activity] 类名
*
* 留空则使用 [Context.registerModuleAppActivities] 时设置的类名
*
* - ❗代理的 [Activity] 类名必须存在于宿主的 AndroidMainifest 清单中
* @return [String]
*/
open val proxyClassName get() = ""
/**
* 设置当前代理的 [Activity] 主题
* @return [Int]
*/
open val moduleTheme get() = -1
override fun getClassLoader(): ClassLoader? = ModuleClassLoader.instance()
@CallSuper
override fun onConfigurationChanged(newConfig: Configuration) {
if (YukiXposedModule.isXposedEnvironment) injectModuleAppResources()
super.onConfigurationChanged(newConfig)
}
@CallSuper
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
savedInstanceState.getBundle("android:viewHierarchyState")?.classLoader = classLoader
super.onRestoreInstanceState(savedInstanceState)
}
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
if (YukiXposedModule.isXposedEnvironment && moduleTheme != -1) setTheme(moduleTheme)
super.onCreate(savedInstanceState)
}
}

View File

@@ -0,0 +1,47 @@
/*
* 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/8/14.
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config
import android.app.Activity
import android.content.Intent
/**
* 当前代理的 [Activity] 参数配置类
*/
internal object ActivityProxyConfig {
/**
* 用于代理的 [Intent] 名称
*/
internal var proxyIntentName = ""
/**
* 需要代理的 [Activity] 类名
*/
internal var proxyClassName = ""
}

View File

@@ -0,0 +1,336 @@
/*
* 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/8/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate
import android.app.Activity
import android.app.Application
import android.app.Instrumentation
import android.app.UiAutomation
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.os.Looper
import android.os.PersistableBundle
import android.os.TestLooperManager
import android.view.KeyEvent
import android.view.MotionEvent
import com.highcapable.yukihookapi.hook.factory.buildOf
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources
import com.highcapable.yukihookapi.hook.factory.processName
import com.highcapable.yukihookapi.hook.factory.toClass
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
/**
* 代理当前 [Instrumentation]
* @param baseInstance 原始实例
*/
internal class InstrumentationDelegate private constructor(private val baseInstance: Instrumentation) : Instrumentation() {
internal companion object {
/**
* 从 [Instrumentation] 创建 [InstrumentationDelegate] 实例
* @param baseInstance [Instrumentation] 实例
* @return [InstrumentationDelegate]
*/
internal fun wrapper(baseInstance: Instrumentation) = InstrumentationDelegate(baseInstance)
}
/**
* 注入当前 [Activity] 生命周期
* @param icicle [Bundle]
*/
private fun Activity.injectLifecycle(icicle: Bundle?) {
if (icicle != null && current().name.startsWith(YukiXposedModule.modulePackageName))
icicle.classLoader = AppParasitics.baseClassLoader
if (current().name.startsWith(YukiXposedModule.modulePackageName)) injectModuleAppResources()
}
override fun newActivity(cl: ClassLoader?, className: String?, intent: Intent?): Activity? = try {
baseInstance.newActivity(cl, className, intent)
} catch (e: Throwable) {
if (className?.startsWith(YukiXposedModule.modulePackageName) == true)
className.toClass().buildOf<Activity>() ?: throw e
else throw e
}
override fun onCreate(arguments: Bundle?) {
baseInstance.onCreate(arguments)
}
override fun start() {
baseInstance.start()
}
override fun onStart() {
baseInstance.onStart()
}
override fun onException(obj: Any?, e: Throwable?) = baseInstance.onException(obj, e)
override fun sendStatus(resultCode: Int, results: Bundle?) {
baseInstance.sendStatus(resultCode, results)
}
override fun addResults(results: Bundle?) {
if (Build.VERSION.SDK_INT >= 26) baseInstance.addResults(results)
}
override fun finish(resultCode: Int, results: Bundle?) {
baseInstance.finish(resultCode, results)
}
override fun setAutomaticPerformanceSnapshots() {
baseInstance.setAutomaticPerformanceSnapshots()
}
override fun startPerformanceSnapshot() {
baseInstance.startPerformanceSnapshot()
}
override fun endPerformanceSnapshot() {
baseInstance.endPerformanceSnapshot()
}
override fun onDestroy() {
baseInstance.onDestroy()
}
override fun getContext(): Context? = baseInstance.context
override fun getComponentName(): ComponentName? = baseInstance.componentName
override fun getTargetContext(): Context? = baseInstance.targetContext
override fun getProcessName(): String? =
if (Build.VERSION.SDK_INT >= 26) baseInstance.processName else AppParasitics.systemContext.processName
override fun isProfiling() = baseInstance.isProfiling
override fun startProfiling() {
baseInstance.startProfiling()
}
override fun stopProfiling() {
baseInstance.stopProfiling()
}
override fun setInTouchMode(inTouch: Boolean) {
baseInstance.setInTouchMode(inTouch)
}
override fun waitForIdle(recipient: Runnable?) {
baseInstance.waitForIdle(recipient)
}
override fun waitForIdleSync() {
baseInstance.waitForIdleSync()
}
override fun runOnMainSync(runner: Runnable?) {
baseInstance.runOnMainSync(runner)
}
override fun startActivitySync(intent: Intent?): Activity? = baseInstance.startActivitySync(intent)
override fun startActivitySync(intent: Intent, options: Bundle?): Activity =
if (Build.VERSION.SDK_INT >= 28) baseInstance.startActivitySync(intent, options) else error("Operating system not supported")
override fun addMonitor(monitor: ActivityMonitor?) {
baseInstance.addMonitor(monitor)
}
override fun addMonitor(cls: String?, result: ActivityResult?, block: Boolean): ActivityMonitor? =
baseInstance.addMonitor(cls, result, block)
override fun addMonitor(filter: IntentFilter?, result: ActivityResult?, block: Boolean): ActivityMonitor? =
baseInstance.addMonitor(filter, result, block)
override fun checkMonitorHit(monitor: ActivityMonitor?, minHits: Int) = baseInstance.checkMonitorHit(monitor, minHits)
override fun waitForMonitor(monitor: ActivityMonitor?): Activity? = baseInstance.waitForMonitor(monitor)
override fun waitForMonitorWithTimeout(monitor: ActivityMonitor?, timeOut: Long): Activity? =
baseInstance.waitForMonitorWithTimeout(monitor, timeOut)
override fun removeMonitor(monitor: ActivityMonitor?) {
baseInstance.removeMonitor(monitor)
}
override fun invokeContextMenuAction(targetActivity: Activity?, id: Int, flag: Int) =
baseInstance.invokeContextMenuAction(targetActivity, id, flag)
override fun invokeMenuActionSync(targetActivity: Activity?, id: Int, flag: Int) =
baseInstance.invokeMenuActionSync(targetActivity, id, flag)
override fun sendCharacterSync(keyCode: Int) {
baseInstance.sendCharacterSync(keyCode)
}
override fun sendKeyDownUpSync(key: Int) {
baseInstance.sendKeyDownUpSync(key)
}
override fun sendKeySync(event: KeyEvent?) {
baseInstance.sendKeySync(event)
}
override fun sendPointerSync(event: MotionEvent?) {
baseInstance.sendPointerSync(event)
}
override fun sendStringSync(text: String?) {
baseInstance.sendStringSync(text)
}
override fun sendTrackballEventSync(event: MotionEvent?) {
baseInstance.sendTrackballEventSync(event)
}
override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application? =
baseInstance.newApplication(cl, className, context)
override fun callApplicationOnCreate(app: Application?) {
baseInstance.callApplicationOnCreate(app)
}
override fun newActivity(
clazz: Class<*>?, context: Context?,
token: IBinder?, application: Application?,
intent: Intent?, info: ActivityInfo?,
title: CharSequence?, parent: Activity?,
id: String?, lastNonConfigurationInstance: Any?
): Activity? = baseInstance.newActivity(
clazz, context,
token, application,
intent, info, title,
parent, id, lastNonConfigurationInstance
)
override fun callActivityOnCreate(activity: Activity, icicle: Bundle?, persistentState: PersistableBundle?) {
activity.injectLifecycle(icicle)
baseInstance.callActivityOnCreate(activity, icicle, persistentState)
}
override fun callActivityOnCreate(activity: Activity, icicle: Bundle?) {
activity.injectLifecycle(icicle)
baseInstance.callActivityOnCreate(activity, icicle)
}
override fun callActivityOnDestroy(activity: Activity?) {
baseInstance.callActivityOnDestroy(activity)
}
override fun callActivityOnRestoreInstanceState(activity: Activity, savedInstanceState: Bundle) {
baseInstance.callActivityOnRestoreInstanceState(activity, savedInstanceState)
}
override fun callActivityOnRestoreInstanceState(activity: Activity, savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
baseInstance.callActivityOnRestoreInstanceState(activity, savedInstanceState, persistentState)
}
override fun callActivityOnPostCreate(activity: Activity, savedInstanceState: Bundle?) {
baseInstance.callActivityOnPostCreate(activity, savedInstanceState)
}
override fun callActivityOnPostCreate(activity: Activity, savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
baseInstance.callActivityOnPostCreate(activity, savedInstanceState, persistentState)
}
override fun callActivityOnNewIntent(activity: Activity?, intent: Intent?) {
baseInstance.callActivityOnNewIntent(activity, intent)
}
override fun callActivityOnStart(activity: Activity?) {
baseInstance.callActivityOnStart(activity)
}
override fun callActivityOnRestart(activity: Activity?) {
baseInstance.callActivityOnRestart(activity)
}
override fun callActivityOnPause(activity: Activity?) {
baseInstance.callActivityOnPause(activity)
}
override fun callActivityOnResume(activity: Activity?) {
baseInstance.callActivityOnResume(activity)
}
override fun callActivityOnStop(activity: Activity?) {
baseInstance.callActivityOnStop(activity)
}
override fun callActivityOnUserLeaving(activity: Activity?) {
baseInstance.callActivityOnUserLeaving(activity)
}
override fun callActivityOnSaveInstanceState(activity: Activity, outState: Bundle) {
baseInstance.callActivityOnSaveInstanceState(activity, outState)
}
override fun callActivityOnSaveInstanceState(activity: Activity, outState: Bundle, outPersistentState: PersistableBundle) {
baseInstance.callActivityOnSaveInstanceState(activity, outState, outPersistentState)
}
override fun callActivityOnPictureInPictureRequested(activity: Activity) {
if (Build.VERSION.SDK_INT >= 30) baseInstance.callActivityOnPictureInPictureRequested(activity)
}
@Deprecated("Deprecated in Java")
@Suppress("DEPRECATION")
override fun startAllocCounting() {
baseInstance.startAllocCounting()
}
@Deprecated("Deprecated in Java")
@Suppress("DEPRECATION")
override fun stopAllocCounting() {
baseInstance.stopAllocCounting()
}
override fun getAllocCounts(): Bundle? = baseInstance.allocCounts
override fun getBinderCounts(): Bundle? = baseInstance.binderCounts
override fun getUiAutomation(): UiAutomation? = baseInstance.uiAutomation
override fun getUiAutomation(flags: Int): UiAutomation? =
if (Build.VERSION.SDK_INT >= 24) baseInstance.getUiAutomation(flags) else error("Operating system not supported")
override fun acquireLooperManager(looper: Looper?): TestLooperManager? =
if (Build.VERSION.SDK_INT >= 26) baseInstance.acquireLooperManager(looper) else error("Operating system not supported")
}

View File

@@ -0,0 +1,114 @@
/*
* 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 2023/4/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.caller
import android.app.Activity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Message
import com.highcapable.yukihookapi.annotation.YukiGenerateApi
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.type.android.ActivityThreadClass
import com.highcapable.yukihookapi.hook.type.android.ClientTransactionClass
import com.highcapable.yukihookapi.hook.type.android.IBinderClass
import com.highcapable.yukihookapi.hook.type.android.IntentClass
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config.ActivityProxyConfig
/**
* 代理当前 [Handler.Callback] 调用类
*
* - ❗装载代码将自动生成 - 请勿手动调用
*/
@YukiGenerateApi
object HandlerDelegateCaller {
/** 启动 [Activity] */
private const val LAUNCH_ACTIVITY = 100
/** 执行事务处理 */
private const val EXECUTE_TRANSACTION = 159
/**
* 调用代理的 [Handler.Callback.handleMessage] 方法
*
* - ❗装载代码将自动生成 - 请勿手动调用
* @param baseInstance 原始实例
* @param msg 当前消息实例
* @return [Boolean]
*/
@YukiGenerateApi
fun callHandleMessage(baseInstance: Handler.Callback?, msg: Message): Boolean {
when (msg.what) {
LAUNCH_ACTIVITY -> runCatching {
msg.obj.current(ignored = true).field { name = "intent" }.apply {
cast<Intent?>()?.also { intent ->
IntentClass.field { name = "mExtras" }.ignored().get(intent).cast<Bundle?>()
?.classLoader = AppParasitics.currentApplication?.classLoader
@Suppress("DEPRECATION")
if (intent.hasExtra(ActivityProxyConfig.proxyIntentName))
set(intent.getParcelableExtra(ActivityProxyConfig.proxyIntentName))
}
}
}.onFailure { yLoggerE(msg = "Activity Proxy got an Exception in msg.what [$LAUNCH_ACTIVITY]", e = it) }
EXECUTE_TRANSACTION -> msg.obj?.runCatching client@{
ClientTransactionClass.method { name = "getCallbacks" }.ignored().get(this).list<Any?>().takeIf { it.isNotEmpty() }
?.forEach { item ->
item?.current(ignored = true)?.takeIf { it.name.contains("LaunchActivityItem") }?.field { name = "mIntent" }
?.apply {
cast<Intent?>()?.also { intent ->
IntentClass.field { name = "mExtras" }.ignored().get(intent).cast<Bundle?>()
?.classLoader = AppParasitics.currentApplication?.classLoader
@Suppress("DEPRECATION")
if (intent.hasExtra(ActivityProxyConfig.proxyIntentName))
intent.getParcelableExtra<Intent>(ActivityProxyConfig.proxyIntentName).also { subIntent ->
if (Build.VERSION.SDK_INT >= 31)
ActivityThreadClass.method { name = "currentActivityThread" }.ignored().get().call()
?.current(ignored = true)?.method {
name = "getLaunchingActivity"
param(IBinderClass)
}?.call(this@client.current(ignored = true).method { name = "getActivityToken" }.call())
?.current(ignored = true)?.field { name = "intent" }?.set(subIntent)
set(subIntent)
}
}
}
}
}?.onFailure { yLoggerE(msg = "Activity Proxy got an Exception in msg.what [$EXECUTE_TRANSACTION]", e = it) }
}
return baseInstance?.handleMessage(msg) ?: false
}
}

View File

@@ -0,0 +1,105 @@
/*
* 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 2023/4/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.caller
import android.app.Activity
import android.app.ActivityManager
import android.content.Intent
import com.highcapable.yukihookapi.annotation.YukiGenerateApi
import com.highcapable.yukihookapi.hook.factory.buildOf
import com.highcapable.yukihookapi.hook.factory.classOf
import com.highcapable.yukihookapi.hook.factory.extends
import com.highcapable.yukihookapi.hook.factory.hasClass
import com.highcapable.yukihookapi.hook.factory.toClassOrNull
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppActivity
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppCompatActivity
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.config.ActivityProxyConfig
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
/**
* 代理当前 [ActivityManager] 调用类
*
* - ❗装载代码将自动生成 - 请勿手动调用
*/
@YukiGenerateApi
object IActivityManagerProxyCaller {
/**
* 获取当前使用的 [ClassLoader]
*
* - ❗装载代码将自动生成 - 请勿手动调用
* @return [ClassLoader]
*/
@YukiGenerateApi
val currentClassLoader get() = AppParasitics.baseClassLoader
/**
* 调用代理的 [InvocationHandler.invoke] 方法
*
* - ❗装载代码将自动生成 - 请勿手动调用
* @param baseInstance 原始实例
* @param method 被调用方法
* @param args 被调用方法参数
* @return [Any] or null
*/
@YukiGenerateApi
fun callInvoke(baseInstance: Any, method: Method?, args: Array<Any>?): Any? {
if (method?.name == "startActivity") args?.indexOfFirst { it is Intent }?.also { index ->
val argsInstance = (args[index] as? Intent) ?: return@also
val component = argsInstance.component
/**
* 使用宿主包名判断当前启动的 [Activity] 位于当前宿主
* 使用默认的 [ClassLoader] 判断当前 [Class] 处于模块中
*/
if (component != null &&
component.packageName == AppParasitics.currentPackageName &&
component.className.hasClass()
) args[index] = Intent().apply {
/**
* 验证类名是否存在
* @return [String] or null
*/
fun String.verify() = if (hasClass(AppParasitics.hostApplication?.classLoader)) this else null
setClassName(component.packageName, component.className.toClassOrNull()?.runCatching {
when {
this extends classOf<ModuleAppActivity>() -> buildOf<ModuleAppActivity>()?.proxyClassName?.verify()
this extends classOf<ModuleAppCompatActivity>() -> buildOf<ModuleAppCompatActivity>()?.proxyClassName?.verify()
else -> null
}
}?.getOrNull() ?: ActivityProxyConfig.proxyClassName)
putExtra(ActivityProxyConfig.proxyIntentName, argsInstance)
}
}
return method?.invoke(baseInstance, *(args ?: emptyArray()))
}
}

View File

@@ -0,0 +1,50 @@
/*
* 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 2023/4/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.impl
import android.os.Handler
/**
* 代理当前 [Handler.Callback] 调用接口实现
*/
internal object HandlerDelegateImpl {
/**
* 获取 [Handler.Callback] 实例 [Class] 名称
* @return [String]
*/
internal val wrapperClassName get() = HandlerDelegateImpl_Impl.wrapperClassName
/**
* 从 [Handler.Callback] 创建实例
* @param baseInstance [Handler.Callback] 实例 - 可空
* @return [Handler.Callback]
*/
internal fun createWrapper(baseInstance: Handler.Callback? = null) = HandlerDelegateImpl_Impl.createWrapper(baseInstance)
}

View File

@@ -0,0 +1,45 @@
/*
* 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 2023/4/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
package com.highcapable.yukihookapi.hook.xposed.parasitic.activity.delegate.impl
import android.app.ActivityManager
/**
* 代理当前 [ActivityManager] 调用接口实现
*/
internal object IActivityManagerProxyImpl {
/**
* 创建 [ActivityManager] 代理
* @param clazz 代理的目标 [Class]
* @param instance 代理的目标实例
* @return [Any] 代理包装后的实例
*/
internal fun createWrapper(clazz: Class<*>?, instance: Any) = IActivityManagerProxyImpl_Impl.createWrapper(clazz, instance)
}

View File

@@ -0,0 +1,95 @@
/*
* 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/8/15.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/ui/CommonContextWrapper.java
*/
@file:Suppress("unused", "DEPRECATION")
package com.highcapable.yukihookapi.hook.xposed.parasitic.context.wrapper
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import android.view.ContextThemeWrapper
import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.parasitic.reference.ModuleClassLoader
/**
* 代理 [ContextThemeWrapper]
*
* 通过包装 - 你可以轻松在 (Xposed) 宿主环境使用来自模块的主题资源
* @param baseContext 原始 [Context]
* @param theme 使用的主题
* @param configuration 使用的 [Configuration]
*/
class ModuleContextThemeWrapper private constructor(baseContext: Context, theme: Int, configuration: Configuration?) :
ContextThemeWrapper(baseContext, theme) {
internal companion object {
/**
* 从 [Context] 创建 [ModuleContextThemeWrapper]
* @param baseContext 对接的 [Context]
* @param theme 需要使用的主题
* @param configuration 使用的 [Configuration]
* @return [ModuleContextThemeWrapper]
* @throws IllegalStateException 如果重复装载
*/
internal fun wrapper(baseContext: Context, theme: Int, configuration: Configuration?) =
if (baseContext !is ModuleContextThemeWrapper)
ModuleContextThemeWrapper(baseContext, theme, configuration)
else error("ModuleContextThemeWrapper already loaded")
}
/** 创建用于替换的 [Resources] */
private var baseResources: Resources? = null
init {
configuration?.also {
baseResources = baseContext.createConfigurationContext(it)?.resources
baseResources?.updateConfiguration(it, baseContext.resources.displayMetrics)
}
if (YukiXposedModule.isXposedEnvironment) resources?.injectModuleAppResources()
}
/**
* 设置当前 [ModuleContextThemeWrapper] 的 [Configuration]
*
* 设置后会自动调用 [Resources.updateConfiguration]
* @param initiate [Configuration] 方法体
* @return [ModuleContextThemeWrapper]
*/
fun applyConfiguration(initiate: Configuration.() -> Unit): ModuleContextThemeWrapper {
resources?.configuration?.apply(initiate)
resources?.updateConfiguration(resources?.configuration, resources?.displayMetrics)
return this
}
override fun getClassLoader(): ClassLoader = ModuleClassLoader.instance()
override fun getResources(): Resources? = baseResources ?: super.getResources()
}

View File

@@ -0,0 +1,99 @@
/*
* 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/8/8.
* Thanks for providing https://github.com/cinit/QAuxiliary/blob/main/app/src/main/java/io/github/qauxv/lifecycle/Parasitics.java
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.xposed.parasitic.reference
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
/**
* 自动处理 (Xposed) 宿主环境与模块环境的 [ClassLoader]
*/
class ModuleClassLoader private constructor() : ClassLoader(AppParasitics.baseClassLoader) {
companion object {
/** 当前 [ModuleClassLoader] 单例 */
private var instance: ModuleClassLoader? = null
/** 排除的 Hook APP (宿主) [Class] 类名数组 */
private val excludeHostClasses = HashSet<String>()
/** 排除的模块 [Class] 类名数组 */
private val excludeModuleClasses = HashSet<String>()
/**
* 获取 [ModuleClassLoader] 单例
* @return [ModuleClassLoader]
*/
internal fun instance() = instance ?: ModuleClassLoader().apply { instance = this }
/**
* 添加到 Hook APP (宿主) [Class] 排除列表
*
* 排除列表中的 [Class] 将会使用宿主的 [ClassLoader] 进行装载
*
* - ❗排除列表仅会在 (Xposed) 宿主环境生效
* @param name 需要添加的 [Class] 完整类名
*/
fun excludeHostClasses(vararg name: String) {
excludeHostClasses.addAll(name.toList())
}
/**
* 添加到模块 [Class] 排除列表
*
* 排除列表中的 [Class] 将会使用模块 (当前宿主环境的模块注入进程) 的 [ClassLoader] 进行装载
*
* - ❗排除列表仅会在 (Xposed) 宿主环境生效
* @param name 需要添加的 [Class] 完整类名
*/
fun excludeModuleClasses(vararg name: String) {
excludeModuleClasses.addAll(name.toList())
}
init {
excludeHostClasses.add("androidx.lifecycle.ReportFragment")
}
}
/** 默认 [ClassLoader] */
private val baseLoader get() = AppParasitics.baseClassLoader
override fun loadClass(name: String, resolve: Boolean): Class<*> {
if (YukiXposedModule.isXposedEnvironment.not()) return baseLoader.loadClass(name)
return AppParasitics.currentApplication?.classLoader?.let { hostLoader ->
excludeHostClasses.takeIf { it.isNotEmpty() }?.forEach { runCatching { if (name == it) return@let hostLoader.loadClass(name) } }
excludeModuleClasses.takeIf { it.isNotEmpty() }?.forEach { runCatching { if (name == it) return@let baseLoader.loadClass(name) } }
runCatching { return@let baseLoader.loadClass(name) }
runCatching { baseLoader.loadClass(name) }.getOrNull() ?: hostLoader.loadClass(name)
} ?: super.loadClass(name, resolve)
}
}

View File

@@ -0,0 +1,686 @@
/*
* 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/8.
*/
@file:Suppress("unused", "MemberVisibilityCanBePrivate", "StaticFieldLeak", "SetWorldReadable", "CommitPrefEdits", "UNCHECKED_CAST")
package com.highcapable.yukihookapi.hook.xposed.prefs
import android.content.Context
import android.content.SharedPreferences
import android.util.ArrayMap
import androidx.preference.PreferenceFragmentCompat
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.log.yLoggerE
import com.highcapable.yukihookapi.hook.log.yLoggerW
import com.highcapable.yukihookapi.hook.xposed.bridge.YukiXposedModule
import com.highcapable.yukihookapi.hook.xposed.bridge.delegate.XSharedPreferencesDelegate
import com.highcapable.yukihookapi.hook.xposed.parasitic.AppParasitics
import com.highcapable.yukihookapi.hook.xposed.prefs.data.PrefsData
import com.highcapable.yukihookapi.hook.xposed.prefs.ui.ModulePreferenceFragment
import de.robv.android.xposed.XSharedPreferences
import java.io.File
/**
* [YukiHookAPI] 对 [SharedPreferences]、[XSharedPreferences] 的扩展存储桥实现
*
* 在不同环境智能选择存取使用的对象
*
* - ❗模块与宿主之前共享数据存储为实验性功能 - 仅在 LSPosed 环境测试通过 - EdXposed 理论也可以使用但不再推荐
*
* 对于在模块环境中使用 [PreferenceFragmentCompat] - [YukiHookAPI] 提供了 [ModulePreferenceFragment] 来实现同样的功能
*
* 详情请参考 [API 文档 - YukiHookPrefsBridge](https://fankes.github.io/YukiHookAPI/zh-cn/api/public/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookPrefsBridge)
*
* For English version, see [API Document - YukiHookPrefsBridge](https://fankes.github.io/YukiHookAPI/en/api/public/com/highcapable/yukihookapi/hook/xposed/prefs/YukiHookPrefsBridge)
* @param context 上下文实例 - 默认空
*/
class YukiHookPrefsBridge private constructor(private var context: Context? = null) {
internal companion object {
/** 当前是否为 (Xposed) 宿主环境 */
private val isXposedEnvironment = YukiXposedModule.isXposedEnvironment
/** 当前缓存的 [XSharedPreferencesDelegate] 实例数组 */
private val xPrefs = ArrayMap<String, XSharedPreferencesDelegate>()
/** 当前缓存的 [SharedPreferences] 实例数组 */
private val sPrefs = ArrayMap<String, SharedPreferences>()
/**
* 创建 [YukiHookPrefsBridge] 对象
* @param context 实例 - (Xposed) 宿主环境为空
* @return [YukiHookPrefsBridge]
*/
internal fun from(context: Context? = null) = YukiHookPrefsBridge(context)
/**
* 设置全局可读可写
* @param context 实例
* @param prefsFileName Sp 文件名
*/
internal fun makeWorldReadable(context: Context?, prefsFileName: String) {
runCatching {
context?.also {
File(File(it.applicationInfo.dataDir, "shared_prefs"), prefsFileName).apply {
setReadable(true, false)
setExecutable(true, false)
}
}
}
}
}
/** 存储名称 */
private var prefsName = ""
/** 是否使用新版存储方式 EdXposed、LSPosed */
private var isUsingNewXSharedPreferences = false
/** 是否启用原生存储方式 */
private var isUsingNativeStorage = false
/**
* 获取当前存储名称 - 默认包名 + _preferences
* @return [String]
*/
private val currentPrefsName
get() = prefsName.ifBlank {
if (isUsingNativeStorage) "${context?.packageName ?: "unknown"}_preferences"
else "${YukiXposedModule.modulePackageName.ifBlank { context?.packageName ?: "unknown" }}_preferences"
}
/** 检查 API 装载状态 */
private fun checkApi() {
if (YukiHookAPI.isLoadedFromBaseContext) error("YukiHookPrefsBridge not allowed in Custom Hook API")
if (isXposedEnvironment && YukiXposedModule.modulePackageName.isBlank())
error("Xposed modulePackageName load failed, please reset and rebuild it")
}
/**
* 设置全局可读可写
* @param callback 回调方法体
* @return [T]
*/
private inline fun <T> makeWorldReadable(callback: () -> T): T {
val result = callback()
if (isXposedEnvironment.not() && isUsingNewXSharedPreferences.not())
runCatching { makeWorldReadable(context, prefsFileName = "$currentPrefsName.xml") }
return result
}
/**
* 获取当前 [XSharedPreferences] 对象
* @return [XSharedPreferences]
*/
private val currentXsp
get() = checkApi().let {
runCatching {
(xPrefs[currentPrefsName]?.instance ?: XSharedPreferencesDelegate.from(YukiXposedModule.modulePackageName, currentPrefsName)
.also {
xPrefs[currentPrefsName] = it
}.instance).apply {
makeWorldReadable()
reload()
}
}.onFailure { yLoggerE(msg = it.message ?: "Operating system not supported", e = it) }.getOrNull()
?: error("Cannot load the XSharedPreferences, maybe is your Hook Framework not support it")
}
/**
* 获取当前 [SharedPreferences] 对象
* @return [SharedPreferences]
*/
private val currentSp
get() = checkApi().let {
runCatching {
@Suppress("DEPRECATION", "WorldReadableFiles")
sPrefs[context.toString() + currentPrefsName] ?: context?.getSharedPreferences(currentPrefsName, Context.MODE_WORLD_READABLE)
?.also {
isUsingNewXSharedPreferences = true
sPrefs[context.toString() + currentPrefsName] = it
} ?: error("YukiHookPrefsBridge missing Context instance")
}.getOrElse {
sPrefs[context.toString() + currentPrefsName] ?: context?.getSharedPreferences(currentPrefsName, Context.MODE_PRIVATE)?.also {
isUsingNewXSharedPreferences = false
sPrefs[context.toString() + currentPrefsName] = it
} ?: error("YukiHookPrefsBridge missing Context instance")
}
}
/**
* 获取 [XSharedPreferences] 是否可读
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [isPreferencesAvailable]
* @return [Boolean]
*/
@Deprecated(message = "请使用新方式来实现此功能", ReplaceWith("isPreferencesAvailable"))
val isXSharePrefsReadable get() = isPreferencesAvailable
/**
* 获取 [YukiHookPrefsBridge] 是否正处于 EdXposed/LSPosed 的最高权限运行
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [isPreferencesAvailable]
* @return [Boolean]
*/
@Deprecated(message = "请使用新方式来实现此功能", ReplaceWith("isPreferencesAvailable"))
val isRunInNewXShareMode get() = isPreferencesAvailable
/**
* 获取当前 [YukiHookPrefsBridge] 的可用状态
*
* - 在 (Xposed) 宿主环境中返回 [XSharedPreferences] 可用状态 (可读)
*
* - 在模块环境中返回当前是否处于 New XSharedPreferences 模式 (可读可写)
* @return [Boolean]
*/
val isPreferencesAvailable
get() = if (isXposedEnvironment)
(runCatching { currentXsp.let { it.file.exists() && it.file.canRead() } }.getOrNull() ?: false)
else runCatching {
/** 执行一次装载 */
currentSp.edit()
isUsingNewXSharedPreferences
}.getOrNull() ?: false
/**
* 自定义 Sp 存储名称
* @param name 自定义的 Sp 存储名称
* @return [YukiHookPrefsBridge]
*/
fun name(name: String): YukiHookPrefsBridge {
prefsName = name
return this
}
/**
* 忽略缓存直接读取键值
*
* - ❗此方法及功能已被移除 - 在之后的版本中将直接被删除
*
* - ❗键值的直接缓存功能已被移除 - 因为其存在内存溢出 (OOM) 问题
* @return [YukiHookPrefsBridge]
*/
@Deprecated(message = "此方法及功能已被移除,请删除此方法", ReplaceWith("this"))
fun direct() = this
/**
* 忽略当前环境直接使用 [Context.getSharedPreferences] 存取数据
* @return [YukiHookPrefsBridge]
* @throws IllegalStateException 如果 [context] 为空
*/
fun native(): YukiHookPrefsBridge {
if (isXposedEnvironment && context == null) context = AppParasitics.currentApplication
?: error("The Host App's Context has not yet initialized successfully, the native function cannot be used at this time")
isUsingNativeStorage = true
return this
}
/**
* 获取 [String] 键值
*
* - 智能识别对应环境读取键值数据
*
* - 建议使用 [PrefsData] 创建模板并使用 [get] 获取数据
* @param key 键值名称
* @param value 默认数据 - ""
* @return [String]
*/
fun getString(key: String, value: String = "") = makeWorldReadable {
if (isXposedEnvironment && isUsingNativeStorage.not())
currentXsp.getString(key, value) ?: value
else currentSp.getString(key, value) ?: value
}
/**
* 获取 [Set]<[String]> 键值
*
* - 智能识别对应环境读取键值数据
*
* - 建议使用 [PrefsData] 创建模板并使用 [get] 获取数据
* @param key 键值名称
* @param value 默认数据 - [HashSet]<[String]>
* @return [Set]<[String]>
*/
fun getStringSet(key: String, value: Set<String> = hashSetOf()) = makeWorldReadable {
if (isXposedEnvironment && isUsingNativeStorage.not())
currentXsp.getStringSet(key, value) ?: value
else currentSp.getStringSet(key, value) ?: value
}
/**
* 获取 [Boolean] 键值
*
* - 智能识别对应环境读取键值数据
*
* - 建议使用 [PrefsData] 创建模板并使用 [get] 获取数据
* @param key 键值名称
* @param value 默认数据 - false
* @return [Boolean]
*/
fun getBoolean(key: String, value: Boolean = false) = makeWorldReadable {
if (isXposedEnvironment && isUsingNativeStorage.not())
currentXsp.getBoolean(key, value)
else currentSp.getBoolean(key, value)
}
/**
* 获取 [Int] 键值
*
* - 智能识别对应环境读取键值数据
*
* - 建议使用 [PrefsData] 创建模板并使用 [get] 获取数据
* @param key 键值名称
* @param value 默认数据 - 0
* @return [Int]
*/
fun getInt(key: String, value: Int = 0) = makeWorldReadable {
if (isXposedEnvironment && isUsingNativeStorage.not())
currentXsp.getInt(key, value)
else currentSp.getInt(key, value)
}
/**
* 获取 [Float] 键值
*
* - 智能识别对应环境读取键值数据
*
* - 建议使用 [PrefsData] 创建模板并使用 [get] 获取数据
* @param key 键值名称
* @param value 默认数据 - 0f
* @return [Float]
*/
fun getFloat(key: String, value: Float = 0f) = makeWorldReadable {
if (isXposedEnvironment && isUsingNativeStorage.not())
currentXsp.getFloat(key, value)
else currentSp.getFloat(key, value)
}
/**
* 获取 [Long] 键值
*
* - 智能识别对应环境读取键值数据
*
* - 建议使用 [PrefsData] 创建模板并使用 [get] 获取数据
* @param key 键值名称
* @param value 默认数据 - 0L
* @return [Long]
*/
fun getLong(key: String, value: Long = 0L) = makeWorldReadable {
if (isXposedEnvironment && isUsingNativeStorage.not())
currentXsp.getLong(key, value)
else currentSp.getLong(key, value)
}
/**
* 智能获取指定类型的键值
* @param prefs 键值实例
* @param value 默认值 - 未指定默认为 [prefs] 中的 [PrefsData.value]
* @return [T] 只能是 [String]、[Set]<[String]>、[Int]、[Float]、[Long]、[Boolean]
*/
inline fun <reified T> get(prefs: PrefsData<T>, value: T = prefs.value): T = getPrefsData(prefs.key, value) as T
/**
* 智能获取指定类型的键值
*
* 封装方法以调用内联方法
* @param key 键值
* @param value 默认值
* @return [Any]
*/
@PublishedApi
internal fun getPrefsData(key: String, value: Any?): Any = when (value) {
is String -> getString(key, value)
is Set<*> -> getStringSet(key, value as? Set<String> ?: error("Key-Value type ${value.javaClass.name} is not allowed"))
is Int -> getInt(key, value)
is Float -> getFloat(key, value)
is Long -> getLong(key, value)
is Boolean -> getBoolean(key, value)
else -> error("Key-Value type ${value?.javaClass?.name} is not allowed")
}
/**
* 判断当前是否包含 [key] 键值的数据
*
* - 智能识别对应环境读取键值数据
* @return [Boolean] 是否包含
*/
fun contains(key: String) =
if (isXposedEnvironment && isUsingNativeStorage.not())
currentXsp.contains(key)
else currentSp.contains(key)
/**
* 获取全部存储的键值数据
*
* - 智能识别对应环境读取键值数据
*
* - ❗每次调用都会获取实时的数据 - 不受缓存控制 - 请勿在高并发场景中使用
* @return [HashMap] 全部类型的键值数组
*/
fun all() = hashMapOf<String, Any?>().apply {
if (isXposedEnvironment && isUsingNativeStorage.not())
currentXsp.all.forEach { (k, v) -> this[k] = v }
else currentSp.all.forEach { (k, v) -> this[k] = v }
}
/**
* 移除全部包含 [key] 的存储数据
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [edit] 方法
* @param key 键值名称
*/
@Deprecated(message = "此方法因为性能问题已被作废,请转移到新用法", ReplaceWith("edit { remove(key) }"))
fun remove(key: String) = edit { remove(key) }
/**
* 移除 [PrefsData.key] 的存储数据
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [edit] 方法
* @param prefs 键值实例
*/
@Deprecated(message = "此方法因为性能问题已被作废,请转移到新用法", ReplaceWith("edit { remove(prefs) }"))
inline fun <reified T> remove(prefs: PrefsData<T>) = edit { remove(prefs) }
/**
* 移除全部存储数据
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [edit] 方法
*/
@Deprecated(message = "此方法因为性能问题已被作废,请转移到新用法", ReplaceWith("edit { clear() }"))
fun clear() = edit { clear() }
/**
* 存储 [String] 键值
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [edit] 方法
* @param key 键值名称
* @param value 键值数据
*/
@Deprecated(message = "此方法因为性能问题已被作废,请转移到新用法", ReplaceWith("edit { putString(key, value) }"))
fun putString(key: String, value: String) = edit { putString(key, value) }
/**
* 存储 [Set]<[String]> 键值
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [edit] 方法
* @param key 键值名称
* @param value 键值数据
*/
@Deprecated(message = "此方法因为性能问题已被作废,请转移到新用法", ReplaceWith("edit { putStringSet(key, value) }"))
fun putStringSet(key: String, value: Set<String>) = edit { putStringSet(key, value) }
/**
* 存储 [Boolean] 键值
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [edit] 方法
* @param key 键值名称
* @param value 键值数据
*/
@Deprecated(message = "此方法因为性能问题已被作废,请转移到新用法", ReplaceWith("edit { putBoolean(key, value) }"))
fun putBoolean(key: String, value: Boolean) = edit { putBoolean(key, value) }
/**
* 存储 [Int] 键值
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [edit] 方法
* @param key 键值名称
* @param value 键值数据
*/
@Deprecated(message = "此方法因为性能问题已被作废,请转移到新用法", ReplaceWith("edit { putInt(key, value) }"))
fun putInt(key: String, value: Int) = edit { putInt(key, value) }
/**
* 存储 [Float] 键值
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [edit] 方法
* @param key 键值名称
* @param value 键值数据
*/
@Deprecated(message = "此方法因为性能问题已被作废,请转移到新用法", ReplaceWith("edit { putFloat(key, value) }"))
fun putFloat(key: String, value: Float) = edit { putFloat(key, value) }
/**
* 存储 [Long] 键值
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [edit] 方法
* @param key 键值名称
* @param value 键值数据
*/
@Deprecated(message = "此方法因为性能问题已被作废,请转移到新用法", ReplaceWith("edit { putLong(key, value) }"))
fun putLong(key: String, value: Long) = edit { putLong(key, value) }
/**
* 智能存储指定类型的键值
*
* - ❗此方法已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [edit] 方法
*/
@Deprecated(message = "此方法因为性能问题已被作废,请转移到新用法", ReplaceWith("edit { put(prefs, value) }"))
inline fun <reified T> put(prefs: PrefsData<T>, value: T) = edit { put(prefs, value) }
/**
* 创建新的 [Editor]
*
* - 在模块环境中或启用了 [isUsingNativeStorage] 后使用
*
* - ❗在 (Xposed) 宿主环境下只读 - 无法使用
* @return [Editor]
*/
fun edit() = Editor()
/**
* 创建新的 [Editor]
*
* 自动调用 [Editor.apply] 方法
*
* - 在模块环境中或启用了 [isUsingNativeStorage] 后使用
*
* - ❗在 (Xposed) 宿主环境下只读 - 无法使用
* @param initiate 方法体
*/
fun edit(initiate: Editor.() -> Unit) = edit().apply(initiate).apply()
/**
* 清除 [YukiHookPrefsBridge] 中缓存的键值数据
*
* - ❗此方法及功能已被移除 - 在之后的版本中将直接被删除
*
* - ❗键值的直接缓存功能已被移除 - 因为其存在内存溢出 (OOM) 问题
* @return [YukiHookPrefsBridge]
*/
@Deprecated(message = "此方法及功能已被移除,请删除此方法")
fun clearCache() {
}
/**
* [YukiHookPrefsBridge] 的存储代理类
*
* - ❗请使用 [edit] 方法来获取 [Editor]
*
* - 在模块环境中或启用了 [isUsingNativeStorage] 后使用
*
* - ❗在 (Xposed) 宿主环境下只读 - 无法使用
*/
inner class Editor internal constructor() {
/** 创建新的存储代理类 */
private var editor = runCatching { currentSp.edit() }.getOrNull()
/**
* 移除全部包含 [key] 的存储数据
* @param key 键值名称
* @return [Editor]
*/
fun remove(key: String) = specifiedScope { editor?.remove(key) }
/**
* 移除 [PrefsData.key] 的存储数据
* @param prefs 键值实例
* @return [Editor]
*/
inline fun <reified T> remove(prefs: PrefsData<T>) = remove(prefs.key)
/**
* 移除全部存储数据
* @return [Editor]
*/
fun clear() = specifiedScope { editor?.clear() }
/**
* 存储 [String] 键值
*
* - 建议使用 [PrefsData] 创建模板并使用 [put] 存储数据
* @param key 键值名称
* @param value 键值数据
* @return [Editor]
*/
fun putString(key: String, value: String) = specifiedScope { editor?.putString(key, value) }
/**
* 存储 [Set]<[String]> 键值
*
* - 建议使用 [PrefsData] 创建模板并使用 [put] 存储数据
* @param key 键值名称
* @param value 键值数据
* @return [Editor]
*/
fun putStringSet(key: String, value: Set<String>) = specifiedScope { editor?.putStringSet(key, value) }
/**
* 存储 [Boolean] 键值
*
* - 建议使用 [PrefsData] 创建模板并使用 [put] 存储数据
* @param key 键值名称
* @param value 键值数据
* @return [Editor]
*/
fun putBoolean(key: String, value: Boolean) = specifiedScope { editor?.putBoolean(key, value) }
/**
* 存储 [Int] 键值
*
* - 建议使用 [PrefsData] 创建模板并使用 [put] 存储数据
* @param key 键值名称
* @param value 键值数据
* @return [Editor]
*/
fun putInt(key: String, value: Int) = specifiedScope { editor?.putInt(key, value) }
/**
* 存储 [Float] 键值
*
* - 建议使用 [PrefsData] 创建模板并使用 [put] 存储数据
* @param key 键值名称
* @param value 键值数据
* @return [Editor]
*/
fun putFloat(key: String, value: Float) = specifiedScope { editor?.putFloat(key, value) }
/**
* 存储 [Long] 键值
*
* - 建议使用 [PrefsData] 创建模板并使用 [put] 存储数据
* @param key 键值名称
* @param value 键值数据
* @return [Editor]
*/
fun putLong(key: String, value: Long) = specifiedScope { editor?.putLong(key, value) }
/**
* 智能存储指定类型的键值
* @param prefs 键值实例
* @param value 要存储的值 - 只能是 [String]、[Set]<[String]>、[Int]、[Float]、[Long]、[Boolean]
* @return [Editor]
*/
inline fun <reified T> put(prefs: PrefsData<T>, value: T) = putPrefsData(prefs.key, value)
/**
* 智能存储指定类型的键值
*
* 封装方法以调用内联方法
* @param key 键值
* @param value 要存储的值 - 只能是 [String]、[Set]<[String]>、[Int]、[Float]、[Long]、[Boolean]
* @return [Editor]
*/
@PublishedApi
internal fun putPrefsData(key: String, value: Any?) = when (value) {
is String -> putString(key, value)
is Set<*> -> putStringSet(key, value as? Set<String> ?: error("Key-Value type ${value.javaClass.name} is not allowed"))
is Int -> putInt(key, value)
is Float -> putFloat(key, value)
is Long -> putLong(key, value)
is Boolean -> putBoolean(key, value)
else -> error("Key-Value type ${value?.javaClass?.name} is not allowed")
}
/**
* 提交更改 (同步)
* @return [Boolean] 是否成功
*/
fun commit() = makeWorldReadable { editor?.commit() ?: false }
/** 提交更改 (异步) */
fun apply() = makeWorldReadable { editor?.apply() ?: Unit }
/**
* 仅在模块环境或 [isUsingNativeStorage] 执行
*
* 非模块环境使用会打印警告信息
* @param callback 在模块环境执行
* @return [Editor]
*/
private inline fun specifiedScope(callback: () -> Unit): Editor {
if (isXposedEnvironment.not() || isUsingNativeStorage) callback()
else yLoggerW(msg = "YukiHookPrefsBridge.Editor not allowed in Xposed Environment")
return this
}
}
}

View File

@@ -0,0 +1,44 @@
/*
* 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/3/27.
*/
package com.highcapable.yukihookapi.hook.xposed.prefs.data
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookPrefsBridge
import java.io.Serializable
/**
* 键值对存储构造类
*
* 这个类是对 [YukiHookPrefsBridge] 的一个扩展用法
*
* 详情请参考 [API 文档 - PrefsData](https://fankes.github.io/YukiHookAPI/zh-cn/api/public/com/highcapable/yukihookapi/hook/xposed/prefs/data/PrefsData)
*
* For English version, see [API Document - PrefsData](https://fankes.github.io/YukiHookAPI/en/api/public/com/highcapable/yukihookapi/hook/xposed/prefs/data/PrefsData)
* @param key 键值
* @param value 默认值
*/
data class PrefsData<T>(var key: String, var value: T) : Serializable

View File

@@ -0,0 +1,109 @@
/*
* 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/4/17.
*/
package com.highcapable.yukihookapi.hook.xposed.prefs.ui
import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import androidx.annotation.CallSuper
import androidx.fragment.app.Fragment
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager
import androidx.preference.PreferenceScreen
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.utils.unit
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookPrefsBridge
/**
* 这是对使用 [YukiHookAPI] Xposed 模块实现中的一个扩展功能
*
* 此类接管了 [PreferenceFragmentCompat] 并对其实现了 Sp 存储在 Xposed 模块中的全局可读可写
*
* 在你使用 [PreferenceFragmentCompat] 的实例中 - 将继承对象换成此类
*
* 然后请将重写方法由 [onCreatePreferences] 替换为 [onCreatePreferencesInModuleApp] 即可
*
* 详情请参考 [API 文档 - ModulePreferenceFragment](https://fankes.github.io/YukiHookAPI/zh-cn/api/public/com/highcapable/yukihookapi/hook/xposed/prefs/ModulePreferenceFragment)
*
* For English version, see [API Document - ModulePreferenceFragment](https://fankes.github.io/YukiHookAPI/en/api/public/com/highcapable/yukihookapi/hook/xposed/prefs/ModulePreferenceFragment)
*/
abstract class ModulePreferenceFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedPreferenceChangeListener {
/**
* 获得 Sp 存储名称
* @return [String]
*/
private val prefsName get() = "${activity?.packageName}_preferences"
/**
* 获取当前 [Fragment] 绑定的 [Activity]
* @return [Activity]
* @throws IllegalStateException 如果 [Fragment] 已被销毁或未正确装载
*/
private val currentActivity get() = requireActivity()
/**
* 获取应用默认的 [SharedPreferences]
* @return [SharedPreferences]
*/
private val currentSharedPrefs get() = PreferenceManager.getDefaultSharedPreferences(currentActivity)
@CallSuper
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
currentSharedPrefs.registerOnSharedPreferenceChangeListener(this)
makeNewXShareReadableIfPossible()
onCreatePreferencesInModuleApp(savedInstanceState, rootKey)
}
@CallSuper
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
makeNewXShareReadableIfPossible()
}
@CallSuper
override fun onDestroy() {
currentSharedPrefs.unregisterOnSharedPreferenceChangeListener(this)
super.onDestroy()
}
/**
* 对接原始方法 [onCreatePreferences]
*
* 请重写此方法以实现模块 Sp 存储的自动化设置全局可读可写数据操作
* @param savedInstanceState If the fragment is being re-created from a previous saved state, this is the state.
* @param rootKey If non-null, this preference fragment should be rooted at the [PreferenceScreen] with this key.
*/
abstract fun onCreatePreferencesInModuleApp(savedInstanceState: Bundle?, rootKey: String?)
/** 设置自动适配模块 Sp 存储全局可读可写 */
private fun makeNewXShareReadableIfPossible() = runCatching {
@Suppress("DEPRECATION", "WorldReadableFiles")
currentActivity.getSharedPreferences(prefsName, Context.MODE_WORLD_READABLE)
}.onFailure { YukiHookPrefsBridge.makeWorldReadable(currentActivity, prefsFileName = "$prefsName.xml") }.unit()
}

View File

@@ -0,0 +1,96 @@
/*
* 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/2.
* This file is Modified by fankes on 2022/4/22.
*/
@file:Suppress("unused")
package com.highcapable.yukihookapi.hook.xposed.proxy
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed
import com.highcapable.yukihookapi.hook.factory.configs
import com.highcapable.yukihookapi.hook.factory.encase
import com.highcapable.yukihookapi.hook.xposed.bridge.event.YukiXposedEvent
/**
* [YukiHookAPI] 的 Xposed 装载 API 调用接口
*
* - ❗请在此类上添加注解 [InjectYukiHookWithXposed] 标记模块 Hook 入口
*
* [YukiHookAPI] 初始化时将自动调用 [onInit] 方法
*
* Hook 开始时将自动调用 [onHook] 方法
*
* 请在 [onInit] 中调用 [YukiHookAPI.configs] 或直接调用 [configs]
*
* 请在 [onHook] 中调用 [YukiHookAPI.encase] 或直接调用 [encase]
*
* 你还可以实现监听原生 Xposed API 功能 - 重写 [onXposedEvent] 方法即可
*
* 详情请参考 [IYukiHookXposedInit 接口](https://fankes.github.io/YukiHookAPI/zh-cn/config/xposed-using#iyukihookxposedinit-%E6%8E%A5%E5%8F%A3)
*
* For English version, see [IYukiHookXposedInit Interface](https://fankes.github.io/YukiHookAPI/en/config/xposed-using#iyukihookxposedinit-interface)
*/
interface IYukiHookXposedInit {
/**
* 配置 [YukiHookAPI.Configs] 的初始化方法
*
* - ❗在这里只能进行初始化配置 - 不能进行 Hook 操作
*
* 此方法可选 - 你也可以选择不对 [YukiHookAPI.Configs] 进行配置
*/
fun onInit() {}
/**
* 模块装载调用入口方法
*
* Xposed API
*
* 调用 [YukiHookAPI.encase] 或直接调用 [encase] 开始 Hook
*/
fun onHook()
/**
* 监听 Xposed 原生装载事件
*
* 若你的 Hook 事件中存在需要兼容的原生 Xposed 功能 - 可在这里实现
*
* 请在这里使用 [YukiXposedEvent] 创建回调事件监听
*
* 可监听的事件如下:
*
* [YukiXposedEvent.onInitZygote]
*
* [YukiXposedEvent.onHandleLoadPackage]
*
* [YukiXposedEvent.onHandleInitPackageResources]
*
* - ❗此接口仅供监听和实现原生 Xposed API 的功能 - 请不要在这里操作 [YukiHookAPI]
*/
fun onXposedEvent() {}
}

View File

@@ -0,0 +1,60 @@
/*
* 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/2.
*/
@file:Suppress("unused", "DeprecatedCallableAddReplaceWith")
package com.highcapable.yukihookapi.hook.xposed.proxy
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.log.yLoggerW
/**
* [YukiHookAPI] 的 Xposed 装载 API 调用接口
*
* - ❗此接口已弃用 - 在之后的版本中将直接被删除
*
* - ❗请现在转移到 [IYukiHookXposedInit] 否则此接口的声明将在自动处理程序中被拦截
*/
@Deprecated(message = "此接口的命名和功能已被弃用", ReplaceWith("IYukiHookXposedInit"), level = DeprecationLevel.ERROR)
interface YukiHookXposedInitProxy {
/**
* - ❗此方法已过时
*
* - ❗请将接口转移到 [IYukiHookXposedInit]
*/
@Deprecated(message = "请将接口转移到 IYukiHookXposedInit", level = DeprecationLevel.ERROR)
fun onInit() = yLoggerW(msg = "YukiHookXposedInitProxy was deprecated")
/**
* - ❗此方法已过时
*
* - ❗请将接口转移到 [IYukiHookXposedInit]
*/
@Deprecated(message = "请将接口转移到 IYukiHookXposedInit", level = DeprecationLevel.ERROR)
fun onHook() = yLoggerW(msg = "YukiHookXposedInitProxy was deprecated")
}