This commit is contained in:
2022-02-09 00:50:46 +08:00
parent d8091684f7
commit acff94d32b
9 changed files with 375 additions and 12 deletions

View File

@@ -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
*/

View File

@@ -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]

View File

@@ -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]
*

View File

@@ -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()
}
}