mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 05:37:48 +02:00
refactor: mapper
This commit is contained in:
@ -195,7 +195,7 @@ class RemoteSideContext(
|
||||
}
|
||||
}
|
||||
|
||||
if (mappings.isMappingsOutdated() || !mappings.isMappingsLoaded()) {
|
||||
if (mappings.isMappingsOutdated() || !mappings.isMappingsLoaded) {
|
||||
requirements = requirements or Requirements.MAPPINGS
|
||||
}
|
||||
|
||||
|
@ -110,7 +110,7 @@ class HomeSection : Section() {
|
||||
}
|
||||
|
||||
override fun onResumed() {
|
||||
if (!context.mappings.isMappingsLoaded()) {
|
||||
if (!context.mappings.isMappingsLoaded) {
|
||||
context.mappings.init(context.androidContext)
|
||||
}
|
||||
context.coroutineScope.launch {
|
||||
|
@ -1,44 +1,24 @@
|
||||
package me.rhunk.snapenhance.common.bridge.wrapper
|
||||
|
||||
import android.content.Context
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonParser
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.rhunk.snapenhance.common.BuildConfig
|
||||
import me.rhunk.snapenhance.common.Constants
|
||||
import me.rhunk.snapenhance.common.bridge.FileLoaderWrapper
|
||||
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.mapper.Mapper
|
||||
import me.rhunk.snapenhance.mapper.impl.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.system.measureTimeMillis
|
||||
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
||||
import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ClassMapper
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteArray(Charsets.UTF_8)) {
|
||||
companion object {
|
||||
private val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
private val mappers = arrayOf(
|
||||
BCryptClassMapper::class,
|
||||
CallbackMapper::class,
|
||||
DefaultMediaItemMapper::class,
|
||||
MediaQualityLevelProviderMapper::class,
|
||||
OperaPageViewControllerMapper::class,
|
||||
PlusSubscriptionMapper::class,
|
||||
ScCameraSettingsMapper::class,
|
||||
StoryBoostStateMapper::class,
|
||||
FriendsFeedEventDispatcherMapper::class,
|
||||
CompositeConfigurationProviderMapper::class,
|
||||
ScoreUpdateMapper::class,
|
||||
FriendRelationshipChangerMapper::class,
|
||||
ViewBinderMapper::class,
|
||||
FriendingDataSourcesMapper::class,
|
||||
OperaViewerParamsMapper::class,
|
||||
)
|
||||
}
|
||||
|
||||
private lateinit var context: Context
|
||||
|
||||
private val mappings = ConcurrentHashMap<String, Any>()
|
||||
private var mappingUniqueHash: Long = 0
|
||||
var isMappingsLoaded = false
|
||||
private set
|
||||
|
||||
private val mappers = ClassMapper.DEFAULT_MAPPERS.associateBy { it::class }
|
||||
|
||||
private fun getUniqueBuildId() = (getSnapchatPackageInfo()?.longVersionCode ?: -1) xor BuildConfig.BUILD_HASH.hashCode().toLong()
|
||||
|
||||
@ -63,8 +43,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
||||
}.getOrNull()
|
||||
|
||||
fun getGeneratedBuildNumber() = mappingUniqueHash
|
||||
fun isMappingsOutdated() = mappingUniqueHash != getUniqueBuildId() || isMappingsLoaded().not()
|
||||
fun isMappingsLoaded() = mappings.isNotEmpty()
|
||||
fun isMappingsOutdated() = mappingUniqueHash != getUniqueBuildId() || isMappingsLoaded.not()
|
||||
|
||||
private fun loadCached() {
|
||||
if (!isFileExists()) {
|
||||
@ -74,64 +53,39 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
||||
mappingUniqueHash = it["unique_hash"].asLong
|
||||
}
|
||||
|
||||
mappingsObject.entrySet().forEach { (key, value): Map.Entry<String, JsonElement> ->
|
||||
if (value.isJsonArray) {
|
||||
mappings[key] = gson.fromJson(value, ArrayList::class.java)
|
||||
return@forEach
|
||||
mappingsObject.entrySet().forEach { (key, value) ->
|
||||
mappers.values.firstOrNull { it.mapperName == key }?.let { mapper ->
|
||||
mapper.readFromJson(value.asJsonObject)
|
||||
mapper.classLoader = context.classLoader
|
||||
}
|
||||
if (value.isJsonObject) {
|
||||
mappings[key] = gson.fromJson(value, ConcurrentHashMap::class.java)
|
||||
return@forEach
|
||||
}
|
||||
mappings[key] = value.asString
|
||||
}
|
||||
isMappingsLoaded = true
|
||||
}
|
||||
|
||||
fun refresh() {
|
||||
mappingUniqueHash = getUniqueBuildId()
|
||||
val mapper = Mapper(*mappers)
|
||||
val classMapper = ClassMapper(*mappers.values.toTypedArray())
|
||||
|
||||
runCatching {
|
||||
mapper.loadApk(getSnapchatPackageInfo()?.applicationInfo?.sourceDir ?: throw Exception("Failed to get APK"))
|
||||
classMapper.loadApk(getSnapchatPackageInfo()?.applicationInfo?.sourceDir ?: throw Exception("Failed to get APK"))
|
||||
}.onFailure {
|
||||
throw Exception("Failed to load APK", it)
|
||||
}
|
||||
|
||||
measureTimeMillis {
|
||||
val result = mapper.start().apply {
|
||||
runBlocking {
|
||||
val result = classMapper.run().apply {
|
||||
addProperty("unique_hash", mappingUniqueHash)
|
||||
}
|
||||
write(result.toString().toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
fun getMappedObject(key: String): Any {
|
||||
if (mappings.containsKey(key)) {
|
||||
return mappings[key]!!
|
||||
}
|
||||
throw Exception("No mapping found for $key")
|
||||
}
|
||||
|
||||
fun getMappedObjectNullable(key: String): Any? {
|
||||
return mappings[key]
|
||||
}
|
||||
|
||||
fun getMappedClass(className: String): Class<*>? {
|
||||
return runCatching {
|
||||
context.classLoader.loadClass(getMappedObject(className) as? String)
|
||||
}.getOrNull()
|
||||
}
|
||||
|
||||
fun getMappedClass(key: String, subKey: String): Class<*> {
|
||||
return context.classLoader.loadClass(getMappedValue(key, subKey))
|
||||
}
|
||||
|
||||
fun getMappedValue(key: String, subKey: String): String? {
|
||||
return getMappedMap(key)?.get(subKey) as? String
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun getMappedMap(key: String): Map<String, *>? {
|
||||
return getMappedObjectNullable(key) as? Map<String, *>
|
||||
fun <T : AbstractClassMapper> useMapper(type: KClass<T>, callback: T.() -> Unit) {
|
||||
mappers[type]?.let {
|
||||
callback(it as? T ?: return)
|
||||
} ?: run {
|
||||
AbstractLogger.directError("Mapper ${type.simpleName} is not registered", Throwable())
|
||||
}
|
||||
}
|
||||
}
|
@ -91,7 +91,7 @@ class SnapEnhance {
|
||||
hookMainActivity("onCreate") {
|
||||
val isMainActivityNotNull = appContext.mainActivity != null
|
||||
appContext.mainActivity = this
|
||||
if (isMainActivityNotNull || !appContext.mappings.isMappingsLoaded()) return@hookMainActivity
|
||||
if (isMainActivityNotNull || !appContext.mappings.isMappingsLoaded) return@hookMainActivity
|
||||
onActivityCreate()
|
||||
jetpackComposeResourceHook()
|
||||
appContext.actionManager.onNewIntent(intent)
|
||||
@ -146,7 +146,7 @@ class SnapEnhance {
|
||||
database.init()
|
||||
eventDispatcher.init()
|
||||
//if mappings aren't loaded, we can't initialize features
|
||||
if (!mappings.isMappingsLoaded()) return
|
||||
if (!mappings.isMappingsLoaded) return
|
||||
bridgeClient.registerMessagingBridge(messagingBridge)
|
||||
features.init()
|
||||
scriptRuntime.connect(bridgeClient.getScriptingInterface())
|
||||
|
@ -12,6 +12,7 @@ import me.rhunk.snapenhance.core.features.impl.experiments.AddFriendSourceSpoof
|
||||
import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
|
||||
import me.rhunk.snapenhance.core.messaging.EnumBulkAction
|
||||
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
|
||||
import me.rhunk.snapenhance.mapper.impl.FriendRelationshipChangerMapper
|
||||
|
||||
class BulkMessagingAction : AbstractAction() {
|
||||
private val translation by lazy { context.translation.getCategory("bulk_messaging_action") }
|
||||
@ -145,11 +146,10 @@ class BulkMessagingAction : AbstractAction() {
|
||||
}
|
||||
|
||||
private fun removeFriend(userId: String) {
|
||||
val friendRelationshipChangerMapping = context.mappings.getMappedMap("FriendRelationshipChanger") ?: throw Exception("Failed to get FriendRelationshipChanger mapping")
|
||||
context.mappings.useMapper(FriendRelationshipChangerMapper::class) {
|
||||
val friendRelationshipChangerInstance = context.feature(AddFriendSourceSpoof::class).friendRelationshipChangerInstance!!
|
||||
|
||||
val removeFriendMethod = friendRelationshipChangerInstance::class.java.methods.first {
|
||||
it.name == friendRelationshipChangerMapping["removeFriendMethod"].toString()
|
||||
it.name == this.removeFriendMethod.get()
|
||||
}
|
||||
|
||||
val completable = removeFriendMethod.invoke(friendRelationshipChangerInstance,
|
||||
@ -163,4 +163,6 @@ class BulkMessagingAction : AbstractAction() {
|
||||
it.name == "subscribe" && it.parameterTypes.isEmpty()
|
||||
}.invoke(completable)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -18,17 +18,15 @@ import me.rhunk.snapenhance.core.wrapper.impl.Message
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.MessageContent
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.MessageDestinations
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.mapper.impl.ViewBinderMapper
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class EventDispatcher(
|
||||
private val context: ModContext
|
||||
) : Manager {
|
||||
private fun findClass(name: String) = context.androidContext.classLoader.loadClass(name)
|
||||
|
||||
private fun hookViewBinder() {
|
||||
context.mappings.useMapper(ViewBinderMapper::class) {
|
||||
val cachedHooks = mutableListOf<String>()
|
||||
val viewBinderMappings = runCatching { context.mappings.getMappedMap("ViewBinder") }.getOrNull() ?: return
|
||||
|
||||
fun cacheHook(clazz: Class<*>, block: Class<*>.() -> Unit) {
|
||||
if (!cachedHooks.contains(clazz.name)) {
|
||||
clazz.block()
|
||||
@ -36,15 +34,15 @@ class EventDispatcher(
|
||||
}
|
||||
}
|
||||
|
||||
findClass(viewBinderMappings["class"].toString()).hookConstructor(HookStage.AFTER) { methodParam ->
|
||||
classReference.get()?.hookConstructor(HookStage.AFTER) { methodParam ->
|
||||
cacheHook(
|
||||
methodParam.thisObject<Any>()::class.java
|
||||
) {
|
||||
hook(viewBinderMappings["bindMethod"].toString(), HookStage.AFTER) bindViewMethod@{ param ->
|
||||
hook(bindMethod.get().toString(), HookStage.AFTER) bindViewMethod@{ param ->
|
||||
val instance = param.thisObject<Any>()
|
||||
val view = instance::class.java.methods.first {
|
||||
it.name == viewBinderMappings["getViewMethod"].toString()
|
||||
}.invoke(instance) as? View ?: return@bindViewMethod
|
||||
val view = instance::class.java.methods.firstOrNull {
|
||||
it.name == getViewMethod.get().toString()
|
||||
}?.invoke(instance) as? View ?: return@bindViewMethod
|
||||
|
||||
context.event.post(
|
||||
BindViewEvent(
|
||||
@ -58,6 +56,8 @@ class EventDispatcher(
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun init() {
|
||||
context.classCache.conversationManager.hook("sendMessageWithContent", HookStage.BEFORE) { param ->
|
||||
|
@ -8,6 +8,7 @@ import me.rhunk.snapenhance.core.util.hook.Hooker
|
||||
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.mapper.impl.CompositeConfigurationProviderMapper
|
||||
|
||||
data class ConfigKeyInfo(
|
||||
val category: String?,
|
||||
@ -23,16 +24,14 @@ data class ConfigFilter(
|
||||
|
||||
class ConfigurationOverride : Feature("Configuration Override", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||
override fun init() {
|
||||
val compositeConfigurationProviderMappings = context.mappings.getMappedMap("CompositeConfigurationProvider") ?: throw Exception("Failed to get compositeConfigurationProviderMappings")
|
||||
val enumMappings = compositeConfigurationProviderMappings["enum"] as Map<*, *>
|
||||
|
||||
context.mappings.useMapper(CompositeConfigurationProviderMapper::class) {
|
||||
fun getConfigKeyInfo(key: Any?) = runCatching {
|
||||
if (key == null) return@runCatching null
|
||||
val keyClassMethods = key::class.java.methods
|
||||
val keyName = keyClassMethods.firstOrNull { it.name == "getName" }?.invoke(key)?.toString() ?: key.toString()
|
||||
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
|
||||
val category = keyClassMethods.firstOrNull { it.name == configEnumMapping["getCategory"]?.get().toString() }?.invoke(key)?.toString() ?: return null
|
||||
val valueHolder = keyClassMethods.firstOrNull { it.name == configEnumMapping["getValue"]?.get().toString() }?.invoke(key) ?: return null
|
||||
val defaultValue = valueHolder.getObjectField(configEnumMapping["defaultValueField"]?.get().toString()) ?: return null
|
||||
ConfigKeyInfo(category, keyName, defaultValue)
|
||||
}.onFailure {
|
||||
context.log.error("Failed to get config key info", it)
|
||||
@ -69,8 +68,8 @@ class ConfigurationOverride : Feature("Configuration Override", loadParams = Fea
|
||||
overrideProperty(it, { context.config.global.blockAds.get() }, { "http://127.0.0.1" })
|
||||
}
|
||||
|
||||
findClass(compositeConfigurationProviderMappings["class"].toString()).hook(
|
||||
compositeConfigurationProviderMappings["getProperty"].toString(),
|
||||
classReference.getAsClass()?.hook(
|
||||
getProperty.getAsString()!!,
|
||||
HookStage.AFTER
|
||||
) { param ->
|
||||
val propertyKey = getConfigKeyInfo(param.argNullable<Any>(0)) ?: return@hook
|
||||
@ -81,15 +80,15 @@ class ConfigurationOverride : Feature("Configuration Override", loadParams = Fea
|
||||
}
|
||||
}
|
||||
|
||||
findClass(compositeConfigurationProviderMappings["class"].toString()).hook(
|
||||
compositeConfigurationProviderMappings["observeProperty"].toString(),
|
||||
classReference.get()?.hook(
|
||||
observeProperty.getAsString()!!,
|
||||
HookStage.BEFORE
|
||||
) { param ->
|
||||
val enumData = param.arg<Any>(0)
|
||||
val key = enumData.toString()
|
||||
val setValue: (Any?) -> Unit = { value ->
|
||||
val valueHolder = XposedHelpers.callMethod(enumData, enumMappings["getValue"].toString())
|
||||
valueHolder.setObjectField(enumMappings["defaultValueField"].toString(), value)
|
||||
val valueHolder = XposedHelpers.callMethod(enumData, configEnumMapping["getValue"]?.getAsString())
|
||||
valueHolder.setObjectField(configEnumMapping["defaultValueField"]?.getAsString()!!, value)
|
||||
}
|
||||
|
||||
propertyOverrides[key]?.let { (filter, value) ->
|
||||
@ -100,10 +99,10 @@ class ConfigurationOverride : Feature("Configuration Override", loadParams = Fea
|
||||
}
|
||||
|
||||
runCatching {
|
||||
val appExperimentProviderMappings = compositeConfigurationProviderMappings["appExperimentProvider"] as Map<*, *>
|
||||
val customBooleanPropertyRules = mutableListOf<(ConfigKeyInfo) -> Boolean>()
|
||||
|
||||
findClass(appExperimentProviderMappings["GetBooleanAppExperimentClass"].toString()).hook("invoke", HookStage.BEFORE) { param ->
|
||||
appExperimentProvider["getBooleanAppExperimentClass"]?.getAsClass()
|
||||
?.hook("invoke", HookStage.BEFORE) { param ->
|
||||
val keyInfo = getConfigKeyInfo(param.arg(1)) ?: return@hook
|
||||
if (customBooleanPropertyRules.any { it(keyInfo) }) {
|
||||
param.setResult(true)
|
||||
@ -115,17 +114,19 @@ class ConfigurationOverride : Feature("Configuration Override", loadParams = Fea
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Hooker.ephemeralHookConstructor(
|
||||
findClass(compositeConfigurationProviderMappings["class"].toString()),
|
||||
classReference.get()!!,
|
||||
HookStage.AFTER
|
||||
) { constructorParam ->
|
||||
val instance = constructorParam.thisObject<Any>()
|
||||
val appExperimentProviderInstance = instance::class.java.fields.firstOrNull {
|
||||
findClass(appExperimentProviderMappings["class"].toString()).isAssignableFrom(it.type)
|
||||
appExperimentProvider["class"]?.getAsClass()?.isAssignableFrom(it.type) == true
|
||||
}?.get(instance) ?: return@ephemeralHookConstructor
|
||||
|
||||
appExperimentProviderInstance::class.java.methods.first {
|
||||
it.name == appExperimentProviderMappings["hasExperimentMethod"].toString()
|
||||
it.name == appExperimentProvider["hasExperimentMethod"]?.getAsString().toString()
|
||||
}.hook(HookStage.BEFORE) { param ->
|
||||
val keyInfo = getConfigKeyInfo(param.arg(0)) ?: return@hook
|
||||
if (customBooleanPropertyRules.any { it(keyInfo) }) {
|
||||
@ -147,4 +148,5 @@ class ConfigurationOverride : Feature("Configuration Override", loadParams = Fea
|
||||
context.log.error("Failed to hook appExperimentProvider", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.mapper.impl.OperaViewerParamsMapper
|
||||
|
||||
class OperaViewerParamsOverride : Feature("OperaViewerParamsOverride", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
data class OverrideKey(
|
||||
@ -17,7 +18,6 @@ class OperaViewerParamsOverride : Feature("OperaViewerParamsOverride", loadParam
|
||||
)
|
||||
|
||||
override fun onActivityCreate() {
|
||||
val operaViewerParamsMappings = context.mappings.getMappedMap("OperaViewerParams") ?: throw Exception("Failed to get operaViewerParamsMappings")
|
||||
val overrideMap = mutableMapOf<String, Override>()
|
||||
|
||||
fun overrideParam(key: String, filter: (value: Any?) -> Boolean, value: (overrideKey: OverrideKey, value: Any?) -> Any?) {
|
||||
@ -36,7 +36,8 @@ class OperaViewerParamsOverride : Feature("OperaViewerParamsOverride", loadParam
|
||||
})
|
||||
}
|
||||
|
||||
findClass(operaViewerParamsMappings["class"].toString()).hook(operaViewerParamsMappings["putMethod"].toString(), HookStage.BEFORE) { param ->
|
||||
context.mappings.useMapper(OperaViewerParamsMapper::class) {
|
||||
classReference.get()?.hook(putMethod.get()!!, HookStage.BEFORE) { param ->
|
||||
val key = param.argNullable<Any>(0)?.let { key ->
|
||||
val fields = key::class.java.fields
|
||||
OverrideKey(
|
||||
@ -61,4 +62,5 @@ class OperaViewerParamsOverride : Feature("OperaViewerParamsOverride", loadParam
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -47,6 +47,7 @@ import me.rhunk.snapenhance.core.wrapper.impl.media.dash.SnapPlaylistItem
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.media.opera.Layer
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.media.opera.ParamMap
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.media.toKeyPair
|
||||
import me.rhunk.snapenhance.mapper.impl.OperaPageViewControllerMapper
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.nio.file.Paths
|
||||
import java.util.UUID
|
||||
@ -462,15 +463,13 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
||||
}
|
||||
|
||||
override fun asyncOnActivityCreate() {
|
||||
val operaViewerControllerClass: Class<*> = context.mappings.getMappedClass("OperaPageViewController", "class")
|
||||
|
||||
context.mappings.useMapper(OperaPageViewControllerMapper::class) {
|
||||
val onOperaViewStateCallback: (HookAdapter) -> Unit = onOperaViewStateCallback@{ param ->
|
||||
|
||||
val viewState = (param.thisObject() as Any).getObjectField(context.mappings.getMappedValue("OperaPageViewController", "viewStateField")!!).toString()
|
||||
val viewState = (param.thisObject() as Any).getObjectField(viewStateField.get()!!).toString()
|
||||
if (viewState != "FULLY_DISPLAYED") {
|
||||
return@onOperaViewStateCallback
|
||||
}
|
||||
val operaLayerList = (param.thisObject() as Any).getObjectField(context.mappings.getMappedValue("OperaPageViewController", "layerListField")!!) as ArrayList<*>
|
||||
val operaLayerList = (param.thisObject() as Any).getObjectField(layerListField.get()!!) as ArrayList<*>
|
||||
val mediaParamMap: ParamMap = operaLayerList.map { Layer(it) }.first().paramMap
|
||||
|
||||
if (!mediaParamMap.containsKey("image_media_info") && !mediaParamMap.containsKey("video_media_info_list"))
|
||||
@ -502,13 +501,14 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
||||
}
|
||||
}
|
||||
|
||||
arrayOf("onDisplayStateChange", "onDisplayStateChangeGesture").forEach { methodName ->
|
||||
operaViewerControllerClass.hook(
|
||||
context.mappings.getMappedValue("OperaPageViewController", methodName) ?: return@forEach,
|
||||
arrayOf(onDisplayStateChange, onDisplayStateChangeGesture).forEach { methodName ->
|
||||
classReference.get()?.hook(
|
||||
methodName.get() ?: return@forEach,
|
||||
HookStage.AFTER, onOperaViewStateCallback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadMessageAttachments(
|
||||
friendInfo: FriendInfo,
|
||||
|
@ -5,20 +5,19 @@ import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||
import me.rhunk.snapenhance.mapper.impl.FriendRelationshipChangerMapper
|
||||
|
||||
class AddFriendSourceSpoof : Feature("AddFriendSourceSpoof", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
var friendRelationshipChangerInstance: Any? = null
|
||||
private set
|
||||
|
||||
override fun onActivityCreate() {
|
||||
val friendRelationshipChangerMapping = context.mappings.getMappedMap("FriendRelationshipChanger") ?: throw Exception("Failed to get friendRelationshipChangerMapping")
|
||||
|
||||
findClass(friendRelationshipChangerMapping["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
||||
context.mappings.useMapper(FriendRelationshipChangerMapper::class) {
|
||||
classReference.get()?.hookConstructor(HookStage.AFTER) { param ->
|
||||
friendRelationshipChangerInstance = param.thisObject()
|
||||
}
|
||||
|
||||
findClass(friendRelationshipChangerMapping["class"].toString())
|
||||
.hook(friendRelationshipChangerMapping["addFriendMethod"].toString(), HookStage.BEFORE) { param ->
|
||||
classReference.get()?.hook(addFriendMethod.get()!!, HookStage.BEFORE) { param ->
|
||||
val spoofedSource = context.config.experimental.addFriendSourceSpoof.getNullable() ?: return@hook
|
||||
|
||||
fun setEnum(index: Int, value: String) {
|
||||
@ -63,4 +62,5 @@ class AddFriendSourceSpoof : Feature("AddFriendSourceSpoof", loadParams = Featur
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,13 +4,14 @@ import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||
import me.rhunk.snapenhance.mapper.impl.StoryBoostStateMapper
|
||||
|
||||
class InfiniteStoryBoost : Feature("InfiniteStoryBoost", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||
override fun asyncOnActivityCreate() {
|
||||
if (!context.config.experimental.infiniteStoryBoost.get()) return
|
||||
val storyBoostStateClass = context.mappings.getMappedClass("StoryBoostStateClass") ?: throw Exception("Failed to get storyBoostStateClass")
|
||||
|
||||
storyBoostStateClass.hookConstructor(HookStage.BEFORE) { param ->
|
||||
context.mappings.useMapper(StoryBoostStateMapper::class) {
|
||||
classReference.get()?.hookConstructor(HookStage.BEFORE) { param ->
|
||||
val startTimeMillis = param.arg<Long>(1)
|
||||
//reset timestamp if it's more than 24 hours
|
||||
if (System.currentTimeMillis() - startTimeMillis > 86400000) {
|
||||
@ -19,4 +20,5 @@ class InfiniteStoryBoost : Feature("InfiniteStoryBoost", loadParams = FeatureLoa
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,20 +3,21 @@ package me.rhunk.snapenhance.core.features.impl.experiments
|
||||
import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
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.mapper.impl.BCryptClassMapper
|
||||
|
||||
class MeoPasscodeBypass : Feature("Meo Passcode Bypass", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||
override fun asyncOnActivityCreate() {
|
||||
val bcrypt = context.mappings.getMappedMap("BCrypt") ?: throw Exception("Failed to get bcrypt mappings")
|
||||
if (!context.config.experimental.meoPasscodeBypass.get()) return
|
||||
|
||||
Hooker.hook(
|
||||
context.androidContext.classLoader.loadClass(bcrypt["class"].toString()),
|
||||
bcrypt["hashMethod"].toString(),
|
||||
context.mappings.useMapper(BCryptClassMapper::class) {
|
||||
classReference.get()?.hook(
|
||||
hashMethod.get()!!,
|
||||
HookStage.BEFORE,
|
||||
{ context.config.experimental.meoPasscodeBypass.get() },
|
||||
) { param ->
|
||||
//set the hash to the result of the method
|
||||
param.setResult(param.arg(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,17 +4,19 @@ import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||
import me.rhunk.snapenhance.mapper.impl.ScoreUpdateMapper
|
||||
import java.lang.reflect.Constructor
|
||||
|
||||
class NoFriendScoreDelay : Feature("NoFriendScoreDelay", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
override fun onActivityCreate() {
|
||||
if (!context.config.experimental.noFriendScoreDelay.get()) return
|
||||
val scoreUpdateClass = context.mappings.getMappedClass("ScoreUpdate") ?: throw Exception("Failed to get scoreUpdateClass")
|
||||
|
||||
scoreUpdateClass.hookConstructor(HookStage.BEFORE) { param ->
|
||||
context.mappings.useMapper(ScoreUpdateMapper::class) {
|
||||
classReference.get()?.hookConstructor(HookStage.BEFORE) { param ->
|
||||
val constructor = param.method() as Constructor<*>
|
||||
if (constructor.parameterTypes.size < 3 || constructor.parameterTypes[3] != java.util.Collection::class.java) return@hookConstructor
|
||||
param.setArg(2, 0L)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||
import me.rhunk.snapenhance.mapper.impl.DefaultMediaItemMapper
|
||||
import java.io.File
|
||||
|
||||
class BypassVideoLengthRestriction :
|
||||
@ -45,21 +46,23 @@ class BypassVideoLengthRestriction :
|
||||
}
|
||||
}
|
||||
|
||||
context.mappings.getMappedClass("DefaultMediaItem")
|
||||
?.hookConstructor(HookStage.BEFORE) { param ->
|
||||
context.mappings.useMapper(DefaultMediaItemMapper::class) {
|
||||
defaultMediaItem.getAsClass()?.hookConstructor(HookStage.BEFORE) { param ->
|
||||
//set the video length argument
|
||||
param.setArg(5, -1L)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: allow split from any source
|
||||
if (mode == "split") {
|
||||
val cameraRollId = context.mappings.getMappedMap("CameraRollMediaId") ?: throw Exception("Failed to get cameraRollId mappings")
|
||||
// memories grid
|
||||
findClass(cameraRollId["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
||||
context.mappings.useMapper(DefaultMediaItemMapper::class) {
|
||||
cameraRollMediaId.getAsClass()?.hookConstructor(HookStage.AFTER) { param ->
|
||||
//set the durationMs field
|
||||
param.thisObject<Any>()
|
||||
.setObjectField(cameraRollId["durationMsField"].toString(), -1L)
|
||||
.setObjectField(durationMsField.get()!!, -1L)
|
||||
}
|
||||
}
|
||||
|
||||
// chat camera roll grid
|
||||
|
@ -4,20 +4,20 @@ import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.mapper.impl.MediaQualityLevelProviderMapper
|
||||
import java.lang.reflect.Method
|
||||
|
||||
class MediaQualityLevelOverride : Feature("MediaQualityLevelOverride", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||
override fun init() {
|
||||
val enumQualityLevel = context.mappings.getMappedClass("EnumQualityLevel") ?: throw Exception("Failed to get enumQualityLevelMappings")
|
||||
val mediaQualityLevelProvider = context.mappings.getMappedMap("MediaQualityLevelProvider") ?: throw Exception("Failed to get mediaQualityLevelProviderMappings")
|
||||
if (!context.config.global.forceUploadSourceQuality.get()) return
|
||||
|
||||
val forceMediaSourceQuality by context.config.global.forceUploadSourceQuality
|
||||
|
||||
context.androidContext.classLoader.loadClass(mediaQualityLevelProvider["class"].toString()).hook(
|
||||
mediaQualityLevelProvider["method"].toString(),
|
||||
HookStage.BEFORE,
|
||||
{ forceMediaSourceQuality }
|
||||
context.mappings.useMapper(MediaQualityLevelProviderMapper::class) {
|
||||
mediaQualityLevelProvider.getAsClass()?.hook(
|
||||
mediaQualityLevelProviderMethod.getAsString()!!,
|
||||
HookStage.BEFORE
|
||||
) { param ->
|
||||
param.setResult(enumQualityLevel.enumConstants.firstOrNull { it.toString() == "LEVEL_MAX" } )
|
||||
param.setResult((param.method() as Method).returnType.enumConstants.firstOrNull { it.toString() == "LEVEL_MAX" } )
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,8 +3,9 @@ package me.rhunk.snapenhance.core.features.impl.global
|
||||
import me.rhunk.snapenhance.core.features.Feature
|
||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
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.hookConstructor
|
||||
import me.rhunk.snapenhance.mapper.impl.PlusSubscriptionMapper
|
||||
|
||||
class SnapchatPlus: Feature("SnapchatPlus", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||
private val originalSubscriptionTime = (System.currentTimeMillis() - 7776000000L)
|
||||
@ -13,9 +14,8 @@ class SnapchatPlus: Feature("SnapchatPlus", loadParams = FeatureLoadParams.INIT_
|
||||
override fun init() {
|
||||
if (!context.config.global.snapchatPlus.get()) return
|
||||
|
||||
val subscriptionInfoClass = context.mappings.getMappedClass("SubscriptionInfoClass") ?: throw Exception("Failed to get subscriptionInfoClass")
|
||||
|
||||
Hooker.hookConstructor(subscriptionInfoClass, HookStage.BEFORE) { param ->
|
||||
context.mappings.useMapper(PlusSubscriptionMapper::class) {
|
||||
classReference.get()?.hookConstructor(HookStage.BEFORE) { param ->
|
||||
if (param.arg<Int>(0) == 2) return@hookConstructor
|
||||
//subscription tier
|
||||
param.setArg(0, 2)
|
||||
@ -25,6 +25,7 @@ class SnapchatPlus: Feature("SnapchatPlus", loadParams = FeatureLoadParams.INIT_
|
||||
param.setArg(2, originalSubscriptionTime)
|
||||
param.setArg(3, expirationTimeMillis)
|
||||
}
|
||||
}
|
||||
|
||||
// optional as ConfigurationOverride does this too
|
||||
if (context.config.experimental.hiddenSnapchatPlusFeatures.get()) {
|
||||
|
@ -13,6 +13,7 @@ import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.Message
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE, loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||
@ -70,7 +71,8 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
|
||||
|
||||
override fun asyncOnActivityCreate() {
|
||||
// called when enter in a conversation
|
||||
context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback").hook(
|
||||
context.mappings.useMapper(CallbackMapper::class) {
|
||||
callbacks.getClass("FetchConversationWithMessagesCallback")?.hook(
|
||||
"onFetchConversationWithMessagesComplete",
|
||||
HookStage.BEFORE,
|
||||
{ autoSaveFilter.isNotEmpty() }
|
||||
@ -86,6 +88,7 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.event.subscribe(
|
||||
ConversationUpdateEvent::class,
|
||||
|
@ -18,6 +18,8 @@ import me.rhunk.snapenhance.core.wrapper.impl.Message
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.Snapchatter
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.toSnapUUID
|
||||
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||
import me.rhunk.snapenhance.mapper.impl.FriendsFeedEventDispatcherMapper
|
||||
import java.util.concurrent.Future
|
||||
|
||||
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
||||
@ -47,7 +49,8 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
||||
}
|
||||
}
|
||||
|
||||
context.mappings.getMappedClass("callbacks", "ConversationManagerDelegate").apply {
|
||||
context.mappings.useMapper(CallbackMapper::class) {
|
||||
callbacks.getClass("ConversationManagerDelegate")?.apply {
|
||||
hookConstructor(HookStage.AFTER) { param ->
|
||||
conversationManagerDelegate = param.thisObject()
|
||||
}
|
||||
@ -57,17 +60,20 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
||||
conversation = param.argNullable(1),
|
||||
messages = param.arg<ArrayList<*>>(2).map { Message(it) },
|
||||
).apply { adapter = param }) {
|
||||
param.setArg(2, messages.map { it.instanceNonNull() }.toCollection(ArrayList()))
|
||||
param.setArg(
|
||||
2,
|
||||
messages.map { it.instanceNonNull() }.toCollection(ArrayList())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.mappings.getMappedClass("callbacks", "IdentityDelegate").apply {
|
||||
callbacks.getClass("IdentityDelegate")?.apply {
|
||||
hookConstructor(HookStage.AFTER) {
|
||||
identityDelegate = it.thisObject()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getFeedCachedMessageIds(conversationId: String) = feedCachedSnapMessages[conversationId]
|
||||
|
||||
@ -96,10 +102,10 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
||||
}
|
||||
|
||||
override fun onActivityCreate() {
|
||||
context.mappings.getMappedObjectNullable("FriendsFeedEventDispatcher").let { it as? Map<*, *> }?.let { mappings ->
|
||||
findClass(mappings["class"].toString()).hook("onItemLongPress", HookStage.BEFORE) { param ->
|
||||
context.mappings.useMapper(FriendsFeedEventDispatcherMapper::class) {
|
||||
classReference.getAsClass()?.hook("onItemLongPress", HookStage.BEFORE) { param ->
|
||||
val viewItemContainer = param.arg<Any>(0)
|
||||
val viewItem = viewItemContainer.getObjectField(mappings["viewModelField"].toString()).toString()
|
||||
val viewItem = viewItemContainer.getObjectField(viewModelField.get()!!).toString()
|
||||
val conversationId = viewItem.substringAfter("conversationId: ").substring(0, 36).also {
|
||||
if (it.startsWith("null")) return@hook
|
||||
}
|
||||
@ -109,7 +115,6 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
context.classCache.feedEntry.hookConstructor(HookStage.AFTER) { param ->
|
||||
val instance = param.thisObject<Any>()
|
||||
val interactionInfo = instance.getObjectFieldOrNull("mInteractionInfo") ?: return@hookConstructor
|
||||
@ -122,17 +127,21 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
||||
}.sortedBy { it.orderKey }.mapNotNull { it.messageDescriptor?.messageId }
|
||||
}
|
||||
|
||||
context.mappings.getMappedClass("callbacks", "GetOneOnOneConversationIdsCallback").hook("onSuccess", HookStage.BEFORE) { param ->
|
||||
context.mappings.useMapper(CallbackMapper::class) {
|
||||
callbacks.getClass("GetOneOnOneConversationIdsCallback")?.hook("onSuccess", HookStage.BEFORE) { param ->
|
||||
val userIdToConversation = (param.arg<ArrayList<*>>(0))
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.get(0) ?: return@hook
|
||||
|
||||
lastFetchConversationUUID = SnapUUID(userIdToConversation.getObjectField("mConversationId"))
|
||||
lastFetchConversationUserUUID = SnapUUID(userIdToConversation.getObjectField("mUserId"))
|
||||
lastFetchConversationUUID =
|
||||
SnapUUID(userIdToConversation.getObjectField("mConversationId"))
|
||||
lastFetchConversationUserUUID =
|
||||
SnapUUID(userIdToConversation.getObjectField("mUserId"))
|
||||
}
|
||||
}
|
||||
|
||||
with(context.classCache.conversationManager) {
|
||||
Hooker.hook(this, "enterConversation", HookStage.BEFORE) { param ->
|
||||
context.classCache.conversationManager.apply {
|
||||
hook("enterConversation", HookStage.BEFORE) { param ->
|
||||
openedConversationUUID = SnapUUID(param.arg(0))
|
||||
if (context.config.messaging.bypassMessageRetentionPolicy.get()) {
|
||||
val callback = param.argNullable<Any>(2) ?: return@hook
|
||||
@ -141,7 +150,7 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
||||
}
|
||||
}
|
||||
|
||||
Hooker.hook(this, "exitConversation", HookStage.BEFORE) {
|
||||
hook("exitConversation", HookStage.BEFORE) {
|
||||
openedConversationUUID = null
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||
import me.rhunk.snapenhance.core.util.ktx.getIdentifier
|
||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@ -43,8 +44,8 @@ class HalfSwipeNotifier : Feature("Half Swipe Notifier", loadParams = FeatureLoa
|
||||
presenceService = it.thisObject()
|
||||
}
|
||||
|
||||
context.mappings.getMappedClass("callbacks", "PresenceServiceDelegate")
|
||||
.hook("notifyActiveConversationsChanged", HookStage.BEFORE) {
|
||||
context.mappings.useMapper(CallbackMapper::class) {
|
||||
callbacks.getClass("PresenceServiceDelegate")?.hook("notifyActiveConversationsChanged", HookStage.BEFORE) {
|
||||
val activeConversations = presenceService::class.java.methods.find { it.name == "getActiveConversations" }?.invoke(presenceService) as? Map<*, *> ?: return@hook // conversationId, conversationInfo (this.mPeekingParticipants)
|
||||
|
||||
if (activeConversations.isEmpty()) {
|
||||
@ -77,6 +78,7 @@ class HalfSwipeNotifier : Feature("Half Swipe Notifier", loadParams = FeatureLoa
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startPeeking(conversationId: String, userId: String) {
|
||||
startPeekingTimestamps[conversationId + userId] = System.currentTimeMillis()
|
||||
|
@ -17,6 +17,7 @@ import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.ScSize
|
||||
import me.rhunk.snapenhance.mapper.impl.ScCameraSettingsMapper
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
@ -62,7 +63,8 @@ class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACT
|
||||
}
|
||||
}
|
||||
|
||||
context.mappings.getMappedClass("ScCameraSettings")?.hookConstructor(HookStage.BEFORE) { param ->
|
||||
context.mappings.useMapper(ScCameraSettingsMapper::class) {
|
||||
classReference.get()?.hookConstructor(HookStage.BEFORE) { param ->
|
||||
val previewResolution = ScSize(param.argNullable(2))
|
||||
val captureResolution = ScSize(param.argNullable(3))
|
||||
|
||||
@ -78,6 +80,7 @@ class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (config.blackPhotos.get()) {
|
||||
findClass("android.media.ImageReader\$SurfaceImage").hook("getPlanes", HookStage.AFTER) { param ->
|
||||
|
@ -9,6 +9,7 @@ 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.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||
|
||||
class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType = MessagingRuleType.HIDE_FRIEND_FEED, loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||
private fun createDeletedFeedEntry(conversationId: String) = context.gson.fromJson(
|
||||
@ -41,20 +42,20 @@ class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType
|
||||
override fun init() {
|
||||
if (!context.config.userInterface.hideFriendFeedEntry.get()) return
|
||||
|
||||
context.mappings.useMapper(CallbackMapper::class) {
|
||||
arrayOf(
|
||||
"QueryFeedCallback" to "onQueryFeedComplete",
|
||||
"FeedManagerDelegate" to "onFeedEntriesUpdated",
|
||||
"FeedManagerDelegate" to "onInternalSyncFeed",
|
||||
"SyncFeedCallback" to "onSyncFeedComplete",
|
||||
).forEach { (callbackName, methodName) ->
|
||||
context.mappings.getMappedClass("callbacks", callbackName)
|
||||
.hook(methodName, HookStage.BEFORE) { param ->
|
||||
findClass(callbacks.get()!![callbackName] ?: return@forEach).hook(methodName, HookStage.BEFORE) { param ->
|
||||
filterFriendFeed(param.arg(0))
|
||||
}
|
||||
}
|
||||
|
||||
context.mappings.getMappedClass("callbacks", "FetchAndSyncFeedCallback")
|
||||
.hook("onFetchAndSyncFeedComplete", HookStage.BEFORE) { param ->
|
||||
callbacks.getClass("FetchAndSyncFeedCallback")
|
||||
?.hook("onFetchAndSyncFeedComplete", HookStage.BEFORE) { param ->
|
||||
val deletedConversations: ArrayList<Any> = param.arg(2)
|
||||
filterFriendFeed(param.arg(0), deletedConversations)
|
||||
|
||||
@ -66,6 +67,7 @@ class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getRuleState() = RuleState.WHITELIST
|
||||
}
|
@ -5,17 +5,19 @@ import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||
import me.rhunk.snapenhance.mapper.impl.FriendingDataSourcesMapper
|
||||
|
||||
class HideQuickAddFriendFeed : Feature("HideQuickAddFriendFeed", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
override fun onActivityCreate() {
|
||||
if (!context.config.userInterface.hideQuickAddFriendFeed.get()) return
|
||||
|
||||
val friendingDataSource = context.mappings.getMappedMap("FriendingDataSources") ?: throw Exception("Failed to get friendingDataSourceMappings")
|
||||
findClass(friendingDataSource["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
||||
context.mappings.useMapper(FriendingDataSourcesMapper::class) {
|
||||
classReference.getAsClass()?.hookConstructor(HookStage.AFTER) { param ->
|
||||
param.thisObject<Any>().setObjectField(
|
||||
friendingDataSource["quickAddSourceListField"].toString(),
|
||||
quickAddSourceListField.get()!!,
|
||||
arrayListOf<Any>()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -19,6 +19,7 @@ import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import me.rhunk.snapenhance.core.util.ktx.getDimens
|
||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.core.util.media.PreviewUtils
|
||||
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||
import java.io.File
|
||||
|
||||
class SnapPreview : Feature("SnapPreview", loadParams = FeatureLoadParams.INIT_SYNC or FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
@ -29,7 +30,8 @@ class SnapPreview : Feature("SnapPreview", loadParams = FeatureLoadParams.INIT_S
|
||||
|
||||
override fun init() {
|
||||
if (!isEnabled) return
|
||||
context.mappings.getMappedClass("callbacks", "ContentCallback").hook("handleContentResult", HookStage.BEFORE) { param ->
|
||||
context.mappings.useMapper(CallbackMapper::class) {
|
||||
callbacks.getClass("ContentCallback")?.hook("handleContentResult", HookStage.BEFORE) { param ->
|
||||
val contentResult = param.arg<Any>(0)
|
||||
val classMethods = contentResult::class.java.methods
|
||||
|
||||
@ -42,6 +44,7 @@ class SnapPreview : Feature("SnapPreview", loadParams = FeatureLoadParams.INIT_S
|
||||
mediaFileCache[mediaId.substringAfter("-")] = File(filePath.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DiscouragedApi")
|
||||
override fun onActivityCreate() {
|
||||
|
@ -8,6 +8,7 @@ import me.rhunk.snapenhance.core.util.CallbackBuilder
|
||||
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.MessageDestinations
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||
|
||||
class MessageSender(
|
||||
private val context: ModContext,
|
||||
@ -55,7 +56,13 @@ class MessageSender(
|
||||
|
||||
}
|
||||
|
||||
private val sendMessageCallback by lazy { context.mappings.getMappedClass("callbacks", "SendMessageCallback") }
|
||||
private val sendMessageCallback by lazy {
|
||||
lateinit var result: Class<*>
|
||||
context.mappings.useMapper(CallbackMapper::class) {
|
||||
result = callbacks.getClass("SendMessageCallback") ?: return@useMapper
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
private fun createLocalMessageContentTemplate(
|
||||
contentType: ContentType,
|
||||
|
@ -6,6 +6,7 @@ import me.rhunk.snapenhance.core.util.CallbackBuilder
|
||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||
|
||||
typealias CallbackResult = (error: String?) -> Unit
|
||||
|
||||
@ -26,20 +27,29 @@ class ConversationManager(
|
||||
private val getOneOnOneConversationIds by lazy { findMethodByName("getOneOnOneConversationIds") }
|
||||
|
||||
|
||||
private fun getCallbackClass(name: String): Class<*> {
|
||||
lateinit var result: Class<*>
|
||||
context.mappings.useMapper(CallbackMapper::class) {
|
||||
result = context.androidContext.classLoader.loadClass(callbacks.get()!![name])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
fun updateMessage(conversationId: String, messageId: Long, action: MessageUpdate, onResult: CallbackResult = {}) {
|
||||
updateMessageMethod.invoke(
|
||||
instanceNonNull(),
|
||||
SnapUUID.fromString(conversationId).instanceNonNull(),
|
||||
messageId,
|
||||
context.classCache.messageUpdateEnum.enumConstants.first { it.toString() == action.toString() },
|
||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback"))
|
||||
CallbackBuilder(getCallbackClass("Callback"))
|
||||
.override("onSuccess") { onResult(null) }
|
||||
.override("onError") { onResult(it.arg<Any>(0).toString()) }.build()
|
||||
)
|
||||
}
|
||||
|
||||
fun fetchConversationWithMessagesPaginated(conversationId: String, lastMessageId: Long, amount: Int, onSuccess: (message: List<Message>) -> Unit, onError: (error: String) -> Unit) {
|
||||
val callback = CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback"))
|
||||
val callback = CallbackBuilder(getCallbackClass("FetchConversationWithMessagesCallback"))
|
||||
.override("onFetchConversationWithMessagesComplete") { param ->
|
||||
onSuccess(param.arg<List<*>>(1).map { Message(it) })
|
||||
}
|
||||
@ -54,7 +64,7 @@ class ConversationManager(
|
||||
fetchConversationWithMessagesMethod.invoke(
|
||||
instanceNonNull(),
|
||||
conversationId.toSnapUUID().instanceNonNull(),
|
||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback"))
|
||||
CallbackBuilder(getCallbackClass("FetchConversationWithMessagesCallback"))
|
||||
.override("onFetchConversationWithMessagesComplete") { param ->
|
||||
onSuccess(param.arg<List<*>>(1).map { Message(it) })
|
||||
}
|
||||
@ -70,7 +80,7 @@ class ConversationManager(
|
||||
instanceNonNull(),
|
||||
conversationId.toSnapUUID().instanceNonNull(),
|
||||
messageId,
|
||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback"))
|
||||
CallbackBuilder(getCallbackClass("Callback"))
|
||||
.override("onSuccess") { onResult(null) }
|
||||
.override("onError") { onResult(it.arg<Any>(0).toString()) }.build()
|
||||
)
|
||||
@ -81,7 +91,7 @@ class ConversationManager(
|
||||
instanceNonNull(),
|
||||
conversationId.toSnapUUID().instanceNonNull(),
|
||||
messageId,
|
||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback"))
|
||||
CallbackBuilder(getCallbackClass("FetchMessageCallback"))
|
||||
.override("onFetchMessageComplete") { param ->
|
||||
onSuccess(Message(param.arg(0)))
|
||||
}
|
||||
@ -100,7 +110,7 @@ class ConversationManager(
|
||||
fetchMessageByServerId.invoke(
|
||||
instanceNonNull(),
|
||||
serverMessageIdentifier,
|
||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback"))
|
||||
CallbackBuilder(getCallbackClass("FetchMessageCallback"))
|
||||
.override("onFetchMessageComplete") { param ->
|
||||
onSuccess(Message(param.arg(0)))
|
||||
}
|
||||
@ -119,7 +129,7 @@ class ConversationManager(
|
||||
setObjectField("mServerMessageId", it)
|
||||
}
|
||||
},
|
||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessagesByServerIdsCallback"))
|
||||
CallbackBuilder(getCallbackClass("FetchMessagesByServerIdsCallback"))
|
||||
.override("onSuccess") { param ->
|
||||
onSuccess(param.arg<List<*>>(0).mapNotNull {
|
||||
Message(it?.getObjectField("mMessage") ?: return@mapNotNull null)
|
||||
@ -132,14 +142,14 @@ class ConversationManager(
|
||||
}
|
||||
|
||||
fun clearConversation(conversationId: String, onSuccess: () -> Unit, onError: (error: String) -> Unit) {
|
||||
val callback = CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback"))
|
||||
val callback = CallbackBuilder(getCallbackClass("Callback"))
|
||||
.override("onSuccess") { onSuccess() }
|
||||
.override("onError") { onError(it.arg<Any>(0).toString()) }.build()
|
||||
clearConversation.invoke(instanceNonNull(), conversationId.toSnapUUID().instanceNonNull(), callback)
|
||||
}
|
||||
|
||||
fun getOneOnOneConversationIds(userIds: List<String>, onSuccess: (List<Pair<String, String>>) -> Unit, onError: (error: String) -> Unit) {
|
||||
val callback = CallbackBuilder(context.mappings.getMappedClass("callbacks", "GetOneOnOneConversationIdsCallback"))
|
||||
val callback = CallbackBuilder(getCallbackClass("GetOneOnOneConversationIdsCallback"))
|
||||
.override("onSuccess") { param ->
|
||||
onSuccess(param.arg<ArrayList<*>>(0).map {
|
||||
SnapUUID(it.getObjectField("mUserId")).toString() to SnapUUID(it.getObjectField("mConversationId")).toString()
|
||||
|
@ -1,8 +1,103 @@
|
||||
package me.rhunk.snapenhance.mapper
|
||||
|
||||
abstract class AbstractClassMapper {
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
|
||||
abstract class AbstractClassMapper(
|
||||
val mapperName: String
|
||||
) {
|
||||
lateinit var classLoader: ClassLoader
|
||||
|
||||
private val gson = Gson()
|
||||
private val values = mutableMapOf<String, Any?>()
|
||||
private val mappers = mutableListOf<MapperContext.() -> Unit>()
|
||||
|
||||
private fun findClassSafe(className: String?) = runCatching {
|
||||
classLoader.loadClass(className)
|
||||
}.onFailure {
|
||||
Log.e("Mapper", it.stackTraceToString())
|
||||
}.getOrNull()
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inner class PropertyDelegate<T>(
|
||||
private val key: String,
|
||||
defaultValue: Any? = null,
|
||||
private val setter: (Any?) -> Unit = { values[key] = it },
|
||||
private val getter: (Any?) -> T? = { it as? T }
|
||||
) {
|
||||
init {
|
||||
values[key] = defaultValue
|
||||
}
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: Any?): T? {
|
||||
return getter(values[key])
|
||||
}
|
||||
|
||||
operator fun setValue(thisRef: Any?, property: Any?, value: Any?) {
|
||||
setter(value)
|
||||
}
|
||||
|
||||
fun set(value: String?) {
|
||||
values[key] = value
|
||||
}
|
||||
|
||||
fun get(): T? {
|
||||
return getter(values[key])
|
||||
}
|
||||
|
||||
fun getAsClass(): Class<*>? {
|
||||
return getter(values[key]) as? Class<*>
|
||||
}
|
||||
|
||||
fun getAsString(): String? {
|
||||
return getter(values[key])?.toString()
|
||||
}
|
||||
|
||||
fun getClass(key: String): Class<*>? {
|
||||
return (get() as? Map<String, String?>)?.let {
|
||||
findClassSafe(it[key].toString())
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString() = getter(values[key]).toString()
|
||||
}
|
||||
|
||||
fun string(key: String): PropertyDelegate<String> = PropertyDelegate(key, null)
|
||||
|
||||
fun classReference(key: String): PropertyDelegate<Class<*>> = PropertyDelegate(key, getter = { findClassSafe(it as? String) })
|
||||
|
||||
fun map(key: String, value: MutableMap<String, String?> = mutableMapOf()): PropertyDelegate<MutableMap<String, String?>> = PropertyDelegate(key, value)
|
||||
|
||||
fun readFromJson(json: JsonObject) {
|
||||
values.forEach { (key, _) ->
|
||||
runCatching {
|
||||
val jsonElement = json.get(key) ?: return@forEach
|
||||
when (jsonElement) {
|
||||
is JsonObject -> values[key] = gson.fromJson(jsonElement, HashMap::class.java)
|
||||
else -> values[key] = jsonElement.asString
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("Mapper","Failed to deserialize property $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun writeFromJson(json: JsonObject) {
|
||||
values.forEach { (key, value) ->
|
||||
runCatching {
|
||||
when (value) {
|
||||
is String -> json.addProperty(key, value)
|
||||
is Class<*> -> json.addProperty(key, value.name)
|
||||
is Map<*, *> -> json.add(key, gson.toJsonTree(value))
|
||||
else -> json.addProperty(key, value.toString())
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("Mapper","Failed to serialize property $key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun mapper(task: MapperContext.() -> Unit) {
|
||||
mappers.add(task)
|
||||
}
|
||||
|
@ -3,8 +3,8 @@ package me.rhunk.snapenhance.mapper
|
||||
import com.google.gson.JsonObject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.mapper.impl.*
|
||||
import org.jf.dexlib2.Opcodes
|
||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
@ -12,12 +12,33 @@ import java.io.BufferedInputStream
|
||||
import java.io.InputStream
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class Mapper(
|
||||
private vararg val mappers: KClass<out AbstractClassMapper> = arrayOf()
|
||||
class ClassMapper(
|
||||
private vararg val mappers: AbstractClassMapper = DEFAULT_MAPPERS,
|
||||
) {
|
||||
private val classes = mutableListOf<ClassDef>()
|
||||
|
||||
companion object {
|
||||
val DEFAULT_MAPPERS get() = arrayOf(
|
||||
BCryptClassMapper(),
|
||||
CallbackMapper(),
|
||||
DefaultMediaItemMapper(),
|
||||
MediaQualityLevelProviderMapper(),
|
||||
OperaPageViewControllerMapper(),
|
||||
PlusSubscriptionMapper(),
|
||||
ScCameraSettingsMapper(),
|
||||
StoryBoostStateMapper(),
|
||||
FriendsFeedEventDispatcherMapper(),
|
||||
CompositeConfigurationProviderMapper(),
|
||||
ScoreUpdateMapper(),
|
||||
FriendRelationshipChangerMapper(),
|
||||
ViewBinderMapper(),
|
||||
FriendingDataSourcesMapper(),
|
||||
OperaViewerParamsMapper(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
fun loadApk(path: String) {
|
||||
val apkFile = ZipFile(path)
|
||||
val apkEntries = apkFile.entries().toList()
|
||||
@ -50,11 +71,9 @@ class Mapper(
|
||||
}
|
||||
}
|
||||
|
||||
fun start(): JsonObject {
|
||||
val mappers = mappers.map { it.java.constructors.first().newInstance() as AbstractClassMapper }
|
||||
suspend fun run(): JsonObject {
|
||||
val context = MapperContext(classes.associateBy { it.type })
|
||||
|
||||
runBlocking {
|
||||
withContext(Dispatchers.IO) {
|
||||
mappers.forEach { mapper ->
|
||||
launch {
|
||||
@ -62,8 +81,13 @@ class Mapper(
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return context.exportToJson()
|
||||
val outputJson = JsonObject()
|
||||
mappers.forEach { mapper ->
|
||||
outputJson.add(mapper.mapperName, JsonObject().apply {
|
||||
mapper.writeFromJson(this)
|
||||
})
|
||||
}
|
||||
return outputJson
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
package me.rhunk.snapenhance.mapper
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.google.gson.JsonObject
|
||||
import org.jf.dexlib2.iface.ClassDef
|
||||
|
||||
class MapperContext(
|
||||
@ -19,40 +17,4 @@ class MapperContext(
|
||||
if (name == null) return null
|
||||
return classMap[name.toString()]
|
||||
}
|
||||
|
||||
private val mappings = mutableMapOf<String, Any?>()
|
||||
|
||||
fun addMapping(key: String, vararg array: Pair<String, Any?>) {
|
||||
mappings[key] = array.toMap()
|
||||
}
|
||||
|
||||
fun addMapping(key: String, value: String) {
|
||||
mappings[key] = value
|
||||
}
|
||||
|
||||
fun getStringMapping(key: String): String? {
|
||||
return mappings[key] as? String
|
||||
}
|
||||
|
||||
fun getMapMapping(key: String): Map<*, *>? {
|
||||
return mappings[key] as? Map<*, *>
|
||||
}
|
||||
|
||||
fun exportToJson(): JsonObject {
|
||||
val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
val json = JsonObject()
|
||||
for ((key, value) in mappings) {
|
||||
when (value) {
|
||||
is String -> json.addProperty(key, value)
|
||||
is Map<*, *> -> {
|
||||
val obj = JsonObject()
|
||||
for ((k, v) in value) {
|
||||
obj.add(k.toString(), gson.toJsonTree(v))
|
||||
}
|
||||
json.add(key, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
return json
|
||||
}
|
||||
}
|
@ -6,7 +6,10 @@ import me.rhunk.snapenhance.mapper.ext.getStaticConstructor
|
||||
import me.rhunk.snapenhance.mapper.ext.isFinal
|
||||
import org.jf.dexlib2.iface.instruction.formats.ArrayPayload
|
||||
|
||||
class BCryptClassMapper : AbstractClassMapper() {
|
||||
class BCryptClassMapper : AbstractClassMapper("BCryptClass") {
|
||||
val classReference = classReference("class")
|
||||
val hashMethod = string("hashMethod")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
@ -17,17 +20,15 @@ class BCryptClassMapper : AbstractClassMapper() {
|
||||
}
|
||||
|
||||
if (isBcryptClass == true) {
|
||||
val hashMethod = clazz.methods.first {
|
||||
val hashDexMethod = clazz.methods.first {
|
||||
it.parameterTypes.size == 2 &&
|
||||
it.parameterTypes[0] == "Ljava/lang/String;" &&
|
||||
it.parameterTypes[1] == "Ljava/lang/String;" &&
|
||||
it.returnType == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
addMapping("BCrypt",
|
||||
"class" to clazz.getClassName(),
|
||||
"hashMethod" to hashMethod.name
|
||||
)
|
||||
hashMethod.set(hashDexMethod.name)
|
||||
classReference.set(clazz.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -4,11 +4,12 @@ import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.getSuperClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.isFinal
|
||||
import org.jf.dexlib2.Opcode
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction21t
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction22t
|
||||
|
||||
class CallbackMapper : AbstractClassMapper() {
|
||||
class CallbackMapper : AbstractClassMapper("Callbacks") {
|
||||
val callbacks = map("callbacks")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
val callbackClasses = classes.filter { clazz ->
|
||||
@ -32,7 +33,7 @@ class CallbackMapper : AbstractClassMapper() {
|
||||
it.getSuperClassName()!!.substringAfterLast("/") to it.getClassName()
|
||||
}
|
||||
|
||||
addMapping("callbacks", *callbackClasses.toTypedArray())
|
||||
callbacks.get()?.putAll(callbackClasses)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,32 @@
|
||||
package me.rhunk.snapenhance.mapper.impl
|
||||
|
||||
import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.*
|
||||
import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.hasStaticConstructorString
|
||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction21c
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import org.jf.dexlib2.iface.reference.FieldReference
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
class CompositeConfigurationProviderMapper : AbstractClassMapper() {
|
||||
class CompositeConfigurationProviderMapper : AbstractClassMapper("CompositeConfigurationProvider") {
|
||||
val classReference = classReference("class")
|
||||
val observeProperty = string("observeProperty")
|
||||
val getProperty = string("getProperty")
|
||||
val configEnumMapping = mapOf(
|
||||
"class" to classReference("enumClass"),
|
||||
"getValue" to string("enumGetValue"),
|
||||
"getCategory" to string("enumGetCategory"),
|
||||
"defaultValueField" to string("enumDefaultValueField")
|
||||
)
|
||||
val appExperimentProvider = mapOf(
|
||||
"class" to classReference("appExperimentProviderClass"),
|
||||
"getBooleanAppExperimentClass" to classReference("getBooleanAppExperimentClass"),
|
||||
"hasExperimentMethod" to string("hasExperimentMethod")
|
||||
)
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (classDef in classes) {
|
||||
@ -68,24 +86,21 @@ class CompositeConfigurationProviderMapper : AbstractClassMapper() {
|
||||
it.type == "Ljava/lang/Object;"
|
||||
}
|
||||
|
||||
addMapping("CompositeConfigurationProvider",
|
||||
"class" to classDef.getClassName(),
|
||||
"observeProperty" to observePropertyMethod.name,
|
||||
"getProperty" to getPropertyMethod.name,
|
||||
"enum" to mapOf(
|
||||
"class" to configEnumInterface.getClassName(),
|
||||
"getValue" to enumGetDefaultValueMethod.name,
|
||||
"getCategory" to enumGetCategoryMethod.name,
|
||||
"defaultValueField" to defaultValueField.name
|
||||
),
|
||||
"appExperimentProvider" to (hasExperimentMethodReference?.let {
|
||||
mapOf(
|
||||
"class" to getClass(it.definingClass)?.getClassName(),
|
||||
"GetBooleanAppExperimentClass" to getBooleanAppExperimentClass,
|
||||
"hasExperimentMethod" to hasExperimentMethodReference.name
|
||||
)
|
||||
})
|
||||
)
|
||||
classReference.set(classDef.getClassName())
|
||||
observeProperty.set(observePropertyMethod.name)
|
||||
getProperty.set(getPropertyMethod.name)
|
||||
|
||||
configEnumMapping["class"]?.set(configEnumInterface.getClassName())
|
||||
configEnumMapping["getValue"]?.set(enumGetDefaultValueMethod.name)
|
||||
configEnumMapping["getCategory"]?.set(enumGetCategoryMethod.name)
|
||||
configEnumMapping["defaultValueField"]?.set(defaultValueField.name)
|
||||
|
||||
hasExperimentMethodReference?.let {
|
||||
appExperimentProvider["class"]?.set(getClass(it.definingClass)?.getClassName())
|
||||
appExperimentProvider["getBooleanAppExperimentClass"]?.set(getBooleanAppExperimentClass)
|
||||
appExperimentProvider["hasExperimentMethod"]?.set(hasExperimentMethodReference.name)
|
||||
}
|
||||
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -5,16 +5,21 @@ import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.isAbstract
|
||||
|
||||
class DefaultMediaItemMapper : AbstractClassMapper() {
|
||||
class DefaultMediaItemMapper : AbstractClassMapper("DefaultMediaItem") {
|
||||
val cameraRollMediaId = classReference("cameraRollMediaIdClass")
|
||||
val durationMsField = string("durationMsField")
|
||||
val defaultMediaItem = classReference("defaultMediaItemClass")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
if (clazz.methods.find { it.name == "toString" }?.implementation?.findConstString("CameraRollMediaId", contains = true) != true) {
|
||||
continue
|
||||
}
|
||||
val durationMsField = clazz.fields.firstOrNull { it.type == "J" } ?: continue
|
||||
val durationMsDexField = clazz.fields.firstOrNull { it.type == "J" } ?: continue
|
||||
|
||||
addMapping("CameraRollMediaId", "class" to clazz.getClassName(), "durationMsField" to durationMsField.name)
|
||||
cameraRollMediaId.set(clazz.getClassName())
|
||||
durationMsField.set(durationMsDexField.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
@ -29,7 +34,7 @@ class DefaultMediaItemMapper : AbstractClassMapper() {
|
||||
val constructorParameters = clazz.directMethods.firstOrNull { it.name == "<init>" }?.parameterTypes ?: continue
|
||||
if (constructorParameters.size < 6 || constructorParameters[5] != "J") continue
|
||||
|
||||
addMapping("DefaultMediaItem", clazz.getClassName())
|
||||
defaultMediaItem.set(clazz.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -5,12 +5,16 @@ import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||
|
||||
class FriendRelationshipChangerMapper : AbstractClassMapper() {
|
||||
class FriendRelationshipChangerMapper : AbstractClassMapper("FriendRelationshipChanger") {
|
||||
val classReference = classReference("class")
|
||||
val addFriendMethod = string("addFriendMethod")
|
||||
val removeFriendMethod = string("removeFriendMethod")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (classDef in classes) {
|
||||
classDef.methods.firstOrNull { it.name == "<init>" }?.implementation?.findConstString("FriendRelationshipChangerImpl")?.takeIf { it } ?: continue
|
||||
val addFriendMethod = classDef.methods.first {
|
||||
val addFriendDexMethod = classDef.methods.first {
|
||||
it.parameterTypes.size > 4 &&
|
||||
getClass(it.parameterTypes[1])?.isEnum() == true &&
|
||||
getClass(it.parameterTypes[2])?.isEnum() == true &&
|
||||
@ -18,7 +22,7 @@ class FriendRelationshipChangerMapper : AbstractClassMapper() {
|
||||
it.parameters[4].type == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
val removeFriendMethod = classDef.methods.first {
|
||||
val removeFriendDexMethod = classDef.methods.firstOrNull {
|
||||
it.parameterTypes.size == 5 &&
|
||||
it.parameterTypes[0] == "Ljava/lang/String;" &&
|
||||
getClass(it.parameterTypes[1])?.isEnum() == true &&
|
||||
@ -26,11 +30,12 @@ class FriendRelationshipChangerMapper : AbstractClassMapper() {
|
||||
it.parameterTypes[3] == "Ljava/lang/String;"
|
||||
}
|
||||
|
||||
addMapping("FriendRelationshipChanger",
|
||||
"class" to classDef.getClassName(),
|
||||
"addFriendMethod" to addFriendMethod.name,
|
||||
"removeFriendMethod" to removeFriendMethod.name
|
||||
)
|
||||
this@FriendRelationshipChangerMapper.apply {
|
||||
classReference.set(classDef.getClassName())
|
||||
addFriendMethod.set(addFriendDexMethod.name)
|
||||
removeFriendMethod.set(removeFriendDexMethod?.name)
|
||||
}
|
||||
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,10 @@ import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.searchNextFieldReference
|
||||
|
||||
class FriendingDataSourcesMapper: AbstractClassMapper() {
|
||||
class FriendingDataSourcesMapper: AbstractClassMapper("FriendingDataSources") {
|
||||
val classReference = classReference("class")
|
||||
val quickAddSourceListField = string("quickAddSourceListField")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (classDef in classes) {
|
||||
@ -15,13 +18,11 @@ class FriendingDataSourcesMapper: AbstractClassMapper() {
|
||||
val toStringMethod = classDef.methods.firstOrNull { it.name == "toString" } ?: continue
|
||||
if (toStringMethod.implementation?.findConstString("quickaddSource", contains = true) != true) continue
|
||||
|
||||
val quickAddSourceListField = toStringMethod.implementation?.searchNextFieldReference("quickaddSource", contains = true)
|
||||
val quickAddSourceListDexField = toStringMethod.implementation?.searchNextFieldReference("quickaddSource", contains = true)
|
||||
?: continue
|
||||
|
||||
addMapping("FriendingDataSources",
|
||||
"class" to classDef.getClassName(),
|
||||
"quickAddSourceListField" to quickAddSourceListField.name
|
||||
)
|
||||
classReference.set(classDef.getClassName())
|
||||
quickAddSourceListField.set(quickAddSourceListDexField.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,10 @@ import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
|
||||
|
||||
class FriendsFeedEventDispatcherMapper : AbstractClassMapper() {
|
||||
class FriendsFeedEventDispatcherMapper : AbstractClassMapper("FriendsFeedEventDispatcher") {
|
||||
val classReference = classReference("class")
|
||||
val viewModelField = string("viewModelField")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
@ -13,15 +16,13 @@ class FriendsFeedEventDispatcherMapper : AbstractClassMapper() {
|
||||
val onItemLongPress = clazz.methods.first { it.name == "onItemLongPress" }
|
||||
val viewHolderContainerClass = getClass(onItemLongPress.parameterTypes[0]) ?: continue
|
||||
|
||||
val viewModelField = viewHolderContainerClass.fields.firstOrNull { field ->
|
||||
val viewModelDexField = viewHolderContainerClass.fields.firstOrNull { field ->
|
||||
val typeClass = getClass(field.type) ?: return@firstOrNull false
|
||||
typeClass.methods.firstOrNull {it.name == "toString"}?.implementation?.findConstString("FriendFeedItemViewModel", contains = true) == true
|
||||
}?.name ?: continue
|
||||
|
||||
addMapping("FriendsFeedEventDispatcher",
|
||||
"class" to clazz.getClassName(),
|
||||
"viewModelField" to viewModelField
|
||||
)
|
||||
classReference.set(clazz.getClassName())
|
||||
viewModelField.set(viewModelDexField)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,10 @@ import me.rhunk.snapenhance.mapper.ext.isAbstract
|
||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||
import org.jf.dexlib2.AccessFlags
|
||||
|
||||
class MediaQualityLevelProviderMapper : AbstractClassMapper() {
|
||||
class MediaQualityLevelProviderMapper : AbstractClassMapper("MediaQualityLevelProvider") {
|
||||
val mediaQualityLevelProvider = classReference("mediaQualityLevelProvider")
|
||||
val mediaQualityLevelProviderMethod = string("mediaQualityLevelProviderMethod")
|
||||
|
||||
init {
|
||||
var enumQualityLevel : String? = null
|
||||
|
||||
@ -20,7 +23,6 @@ class MediaQualityLevelProviderMapper : AbstractClassMapper() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
addMapping("EnumQualityLevel", enumQualityLevel ?: return@mapper)
|
||||
}
|
||||
|
||||
mapper {
|
||||
@ -31,10 +33,8 @@ class MediaQualityLevelProviderMapper : AbstractClassMapper() {
|
||||
if (clazz.fields.none { it.accessFlags and AccessFlags.TRANSIENT.value != 0 }) continue
|
||||
|
||||
clazz.methods.firstOrNull { it.returnType == "L$enumQualityLevel;" }?.let {
|
||||
addMapping("MediaQualityLevelProvider",
|
||||
"class" to clazz.getClassName(),
|
||||
"method" to it.name
|
||||
)
|
||||
mediaQualityLevelProvider.set(clazz.getClassName())
|
||||
mediaQualityLevelProviderMethod.set(it.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,13 @@ import me.rhunk.snapenhance.mapper.ext.hasStaticConstructorString
|
||||
import me.rhunk.snapenhance.mapper.ext.isAbstract
|
||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||
|
||||
class OperaPageViewControllerMapper : AbstractClassMapper() {
|
||||
class OperaPageViewControllerMapper : AbstractClassMapper("OperaPageViewController") {
|
||||
val classReference = classReference("class")
|
||||
val viewStateField = string("viewStateField")
|
||||
val layerListField = string("layerListField")
|
||||
val onDisplayStateChange = string("onDisplayStateChange")
|
||||
val onDisplayStateChangeGesture = string("onDisplayStateChangeGesture")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
@ -16,37 +22,35 @@ class OperaPageViewControllerMapper : AbstractClassMapper() {
|
||||
continue
|
||||
}
|
||||
|
||||
val viewStateField = clazz.fields.first { field ->
|
||||
val viewStateDexField = clazz.fields.first { field ->
|
||||
val fieldClass = getClass(field.type) ?: return@first false
|
||||
fieldClass.isEnum() && fieldClass.hasStaticConstructorString("FULLY_DISPLAYED")
|
||||
}
|
||||
|
||||
val layerListField = clazz.fields.first { it.type == "Ljava/util/ArrayList;" }
|
||||
val layerListDexField = clazz.fields.first { it.type == "Ljava/util/ArrayList;" }
|
||||
|
||||
val onDisplayStateChange = clazz.methods.firstOrNull {
|
||||
val onDisplayStateChangeDexMethod = clazz.methods.firstOrNull {
|
||||
if (it.returnType != "V" || it.parameterTypes.size != 1) return@firstOrNull false
|
||||
val firstParameterType = getClass(it.parameterTypes[0]) ?: return@firstOrNull false
|
||||
if (firstParameterType.type == clazz.type || !firstParameterType.isAbstract()) return@firstOrNull false
|
||||
//check if the class contains a field with the enumViewStateClass type
|
||||
firstParameterType.fields.any { field ->
|
||||
field.type == viewStateField.type
|
||||
field.type == viewStateDexField.type
|
||||
}
|
||||
}
|
||||
|
||||
val onDisplayStateChangeGesture = clazz.methods.first {
|
||||
val onDisplayStateChangeGestureDexMethod = clazz.methods.first {
|
||||
if (it.returnType != "V" || it.parameterTypes.size != 2) return@first false
|
||||
val firstParameterType = getClass(it.parameterTypes[0]) ?: return@first false
|
||||
val secondParameterType = getClass(it.parameterTypes[1]) ?: return@first false
|
||||
firstParameterType.isEnum() && secondParameterType.isEnum()
|
||||
}
|
||||
|
||||
addMapping("OperaPageViewController",
|
||||
"class" to clazz.getClassName(),
|
||||
"viewStateField" to viewStateField.name,
|
||||
"layerListField" to layerListField.name,
|
||||
"onDisplayStateChange" to onDisplayStateChange?.name,
|
||||
"onDisplayStateChangeGesture" to onDisplayStateChangeGesture.name
|
||||
)
|
||||
classReference.set(clazz.getClassName())
|
||||
viewStateField.set(viewStateDexField.name)
|
||||
layerListField.set(layerListDexField.name)
|
||||
onDisplayStateChange.set(onDisplayStateChangeDexMethod?.name)
|
||||
onDisplayStateChangeGesture.set(onDisplayStateChangeGestureDexMethod.name)
|
||||
|
||||
return@mapper
|
||||
}
|
||||
|
@ -6,14 +6,17 @@ import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
|
||||
import org.jf.dexlib2.iface.reference.MethodReference
|
||||
|
||||
class OperaViewerParamsMapper : AbstractClassMapper() {
|
||||
class OperaViewerParamsMapper : AbstractClassMapper("OperaViewerParams") {
|
||||
val classReference = classReference("class")
|
||||
val putMethod = string("putMethod")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (classDef in classes) {
|
||||
classDef.fields.firstOrNull { it.type == "Ljava/util/concurrent/ConcurrentHashMap;" } ?: continue
|
||||
if (classDef.methods.firstOrNull { it.name == "toString" }?.implementation?.findConstString("Params") != true) continue
|
||||
|
||||
val putMethod = classDef.methods.firstOrNull { method ->
|
||||
val putDexMethod = classDef.methods.firstOrNull { method ->
|
||||
method.implementation?.instructions?.any {
|
||||
val instruction = it as? Instruction35c ?: return@any false
|
||||
val reference = instruction.reference as? MethodReference ?: return@any false
|
||||
@ -21,10 +24,9 @@ class OperaViewerParamsMapper : AbstractClassMapper() {
|
||||
} == true
|
||||
} ?: return@mapper
|
||||
|
||||
addMapping("OperaViewerParams",
|
||||
"class" to classDef.getClassName(),
|
||||
"putMethod" to putMethod.name
|
||||
)
|
||||
classReference.set(classDef.getClassName())
|
||||
putMethod.set(putDexMethod.name)
|
||||
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,14 @@ import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
|
||||
class PlusSubscriptionMapper : AbstractClassMapper(){
|
||||
class PlusSubscriptionMapper : AbstractClassMapper("PlusSubscription"){
|
||||
val classReference = classReference("class")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
if (clazz.directMethods.filter { it.name == "<init>" }.none {
|
||||
it.parameters.size == 4 &&
|
||||
it.parameterTypes.size > 3 &&
|
||||
it.parameterTypes[0] == "I" &&
|
||||
it.parameterTypes[1] == "I" &&
|
||||
it.parameterTypes[2] == "J" &&
|
||||
@ -21,7 +23,7 @@ class PlusSubscriptionMapper : AbstractClassMapper(){
|
||||
}
|
||||
|
||||
if (isPlusSubscriptionInfoClass == true) {
|
||||
addMapping("SubscriptionInfoClass", clazz.getClassName())
|
||||
classReference.set(clazz.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
import me.rhunk.snapenhance.mapper.ext.getStaticConstructor
|
||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||
|
||||
class ScCameraSettingsMapper : AbstractClassMapper() {
|
||||
class ScCameraSettingsMapper : AbstractClassMapper("ScCameraSettings") {
|
||||
val classReference = classReference("class")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
@ -15,7 +17,7 @@ class ScCameraSettingsMapper : AbstractClassMapper() {
|
||||
val firstParameter = getClass(firstConstructor.parameterTypes[0]) ?: continue
|
||||
if (!firstParameter.isEnum() || firstParameter.getStaticConstructor()?.implementation?.findConstString("CONTINUOUS_PICTURE") != true) continue
|
||||
|
||||
addMapping("ScCameraSettings", clazz.getClassName())
|
||||
classReference.set(clazz.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
|
||||
class ScoreUpdateMapper : AbstractClassMapper() {
|
||||
class ScoreUpdateMapper : AbstractClassMapper("ScoreUpdate") {
|
||||
val classReference = classReference("class")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (classDef in classes) {
|
||||
@ -18,7 +20,7 @@ class ScoreUpdateMapper : AbstractClassMapper() {
|
||||
it.name == "toString"
|
||||
}?.implementation?.findConstString("Friend.sq:selectFriendUserScoresNeedToUpdate") != true) continue
|
||||
|
||||
addMapping("ScoreUpdate", classDef.getClassName())
|
||||
classReference.set(classDef.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||
import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||
|
||||
class StoryBoostStateMapper : AbstractClassMapper() {
|
||||
class StoryBoostStateMapper : AbstractClassMapper("StoryBoostState") {
|
||||
val classReference = classReference("class")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
@ -14,7 +16,7 @@ class StoryBoostStateMapper : AbstractClassMapper() {
|
||||
|
||||
if (clazz.methods.firstOrNull { it.name == "toString" }?.implementation?.findConstString("StoryBoostState", contains = true) != true) continue
|
||||
|
||||
addMapping("StoryBoostStateClass", clazz.getClassName())
|
||||
classReference.set(clazz.getClassName())
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,17 @@ import me.rhunk.snapenhance.mapper.ext.isAbstract
|
||||
import me.rhunk.snapenhance.mapper.ext.isInterface
|
||||
import java.lang.reflect.Modifier
|
||||
|
||||
class ViewBinderMapper : AbstractClassMapper() {
|
||||
class ViewBinderMapper : AbstractClassMapper("ViewBinder") {
|
||||
val classReference = classReference("class")
|
||||
val bindMethod = string("bindMethod")
|
||||
val getViewMethod = string("getViewMethod")
|
||||
|
||||
init {
|
||||
mapper {
|
||||
for (clazz in classes) {
|
||||
if (!clazz.isAbstract() || clazz.isInterface()) continue
|
||||
|
||||
val getViewMethod = clazz.methods.firstOrNull { it.returnType == "Landroid/view/View;" && it.parameterTypes.size == 0 } ?: continue
|
||||
val getViewDexMethod = clazz.methods.firstOrNull { it.returnType == "Landroid/view/View;" && it.parameterTypes.size == 0 } ?: continue
|
||||
|
||||
// update view
|
||||
clazz.methods.filter {
|
||||
@ -21,17 +25,15 @@ class ViewBinderMapper : AbstractClassMapper() {
|
||||
if (it.size != 1) return@also
|
||||
}.firstOrNull() ?: continue
|
||||
|
||||
val bindMethod = clazz.methods.filter {
|
||||
val bindDexMethod = clazz.methods.filter {
|
||||
Modifier.isAbstract(it.accessFlags) && it.parameterTypes.size == 2 && it.parameterTypes[0] == it.parameterTypes[1] && it.returnType == "V"
|
||||
}.also {
|
||||
if (it.size != 1) return@also
|
||||
}.firstOrNull() ?: continue
|
||||
|
||||
addMapping("ViewBinder",
|
||||
"class" to clazz.getClassName(),
|
||||
"bindMethod" to bindMethod.name,
|
||||
"getViewMethod" to getViewMethod.name
|
||||
)
|
||||
classReference.set(clazz.getClassName())
|
||||
bindMethod.set(bindDexMethod.name)
|
||||
getViewMethod.set(getViewDexMethod.name)
|
||||
return@mapper
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package me.rhunk.snapenhance.mapper.tests
|
||||
|
||||
import com.google.gson.GsonBuilder
|
||||
import me.rhunk.snapenhance.mapper.Mapper
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.rhunk.snapenhance.mapper.ClassMapper
|
||||
import me.rhunk.snapenhance.mapper.impl.*
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
@ -10,28 +11,14 @@ import java.io.File
|
||||
class TestMappings {
|
||||
@Test
|
||||
fun testMappings() {
|
||||
val mapper = Mapper(
|
||||
BCryptClassMapper::class,
|
||||
CallbackMapper::class,
|
||||
DefaultMediaItemMapper::class,
|
||||
MediaQualityLevelProviderMapper::class,
|
||||
OperaPageViewControllerMapper::class,
|
||||
PlusSubscriptionMapper::class,
|
||||
ScCameraSettingsMapper::class,
|
||||
StoryBoostStateMapper::class,
|
||||
FriendsFeedEventDispatcherMapper::class,
|
||||
CompositeConfigurationProviderMapper::class,
|
||||
ScoreUpdateMapper::class,
|
||||
FriendRelationshipChangerMapper::class,
|
||||
ViewBinderMapper::class,
|
||||
FriendingDataSourcesMapper::class,
|
||||
OperaViewerParamsMapper::class,
|
||||
)
|
||||
val classMapper = ClassMapper()
|
||||
|
||||
val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
val apkFile = File(System.getenv("SNAPCHAT_APK")!!)
|
||||
mapper.loadApk(apkFile.absolutePath)
|
||||
val result = mapper.start()
|
||||
classMapper.loadApk(apkFile.absolutePath)
|
||||
runBlocking {
|
||||
val result = classMapper.run()
|
||||
println("Mappings: ${gson.toJson(result)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user