refactor: migrate and update to YukiHookAPI 1.3.0

This commit is contained in:
2025-06-25 23:20:11 +08:00
parent 276820fdc5
commit 147e4c117e
9 changed files with 315 additions and 227 deletions

View File

@@ -82,6 +82,8 @@ dependencies {
implementation(com.highcapable.yukihookapi.api)
ksp(com.highcapable.yukihookapi.ksp.xposed)
ksp(com.highcapable.hikage.hikage.compiler)
implementation(com.highcapable.kavaref.kavaref.core)
implementation(com.highcapable.kavaref.kavaref.extension)
implementation(com.highcapable.hikage.hikage.core)
implementation(com.highcapable.hikage.hikage.extension)
implementation(com.highcapable.hikage.hikage.widget.androidx)

View File

@@ -26,8 +26,11 @@ package com.fankes.tsbattery.hook.entity
import android.app.Activity
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.os.Message
import android.view.View
import android.view.ViewGroup
import androidx.core.app.ServiceCompat
@@ -44,29 +47,16 @@ import com.fankes.tsbattery.hook.factory.startModuleSettings
import com.fankes.tsbattery.hook.helper.DexKitHelper
import com.fankes.tsbattery.utils.factory.appVersionName
import com.fankes.tsbattery.utils.factory.dp
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.kavaref.KavaRef.Companion.resolve
import com.highcapable.kavaref.extension.ArrayClass
import com.highcapable.kavaref.extension.VariousClass
import com.highcapable.kavaref.extension.classOf
import com.highcapable.kavaref.extension.createInstanceAsTypeOrNull
import com.highcapable.kavaref.extension.createInstanceOrNull
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.buildOf
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities
import com.highcapable.yukihookapi.hook.log.YLog
import com.highcapable.yukihookapi.hook.type.android.BuildClass
import com.highcapable.yukihookapi.hook.type.android.BundleClass
import com.highcapable.yukihookapi.hook.type.android.ContextClass
import com.highcapable.yukihookapi.hook.type.android.IntentClass
import com.highcapable.yukihookapi.hook.type.android.MessageClass
import com.highcapable.yukihookapi.hook.type.java.AnyArrayClass
import com.highcapable.yukihookapi.hook.type.java.AnyClass
import com.highcapable.yukihookapi.hook.type.java.BooleanType
import com.highcapable.yukihookapi.hook.type.java.CharSequenceClass
import com.highcapable.yukihookapi.hook.type.java.IntType
import com.highcapable.yukihookapi.hook.type.java.ListClass
import com.highcapable.yukihookapi.hook.type.java.LongType
import com.highcapable.yukihookapi.hook.type.java.StringClass
import com.highcapable.yukihookapi.hook.type.java.UnitType
import java.lang.reflect.Method
import java.lang.reflect.Proxy
@@ -159,7 +149,9 @@ object QQTIMHooker : YukiBaseHooker() {
* 通过 [Activity] or [Fragment] 实例得到上下文
* @return [Activity] or null
*/
private fun Any.compatToActivity() = if (this is Activity) this else current().method { name = "getActivity"; superClass() }.invoke()
private fun Any.compatToActivity() = if (this !is Activity)
resolve().optional().firstMethodOrNull { name = "getActivity"; superclass() }?.invoke()
else this
/** 使用 DexKit 进行搜索 */
private fun searchUsingDexKit() {
@@ -172,7 +164,7 @@ object QQTIMHooker : YukiBaseHooker() {
declaredClass(baseChatPieClassName)
usingStrings("remainScreenOn")
paramCount = 0
returnType = UnitType.name
returnType = Void.TYPE.name
}
}.singleOrNull()?.getMethodInstance(classLoader)
DexKitData.BaseChatPie_CancelRemainScreenOnMethod =
@@ -181,7 +173,7 @@ object QQTIMHooker : YukiBaseHooker() {
declaredClass(baseChatPieClassName)
usingStrings("cancelRemainScreenOn")
paramCount = 0
returnType = UnitType.name
returnType = Void.TYPE.name
}
}.singleOrNull()?.getMethodInstance(classLoader)
}
@@ -192,11 +184,11 @@ object QQTIMHooker : YukiBaseHooker() {
methods {
add {
name = "<init>"
paramTypes(ContextClass.name, IntType.name, CharSequenceClass.name, IntType.name)
paramTypes(classOf<Context>().name, classOf<Int>().name, classOf<CharSequence>().name, classOf<Int>().name)
}
add {
paramTypes(kotlinFunction0)
returnType = UnitType.name
returnType = Void.TYPE.name
}
}
fields { count(6..Int.MAX_VALUE) }
@@ -208,7 +200,7 @@ object QQTIMHooker : YukiBaseHooker() {
matcher {
declaredClass = className
paramTypes(kotlinFunction0)
returnType = UnitType.name
returnType = Void.TYPE.name
usingNumbers(2)
}
}.singleOrNull()?.getMethodInstance(classLoader)
@@ -246,23 +238,23 @@ object QQTIMHooker : YukiBaseHooker() {
/** Hook CoreService QQ、TIM */
private fun hookCoreService() {
CoreServiceClass?.apply {
CoreServiceClass?.resolve()?.optional()?.apply {
if (isQQ) {
method {
firstMethodOrNull {
name = "startTempService"
}.ignored().hook().intercept()
method {
}?.hook()?.intercept()
firstMethodOrNull {
name = "startCoreService"
param(BooleanType)
}.ignored().hook().intercept()
method {
parameters(Boolean::class)
}?.hook()?.intercept()
firstMethodOrNull {
name = "onStartCommand"
param(IntentClass, IntType, IntType)
}.ignored().hook().replaceTo(any = 2)
parameters(Intent::class, Int::class, Int::class)
}?.hook()?.replaceTo(any = 2)
}
method {
firstMethodOrNull {
name = "onCreate"
}.ignored().hook().after {
}?.hook()?.after {
if (ConfigData.isEnableKillQQTimCoreService)
instance<Service>().apply {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
@@ -271,10 +263,10 @@ object QQTIMHooker : YukiBaseHooker() {
}
}
}
CoreService_KernelServiceClass?.apply {
method {
CoreService_KernelServiceClass?.resolve()?.optional()?.apply {
firstMethodOrNull {
name = "onCreate"
}.ignored().hook().after {
}?.hook()?.after {
if (ConfigData.isEnableKillQQTimCoreServiceChild)
instance<Service>().apply {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
@@ -282,10 +274,10 @@ object QQTIMHooker : YukiBaseHooker() {
YLog.debug("Shutdown CoreService\$KernelService OK!")
}
}
method {
firstMethodOrNull {
name = "onStartCommand"
param(IntentClass, IntType, IntType)
}.ignored().hook().replaceTo(any = 2)
parameters(Intent::class, Int::class, Int::class)
}?.hook()?.replaceTo(any = 2)
}
}
@@ -298,40 +290,46 @@ object QQTIMHooker : YukiBaseHooker() {
* 旧版本理论上没有这个类
*/
"${PackageName.QQ}.msf.service.y".toClassOrNull()
?.method {
?.resolve()
?.optional(silent = true)
?.firstMethodOrNull {
name = "a"
param(StringClass, LongType)
returnType = UnitType
}?.ignored()?.hook()?.intercept()
parameters(String::class, Long::class)
returnType = Void.TYPE
}?.hook()?.intercept()
/**
* 干掉自动上传服务的电源锁
* 每个版本的差异暂未做排查
*/
"com.tencent.upload.impl.UploadServiceImpl".toClassOrNull()
?.method {
?.resolve()
?.optional(silent = true)
?.firstMethodOrNull {
name = "acquireWakeLockIfNot"
}?.ignored()?.hook()?.intercept()
}?.hook()?.intercept()
/**
* Hook 掉一个一像素保活 Activity 真的我怎么都想不到讯哥的程序员做出这种事情
* 这个东西经过测试会在锁屏的时候吊起来,解锁的时候自动 finish(),无限耍流氓耗电
* 2022/1/25 后期查证:锁屏界面消息快速回复窗口的解锁后拉起保活界面,也是毒瘤
*/
"${PackageName.QQ}.activity.QQLSUnlockActivity".toClassOrNull()
?.method {
?.resolve()
?.optional(silent = true)
?.firstMethodOrNull {
name = "onCreate"
param(BundleClass)
}?.ignored()?.hook {
parameters(Bundle::class)
}?.hook {
var origDevice = ""
before {
/** 由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂 */
origDevice = Build.MANUFACTURER
if (Build.MANUFACTURER.lowercase() == "xiaomi")
BuildClass.field { name = "MANUFACTURER" }.get().set("HUAWEI")
Build::class.resolve().firstField { name = "MANUFACTURER" }.set("HUAWEI")
}
after {
instance<Activity>().finish()
/** 这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号 */
BuildClass.field { name = "MANUFACTURER" }.get().set(origDevice)
Build::class.resolve().firstField { name = "MANUFACTURER" }.set(origDevice)
}
}
/**
@@ -341,9 +339,11 @@ object QQTIMHooker : YukiBaseHooker() {
* 2022/1/25 后期查证:锁屏界面消息快速回复窗口
*/
VariousClass("${PackageName.QQ}.activity.QQLSActivity\$14", "ktq").toClassOrNull()
?.method {
?.resolve()
?.optional(silent = true)
?.firstMethodOrNull {
name = "run"
}?.ignored()?.hook()?.intercept()
}?.hook()?.intercept()
/**
* 这个是毒瘤核心类
* WakeLockMonitor
@@ -354,92 +354,98 @@ object QQTIMHooker : YukiBaseHooker() {
* 👮🏻 经过排查 Play 版本没这个类...... Emmmm 不想说啥了
* ✅ 备注8.9.x 版本已经基本移除了这个功能,没有再发现存在这个类
*/
"com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor".toClassOrNull()?.apply {
method {
name = "onHook"
param(StringClass, AnyClass, AnyArrayClass, AnyClass)
}.ignored().hook().intercept()
method {
name = "doReport"
param("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor\$WakeLockEntity", IntType)
}.ignored().hook().intercept()
method {
name = "afterHookedMethod"
param("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
}.ignored().hook().intercept()
method {
name = "beforeHookedMethod"
param("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
}.ignored().hook().intercept()
method {
name = "onAppBackground"
}.ignored().hook().intercept()
method {
name = "onOtherProcReport"
param(BundleClass)
}.ignored().hook().intercept()
method {
name = "onProcessRun30Min"
}.ignored().hook().intercept()
method {
name = "onProcessBG5Min"
}.ignored().hook().intercept()
method {
name = "writeReport"
param(BooleanType)
}.ignored().hook().intercept()
}
"com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor".toClassOrNull()
?.resolve()
?.optional(silent = true)
?.apply {
firstMethodOrNull {
name = "onHook"
parameters(String::class, Any::class, ArrayClass(Any::class), Any::class)
}?.hook()?.intercept()
firstMethodOrNull {
name = "doReport"
parameters("com.tencent.qapmsdk.qqbattery.monitor.WakeLockMonitor\$WakeLockEntity", Int::class)
}?.hook()?.intercept()
firstMethodOrNull {
name = "afterHookedMethod"
parameters("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
}?.hook()?.intercept()
firstMethodOrNull {
name = "beforeHookedMethod"
parameters("com.tencent.qapmsdk.qqbattery.monitor.MethodHookParam")
}?.hook()?.intercept()
firstMethodOrNull {
name = "onAppBackground"
}?.hook()?.intercept()
firstMethodOrNull {
name = "onOtherProcReport"
parameters(Bundle::class)
}?.hook()?.intercept()
firstMethodOrNull {
name = "onProcessRun30Min"
}?.hook()?.intercept()
firstMethodOrNull {
name = "onProcessBG5Min"
}?.hook()?.intercept()
firstMethodOrNull {
name = "writeReport"
parameters(Boolean::class)
}?.hook()?.intercept()
}
/**
* 这个是毒瘤核心操作类
* 功能同上、全部拦截
* 👮🏻 经过排查 Play 版本也没这个类...... Emmmm 不想说啥了
* ✅ 备注8.9.x 版本已经基本移除了这个功能,没有再发现存在这个类
*/
"com.tencent.qapmsdk.qqbattery.QQBatteryMonitor".toClassOrNull()?.apply {
method {
name = "start"
}.ignored().hook().intercept()
method {
name = "stop"
}.ignored().hook().intercept()
method {
name = "handleMessage"
param(MessageClass)
}.ignored().hook().intercept()
method {
name = "startMonitorInner"
}.ignored().hook().intercept()
method {
name = "onAppBackground"
}.ignored().hook().intercept()
method {
name = "onAppForeground"
}.ignored().hook().intercept()
method {
name = "setLogWhite"
paramCount = 2
}.ignored().hook().intercept()
method {
name = "setCmdWhite"
paramCount = 2
}.ignored().hook().intercept()
method {
name = "onWriteLog"
param(StringClass, StringClass)
}.ignored().hook().intercept()
method {
name = "onCmdRequest"
param(StringClass)
}.ignored().hook().intercept()
method {
name = "addData"
paramCount = 4
}.ignored().hook().intercept()
method {
name = "onGpsScan"
paramCount = 2
}.ignored().hook().intercept()
}
"com.tencent.qapmsdk.qqbattery.QQBatteryMonitor".toClassOrNull()
?.resolve()
?.optional(silent = true)
?.apply {
firstMethodOrNull {
name = "start"
}?.hook()?.intercept()
firstMethodOrNull {
name = "stop"
}?.hook()?.intercept()
firstMethodOrNull {
name = "handleMessage"
parameters(Message::class)
}?.hook()?.intercept()
firstMethodOrNull {
name = "startMonitorInner"
}?.hook()?.intercept()
firstMethodOrNull {
name = "onAppBackground"
}?.hook()?.intercept()
firstMethodOrNull {
name = "onAppForeground"
}?.hook()?.intercept()
firstMethodOrNull {
name = "setLogWhite"
parameterCount = 2
}?.hook()?.intercept()
firstMethodOrNull {
name = "setCmdWhite"
parameterCount = 2
}?.hook()?.intercept()
firstMethodOrNull {
name = "onWriteLog"
parameters(String::class, String::class)
}?.hook()?.intercept()
firstMethodOrNull {
name = "onCmdRequest"
parameters(String::class)
}?.hook()?.intercept()
firstMethodOrNull {
name = "addData"
parameterCount = 4
}?.hook()?.intercept()
firstMethodOrNull {
name = "onGpsScan"
parameterCount = 2
}?.hook()?.intercept()
}
}
/** Hook QQ 的设置界面添加模块设置入口 (新版) */
@@ -457,27 +463,30 @@ object QQTIMHooker : YukiBaseHooker() {
/** 为了使用图标资源 ID - 这里需要重新注入模块资源防止不生效 */
context.injectModuleAppResources()
val iconResId = if (context.isQQNightMode()) R.mipmap.ic_tsbattery_entry_night else R.mipmap.ic_tsbattery_entry_day
return simpleItemProcessorClass.buildOf(context, R.id.tsbattery_qq_entry_item_id, "TSBattery", iconResId) {
param(ContextClass, IntType, CharSequenceClass, IntType)
}?.also { entryItem ->
return simpleItemProcessorClass.createInstanceOrNull(context, R.id.tsbattery_qq_entry_item_id, "TSBattery", iconResId)?.also { item ->
val onClickMethod = DexKitData.SimpleItemProcessorClass_OnClickMethod ?: error("Could not found processor method")
val proxyOnClick = Proxy.newProxyInstance(appClassLoader, arrayOf(onClickMethod.parameterTypes[0])) { any, method, args ->
if (method.name == "invoke") {
context.startModuleSettings()
kotlinUnit.toClass().field { name = "INSTANCE" }.get().any()
kotlinUnit.toClass().resolve().firstField { name = "INSTANCE" }.get()
} else method.invoke(any, args)
}; onClickMethod.invoke(entryItem, proxyOnClick)
}; onClickMethod.invoke(item, proxyOnClick)
} ?: error("Could not create TSBattery entry item")
}
MainSettingConfigProviderClass?.method {
param(ContextClass)
returnType = ListClass
MainSettingConfigProviderClass?.resolve()?.optional()?.firstMethodOrNull {
parameters(Context::class)
returnType = List::class
}?.hook()?.after {
val context = args().first().cast<Context>() ?: return@after
val processor = result<MutableList<Any?>>() ?: return@after
processor.add(1, processor[0]?.javaClass?.buildOf(arrayListOf<Any>().apply { add(createTSEntryItem(context)) }, "", "") {
param(ListClass, CharSequenceClass, CharSequenceClass)
})
processor.add(
1,
processor[0]?.javaClass?.createInstanceOrNull(
arrayListOf<Any>().apply {
add(createTSEntryItem(context))
}.toList(), "", ""
)
)
}
}
@@ -487,35 +496,38 @@ object QQTIMHooker : YukiBaseHooker() {
*/
private fun hookQQSettingsUiLegacy(instance: Any?) {
/** 当前的顶级 Item 实例 */
val formItemRefRoot = instance?.current()?.field {
type { it == FormSimpleItemClass || it == FormCommonSingleLineItemClass }.index(num = 1)
}?.cast<View?>()
val formItemRefRoot = instance?.resolve()?.optional()?.lastFieldOrNull {
type { it == FormSimpleItemClass || it == FormCommonSingleLineItemClass }
}?.get<View>()
/** 创建一个新的 Item */
FormSimpleItemClass?.buildOf<View>(instance?.compatToActivity()) { param(ContextClass) }?.current {
method {
val item = FormSimpleItemClass?.createInstanceAsTypeOrNull<View>(instance?.compatToActivity())
item?.resolve()?.optional()?.apply {
firstMethodOrNull {
name = "setLeftText"
param(CharSequenceClass)
}.call("TSBattery")
method {
parameters(CharSequence::class)
}?.invoke("TSBattery")
firstMethodOrNull {
name = "setRightText"
param(CharSequenceClass)
}.call(ModuleVersion.toString())
method {
parameters(CharSequence::class)
}?.invoke(ModuleVersion.toString())
firstMethodOrNull {
name = "setBgType"
param(IntType)
}.call(if (isQQ) 0 else 2)
}?.apply { setOnClickListener { context.startModuleSettings() } }?.also { item ->
var listGroup = formItemRefRoot?.parent as? ViewGroup?
val lparam = (if (listGroup?.childCount == 1) {
listGroup = listGroup.parent as? ViewGroup
(formItemRefRoot?.parent as? View?)?.layoutParams
} else formItemRefRoot?.layoutParams)
?: ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
/** 设置圆角和间距 */
if (isQQ) (lparam as? ViewGroup.MarginLayoutParams?)?.setMargins(0, 15.dp(item.context), 0, 0)
/** 将 Item 添加到设置界面 */
listGroup?.also { if (isQQ) it.addView(item, lparam) else it.addView(item, 0, lparam) }
parameters(Int::class)
}?.invoke(if (isQQ) 0 else 2)
}
item ?: return
item.setOnClickListener { it.context.startModuleSettings() }
var listGroup = formItemRefRoot?.parent as? ViewGroup?
val lparam = (if (listGroup?.childCount == 1) {
listGroup = listGroup.parent as? ViewGroup
(formItemRefRoot?.parent as? View?)?.layoutParams
} else formItemRefRoot?.layoutParams)
?: ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
/** 设置圆角和间距 */
if (isQQ) (lparam as? ViewGroup.MarginLayoutParams?)?.setMargins(0, 15.dp(item.context), 0, 0)
/** 将 Item 添加到设置界面 */
listGroup?.also { if (isQQ) it.addView(item, lparam) else it.addView(item, 0, lparam) }
}
override fun onHook() {
@@ -544,22 +556,22 @@ object QQTIMHooker : YukiBaseHooker() {
/** 仅注入主进程 */
withProcess(mainProcessName) {
/** Hook 跳转事件 */
JumpActivityClass?.method {
JumpActivityClass?.resolve()?.optional()?.firstMethodOrNull {
name = "doOnCreate"
param(BundleClass)
parameters(Bundle::class)
}?.hook()?.after { instance<Activity>().jumpToModuleSettings() }
/** Hook 设置界面入口点 */
if (isQQNTVersion) hookQQSettingsUi()
else {
/** 将条目注入设置界面 (Activity) */
QQSettingSettingActivityClass?.method {
QQSettingSettingActivityClass?.resolve()?.optional()?.firstMethodOrNull {
name = "doOnCreate"
param(BundleClass)
parameters(Bundle::class)
}?.hook()?.after { hookQQSettingsUiLegacy(instance) }
/** 将条目注入设置界面 (Fragment) */
QQSettingSettingFragmentClass?.method {
QQSettingSettingFragmentClass?.resolve()?.optional()?.firstMethodOrNull {
name = "doOnCreateView"
paramCount = 3
parameterCount = 3
}?.hook()?.after { hookQQSettingsUiLegacy(instance) }
}
}

View File

@@ -42,14 +42,12 @@ import com.fankes.tsbattery.utils.factory.absoluteStatusBarHeight
import com.fankes.tsbattery.utils.factory.appVersionCode
import com.fankes.tsbattery.utils.factory.appVersionName
import com.fankes.tsbattery.utils.factory.dp
import com.highcapable.kavaref.KavaRef.Companion.resolve
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.injectModuleAppResources
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.factory.processName
import com.highcapable.yukihookapi.hook.factory.registerModuleAppActivities
import com.highcapable.yukihookapi.hook.log.YLog
import com.highcapable.yukihookapi.hook.type.android.ViewClass
/**
* Hook 微信
@@ -114,24 +112,24 @@ object WeChatHooker : YukiBaseHooker() {
/** 仅注入主进程 */
withProcess(mainProcessName) {
/** Hook 跳转事件 */
LauncherUIClass?.method {
LauncherUIClass?.resolve()?.optional()?.firstMethodOrNull {
name = "onResume"
emptyParam()
emptyParameters()
}?.hook()?.after { instance<Activity>().jumpToModuleSettings(isFinish = false) }
/** 向设置界面右上角添加按钮 */
SettingsUIClass?.method {
SettingsUIClass?.resolve()?.optional()?.firstMethodOrNull {
name = "onResume"
emptyParam()
emptyParameters()
}?.hook()?.after {
SettingsUIClass?.method {
SettingsUIClass?.resolve()?.optional()?.firstMethodOrNull {
name = "get_fragment"
emptyParam()
superClass(isOnlySuperClass = true)
}?.get(instance)?.call()?.current()?.method {
emptyParameters()
superclass()
}?.of(instance)?.invoke()?.resolve()?.optional()?.firstMethodOrNull {
name = "getView"
emptyParam()
returnType = ViewClass
superClass(isOnlySuperClass = true)
emptyParameters()
returnType = View::class
superclass()
}?.invoke<ViewGroup?>()?.also {
it.context?.injectModuleAppResources()
runCatching { it.getChildAt(0) as? ViewGroup? }.getOrNull()?.also { rootView ->

View File

@@ -24,18 +24,16 @@ package com.fankes.tsbattery.hook.factory
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.PowerManager
import androidx.appcompat.app.AppCompatDelegate
import com.fankes.tsbattery.const.JumpEvent
import com.fankes.tsbattery.const.PackageName
import com.fankes.tsbattery.hook.entity.QQTIMHooker.toClass
import com.fankes.tsbattery.ui.activity.parasitic.ConfigActivity
import com.highcapable.kavaref.KavaRef.Companion.resolve
import com.highcapable.kavaref.extension.VariousClass
import com.highcapable.yukihookapi.YukiHookAPI
import com.highcapable.yukihookapi.hook.bean.VariousClass
import com.highcapable.yukihookapi.hook.factory.field
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.param.PackageParam
import com.highcapable.yukihookapi.hook.type.android.PowerManager_WakeLockClass
import com.highcapable.yukihookapi.hook.type.java.IntType
import kotlin.system.exitProcess
/** QQ、TIM 存在的类 */
@@ -48,14 +46,27 @@ private val ThemeUtilClass = VariousClass("${PackageName.QQ}.theme.ThemeUtil", "
* QQ、TIM 主题是否为夜间模式
* @return [Boolean]
*/
fun Context.isQQNightMode() = runCatching {
ThemeUtilClass.get(classLoader).method {
name = "getUserCurrentThemeId"
paramCount = 1
}.get().string(MobileQQClass.toClass(classLoader)
.field { name = "sMobileQQ" }.ignored().get().current(ignored = true)?.field { name = "mAppRuntime" }?.any()
).let { it.endsWith("1103") || it.endsWith("2920") }
}.getOrNull() ?: false
fun Context.isQQNightMode(): Boolean {
val sMobileQQ = MobileQQClass.toClass(classLoader).resolve()
.optional()
.firstFieldOrNull {
name = "sMobileQQ"
superclass()
}?.get()
val mAppRuntime = sMobileQQ?.resolve()
?.optional()
?.firstFieldOrNull {
name = "mAppRuntime"
superclass()
}?.get()
val currentThemeId = ThemeUtilClass.load(classLoader).resolve()
.optional()
.firstMethodOrNull {
name = "getUserCurrentThemeId"
parameterCount = 1
}?.invokeQuietly<String>(mAppRuntime)
return currentThemeId?.let { it.endsWith("1103") || it.endsWith("2920") } == true
}
/** 启动模块设置 [Activity] */
fun Context.startModuleSettings() {
@@ -82,14 +93,14 @@ fun Activity.jumpToModuleSettings(isFinish: Boolean = true) {
/** Hook 系统电源锁 */
fun PackageParam.hookSystemWakeLock() {
PowerManager_WakeLockClass.apply {
method {
PowerManager.WakeLock::class.resolve().apply {
firstMethod {
name = "acquireLocked"
emptyParam()
emptyParameters()
}.hook().intercept()
method {
firstMethod {
name = "release"
param(IntType)
parameters(Int::class)
}.hook().intercept()
}
}

View File

@@ -19,23 +19,24 @@
*
* This file is created by fankes on 2022/1/30.
*/
@file:Suppress("DEPRECATION")
package com.fankes.tsbattery.ui.activity.base
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.WindowCompat
import androidx.viewbinding.ViewBinding
import com.fankes.tsbattery.R
import com.fankes.tsbattery.utils.factory.isNotSystemInDarkMode
import com.highcapable.yukihookapi.hook.factory.current
import com.highcapable.yukihookapi.hook.factory.method
import com.highcapable.yukihookapi.hook.type.android.LayoutInflaterClass
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppCompatActivity
import com.highcapable.kavaref.KavaRef.Companion.resolve
import com.highcapable.kavaref.extension.genericSuperclassTypeArguments
import com.highcapable.kavaref.extension.toClassOrNull
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy.ModuleActivity
abstract class BaseActivity<VB : ViewBinding> : ModuleAppCompatActivity() {
abstract class BaseActivity<VB : ViewBinding> : AppCompatActivity(), ModuleActivity {
override val moduleTheme get() = R.style.Theme_TSBattery
@@ -43,11 +44,13 @@ abstract class BaseActivity<VB : ViewBinding> : ModuleAppCompatActivity() {
lateinit var binding: VB
override fun onCreate(savedInstanceState: Bundle?) {
delegate.onCreate(savedInstanceState)
super.onCreate(savedInstanceState)
binding = current().generic()?.argument()?.method {
val bindingClass = javaClass.genericSuperclassTypeArguments().firstOrNull()?.toClassOrNull()
binding = bindingClass?.resolve()?.optional()?.firstMethodOrNull {
name = "inflate"
param(LayoutInflaterClass)
}?.get()?.invoke<VB>(layoutInflater) ?: error("binding failed")
parameters(LayoutInflater::class)
}?.invoke<VB>(layoutInflater) ?: error("binding failed")
if (Build.VERSION.SDK_INT >= 35) binding.root.fitsSystemWindows = true
setContentView(binding.root)
/** 隐藏系统的标题栏 */
@@ -57,6 +60,7 @@ abstract class BaseActivity<VB : ViewBinding> : ModuleAppCompatActivity() {
isAppearanceLightStatusBars = isNotSystemInDarkMode
isAppearanceLightNavigationBars = isNotSystemInDarkMode
}
@Suppress("DEPRECATION")
ResourcesCompat.getColor(resources, R.color.colorThemeBackground, null).also {
window?.statusBarColor = it
window?.navigationBarColor = it
@@ -68,4 +72,16 @@ abstract class BaseActivity<VB : ViewBinding> : ModuleAppCompatActivity() {
/** 回调 [onCreate] 方法 */
abstract fun onCreate()
override fun getClassLoader() = delegate.getClassLoader()
override fun onConfigurationChanged(newConfig: Configuration) {
delegate.onConfigurationChanged(newConfig)
super.onConfigurationChanged(newConfig)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
delegate.onRestoreInstanceState(savedInstanceState)
super.onRestoreInstanceState(savedInstanceState)
}
}

View File

@@ -23,23 +23,26 @@
package com.fankes.tsbattery.ui.activity.base
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.widget.FrameLayout
import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.WindowCompat
import com.fankes.tsbattery.R
import com.fankes.tsbattery.utils.factory.isNotSystemInDarkMode
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.base.ModuleAppCompatActivity
import com.highcapable.yukihookapi.hook.xposed.parasitic.activity.proxy.ModuleActivity
import android.R as Android_R
abstract class BaseActivity2 : ModuleAppCompatActivity() {
abstract class BaseActivity2 : AppCompatActivity(), ModuleActivity {
override val moduleTheme get() = R.style.Theme_TSBattery
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
delegate.onCreate(savedInstanceState)
super.onCreate(savedInstanceState)
/** 隐藏系统的标题栏 */
supportActionBar?.hide()
@@ -60,4 +63,16 @@ abstract class BaseActivity2 : ModuleAppCompatActivity() {
/** 回调 [onCreate] 方法 */
abstract fun onCreate()
override fun getClassLoader() = delegate.getClassLoader()
override fun onConfigurationChanged(newConfig: Configuration) {
delegate.onConfigurationChanged(newConfig)
super.onConfigurationChanged(newConfig)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
delegate.onRestoreInstanceState(savedInstanceState)
super.onRestoreInstanceState(savedInstanceState)
}
}

View File

@@ -635,6 +635,35 @@
android:textColor="@color/colorTextGray"
android:textSize="11sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:layout_marginBottom="10dp"
android:background="@drawable/bg_permotion_round"
android:gravity="center|start"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:layout_width="35dp"
android:layout_height="35dp"
android:layout_marginEnd="10dp"
android:src="@mipmap/ic_kavaref" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:ellipsize="end"
android:lineSpacingExtra="6dp"
android:maxLines="2"
android:text="此模块使用 KavaRef 强力驱动。\n了解更多 https://github.com/HighCapable/KavaRef"
android:textColor="@color/colorTextGray"
android:textSize="11sp" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -22,7 +22,7 @@ repositories:
plugins:
com.android.application:
alias: android-application
version: 8.9.0
version: 8.9.3
org.jetbrains.kotlin.android:
alias: kotlin-android
version: 2.1.10
@@ -43,12 +43,17 @@ libraries:
rovo89-xposed-api
com.highcapable.yukihookapi:
api:
version: 1.2.1
version: 1.3.0
ksp-xposed:
version-ref: <this>::api
com.highcapable.kavaref:
kavaref-core:
version: 1.0.0
kavaref-extension:
version: 1.0.0
com.highcapable.hikage:
hikage-core:
version: 1.0.0
version: 1.0.1
hikage-compiler:
version: 1.0.0
hikage-extension:
@@ -59,19 +64,19 @@ libraries:
version: 1.0.0
org.luckypray:
dexkit:
version: 2.0.3
version: 2.0.6
com.github.duanhong169:
drawabletoolbox:
version: 1.0.7
com.squareup.okhttp3:
okhttp:
version: 5.0.0-alpha.14
version: 5.0.0-alpha.16
androidx.core:
core-ktx:
version: 1.15.0
version: 1.16.0
androidx.appcompat:
appcompat:
version: 1.7.0
version: 1.7.1
com.google.android.material:
material:
version: 1.12.0