mirror of
https://github.com/HighCapable/YukiHookAPI.git
synced 2025-09-04 01:35:17 +08:00
...
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
<!-- 最低xposed版本号 -->
|
||||
<meta-data
|
||||
android:name="xposedminversion"
|
||||
android:value="82" />
|
||||
android:value="93" />
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
@@ -30,10 +30,12 @@
|
||||
package com.highcapable.yukihookapi.demo
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Button
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.Keep
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.highcapable.yukihookapi.hook.factory.modulePrefs
|
||||
import com.highcapable.yukihookapi.hook.xposed.YukiHookModuleStatus
|
||||
|
||||
@Keep
|
||||
@@ -46,9 +48,18 @@ class MainActivity : AppCompatActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
// for test
|
||||
findViewById<Button>(R.id.main_button).setOnClickListener {
|
||||
modulePrefs.apply {
|
||||
putString("data", "这是存储的数据")
|
||||
putBoolean("test_key", true)
|
||||
putString("test_key_name", "存储数据成功,包名:$packageName")
|
||||
}
|
||||
Toast.makeText(this, "存储完成", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
// for test
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Hook 方法返回值测试")
|
||||
.setMessage(test() + "\n变量:$a\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
|
||||
.setMessage(test() + "\n变量:$a\n模块数据:${xptest()}\n模块是否已激活:${YukiHookModuleStatus.isActive()}")
|
||||
.setPositiveButton("下一个") { _, _ ->
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle("Hook 方法参数测试")
|
||||
@@ -90,6 +101,10 @@ class MainActivity : AppCompatActivity() {
|
||||
@Keep
|
||||
private fun test() = "正常显示的一行文字"
|
||||
|
||||
// for test
|
||||
@Keep
|
||||
private fun xptest() = "这里是正常的文字"
|
||||
|
||||
// for test
|
||||
@Keep
|
||||
private fun test(string: String) = string
|
||||
|
@@ -70,6 +70,13 @@ class MainInjecter : YukiHookXposedInitProxy {
|
||||
}.set(instance, "这段文字被修改成功了")
|
||||
}
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "xptest"
|
||||
returnType = StringType
|
||||
}
|
||||
replaceTo(prefs.getString(key = "data", default = "获取 Hook:没数据"))
|
||||
}
|
||||
injectMember {
|
||||
method {
|
||||
name = "toast"
|
||||
@@ -150,7 +157,11 @@ class MainInjecter : YukiHookXposedInitProxy {
|
||||
AlertDialog.Builder(instance())
|
||||
.setCancelable(false)
|
||||
.setTitle("测试 Hook")
|
||||
.setMessage("Hook 已成功")
|
||||
.setMessage(
|
||||
"Hook 已成功\n" +
|
||||
"test_key:${prefs.getBoolean("test_key")}\n" +
|
||||
"test_key_name:${prefs.getString("test_key_name", "默认值")}"
|
||||
)
|
||||
.setPositiveButton("OK") { _, _ ->
|
||||
Toast.makeText(instance(), "Hook Success", Toast.LENGTH_SHORT).show()
|
||||
}.show()
|
||||
|
@@ -1,18 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
tools:context=".MainActivity"
|
||||
tools:ignore="HardcodedText">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="Hello World!" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
<Button
|
||||
android:id="@+id/main_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="存储模块数据" />
|
||||
</LinearLayout>
|
@@ -226,6 +226,7 @@ class YukiHookXposedProcessor : SymbolProcessorProvider {
|
||||
" object : XC_MethodReplacement() {\n" +
|
||||
" override fun replaceHookedMethod(param: MethodHookParam?) = true\n" +
|
||||
" })\n" +
|
||||
" YukiHookAPI.modulePackageName = \"$realPackageName\"\n" +
|
||||
" YukiHookAPI.onXposedLoaded(lpparam)\n" +
|
||||
" }\n" +
|
||||
"}").toByteArray()
|
||||
|
@@ -33,6 +33,7 @@ import android.app.Application
|
||||
import android.content.Context
|
||||
import com.highcapable.yukihookapi.YukiHookAPI.configs
|
||||
import com.highcapable.yukihookapi.YukiHookAPI.encase
|
||||
import com.highcapable.yukihookapi.annotation.DoNotUseField
|
||||
import com.highcapable.yukihookapi.annotation.DoNotUseMethod
|
||||
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
|
||||
import com.highcapable.yukihookapi.hook.factory.hasClass
|
||||
@@ -57,6 +58,14 @@ object YukiHookAPI {
|
||||
/** Xposed Hook API 方法体回调 */
|
||||
private var packageParamCallback: (PackageParam.() -> Unit)? = null
|
||||
|
||||
/**
|
||||
* 预设的 Xposed 模块包名
|
||||
*
|
||||
* - ⚡请勿手动修改 - 会引发未知异常
|
||||
*/
|
||||
@DoNotUseField
|
||||
var modulePackageName = ""
|
||||
|
||||
/**
|
||||
* 配置 YukiHookAPI
|
||||
*/
|
||||
|
@@ -37,6 +37,7 @@ import android.os.Process
|
||||
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.prefs.YukiHookModulePrefs
|
||||
import com.highcapable.yukihookapi.hook.xposed.proxy.YukiHookXposedInitProxy
|
||||
import java.io.BufferedReader
|
||||
import java.io.File
|
||||
@@ -55,6 +56,12 @@ fun YukiHookXposedInitProxy.encase(initiate: PackageParam.() -> Unit) = YukiHook
|
||||
*/
|
||||
fun YukiHookXposedInitProxy.encase(vararg hooker: YukiBaseHooker) = YukiHookAPI.encase(hooker = hooker)
|
||||
|
||||
/**
|
||||
* 获取模块的存取对象
|
||||
* @return [YukiHookModulePrefs]
|
||||
*/
|
||||
val Context.modulePrefs get() = YukiHookModulePrefs(context = this)
|
||||
|
||||
/**
|
||||
* 获取当前进程名称
|
||||
* @return [String]
|
||||
|
@@ -34,6 +34,7 @@ import com.highcapable.yukihookapi.annotation.DoNotUseMethod
|
||||
import com.highcapable.yukihookapi.hook.core.YukiHookCreater
|
||||
import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker
|
||||
import com.highcapable.yukihookapi.hook.param.wrapper.PackageParamWrapper
|
||||
import com.highcapable.yukihookapi.hook.xposed.prefs.YukiHookModulePrefs
|
||||
|
||||
/**
|
||||
* 装载 Hook 的目标 APP 入口对象实现类
|
||||
@@ -75,6 +76,13 @@ open class PackageParam(private var baseParam: PackageParamWrapper? = null) {
|
||||
*/
|
||||
val isFirstApplication get() = packageName == processName
|
||||
|
||||
/**
|
||||
* 获得当前使用的存取数据对象缓存实例
|
||||
*
|
||||
* @return [YukiHookModulePrefs]
|
||||
*/
|
||||
val prefs by lazy { YukiHookModulePrefs() }
|
||||
|
||||
/**
|
||||
* 赋值并克隆另一个 [PackageParam]
|
||||
*
|
||||
|
@@ -0,0 +1,308 @@
|
||||
/**
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (C) 2022 HighCapable
|
||||
*
|
||||
* This file is part of YukiHookAPI.
|
||||
*
|
||||
* 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(
|
||||
"EXPERIMENTAL_API_USAGE", "SetWorldReadable", "CommitPrefEdits",
|
||||
"DEPRECATION", "WorldReadableFiles", "unused"
|
||||
)
|
||||
|
||||
package com.highcapable.yukihookapi.hook.xposed.prefs
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import com.highcapable.yukihookapi.YukiHookAPI
|
||||
import de.robv.android.xposed.XSharedPreferences
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* 实现 Xposed 模块的数据存取
|
||||
*
|
||||
* 对接 [SharedPreferences] 和 [XSharedPreferences]
|
||||
*
|
||||
* 在不同环境智能选择存取使用的对象
|
||||
*
|
||||
* - 请注意此功能为实验性功能 - 仅在 LSPosed 环境测试通过
|
||||
*
|
||||
* - 使用 LSPosed 环境请在 AndroidManifests.xml 中将 "xposedminversion" 最低设置为 93
|
||||
*
|
||||
* - 详见 [New XSharedPreferences](https://github.com/LSPosed/LSPosed/wiki/New-XSharedPreferences#for-the-module)
|
||||
*
|
||||
* - 未使用 LSposed 环境请将你的模块 API 降至 26 以下 - YukiHookAPI 将会尝试使用 [makeWorldReadable] 但仍有可能不成功
|
||||
*
|
||||
* - ⚡当你在模块中存取数据的时候 [context] 必须不能是空的
|
||||
* @param context 上下文实例 - 默认空
|
||||
*/
|
||||
class YukiHookModulePrefs(private val context: Context? = null) {
|
||||
|
||||
/** 存储名称 - 包名 + _preferences */
|
||||
private val prefsName get() = "${YukiHookAPI.modulePackageName.ifBlank { context?.packageName ?: "" }}_preferences"
|
||||
|
||||
/** 是否为 Xposed 环境 */
|
||||
private val isXposedEnvironment = YukiHookAPI.hasXposedBridge
|
||||
|
||||
/** 缓存数据 */
|
||||
private var xPrefCacheKeyValueStrings = HashMap<String, String>()
|
||||
|
||||
/** 缓存数据 */
|
||||
private var xPrefCacheKeyValueBooleans = HashMap<String, Boolean>()
|
||||
|
||||
/** 缓存数据 */
|
||||
private var xPrefCacheKeyValueInts = HashMap<String, Int>()
|
||||
|
||||
/** 缓存数据 */
|
||||
private var xPrefCacheKeyValueLongs = HashMap<String, Long>()
|
||||
|
||||
/** 缓存数据 */
|
||||
private var xPrefCacheKeyValueFloats = HashMap<String, Float>()
|
||||
|
||||
/**
|
||||
* 获得 [XSharedPreferences] 对象
|
||||
* @return [XSharedPreferences]
|
||||
*/
|
||||
private val xPref by lazy {
|
||||
XSharedPreferences(YukiHookAPI.modulePackageName, prefsName).apply {
|
||||
makeWorldReadable()
|
||||
reload()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得 [SharedPreferences] 对象
|
||||
* @return [SharedPreferences]
|
||||
*/
|
||||
private val sPref by lazy {
|
||||
try {
|
||||
context?.getSharedPreferences(prefsName, Context.MODE_WORLD_READABLE)
|
||||
?: error("If you want to use module prefs,you must set the context instance first")
|
||||
} catch (_: Throwable) {
|
||||
context?.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
|
||||
?: error("If you want to use module prefs,you must set the context instance first")
|
||||
}
|
||||
}
|
||||
|
||||
/** 设置全局可读可写 */
|
||||
private fun makeWorldReadable() = runCatching {
|
||||
File(File(context!!.applicationInfo.dataDir, "shared_prefs"), "$prefsName.xml").apply {
|
||||
setReadable(true, false)
|
||||
setExecutable(true, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 [String] 键值
|
||||
*
|
||||
* - 智能识别对应环境读取键值数据
|
||||
* @param key 键值名称
|
||||
* @param default 默认数据 - ""
|
||||
* @return [String]
|
||||
*/
|
||||
fun getString(key: String, default: String = "") =
|
||||
(if (isXposedEnvironment)
|
||||
xPrefCacheKeyValueStrings[key].let {
|
||||
(it ?: xPref.getString(key, default) ?: default).let { value ->
|
||||
xPrefCacheKeyValueStrings[key] = value
|
||||
value
|
||||
}
|
||||
}
|
||||
else sPref.getString(key, default) ?: default).let {
|
||||
makeWorldReadable()
|
||||
it
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 [Boolean] 键值
|
||||
*
|
||||
* - 智能识别对应环境读取键值数据
|
||||
* @param key 键值名称
|
||||
* @param default 默认数据 - false
|
||||
* @return [Boolean]
|
||||
*/
|
||||
fun getBoolean(key: String, default: Boolean = false) =
|
||||
(if (isXposedEnvironment)
|
||||
xPrefCacheKeyValueBooleans[key].let {
|
||||
it ?: xPref.getBoolean(key, default).let { value ->
|
||||
xPrefCacheKeyValueBooleans[key] = value
|
||||
value
|
||||
}
|
||||
}
|
||||
else sPref.getBoolean(key, default)).let {
|
||||
makeWorldReadable()
|
||||
it
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 [Int] 键值
|
||||
*
|
||||
* - 智能识别对应环境读取键值数据
|
||||
* @param key 键值名称
|
||||
* @param default 默认数据 - 0
|
||||
* @return [Int]
|
||||
*/
|
||||
fun getInt(key: String, default: Int = 0) =
|
||||
(if (isXposedEnvironment)
|
||||
xPrefCacheKeyValueInts[key].let {
|
||||
it ?: xPref.getInt(key, default).let { value ->
|
||||
xPrefCacheKeyValueInts[key] = value
|
||||
value
|
||||
}
|
||||
}
|
||||
else sPref.getInt(key, default)).let {
|
||||
makeWorldReadable()
|
||||
it
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 [Float] 键值
|
||||
*
|
||||
* - 智能识别对应环境读取键值数据
|
||||
* @param key 键值名称
|
||||
* @param default 默认数据 - 0f
|
||||
* @return [Float]
|
||||
*/
|
||||
fun getFloat(key: String, default: Float = 0f) =
|
||||
(if (isXposedEnvironment)
|
||||
xPrefCacheKeyValueFloats[key].let {
|
||||
it ?: xPref.getFloat(key, default).let { value ->
|
||||
xPrefCacheKeyValueFloats[key] = value
|
||||
value
|
||||
}
|
||||
}
|
||||
else sPref.getFloat(key, default)).let {
|
||||
makeWorldReadable()
|
||||
it
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 [Long] 键值
|
||||
*
|
||||
* - 智能识别对应环境读取键值数据
|
||||
* @param key 键值名称
|
||||
* @param default 默认数据 - 0L
|
||||
* @return [Long]
|
||||
*/
|
||||
fun getLong(key: String, default: Long = 0L) =
|
||||
(if (isXposedEnvironment)
|
||||
xPrefCacheKeyValueLongs[key].let {
|
||||
it ?: xPref.getLong(key, default).let { value ->
|
||||
xPrefCacheKeyValueLongs[key] = value
|
||||
value
|
||||
}
|
||||
}
|
||||
else sPref.getLong(key, default)).let {
|
||||
makeWorldReadable()
|
||||
it
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除全部包含 [key] 的存储数据
|
||||
*
|
||||
* - 在模块 [Context] 环境中使用
|
||||
*
|
||||
* - ⚡在 [XSharedPreferences] 环境下只读 - 无法使用
|
||||
* @param key 键值名称
|
||||
*/
|
||||
fun remove(key: String) {
|
||||
if (isXposedEnvironment) return
|
||||
sPref.edit().remove(key).apply()
|
||||
makeWorldReadable()
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 [String] 键值
|
||||
*
|
||||
* - 在模块 [Context] 环境中使用
|
||||
*
|
||||
* - ⚡在 [XSharedPreferences] 环境下只读 - 无法使用
|
||||
* @param key 键值名称
|
||||
* @param value 键值数据
|
||||
*/
|
||||
fun putString(key: String, value: String) {
|
||||
if (isXposedEnvironment) return
|
||||
sPref.edit().putString(key, value).apply()
|
||||
makeWorldReadable()
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 [Boolean] 键值
|
||||
*
|
||||
* - 在模块 [Context] 环境中使用
|
||||
*
|
||||
* - ⚡在 [XSharedPreferences] 环境下只读 - 无法使用
|
||||
* @param key 键值名称
|
||||
* @param value 键值数据
|
||||
*/
|
||||
fun putBoolean(key: String, value: Boolean) {
|
||||
if (isXposedEnvironment) return
|
||||
sPref.edit().putBoolean(key, value).apply()
|
||||
makeWorldReadable()
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 [Int] 键值
|
||||
*
|
||||
* - 在模块 [Context] 环境中使用
|
||||
*
|
||||
* - ⚡在 [XSharedPreferences] 环境下只读 - 无法使用
|
||||
* @param key 键值名称
|
||||
* @param value 键值数据
|
||||
*/
|
||||
fun putInt(key: String, value: Int) {
|
||||
if (isXposedEnvironment) return
|
||||
sPref.edit().putInt(key, value).apply()
|
||||
makeWorldReadable()
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 [Float] 键值
|
||||
*
|
||||
* - 在模块 [Context] 环境中使用
|
||||
*
|
||||
* - ⚡在 [XSharedPreferences] 环境下只读 - 无法使用
|
||||
* @param key 键值名称
|
||||
* @param value 键值数据
|
||||
*/
|
||||
fun putFloat(key: String, value: Float) {
|
||||
if (isXposedEnvironment) return
|
||||
sPref.edit().putFloat(key, value).apply()
|
||||
makeWorldReadable()
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储 [Long] 键值
|
||||
*
|
||||
* - 在模块 [Context] 环境中使用
|
||||
*
|
||||
* - ⚡在 [XSharedPreferences] 环境下只读 - 无法使用
|
||||
* @param key 键值名称
|
||||
* @param value 键值数据
|
||||
*/
|
||||
fun putLong(key: String, value: Long) {
|
||||
if (isXposedEnvironment) return
|
||||
sPref.edit().putLong(key, value).apply()
|
||||
makeWorldReadable()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user