mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-06 01:14:31 +02:00
refactor: config override
- fix: hidden sc plus features
This commit is contained in:
parent
233d043d45
commit
2b0b14a60f
@ -4,14 +4,36 @@ import de.robv.android.xposed.XposedHelpers
|
|||||||
import me.rhunk.snapenhance.core.features.Feature
|
import me.rhunk.snapenhance.core.features.Feature
|
||||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||||
|
import me.rhunk.snapenhance.core.util.hook.Hooker
|
||||||
import me.rhunk.snapenhance.core.util.hook.hook
|
import me.rhunk.snapenhance.core.util.hook.hook
|
||||||
|
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||||
|
|
||||||
|
data class ConfigKeyInfo(
|
||||||
|
val category: String?,
|
||||||
|
val name: String?,
|
||||||
|
val defaultValue: Any?
|
||||||
|
)
|
||||||
|
|
||||||
class ConfigurationOverride : Feature("Configuration Override", loadParams = FeatureLoadParams.INIT_SYNC) {
|
class ConfigurationOverride : Feature("Configuration Override", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||||
override fun init() {
|
override fun init() {
|
||||||
val propertyOverrides = mutableMapOf<String, Pair<(() -> Boolean), Any>>()
|
val compositeConfigurationProviderMappings = context.mappings.getMappedMap("CompositeConfigurationProvider")
|
||||||
|
val enumMappings = compositeConfigurationProviderMappings["enum"] as Map<*, *>
|
||||||
|
|
||||||
fun overrideProperty(key: String, filter: () -> Boolean, value: Any) {
|
fun getConfigKeyInfo(key: Any?) = runCatching {
|
||||||
|
if (key == null) return@runCatching null
|
||||||
|
val keyClassMethods = key::class.java.methods
|
||||||
|
val category = keyClassMethods.firstOrNull { it.name == enumMappings["getCategory"].toString() }?.invoke(key)?.toString() ?: return null
|
||||||
|
val valueHolder = keyClassMethods.firstOrNull { it.name == enumMappings["getValue"].toString() }?.invoke(key) ?: return null
|
||||||
|
val defaultValue = valueHolder.getObjectField(enumMappings["defaultValueField"].toString()) ?: return null
|
||||||
|
ConfigKeyInfo(category, key.toString(), defaultValue)
|
||||||
|
}.onFailure {
|
||||||
|
context.log.error("Failed to get config key info", it)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
|
val propertyOverrides = mutableMapOf<String, Pair<((ConfigKeyInfo) -> Boolean), Any>>()
|
||||||
|
|
||||||
|
fun overrideProperty(key: String, filter: (ConfigKeyInfo) -> Boolean, value: Any) {
|
||||||
propertyOverrides[key] = Pair(filter, value)
|
propertyOverrides[key] = Pair(filter, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,8 +55,17 @@ class ConfigurationOverride : Feature("Configuration Override", loadParams = Fea
|
|||||||
overrideProperty(it, { context.config.global.blockAds.get() }, "http://127.0.0.1")
|
overrideProperty(it, { context.config.global.blockAds.get() }, "http://127.0.0.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
val compositeConfigurationProviderMappings = context.mappings.getMappedMap("CompositeConfigurationProvider")
|
findClass(compositeConfigurationProviderMappings["class"].toString()).hook(
|
||||||
val enumMappings = compositeConfigurationProviderMappings["enum"] as Map<*, *>
|
compositeConfigurationProviderMappings["getProperty"].toString(),
|
||||||
|
HookStage.AFTER
|
||||||
|
) { param ->
|
||||||
|
val propertyKey = getConfigKeyInfo(param.argNullable<Any>(0)) ?: return@hook
|
||||||
|
|
||||||
|
propertyOverrides[propertyKey.name]?.let { (filter, value) ->
|
||||||
|
if (!filter(propertyKey)) return@let
|
||||||
|
param.setResult(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
findClass(compositeConfigurationProviderMappings["class"].toString()).hook(
|
findClass(compositeConfigurationProviderMappings["class"].toString()).hook(
|
||||||
compositeConfigurationProviderMappings["observeProperty"].toString(),
|
compositeConfigurationProviderMappings["observeProperty"].toString(),
|
||||||
@ -48,34 +79,59 @@ class ConfigurationOverride : Feature("Configuration Override", loadParams = Fea
|
|||||||
}
|
}
|
||||||
|
|
||||||
propertyOverrides[key]?.let { (filter, value) ->
|
propertyOverrides[key]?.let { (filter, value) ->
|
||||||
if (!filter()) return@let
|
if (!filter(getConfigKeyInfo(enumData) ?: return@let)) return@let
|
||||||
setValue(value)
|
setValue(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findClass(compositeConfigurationProviderMappings["class"].toString()).hook(
|
runCatching {
|
||||||
compositeConfigurationProviderMappings["getProperty"].toString(),
|
val appExperimentProviderMappings = compositeConfigurationProviderMappings["appExperimentProvider"] as Map<*, *>
|
||||||
|
val customBooleanPropertyRules = mutableListOf<(ConfigKeyInfo) -> Boolean>()
|
||||||
|
|
||||||
|
findClass(appExperimentProviderMappings["GetBooleanAppExperimentClass"].toString()).hook("invoke", HookStage.BEFORE) { param ->
|
||||||
|
val keyInfo = getConfigKeyInfo(param.arg(1)) ?: return@hook
|
||||||
|
if (keyInfo.defaultValue !is Boolean) return@hook
|
||||||
|
if (customBooleanPropertyRules.any { it(keyInfo) }) {
|
||||||
|
param.setResult(true)
|
||||||
|
return@hook
|
||||||
|
}
|
||||||
|
propertyOverrides[keyInfo.name]?.let { (filter, value) ->
|
||||||
|
if (!filter(keyInfo)) return@let
|
||||||
|
param.setResult(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Hooker.ephemeralHookConstructor(
|
||||||
|
findClass(compositeConfigurationProviderMappings["class"].toString()),
|
||||||
HookStage.AFTER
|
HookStage.AFTER
|
||||||
) { param ->
|
) { constructorParam ->
|
||||||
val propertyKey = param.arg<Any>(0).toString()
|
val instance = constructorParam.thisObject<Any>()
|
||||||
|
val appExperimentProviderInstance = instance::class.java.fields.firstOrNull {
|
||||||
|
findClass(appExperimentProviderMappings["class"].toString()).isAssignableFrom(it.type)
|
||||||
|
}?.get(instance) ?: return@ephemeralHookConstructor
|
||||||
|
|
||||||
propertyOverrides[propertyKey]?.let { (filter, value) ->
|
appExperimentProviderInstance::class.java.methods.first {
|
||||||
if (!filter()) return@let
|
it.name == appExperimentProviderMappings["hasExperimentMethod"].toString()
|
||||||
param.setResult(value)
|
}.hook(HookStage.BEFORE) { param ->
|
||||||
|
val keyInfo = getConfigKeyInfo(param.arg(0)) ?: return@hook
|
||||||
|
if (keyInfo.defaultValue !is Boolean) return@hook
|
||||||
|
if (customBooleanPropertyRules.any { it(keyInfo) }) {
|
||||||
|
param.setResult(true)
|
||||||
|
return@hook
|
||||||
|
}
|
||||||
|
|
||||||
|
val propertyOverride = propertyOverrides[keyInfo.name] ?: return@hook
|
||||||
|
if (propertyOverride.first(keyInfo)) param.setResult(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
arrayOf("getBoolean", "getInt", "getLong", "getFloat", "getString").forEach { methodName ->
|
if (context.config.experimental.hiddenSnapchatPlusFeatures.get()) {
|
||||||
findClass("android.app.SharedPreferencesImpl").hook(
|
customBooleanPropertyRules.add { key ->
|
||||||
methodName,
|
key.category == "PLUS" && key.name?.endsWith("_GATE") == true
|
||||||
HookStage.BEFORE
|
|
||||||
) { param ->
|
|
||||||
val key = param.argNullable<Any>(0).toString()
|
|
||||||
propertyOverrides[key]?.let { (filter, value) ->
|
|
||||||
if (!filter()) return@let
|
|
||||||
param.setResult(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}.onFailure {
|
||||||
|
context.log.error("Failed to hook appExperimentProvider", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -26,6 +26,7 @@ class SnapchatPlus: Feature("SnapchatPlus", loadParams = FeatureLoadParams.INIT_
|
|||||||
param.setArg(3, expirationTimeMillis)
|
param.setArg(3, expirationTimeMillis)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// optional as ConfigurationOverride does this too
|
||||||
if (context.config.experimental.hiddenSnapchatPlusFeatures.get()) {
|
if (context.config.experimental.hiddenSnapchatPlusFeatures.get()) {
|
||||||
findClass("com.snap.plus.FeatureCatalog").methods.last {
|
findClass("com.snap.plus.FeatureCatalog").methods.last {
|
||||||
!it.name.contains("init") &&
|
!it.name.contains("init") &&
|
||||||
|
@ -113,6 +113,18 @@ object Hooker {
|
|||||||
hookConsumer(param)
|
hookConsumer(param)
|
||||||
}.also { unhooks.addAll(it) }
|
}.also { unhooks.addAll(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun ephemeralHookConstructor(
|
||||||
|
clazz: Class<*>,
|
||||||
|
stage: HookStage,
|
||||||
|
crossinline hookConsumer: (HookAdapter) -> Unit
|
||||||
|
) {
|
||||||
|
val unhooks: MutableSet<XC_MethodHook.Unhook> = HashSet()
|
||||||
|
hookConstructor(clazz, stage) { param->
|
||||||
|
hookConsumer(param)
|
||||||
|
unhooks.forEach{ it.unhook() }
|
||||||
|
}.also { unhooks.addAll(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Class<*>.hookConstructor(
|
fun Class<*>.hookConstructor(
|
||||||
|
@ -20,9 +20,9 @@ class MapperContext(
|
|||||||
return classMap[name.toString()]
|
return classMap[name.toString()]
|
||||||
}
|
}
|
||||||
|
|
||||||
private val mappings = mutableMapOf<String, Any>()
|
private val mappings = mutableMapOf<String, Any?>()
|
||||||
|
|
||||||
fun addMapping(key: String, vararg array: Pair<String, Any>) {
|
fun addMapping(key: String, vararg array: Pair<String, Any?>) {
|
||||||
mappings[key] = array.toMap()
|
mappings[key] = array.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package me.rhunk.snapenhance.mapper.impl
|
package me.rhunk.snapenhance.mapper.impl
|
||||||
|
|
||||||
import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||||
import me.rhunk.snapenhance.mapper.ext.findConstString
|
import me.rhunk.snapenhance.mapper.ext.*
|
||||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
import org.jf.dexlib2.iface.instruction.formats.Instruction21c
|
||||||
import me.rhunk.snapenhance.mapper.ext.hasStaticConstructorString
|
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
|
||||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
import org.jf.dexlib2.iface.reference.FieldReference
|
||||||
|
import org.jf.dexlib2.iface.reference.MethodReference
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
|
|
||||||
class CompositeConfigurationProviderMapper : AbstractClassMapper() {
|
class CompositeConfigurationProviderMapper : AbstractClassMapper() {
|
||||||
@ -32,7 +33,35 @@ class CompositeConfigurationProviderMapper : AbstractClassMapper() {
|
|||||||
it.parameterTypes[2] == enumType.type
|
it.parameterTypes[2] == enumType.type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val hasExperimentMethodReference = observePropertyMethod.implementation?.instructions?.firstOrNull { instruction ->
|
||||||
|
if (instruction !is Instruction35c) return@firstOrNull false
|
||||||
|
(instruction.reference as? MethodReference)?.let { methodRef ->
|
||||||
|
methodRef.returnType == "Z" && methodRef.parameterTypes.size == 1 && methodRef.parameterTypes[0] == configEnumInterface.type
|
||||||
|
} == true
|
||||||
|
}?.let { (it as Instruction35c).reference as MethodReference }
|
||||||
|
|
||||||
|
val getBooleanAppExperimentClass = classDef.methods.first {
|
||||||
|
// search for observeBoolean method
|
||||||
|
it.parameterTypes.size == 1 &&
|
||||||
|
it.parameterTypes[0] == configEnumInterface.type &&
|
||||||
|
it.implementation?.findConstString("observeBoolean") == true
|
||||||
|
}.let { method ->
|
||||||
|
// search for static field invocation of GetBooleanAppExperiment class
|
||||||
|
val getBooleanAppExperimentClassFieldInstruction = method.implementation?.instructions?.firstOrNull { instruction ->
|
||||||
|
if (instruction !is Instruction21c) return@firstOrNull false
|
||||||
|
val fieldReference = instruction.reference as? FieldReference ?: return@firstOrNull false
|
||||||
|
getClass(fieldReference.definingClass)?.methods?.any {
|
||||||
|
it.returnType == "Ljava/lang/Object;" &&
|
||||||
|
it.parameterTypes.size == 2 &&
|
||||||
|
(0..1).all { i -> it.parameterTypes[i] == "Ljava/lang/Object;" }
|
||||||
|
} == true
|
||||||
|
}?.let { (it as Instruction21c).reference as FieldReference }
|
||||||
|
|
||||||
|
getClass(getBooleanAppExperimentClassFieldInstruction?.definingClass)?.getClassName()
|
||||||
|
}
|
||||||
|
|
||||||
val enumGetDefaultValueMethod = configEnumInterface.methods.first { getClass(it.returnType)?.interfaces?.contains("Ljava/io/Serializable;") == true }
|
val enumGetDefaultValueMethod = configEnumInterface.methods.first { getClass(it.returnType)?.interfaces?.contains("Ljava/io/Serializable;") == true }
|
||||||
|
val enumGetCategoryMethod = configEnumInterface.methods.first { it.parameterTypes.size == 0 && getClass(it.returnType)?.isEnum() == true }
|
||||||
val defaultValueField = getClass(enumGetDefaultValueMethod.returnType)!!.fields.first {
|
val defaultValueField = getClass(enumGetDefaultValueMethod.returnType)!!.fields.first {
|
||||||
Modifier.isFinal(it.accessFlags) &&
|
Modifier.isFinal(it.accessFlags) &&
|
||||||
Modifier.isPublic(it.accessFlags) &&
|
Modifier.isPublic(it.accessFlags) &&
|
||||||
@ -46,8 +75,16 @@ class CompositeConfigurationProviderMapper : AbstractClassMapper() {
|
|||||||
"enum" to mapOf(
|
"enum" to mapOf(
|
||||||
"class" to configEnumInterface.getClassName(),
|
"class" to configEnumInterface.getClassName(),
|
||||||
"getValue" to enumGetDefaultValueMethod.name,
|
"getValue" to enumGetDefaultValueMethod.name,
|
||||||
|
"getCategory" to enumGetCategoryMethod.name,
|
||||||
"defaultValueField" to defaultValueField.name
|
"defaultValueField" to defaultValueField.name
|
||||||
|
),
|
||||||
|
"appExperimentProvider" to (hasExperimentMethodReference?.let {
|
||||||
|
mapOf(
|
||||||
|
"class" to getClass(it.definingClass)?.getClassName(),
|
||||||
|
"GetBooleanAppExperimentClass" to getBooleanAppExperimentClass,
|
||||||
|
"hasExperimentMethod" to hasExperimentMethodReference.name
|
||||||
)
|
)
|
||||||
|
})
|
||||||
)
|
)
|
||||||
return@mapper
|
return@mapper
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user