From c2b95b9133379dcd7a9aefd9ebd07a6a18769f82 Mon Sep 17 00:00:00 2001 From: fankesyooni Date: Wed, 26 Jul 2023 02:45:56 +0800 Subject: [PATCH] Modify support QQ 8.9.70 (QQ-NT) hook entry item --- app/proguard-rules.pro | 6 + .../tsbattery/hook/entity/QQTIMHooker.kt | 130 ++++++++++++++---- .../hook/factory/BasicHookFactory.kt | 2 +- .../mipmap-xxhdpi/ic_tsbattery_entry_day.png | Bin 0 -> 3819 bytes .../ic_tsbattery_entry_night.png | Bin 0 -> 3916 bytes app/src/main/res/values/ids.xml | 4 + 6 files changed, 118 insertions(+), 24 deletions(-) create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_tsbattery_entry_day.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_tsbattery_entry_night.png create mode 100644 app/src/main/res/values/ids.xml diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 2bd51b8..63e90b0 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -37,6 +37,12 @@ # 排除注入的 Activity -keep class com.fankes.tsbattery.ui.activity.parasitic.ConfigActivity +# 防止某些类被 R8 混淆 (可能是 BUG) +# FIXME: 已知问题字符串类名 (即使是常量) 也会被 R8 处理为混淆后的类名 +# FIXME: 所以目前只能把不允许 R8 处理的类 keep 掉,同时在当前模块中也不会被混淆就是了 +-keep class kotlin.Unit +-keep class kotlin.jvm.functions.Function0 + -assumenosideeffects class kotlin.jvm.internal.Intrinsics { public static *** throwUninitializedProperty(...); public static *** throwUninitializedPropertyAccessException(...); diff --git a/app/src/main/java/com/fankes/tsbattery/hook/entity/QQTIMHooker.kt b/app/src/main/java/com/fankes/tsbattery/hook/entity/QQTIMHooker.kt index cb77344..b530fdc 100644 --- a/app/src/main/java/com/fankes/tsbattery/hook/entity/QQTIMHooker.kt +++ b/app/src/main/java/com/fankes/tsbattery/hook/entity/QQTIMHooker.kt @@ -23,6 +23,7 @@ package com.fankes.tsbattery.hook.entity import android.app.Activity import android.app.Service +import android.content.Context import android.content.res.Configuration import android.os.Build import android.view.View @@ -30,10 +31,12 @@ import android.view.ViewGroup import androidx.core.app.ServiceCompat import androidx.fragment.app.Fragment import com.fankes.tsbattery.BuildConfig +import com.fankes.tsbattery.R import com.fankes.tsbattery.const.PackageName import com.fankes.tsbattery.data.ConfigData import com.fankes.tsbattery.hook.HookEntry import com.fankes.tsbattery.hook.factory.hookSystemWakeLock +import com.fankes.tsbattery.hook.factory.isQQNightMode import com.fankes.tsbattery.hook.factory.jumpToModuleSettings import com.fankes.tsbattery.hook.factory.startModuleSettings import com.fankes.tsbattery.utils.factory.appVersionName @@ -47,6 +50,7 @@ import com.highcapable.yukihookapi.hook.log.loggerI import com.highcapable.yukihookapi.hook.log.loggerW import com.highcapable.yukihookapi.hook.type.android.* import com.highcapable.yukihookapi.hook.type.java.* +import java.lang.reflect.Proxy /** * Hook QQ、TIM @@ -56,15 +60,24 @@ object QQTIMHooker : YukiBaseHooker() { /** QQ、TIM 存在的类 */ const val JumpActivityClass = "${PackageName.QQ}.activity.JumpActivity" - /** QQ、TIM 存在的类 */ + /** QQ、TIM 存在的类 (NT 版本不再存在) */ private const val QQSettingSettingActivityClass = "${PackageName.QQ}.activity.QQSettingSettingActivity" - /** QQ 新版存在的类 (Pad 模式) */ + /** QQ 新版存在的类 (Pad 模式 - NT 版本不再存在) */ private const val QQSettingSettingFragmentClass = "${PackageName.QQ}.fragment.QQSettingSettingFragment" - /** QQ、TIM 存在的类 */ + /** QQ、TIM 存在的类 (NT 版本不再存在) */ private const val AboutActivityClass = "${PackageName.QQ}.activity.AboutActivity" + /** QQ 新版本存在的类 */ + private const val GeneralSettingActivityClass = "${PackageName.QQ}.activity.GeneralSettingActivity" + + /** QQ 新版本 (NT) 存在的类 */ + private const val MainSettingFragmentClass = "${PackageName.QQ}.setting.main.MainSettingFragment" + + /** QQ 新版本 (NT) 存在的类 */ + private const val MainSettingConfigProviderClass = "${PackageName.QQ}.setting.main.MainSettingConfigProvider" + /** QQ、TIM 新版本存在的类 */ private const val FormSimpleItemClass = "${PackageName.QQ}.widget.FormSimpleItem" @@ -92,6 +105,14 @@ object QQTIMHooker : YukiBaseHooker() { */ private val isQQ get() = packageName == PackageName.QQ + /** + * 当前是否为 QQ 的 NT 版本 + * + * 在 QQ NT 中 [AboutActivityClass] 已被移除 - 以此作为判断条件 + * @return [Boolean] + */ + private val isQQNTVersion get() = isQQ && AboutActivityClass.hasClass().not() + /** 当前宿主的版本 */ private var hostVersionName = "" @@ -543,11 +564,68 @@ object QQTIMHooker : YukiBaseHooker() { }.ignoredHookClassNotFoundFailure() } + /** Hook QQ 的设置界面添加模块设置入口 (新版) */ + private fun hookQQSettingsUi() { + if (MainSettingFragmentClass.hasClass().not()) return loggerE(msg = "Could not found main setting class, hook aborted") + val kotlinUnit = "kotlin.Unit" + val kotlinFunction0 = "kotlin.jvm.functions.Function0" + val simpleItemProcessorClass = searchClass { + from("${PackageName.QQ}.setting.processor").absolute() + constructor { param(ContextClass, IntType, CharSequenceClass, IntType) } + method { + param(kotlinFunction0) + returnType = UnitType + } + field().count { it >= 6 } + }.get() ?: return loggerE(msg = "Could not found processor class, hook aborted") + + /** + * 创建入口点条目 + * @param context 当前实例 + * @return [Any] + */ + fun createTSEntryItem(context: Context): Any { + /** 为了使用图标资源 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 -> + val onClickMethod = simpleItemProcessorClass.method { + param { it[0].name == kotlinFunction0 } + paramCount = 1 + returnType = UnitType + }.giveAll().lastOrNull() ?: 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() + } else method.invoke(any, args) + }; onClickMethod.invoke(entryItem, proxyOnClick) + } ?: error("Could not create TSBattery entry item") + } + MainSettingConfigProviderClass.hook { + injectMember { + method { + param(ContextClass) + returnType = ListClass + } + afterHook { + val context = args().first().cast() ?: return@afterHook + val processor = result>() ?: return@afterHook + processor.add(1, processor[0]?.javaClass?.buildOf(arrayListOf().apply { add(createTSEntryItem(context)) }, "", "") { + param(ListClass, CharSequenceClass, CharSequenceClass) + }) + } + } + } + } + /** - * Hook QQ 的设置界面添加模块设置入口 + * Hook QQ 的设置界面添加模块设置入口 (旧版) * @param instance 当前设置界面实例 */ - private fun hookQQSettingsUI(instance: Any?) { + private fun hookQQSettingsUiLegacy(instance: Any?) { /** 当前的顶级 Item 实例 */ val formItemRefRoot = instance?.current()?.field { type { it.name == FormSimpleItemClass || it.name == FormCommonSingleLineItemClass }.index(num = 1) @@ -590,7 +668,9 @@ object QQTIMHooker : YukiBaseHooker() { /** 不注入此进程防止部分系统发生 X5 浏览器内核崩溃问题 */ if (processName.startsWith(privilegedProcessName)) return@onCreate ConfigData.init(context = this) - registerModuleAppActivities(AboutActivityClass) + if (isQQNTVersion) + registerModuleAppActivities(GeneralSettingActivityClass) + else registerModuleAppActivities(AboutActivityClass) if (ConfigData.isDisableAllHook) return@onCreate hookSystemWakeLock() hookQQBaseChatPie() @@ -611,26 +691,30 @@ object QQTIMHooker : YukiBaseHooker() { afterHook { instance().jumpToModuleSettings() } } } - /** 将条目注入设置界面 (Activity) */ - QQSettingSettingActivityClass.hook { - injectMember { - method { - name = "doOnCreate" - param(BundleClass) + /** Hook 设置界面入口点 */ + if (isQQNTVersion) hookQQSettingsUi() + else { + /** 将条目注入设置界面 (Activity) */ + QQSettingSettingActivityClass.hook { + injectMember { + method { + name = "doOnCreate" + param(BundleClass) + } + afterHook { hookQQSettingsUiLegacy(instance) } } - afterHook { hookQQSettingsUI(instance) } } + /** 将条目注入设置界面 (Fragment) */ + QQSettingSettingFragmentClass.hook { + injectMember { + method { + name = "doOnCreateView" + paramCount = 3 + } + afterHook { hookQQSettingsUiLegacy(instance) } + } + }.ignoredHookClassNotFoundFailure() } - /** 将条目注入设置界面 (Fragment) */ - QQSettingSettingFragmentClass.hook { - injectMember { - method { - name = "doOnCreateView" - paramCount = 3 - } - afterHook { hookQQSettingsUI(instance) } - } - }.ignoredHookClassNotFoundFailure() } } } \ No newline at end of file diff --git a/app/src/main/java/com/fankes/tsbattery/hook/factory/BasicHookFactory.kt b/app/src/main/java/com/fankes/tsbattery/hook/factory/BasicHookFactory.kt index f078f29..3d9ac05 100644 --- a/app/src/main/java/com/fankes/tsbattery/hook/factory/BasicHookFactory.kt +++ b/app/src/main/java/com/fankes/tsbattery/hook/factory/BasicHookFactory.kt @@ -47,7 +47,7 @@ private val ThemeUtilClass = VariousClass("${PackageName.QQ}.theme.ThemeUtil", " * QQ、TIM 主题是否为夜间模式 * @return [Boolean] */ -private fun Context.isQQNightMode() = runCatching { +fun Context.isQQNightMode() = runCatching { ThemeUtilClass.get(classLoader).method { name = "getUserCurrentThemeId" paramCount = 1 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_tsbattery_entry_day.png b/app/src/main/res/mipmap-xxhdpi/ic_tsbattery_entry_day.png new file mode 100644 index 0000000000000000000000000000000000000000..cbaa80a3711a0ac97e598cbecc875676ed9ac22d GIT binary patch literal 3819 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_tsbattery_entry_night.png b/app/src/main/res/mipmap-xxhdpi/ic_tsbattery_entry_night.png new file mode 100644 index 0000000000000000000000000000000000000000..3d82562c7a04875b26087568ff56b970281c6630 GIT binary patch literal 3916 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 + + + \ No newline at end of file