Update to 2.3 support QQ 8.8.38

This commit is contained in:
2021-11-09 19:37:01 +08:00
parent d4202e605a
commit a25d2b814b
7 changed files with 319 additions and 176 deletions

View File

@@ -22,8 +22,8 @@ android {
minSdkVersion 22 minSdkVersion 22
//noinspection ExpiredTargetSdkVersion,OldTargetApi //noinspection ExpiredTargetSdkVersion,OldTargetApi
targetSdkVersion 26 targetSdkVersion 26
versionCode 5 versionCode 6
versionName "2.2" versionName "2.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@@ -4,6 +4,7 @@
package="com.fankes.tsbattery"> package="com.fankes.tsbattery">
<application <application
android:name=".application.TSApplication"
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"

View File

@@ -1,4 +1,4 @@
/* /**
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
* *
* This file is part of TSBattery. * This file is part of TSBattery.
@@ -32,80 +32,82 @@ import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log
import android.view.View import android.view.View
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.annotation.Keep
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.SwitchCompat import androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.utils.widget.ImageFilterView import androidx.constraintlayout.utils.widget.ImageFilterView
import com.fankes.tsbattery.hook.HookMain import com.fankes.tsbattery.hook.HookMedium
import com.fankes.tsbattery.utils.FileUtils import com.fankes.tsbattery.utils.FileUtils
import com.gyf.immersionbar.ImmersionBar import com.gyf.immersionbar.ImmersionBar
import java.io.File import java.io.File
@Keep
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
companion object { companion object {
private const val moduleVersion = BuildConfig.VERSION_NAME private const val moduleVersion = BuildConfig.VERSION_NAME
private const val moduleSupport = "QQ 8.5.5~8.8.23、TIM 2+" private const val moduleSupport = "QQ 8.5.5~8.8.38、TIM 2+"
/** 声明当前实例 */
var instance: MainActivity? = null
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
/** 设置自身实例 */
instance = this
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
/*禁止系统夜间模式对自己造成干扰*/ /** 隐藏系统的标题栏 */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
/*隐藏系统的标题栏*/
supportActionBar?.hide() supportActionBar?.hide()
/*初始化沉浸状态栏*/ /** 初始化沉浸状态栏 */
ImmersionBar.with(this) ImmersionBar.with(this)
.statusBarColor("#FFFFFFFF") .statusBarColor(R.color.white)
.autoDarkModeEnable(false) .autoDarkModeEnable(false)
.statusBarDarkFont(true) .statusBarDarkFont(true)
.navigationBarColor("#FFFFFFFF") .navigationBarColor(R.color.white)
.navigationBarDarkIcon(true) .navigationBarDarkIcon(true)
.fitsSystemWindows(true) .fitsSystemWindows(true)
.init() .init()
/*判断 Hook 状态*/ /** 判断 Hook 状态 */
if (isHooked()) { if (isHooked()) {
findViewById<LinearLayout>(R.id.main_lin_status).setBackgroundResource(R.drawable.green_round) findViewById<LinearLayout>(R.id.main_lin_status).setBackgroundResource(R.drawable.green_round)
findViewById<ImageFilterView>(R.id.main_img_status).setImageResource(R.mipmap.succcess) findViewById<ImageFilterView>(R.id.main_img_status).setImageResource(R.mipmap.succcess)
findViewById<TextView>(R.id.main_text_status).text = "模块已激活" findViewById<TextView>(R.id.main_text_status).text = "模块已激活"
/** 写入激活的模块版本 */
putString(HookMedium.ENABLE_MODULE_VERSION, moduleVersion)
} else } else
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle("模块没有激活") .setTitle("模块没有激活")
.setMessage( .setMessage(
"检测到模块没有激活,模块需要 Xposed 环境依赖,同时需要系统拥有 Root 权限(太极阴可以免 Root),请自行查看本页面使用帮助与说明第三条。\n" + "检测到模块没有激活,模块需要 Xposed 环境依赖,同时需要系统拥有 Root 权限(太极阴可以免 Root),请自行查看本页面使用帮助与说明第三条。\n" +
"太极、应用转生、梦境(Pine)和第三方 Xposed 激活后可能不会提示激活,若想验证是否激活请打开“提示模块运行信息”自行检查,如果生效就代表模块运行正常,这里的激活状态只是一个显示意义上的存在。" "太极、应用转生、梦境(Pine)和第三方 Xposed 激活后可能不会提示激活,若想验证是否激活请打开“提示模块运行信息”自行检查,如果生效就代表模块运行正常,这里的激活状态只是一个显示意义上的存在。\n" +
"太极(无极)在 MIUI 设备上会提示打开授权,请进行允许,然后再次打开本应用查看激活状态。"
) )
.setPositiveButton("我知道了", null) .setPositiveButton("我知道了", null)
.setCancelable(false) .setCancelable(false)
.show() .show()
/*设置文本*/ /** 设置文本 */
findViewById<TextView>(R.id.main_text_version).text = "当前版本:$moduleVersion" findViewById<TextView>(R.id.main_text_version).text = "当前版本:$moduleVersion"
findViewById<TextView>(R.id.main_text_support).text = "支持 $moduleSupport" findViewById<TextView>(R.id.main_text_support).text = "支持 $moduleSupport"
/*初始化 View*/ /** 初始化 View */
val protectModeSwitch = findViewById<SwitchCompat>(R.id.protect_mode_switch) val protectModeSwitch = findViewById<SwitchCompat>(R.id.protect_mode_switch)
val hideIconInLauncherSwitch = findViewById<SwitchCompat>(R.id.hide_icon_in_launcher_switch) val hideIconInLauncherSwitch = findViewById<SwitchCompat>(R.id.hide_icon_in_launcher_switch)
val notifyModuleInfoSwitch = findViewById<SwitchCompat>(R.id.notify_module_info_switch) val notifyModuleInfoSwitch = findViewById<SwitchCompat>(R.id.notify_module_info_switch)
/*获取 Sp 存储的信息*/ /** 获取 Sp 存储的信息 */
protectModeSwitch.isChecked = getBoolean("_white_mode") protectModeSwitch.isChecked = getBoolean("_white_mode")
hideIconInLauncherSwitch.isChecked = getBoolean("_hide_icon") hideIconInLauncherSwitch.isChecked = getBoolean("_hide_icon")
notifyModuleInfoSwitch.isChecked = getBoolean("_tip_run_info") notifyModuleInfoSwitch.isChecked = getBoolean("_tip_run_info")
protectModeSwitch.setOnCheckedChangeListener { btn, b -> protectModeSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_white_mode", b) putBoolean(HookMedium.ENABLE_WHITE_MODE, b)
} }
hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b -> hideIconInLauncherSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_hide_icon", b) putBoolean(HookMedium.ENABLE_HIDE_ICON, b)
packageManager.setComponentEnabledSetting( packageManager.setComponentEnabledSetting(
ComponentName(this@MainActivity, "com.fankes.tsbattery.Home"), ComponentName(this@MainActivity, "com.fankes.tsbattery.Home"),
if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED, if (b) PackageManager.COMPONENT_ENABLED_STATE_DISABLED else PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
@@ -114,16 +116,15 @@ class MainActivity : AppCompatActivity() {
} }
notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b -> notifyModuleInfoSwitch.setOnCheckedChangeListener { btn, b ->
if (!btn.isPressed) return@setOnCheckedChangeListener if (!btn.isPressed) return@setOnCheckedChangeListener
putBoolean("_tip_run_info", b) putBoolean(HookMedium.ENABLE_RUN_INFO, b)
} }
/*项目地址点击事件*/ /** 项目地址点击事件 */
findViewById<View>(R.id.link_with_project_address).setOnClickListener { findViewById<View>(R.id.link_with_project_address).setOnClickListener {
try { try {
val intent = Intent() startActivity(Intent().apply {
intent.action = "android.intent.action.VIEW" action = "android.intent.action.VIEW"
val content_url = Uri.parse("https://github.com/fankes/TSBattery") data = Uri.parse("https://github.com/fankes/TSBattery")
intent.data = content_url })
startActivity(intent)
} catch (e: Exception) { } catch (e: Exception) {
Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show() Toast.makeText(this, "无法启动系统默认浏览器", Toast.LENGTH_SHORT).show()
} }
@@ -132,42 +133,9 @@ class MainActivity : AppCompatActivity() {
/** /**
* 判断模块是否激活 * 判断模块是否激活
* 在 [HookMain] 中 Hook 掉此方法
* @return 激活状态 * @return 激活状态
*/ */
private fun isHooked(): Boolean { private fun isHooked() = HookMedium.isHooked()
Log.d("TSBattery", "isHooked: true")
return isExpModuleActive()
}
/**
* 新增太极判断方式
* @return 是否激活
*/
private fun isExpModuleActive(): Boolean {
var isExp = false
try {
val uri = Uri.parse("content://me.weishu.exposed.CP/")
var result: Bundle? = null
try {
result = contentResolver.call(uri, "active", null, null)
} catch (e: RuntimeException) {
// TaiChi is killed, try invoke
try {
val intent = Intent("me.weishu.exp.ACTION_ACTIVE")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
} catch (e1: Throwable) {
return false
}
}
if (result == null) result = contentResolver.call(uri, "active", null, null)
if (result == null) return false
isExp = result.getBoolean("active", false)
} catch (ignored: Throwable) {
}
return isExp
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@@ -206,6 +174,24 @@ class MainActivity : AppCompatActivity() {
Context.MODE_PRIVATE Context.MODE_PRIVATE
).edit().putBoolean(key, bool).apply() ).edit().putBoolean(key, bool).apply()
setWorldReadable() setWorldReadable()
/** 延迟继续设置强制允许 SP 可读可写 */
Handler().postDelayed({ setWorldReadable() }, 500)
Handler().postDelayed({ setWorldReadable() }, 1000)
Handler().postDelayed({ setWorldReadable() }, 1500)
}
/**
* 保存值
* @param key 名称
* @param value 值
*/
private fun putString(key: String, value: String) {
getSharedPreferences(
packageName + "_preferences",
Context.MODE_PRIVATE
).edit().putString(key, value).apply()
setWorldReadable()
/** 延迟继续设置强制允许 SP 可读可写 */
Handler().postDelayed({ setWorldReadable() }, 500) Handler().postDelayed({ setWorldReadable() }, 500)
Handler().postDelayed({ setWorldReadable() }, 1000) Handler().postDelayed({ setWorldReadable() }, 1000)
Handler().postDelayed({ setWorldReadable() }, 1500) Handler().postDelayed({ setWorldReadable() }, 1500)
@@ -231,4 +217,10 @@ class MainActivity : AppCompatActivity() {
Toast.makeText(this, "无法写入模块设置,请检查权限\n如果此提示一直显示,请不要双开模块", Toast.LENGTH_SHORT).show() Toast.makeText(this, "无法写入模块设置,请检查权限\n如果此提示一直显示,请不要双开模块", Toast.LENGTH_SHORT).show()
} }
} }
override fun onDestroy() {
super.onDestroy()
/** 销毁实例防止内存泄漏 */
instance = null
}
} }

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by fankes on 2021/9/4.
*/
@file:Suppress("unused")
package com.fankes.tsbattery.application
import android.app.Application
import androidx.appcompat.app.AppCompatDelegate
class TSApplication : Application() {
override fun onCreate() {
super.onCreate()
/** 禁止系统夜间模式对自己造成干扰 - 模块要什么夜间模式?😅 (其实是我懒) */
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
}
}

View File

@@ -1,4 +1,4 @@
/* /**
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com) * Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
* *
* This file is part of TSBattery. * This file is part of TSBattery.
@@ -27,6 +27,7 @@ import android.app.AlertDialog
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.widget.Toast
import androidx.annotation.Keep import androidx.annotation.Keep
import com.fankes.tsbattery.utils.XPrefUtils import com.fankes.tsbattery.utils.XPrefUtils
import de.robv.android.xposed.* import de.robv.android.xposed.*
@@ -36,6 +37,65 @@ import java.util.*
@Keep @Keep
class HookMain : IXposedHookLoadPackage { class HookMain : IXposedHookLoadPackage {
/** 仅作用于替换的 Hook 方法体 */
private val replaceToNull = object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any? {
return null
}
}
/** 仅作用于替换的 Hook 方法体 */
private val replaceToTrue = object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any {
return true
}
}
/**
* 干掉目标方法体封装
* @param clazz 类名缩写
* @param name 方法名
*/
private fun XC_LoadPackage.LoadPackageParam.replaceToNull(clazz: String, name: String) {
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.$clazz",
classLoader,
name,
replaceToNull
)
}
/**
* 这个类 BaseChatPie 是控制聊天界面的
* 里面有两个随机混淆的方法
* 这两个方法一个是挂起电源锁常驻亮屏
* 一个是停止常驻亮屏
* 不由分说每个版本混淆的方法名都会变
* 所以说每个版本重新适配 - 也可以提交分支帮我适配
* 8.8.17 版本是 bd be
* 8.8.23 版本是 bf bg
* 8.8.38 版本是 bi bj
* ⚠️ Hook 错了方法会造成闪退!
* @param version QQ 版本
*/
private fun XC_LoadPackage.LoadPackageParam.hookBaseChatPie(version: String) {
when (version) {
"8.8.17" -> {
replaceToNull("activity.aio.core.BaseChatPie", "bd")
replaceToNull("activity.aio.core.BaseChatPie", "be")
}
"8.8.23" -> {
replaceToNull("activity.aio.core.BaseChatPie", "bf")
replaceToNull("activity.aio.core.BaseChatPie", "bg")
}
"8.8.38" -> {
replaceToNull("activity.aio.core.BaseChatPie", "bi")
replaceToNull("activity.aio.core.BaseChatPie", "bj")
}
else -> logD("$version not supported!")
}
}
/** /**
* Print the log * Print the log
* @param content * @param content
@@ -57,29 +117,23 @@ class HookMain : IXposedHookLoadPackage {
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) { override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam?) {
if (lpparam == null) return if (lpparam == null) return
when (lpparam.packageName) { when (lpparam.packageName) {
/*Hook 自身*/ /** Hook 自身 */
"com.fankes.tsbattery" -> "com.fankes.tsbattery" ->
XposedHelpers.findAndHookMethod( XposedHelpers.findAndHookMethod(
"com.fankes.tsbattery.MainActivity", "com.fankes.tsbattery.hook.HookMedium",
lpparam.classLoader, lpparam.classLoader,
"isHooked", "isHooked",
object : XC_MethodReplacement() { replaceToTrue
override fun replaceHookedMethod(param: MethodHookParam?): Any { )
return true /** 经过测试 QQ 与 TIM 这两个是一个模子里面的东西,所以他们的类名也基本上是一样的 */
}
})
/*经过测试 QQ 与 TIM 这两个是一个模子里面的东西,所以他们的类名也基本上是一样的*/
"com.tencent.mobileqq", "com.tencent.tim" -> { "com.tencent.mobileqq", "com.tencent.tim" -> {
try { try {
XposedHelpers.findAndHookMethod( XposedHelpers.findAndHookMethod(
"android.os.PowerManager\$WakeLock", "android.os.PowerManager\$WakeLock",
lpparam.classLoader, lpparam.classLoader,
"acquire", "acquire",
object : XC_MethodReplacement() { replaceToNull
override fun replaceHookedMethod(param: MethodHookParam?): Any? { )
return null
}
})
} catch (e: Throwable) { } catch (e: Throwable) {
logE("handleLoadPackage: hook wakeLock acquire() Failed", e) logE("handleLoadPackage: hook wakeLock acquire() Failed", e)
} }
@@ -89,16 +143,33 @@ class HookMain : IXposedHookLoadPackage {
lpparam.classLoader, lpparam.classLoader,
"acquire", "acquire",
Long::class.java, Long::class.java,
object : XC_MethodReplacement() { replaceToNull
override fun replaceHookedMethod(param: MethodHookParam?): Any? { )
return null
}
})
} catch (e: Throwable) { } catch (e: Throwable) {
logE("handleLoadPackage: hook wakeLock acquire(time) Failed", e) logE("handleLoadPackage: hook wakeLock acquire(time) Failed", e)
} }
/*判断是否开启提示模块运行信息*/ /** 增加通知栏文本显示守护状态 */
if (XPrefUtils.getBoolean("_tip_run_info")) try {
XposedHelpers.findAndHookMethod(
"android.app.Notification\$Builder",
lpparam.classLoader,
"setContentText",
CharSequence::class.java,
object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam?) {
when (param?.args?.get(0) as? CharSequence?) {
"QQ正在后台运行" ->
param.args?.set(0, "QQ正在后台运行 - TSBattery 守护中")
"TIM正在后台运行" ->
param.args?.set(0, "TIM正在后台运行 - TSBattery 守护中")
}
}
})
} catch (e: Throwable) {
logE("handleLoadPackage: hook Notification Failed", e)
}
/** 判断是否开启提示模块运行信息 */
if (XPrefUtils.getBoolean(HookMedium.ENABLE_RUN_INFO))
try { try {
/** /**
* Hook 启动界面的第一个 [Activity] * Hook 启动界面的第一个 [Activity]
@@ -113,56 +184,50 @@ class HookMain : IXposedHookLoadPackage {
object : XC_MethodHook() { object : XC_MethodHook() {
override fun afterHookedMethod(param: MethodHookParam?) { override fun afterHookedMethod(param: MethodHookParam?) {
val self = param!!.thisObject as Activity val self = param?.thisObject as? Activity ?: return
AlertDialog.Builder( try {
self, AlertDialog.Builder(
android.R.style.Theme_Material_Dialog_Alert self,
).setCancelable(false) android.R.style.Theme_Material_Light_Dialog
.setTitle("TSBattery 已激活") ).setCancelable(false)
.setMessage( .setTitle("TSBattery 已激活")
"模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" + .setMessage(
"当前模式:${if (XPrefUtils.getBoolean("_white_mode")) "保守模式" else "完全模式"}" + "[提示模块运行信息功能已打开]\n" +
"\n\n包名:${self.packageName}\n版本:${ "模块工作看起来一切正常,请自行测试是否能达到省电效果。\n\n" +
self.packageManager.getPackageInfo( "已生效模块版本:${XPrefUtils.getString(HookMedium.ENABLE_MODULE_VERSION)}\n" +
self.packageName, "当前模式:${if (XPrefUtils.getBoolean(HookMedium.ENABLE_WHITE_MODE)) "保守模式" else "完全模式"}" +
0 "\n\n包名:${self.packageName}\n版本:${
).versionName self.packageManager.getPackageInfo(
}(${ self.packageName,
self.packageManager.getPackageInfo( 0
self.packageName, ).versionName
0 }(${
).versionCode self.packageManager.getPackageInfo(
})" + "\n\nPS模块只对挂后台锁屏情况下有省电效果请不要将过多的群提醒消息通知打开这样子在使用过程时照样会极其耗电\n" + self.packageName,
"如果你不想看到此提示。请在模块设置中关闭运行信息提醒,此设置默认关闭。\n" + 0
"开发者 酷安 @星夜不荟\n未经允许禁止转载、修改或复制我的劳动成果。" ).versionCode
) })" + "\n\nPS模块只对挂后台锁屏情况下有省电效果请不要将过多的群提醒消息通知打开这样子在使用过程时照样会极其耗电\n" +
.setPositiveButton("我知道了", null) "如果你不想看到此提示。请在模块设置中关闭“提示模块运行信息”,此设置默认关闭。\n" +
.show() "开发者 酷安 @星夜不荟\n未经允许禁止转载、修改或复制我的劳动成果。"
)
.setPositiveButton("我知道了", null)
.show()
} catch (e: Exception) {
Toast.makeText(
self,
"模块已激活,但显示信息弹窗失败了\n$e",
Toast.LENGTH_SHORT
).show()
}
} }
}) })
} catch (e: Exception) { } catch (e: Exception) {
logE("handleLoadPackage: hook SplashActivity Failed", e) logE("handleLoadPackage: hook SplashActivity Failed", e)
} }
/*关闭保守模式后不再仅仅作用于系统电源锁*/ /** 关闭保守模式后不再仅仅作用于系统电源锁 */
if (!XPrefUtils.getBoolean("_white_mode")) { if (!XPrefUtils.getBoolean(HookMedium.ENABLE_WHITE_MODE)) {
val replaceMent = object : XC_MethodReplacement() {
override fun replaceHookedMethod(param: MethodHookParam?): Any? {
return null
}
}
/**
* 这个类 BaseChatPie 是控制聊天界面的
* 里面有两个随机混淆的方法
* 这两个方法一个是挂起电源锁常驻亮屏
* 一个是停止常驻亮屏
* 不由分说每个版本混淆的方法名都会变
* 所以说每个版本重新适配 - 也可以提交分支帮我适配
* 8.8.17 版本是 bd be
* 8.8.23 版本是 bf bg
* ⚠️ Hook 错了方法会造成闪退!
*/
try { try {
/*通过在 SplashActivity 里取到应用的版本号*/ /** 通过在 SplashActivity 里取到应用的版本号 */
XposedHelpers.findAndHookMethod( XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.SplashActivity", "com.tencent.mobileqq.activity.SplashActivity",
lpparam.classLoader, lpparam.classLoader,
@@ -171,45 +236,14 @@ class HookMain : IXposedHookLoadPackage {
object : XC_MethodHook() { object : XC_MethodHook() {
override fun beforeHookedMethod(param: MethodHookParam?) { override fun beforeHookedMethod(param: MethodHookParam?) {
val self = param!!.thisObject as Activity val self = param?.thisObject as? Activity ?: return
val name = self.packageName val name = self.packageName
val version = val version =
self.packageManager.getPackageInfo(name, 0).versionName self.packageManager.getPackageInfo(name, 0).versionName
/*这个地方我们只处理 QQ*/ /** 这个地方我们只处理 QQ */
try { try {
if (name == "com.tencent.mobileqq") { if (name == "com.tencent.mobileqq")
when (version) { lpparam.hookBaseChatPie(version)
"8.8.17" -> {
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bd",
replaceMent
)
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"be",
replaceMent
)
}
"8.8.23" -> {
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bf",
replaceMent
)
XposedHelpers.findAndHookMethod(
"com.tencent.mobileqq.activity.aio.core.BaseChatPie",
lpparam.classLoader,
"bg",
replaceMent
)
}
//TODO 后面的版本逐个适配 此方法没封装 目前比较笨蛋 主要是我懒得写
}
}
} catch (e: Exception) { } catch (e: Exception) {
logE("handleLoadPackage: hook BaseChatPie Failed", e) logE("handleLoadPackage: hook BaseChatPie Failed", e)
} }
@@ -227,7 +261,7 @@ class HookMain : IXposedHookLoadPackage {
"com.tencent.mars.ilink.comm.WakerLock", "com.tencent.mars.ilink.comm.WakerLock",
lpparam.classLoader, lpparam.classLoader,
"lock", Long::class.java, "lock", Long::class.java,
replaceMent replaceToNull
) )
} catch (e: Exception) { } catch (e: Exception) {
logE("handleLoadPackage: hook WakerLock Failed", e) logE("handleLoadPackage: hook WakerLock Failed", e)
@@ -246,7 +280,7 @@ class HookMain : IXposedHookLoadPackage {
private var origDevice = "" private var origDevice = ""
override fun beforeHookedMethod(param: MethodHookParam?) { override fun beforeHookedMethod(param: MethodHookParam?) {
/*由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂*/ /** 由于在 onCreate 里有一行判断只要型号是 xiaomi 的设备就开电源锁,所以说这里临时替换成菊花厂 */
origDevice = Build.MANUFACTURER origDevice = Build.MANUFACTURER
if (Build.MANUFACTURER.toLowerCase(Locale.ROOT) == "xiaomi") if (Build.MANUFACTURER.toLowerCase(Locale.ROOT) == "xiaomi")
XposedHelpers.setStaticObjectField( XposedHelpers.setStaticObjectField(
@@ -258,7 +292,7 @@ class HookMain : IXposedHookLoadPackage {
override fun afterHookedMethod(param: MethodHookParam?) { override fun afterHookedMethod(param: MethodHookParam?) {
(param?.thisObject as? Activity)?.finish() (param?.thisObject as? Activity)?.finish()
/*这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号*/ /** 这里再把型号替换回去 - 不影响应用变量等 Xposed 模块修改的型号 */
XposedHelpers.setStaticObjectField( XposedHelpers.setStaticObjectField(
Build::class.java, Build::class.java,
"MANUFACTURER", "MANUFACTURER",
@@ -276,7 +310,7 @@ class HookMain : IXposedHookLoadPackage {
"com.tencent.mobileqq.activity.QQLSActivity\$14", "com.tencent.mobileqq.activity.QQLSActivity\$14",
lpparam.classLoader, lpparam.classLoader,
"run", "run",
replaceMent replaceToNull
) )
} catch (e: Exception) { } catch (e: Exception) {
logE("handleLoadPackage: hook QQLSActivity Failed", e) logE("handleLoadPackage: hook QQLSActivity Failed", e)
@@ -345,15 +379,15 @@ class HookMain : IXposedHookLoadPackage {
"writeReport", "writeReport",
Boolean::class.java Boolean::class.java
).apply { isAccessible = true } ).apply { isAccessible = true }
XposedBridge.hookMethod(onHook, replaceMent) XposedBridge.hookMethod(onHook, replaceToNull)
XposedBridge.hookMethod(doReport, replaceMent) XposedBridge.hookMethod(doReport, replaceToNull)
XposedBridge.hookMethod(afterHookedMethod, replaceMent) XposedBridge.hookMethod(afterHookedMethod, replaceToNull)
XposedBridge.hookMethod(beforeHookedMethod, replaceMent) XposedBridge.hookMethod(beforeHookedMethod, replaceToNull)
XposedBridge.hookMethod(onAppBackground, replaceMent) XposedBridge.hookMethod(onAppBackground, replaceToNull)
XposedBridge.hookMethod(onOtherProcReport, replaceMent) XposedBridge.hookMethod(onOtherProcReport, replaceToNull)
XposedBridge.hookMethod(onProcessRun30Min, replaceMent) XposedBridge.hookMethod(onProcessRun30Min, replaceToNull)
XposedBridge.hookMethod(onProcessBG5Min, replaceMent) XposedBridge.hookMethod(onProcessBG5Min, replaceToNull)
XposedBridge.hookMethod(writeReport, replaceMent) XposedBridge.hookMethod(writeReport, replaceToNull)
} }
} catch (e: Throwable) { } catch (e: Throwable) {
logE("handleLoadPackage: hook WakerLockMonitor Failed", e) logE("handleLoadPackage: hook WakerLockMonitor Failed", e)

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2021. Fankes Studio(qzmmcn@163.com)
*
* This file is part of TSBattery.
*
* TSBattery is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* TSBattery is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This file is Created by fankes on 2021/9/4.
*/
package com.fankes.tsbattery.hook
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import androidx.annotation.Keep
import com.fankes.tsbattery.MainActivity
@Keep
object HookMedium {
const val ENABLE_HIDE_ICON = "_hide_icon"
const val ENABLE_RUN_INFO = "_tip_run_info"
const val ENABLE_WHITE_MODE = "_white_mode"
const val ENABLE_MODULE_VERSION = "_module_version"
/**
* 判断模块是否激活
* 在 [HookMain] 中 Hook 掉此方法
* @return 激活状态
*/
fun isHooked(): Boolean {
Log.d("TSBattery", "isHooked: true")
return isExpModuleActive()
}
/**
* 新增太极判断方式
* @return 是否激活
*/
private fun isExpModuleActive(): Boolean {
var isExp = false
MainActivity.instance?.also {
try {
val uri = Uri.parse("content://me.weishu.exposed.CP/")
var result: Bundle? = null
try {
result = it.contentResolver.call(uri, "active", null, null)
} catch (e: RuntimeException) {
// TaiChi is killed, try invoke
try {
val intent = Intent("me.weishu.exp.ACTION_ACTIVE")
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
it.startActivity(intent)
} catch (e1: Throwable) {
return false
}
}
if (result == null) result = it.contentResolver.call(uri, "active", null, null)
if (result == null) return false
isExp = result.getBoolean("active", false)
} catch (ignored: Throwable) {
}
}
return isExp
}
}

View File

@@ -26,6 +26,8 @@ object XPrefUtils {
fun getBoolean(key: String) = pref.getBoolean(key, false) fun getBoolean(key: String) = pref.getBoolean(key, false)
fun getString(key: String) = pref.getString(key, "unknown")
private val pref: XSharedPreferences private val pref: XSharedPreferences
get() { get() {
val preferences = XSharedPreferences("com.fankes.tsbattery") val preferences = XSharedPreferences("com.fankes.tsbattery")