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
|
requirements = requirements or Requirements.MAPPINGS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ class HomeSection : Section() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResumed() {
|
override fun onResumed() {
|
||||||
if (!context.mappings.isMappingsLoaded()) {
|
if (!context.mappings.isMappingsLoaded) {
|
||||||
context.mappings.init(context.androidContext)
|
context.mappings.init(context.androidContext)
|
||||||
}
|
}
|
||||||
context.coroutineScope.launch {
|
context.coroutineScope.launch {
|
||||||
|
@ -1,44 +1,24 @@
|
|||||||
package me.rhunk.snapenhance.common.bridge.wrapper
|
package me.rhunk.snapenhance.common.bridge.wrapper
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import com.google.gson.JsonElement
|
|
||||||
import com.google.gson.JsonParser
|
import com.google.gson.JsonParser
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import me.rhunk.snapenhance.common.BuildConfig
|
import me.rhunk.snapenhance.common.BuildConfig
|
||||||
import me.rhunk.snapenhance.common.Constants
|
import me.rhunk.snapenhance.common.Constants
|
||||||
import me.rhunk.snapenhance.common.bridge.FileLoaderWrapper
|
import me.rhunk.snapenhance.common.bridge.FileLoaderWrapper
|
||||||
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
|
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
|
||||||
import me.rhunk.snapenhance.mapper.Mapper
|
import me.rhunk.snapenhance.common.logger.AbstractLogger
|
||||||
import me.rhunk.snapenhance.mapper.impl.*
|
import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import me.rhunk.snapenhance.mapper.ClassMapper
|
||||||
import kotlin.system.measureTimeMillis
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteArray(Charsets.UTF_8)) {
|
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 lateinit var context: Context
|
||||||
|
|
||||||
private val mappings = ConcurrentHashMap<String, Any>()
|
|
||||||
private var mappingUniqueHash: Long = 0
|
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()
|
private fun getUniqueBuildId() = (getSnapchatPackageInfo()?.longVersionCode ?: -1) xor BuildConfig.BUILD_HASH.hashCode().toLong()
|
||||||
|
|
||||||
@ -63,8 +43,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
|||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|
||||||
fun getGeneratedBuildNumber() = mappingUniqueHash
|
fun getGeneratedBuildNumber() = mappingUniqueHash
|
||||||
fun isMappingsOutdated() = mappingUniqueHash != getUniqueBuildId() || isMappingsLoaded().not()
|
fun isMappingsOutdated() = mappingUniqueHash != getUniqueBuildId() || isMappingsLoaded.not()
|
||||||
fun isMappingsLoaded() = mappings.isNotEmpty()
|
|
||||||
|
|
||||||
private fun loadCached() {
|
private fun loadCached() {
|
||||||
if (!isFileExists()) {
|
if (!isFileExists()) {
|
||||||
@ -74,64 +53,39 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
|||||||
mappingUniqueHash = it["unique_hash"].asLong
|
mappingUniqueHash = it["unique_hash"].asLong
|
||||||
}
|
}
|
||||||
|
|
||||||
mappingsObject.entrySet().forEach { (key, value): Map.Entry<String, JsonElement> ->
|
mappingsObject.entrySet().forEach { (key, value) ->
|
||||||
if (value.isJsonArray) {
|
mappers.values.firstOrNull { it.mapperName == key }?.let { mapper ->
|
||||||
mappings[key] = gson.fromJson(value, ArrayList::class.java)
|
mapper.readFromJson(value.asJsonObject)
|
||||||
return@forEach
|
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() {
|
fun refresh() {
|
||||||
mappingUniqueHash = getUniqueBuildId()
|
mappingUniqueHash = getUniqueBuildId()
|
||||||
val mapper = Mapper(*mappers)
|
val classMapper = ClassMapper(*mappers.values.toTypedArray())
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
mapper.loadApk(getSnapchatPackageInfo()?.applicationInfo?.sourceDir ?: throw Exception("Failed to get APK"))
|
classMapper.loadApk(getSnapchatPackageInfo()?.applicationInfo?.sourceDir ?: throw Exception("Failed to get APK"))
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
throw Exception("Failed to load APK", it)
|
throw Exception("Failed to load APK", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
measureTimeMillis {
|
runBlocking {
|
||||||
val result = mapper.start().apply {
|
val result = classMapper.run().apply {
|
||||||
addProperty("unique_hash", mappingUniqueHash)
|
addProperty("unique_hash", mappingUniqueHash)
|
||||||
}
|
}
|
||||||
write(result.toString().toByteArray())
|
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")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun getMappedMap(key: String): Map<String, *>? {
|
fun <T : AbstractClassMapper> useMapper(type: KClass<T>, callback: T.() -> Unit) {
|
||||||
return getMappedObjectNullable(key) as? Map<String, *>
|
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") {
|
hookMainActivity("onCreate") {
|
||||||
val isMainActivityNotNull = appContext.mainActivity != null
|
val isMainActivityNotNull = appContext.mainActivity != null
|
||||||
appContext.mainActivity = this
|
appContext.mainActivity = this
|
||||||
if (isMainActivityNotNull || !appContext.mappings.isMappingsLoaded()) return@hookMainActivity
|
if (isMainActivityNotNull || !appContext.mappings.isMappingsLoaded) return@hookMainActivity
|
||||||
onActivityCreate()
|
onActivityCreate()
|
||||||
jetpackComposeResourceHook()
|
jetpackComposeResourceHook()
|
||||||
appContext.actionManager.onNewIntent(intent)
|
appContext.actionManager.onNewIntent(intent)
|
||||||
@ -146,7 +146,7 @@ class SnapEnhance {
|
|||||||
database.init()
|
database.init()
|
||||||
eventDispatcher.init()
|
eventDispatcher.init()
|
||||||
//if mappings aren't loaded, we can't initialize features
|
//if mappings aren't loaded, we can't initialize features
|
||||||
if (!mappings.isMappingsLoaded()) return
|
if (!mappings.isMappingsLoaded) return
|
||||||
bridgeClient.registerMessagingBridge(messagingBridge)
|
bridgeClient.registerMessagingBridge(messagingBridge)
|
||||||
features.init()
|
features.init()
|
||||||
scriptRuntime.connect(bridgeClient.getScriptingInterface())
|
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.features.impl.messaging.Messaging
|
||||||
import me.rhunk.snapenhance.core.messaging.EnumBulkAction
|
import me.rhunk.snapenhance.core.messaging.EnumBulkAction
|
||||||
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
|
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.FriendRelationshipChangerMapper
|
||||||
|
|
||||||
class BulkMessagingAction : AbstractAction() {
|
class BulkMessagingAction : AbstractAction() {
|
||||||
private val translation by lazy { context.translation.getCategory("bulk_messaging_action") }
|
private val translation by lazy { context.translation.getCategory("bulk_messaging_action") }
|
||||||
@ -145,22 +146,23 @@ class BulkMessagingAction : AbstractAction() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun removeFriend(userId: String) {
|
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 friendRelationshipChangerInstance = context.feature(AddFriendSourceSpoof::class).friendRelationshipChangerInstance!!
|
||||||
|
val removeFriendMethod = friendRelationshipChangerInstance::class.java.methods.first {
|
||||||
|
it.name == this.removeFriendMethod.get()
|
||||||
|
}
|
||||||
|
|
||||||
val removeFriendMethod = friendRelationshipChangerInstance::class.java.methods.first {
|
val completable = removeFriendMethod.invoke(friendRelationshipChangerInstance,
|
||||||
it.name == friendRelationshipChangerMapping["removeFriendMethod"].toString()
|
userId, // userId
|
||||||
|
removeFriendMethod.parameterTypes[1].enumConstants.first { it.toString() == "DELETED_BY_MY_FRIENDS" }, // source
|
||||||
|
null, // unknown
|
||||||
|
null, // unknown
|
||||||
|
null // InteractionPlacementInfo
|
||||||
|
)!!
|
||||||
|
completable::class.java.methods.first {
|
||||||
|
it.name == "subscribe" && it.parameterTypes.isEmpty()
|
||||||
|
}.invoke(completable)
|
||||||
}
|
}
|
||||||
|
|
||||||
val completable = removeFriendMethod.invoke(friendRelationshipChangerInstance,
|
|
||||||
userId, // userId
|
|
||||||
removeFriendMethod.parameterTypes[1].enumConstants.first { it.toString() == "DELETED_BY_MY_FRIENDS" }, // source
|
|
||||||
null, // unknown
|
|
||||||
null, // unknown
|
|
||||||
null // InteractionPlacementInfo
|
|
||||||
)!!
|
|
||||||
completable::class.java.methods.first {
|
|
||||||
it.name == "subscribe" && it.parameterTypes.isEmpty()
|
|
||||||
}.invoke(completable)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,44 +18,44 @@ import me.rhunk.snapenhance.core.wrapper.impl.Message
|
|||||||
import me.rhunk.snapenhance.core.wrapper.impl.MessageContent
|
import me.rhunk.snapenhance.core.wrapper.impl.MessageContent
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.MessageDestinations
|
import me.rhunk.snapenhance.core.wrapper.impl.MessageDestinations
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.ViewBinderMapper
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
class EventDispatcher(
|
class EventDispatcher(
|
||||||
private val context: ModContext
|
private val context: ModContext
|
||||||
) : Manager {
|
) : Manager {
|
||||||
private fun findClass(name: String) = context.androidContext.classLoader.loadClass(name)
|
|
||||||
|
|
||||||
private fun hookViewBinder() {
|
private fun hookViewBinder() {
|
||||||
val cachedHooks = mutableListOf<String>()
|
context.mappings.useMapper(ViewBinderMapper::class) {
|
||||||
val viewBinderMappings = runCatching { context.mappings.getMappedMap("ViewBinder") }.getOrNull() ?: return
|
val cachedHooks = mutableListOf<String>()
|
||||||
|
fun cacheHook(clazz: Class<*>, block: Class<*>.() -> Unit) {
|
||||||
fun cacheHook(clazz: Class<*>, block: Class<*>.() -> Unit) {
|
if (!cachedHooks.contains(clazz.name)) {
|
||||||
if (!cachedHooks.contains(clazz.name)) {
|
clazz.block()
|
||||||
clazz.block()
|
cachedHooks.add(clazz.name)
|
||||||
cachedHooks.add(clazz.name)
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
findClass(viewBinderMappings["class"].toString()).hookConstructor(HookStage.AFTER) { methodParam ->
|
classReference.get()?.hookConstructor(HookStage.AFTER) { methodParam ->
|
||||||
cacheHook(
|
cacheHook(
|
||||||
methodParam.thisObject<Any>()::class.java
|
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 instance = param.thisObject<Any>()
|
||||||
val view = instance::class.java.methods.first {
|
val view = instance::class.java.methods.firstOrNull {
|
||||||
it.name == viewBinderMappings["getViewMethod"].toString()
|
it.name == getViewMethod.get().toString()
|
||||||
}.invoke(instance) as? View ?: return@bindViewMethod
|
}?.invoke(instance) as? View ?: return@bindViewMethod
|
||||||
|
|
||||||
context.event.post(
|
context.event.post(
|
||||||
BindViewEvent(
|
BindViewEvent(
|
||||||
prevModel = param.arg(0),
|
prevModel = param.arg(0),
|
||||||
nextModel = param.argNullable(1),
|
nextModel = param.argNullable(1),
|
||||||
view = view
|
view = view
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.hook.hook
|
||||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.CompositeConfigurationProviderMapper
|
||||||
|
|
||||||
data class ConfigKeyInfo(
|
data class ConfigKeyInfo(
|
||||||
val category: String?,
|
val category: String?,
|
||||||
@ -23,128 +24,129 @@ data class ConfigFilter(
|
|||||||
|
|
||||||
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 compositeConfigurationProviderMappings = context.mappings.getMappedMap("CompositeConfigurationProvider") ?: throw Exception("Failed to get compositeConfigurationProviderMappings")
|
context.mappings.useMapper(CompositeConfigurationProviderMapper::class) {
|
||||||
val enumMappings = compositeConfigurationProviderMappings["enum"] as Map<*, *>
|
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 == 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)
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
fun getConfigKeyInfo(key: Any?) = runCatching {
|
val propertyOverrides = mutableMapOf<String, ConfigFilter>()
|
||||||
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
|
|
||||||
ConfigKeyInfo(category, keyName, defaultValue)
|
|
||||||
}.onFailure {
|
|
||||||
context.log.error("Failed to get config key info", it)
|
|
||||||
}.getOrNull()
|
|
||||||
|
|
||||||
val propertyOverrides = mutableMapOf<String, ConfigFilter>()
|
fun overrideProperty(key: String, filter: (ConfigKeyInfo) -> Boolean, value: (ConfigKeyInfo) -> Any?, isAppExperiment: Boolean = false) {
|
||||||
|
propertyOverrides[key] = ConfigFilter(filter, value, isAppExperiment)
|
||||||
fun overrideProperty(key: String, filter: (ConfigKeyInfo) -> Boolean, value: (ConfigKeyInfo) -> Any?, isAppExperiment: Boolean = false) {
|
|
||||||
propertyOverrides[key] = ConfigFilter(filter, value, isAppExperiment)
|
|
||||||
}
|
|
||||||
|
|
||||||
overrideProperty("STREAK_EXPIRATION_INFO", { context.config.userInterface.streakExpirationInfo.get() },
|
|
||||||
{ true })
|
|
||||||
overrideProperty("TRANSCODING_MAX_QUALITY", { context.config.global.forceUploadSourceQuality.get() },
|
|
||||||
{ true }, isAppExperiment = true)
|
|
||||||
|
|
||||||
overrideProperty("CAMERA_ME_ENABLE_HEVC_RECORDING", { context.config.camera.hevcRecording.get() },
|
|
||||||
{ true })
|
|
||||||
overrideProperty("MEDIA_RECORDER_MAX_QUALITY_LEVEL", { context.config.camera.forceCameraSourceEncoding.get() },
|
|
||||||
{ true })
|
|
||||||
overrideProperty("REDUCE_MY_PROFILE_UI_COMPLEXITY", { context.config.userInterface.mapFriendNameTags.get() },
|
|
||||||
{ true })
|
|
||||||
overrideProperty("ENABLE_LONG_SNAP_SENDING", { context.config.global.disableSnapSplitting.get() },
|
|
||||||
{ true })
|
|
||||||
|
|
||||||
overrideProperty("DF_VOPERA_FOR_STORIES", { context.config.userInterface.verticalStoryViewer.get() },
|
|
||||||
{ true }, isAppExperiment = true)
|
|
||||||
overrideProperty("SPOTLIGHT_5TH_TAB_ENABLED", { context.config.userInterface.disableSpotlight.get() },
|
|
||||||
{ false })
|
|
||||||
|
|
||||||
overrideProperty("BYPASS_AD_FEATURE_GATE", { context.config.global.blockAds.get() },
|
|
||||||
{ true })
|
|
||||||
arrayOf("CUSTOM_AD_TRACKER_URL", "CUSTOM_AD_INIT_SERVER_URL", "CUSTOM_AD_SERVER_URL", "INIT_PRIMARY_URL", "INIT_SHADOW_URL").forEach {
|
|
||||||
overrideProperty(it, { context.config.global.blockAds.get() }, { "http://127.0.0.1" })
|
|
||||||
}
|
|
||||||
|
|
||||||
findClass(compositeConfigurationProviderMappings["class"].toString()).hook(
|
|
||||||
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(propertyKey))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
findClass(compositeConfigurationProviderMappings["class"].toString()).hook(
|
|
||||||
compositeConfigurationProviderMappings["observeProperty"].toString(),
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
propertyOverrides[key]?.let { (filter, value) ->
|
overrideProperty("STREAK_EXPIRATION_INFO", { context.config.userInterface.streakExpirationInfo.get() },
|
||||||
val keyInfo = getConfigKeyInfo(enumData) ?: return@let
|
{ true })
|
||||||
if (!filter(keyInfo)) return@let
|
overrideProperty("TRANSCODING_MAX_QUALITY", { context.config.global.forceUploadSourceQuality.get() },
|
||||||
setValue(value(keyInfo))
|
{ true }, isAppExperiment = true)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runCatching {
|
overrideProperty("CAMERA_ME_ENABLE_HEVC_RECORDING", { context.config.camera.hevcRecording.get() },
|
||||||
val appExperimentProviderMappings = compositeConfigurationProviderMappings["appExperimentProvider"] as Map<*, *>
|
{ true })
|
||||||
val customBooleanPropertyRules = mutableListOf<(ConfigKeyInfo) -> Boolean>()
|
overrideProperty("MEDIA_RECORDER_MAX_QUALITY_LEVEL", { context.config.camera.forceCameraSourceEncoding.get() },
|
||||||
|
{ true })
|
||||||
|
overrideProperty("REDUCE_MY_PROFILE_UI_COMPLEXITY", { context.config.userInterface.mapFriendNameTags.get() },
|
||||||
|
{ true })
|
||||||
|
overrideProperty("ENABLE_LONG_SNAP_SENDING", { context.config.global.disableSnapSplitting.get() },
|
||||||
|
{ true })
|
||||||
|
|
||||||
findClass(appExperimentProviderMappings["GetBooleanAppExperimentClass"].toString()).hook("invoke", HookStage.BEFORE) { param ->
|
overrideProperty("DF_VOPERA_FOR_STORIES", { context.config.userInterface.verticalStoryViewer.get() },
|
||||||
val keyInfo = getConfigKeyInfo(param.arg(1)) ?: return@hook
|
{ true }, isAppExperiment = true)
|
||||||
if (customBooleanPropertyRules.any { it(keyInfo) }) {
|
overrideProperty("SPOTLIGHT_5TH_TAB_ENABLED", { context.config.userInterface.disableSpotlight.get() },
|
||||||
param.setResult(true)
|
{ false })
|
||||||
return@hook
|
|
||||||
}
|
overrideProperty("BYPASS_AD_FEATURE_GATE", { context.config.global.blockAds.get() },
|
||||||
propertyOverrides[keyInfo.name]?.let { (filter, value, isAppExperiment) ->
|
{ true })
|
||||||
if (!isAppExperiment || !filter(keyInfo)) return@let
|
arrayOf("CUSTOM_AD_TRACKER_URL", "CUSTOM_AD_INIT_SERVER_URL", "CUSTOM_AD_SERVER_URL", "INIT_PRIMARY_URL", "INIT_SHADOW_URL").forEach {
|
||||||
param.setResult(value(keyInfo))
|
overrideProperty(it, { context.config.global.blockAds.get() }, { "http://127.0.0.1" })
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Hooker.ephemeralHookConstructor(
|
classReference.getAsClass()?.hook(
|
||||||
findClass(compositeConfigurationProviderMappings["class"].toString()),
|
getProperty.getAsString()!!,
|
||||||
HookStage.AFTER
|
HookStage.AFTER
|
||||||
) { constructorParam ->
|
) { param ->
|
||||||
val instance = constructorParam.thisObject<Any>()
|
val propertyKey = getConfigKeyInfo(param.argNullable<Any>(0)) ?: return@hook
|
||||||
val appExperimentProviderInstance = instance::class.java.fields.firstOrNull {
|
|
||||||
findClass(appExperimentProviderMappings["class"].toString()).isAssignableFrom(it.type)
|
|
||||||
}?.get(instance) ?: return@ephemeralHookConstructor
|
|
||||||
|
|
||||||
appExperimentProviderInstance::class.java.methods.first {
|
propertyOverrides[propertyKey.name]?.let { (filter, value) ->
|
||||||
it.name == appExperimentProviderMappings["hasExperimentMethod"].toString()
|
if (!filter(propertyKey)) return@let
|
||||||
}.hook(HookStage.BEFORE) { param ->
|
param.setResult(value(propertyKey))
|
||||||
val keyInfo = getConfigKeyInfo(param.arg(0)) ?: return@hook
|
}
|
||||||
if (customBooleanPropertyRules.any { it(keyInfo) }) {
|
}
|
||||||
param.setResult(true)
|
|
||||||
return@hook
|
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, configEnumMapping["getValue"]?.getAsString())
|
||||||
|
valueHolder.setObjectField(configEnumMapping["defaultValueField"]?.getAsString()!!, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
propertyOverrides[key]?.let { (filter, value) ->
|
||||||
|
val keyInfo = getConfigKeyInfo(enumData) ?: return@let
|
||||||
|
if (!filter(keyInfo)) return@let
|
||||||
|
setValue(value(keyInfo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
val customBooleanPropertyRules = mutableListOf<(ConfigKeyInfo) -> Boolean>()
|
||||||
|
|
||||||
|
appExperimentProvider["getBooleanAppExperimentClass"]?.getAsClass()
|
||||||
|
?.hook("invoke", HookStage.BEFORE) { param ->
|
||||||
|
val keyInfo = getConfigKeyInfo(param.arg(1)) ?: return@hook
|
||||||
|
if (customBooleanPropertyRules.any { it(keyInfo) }) {
|
||||||
|
param.setResult(true)
|
||||||
|
return@hook
|
||||||
|
}
|
||||||
|
propertyOverrides[keyInfo.name]?.let { (filter, value, isAppExperiment) ->
|
||||||
|
if (!isAppExperiment || !filter(keyInfo)) return@let
|
||||||
|
param.setResult(value(keyInfo))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val propertyOverride = propertyOverrides[keyInfo.name] ?: return@hook
|
|
||||||
if (propertyOverride.isAppExperiment && propertyOverride.filter(keyInfo)) param.setResult(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.config.experimental.hiddenSnapchatPlusFeatures.get()) {
|
|
||||||
customBooleanPropertyRules.add { key ->
|
Hooker.ephemeralHookConstructor(
|
||||||
key.category == "PLUS" && key.defaultValue is Boolean && key.name?.endsWith("_GATE") == true
|
classReference.get()!!,
|
||||||
|
HookStage.AFTER
|
||||||
|
) { constructorParam ->
|
||||||
|
val instance = constructorParam.thisObject<Any>()
|
||||||
|
val appExperimentProviderInstance = instance::class.java.fields.firstOrNull {
|
||||||
|
appExperimentProvider["class"]?.getAsClass()?.isAssignableFrom(it.type) == true
|
||||||
|
}?.get(instance) ?: return@ephemeralHookConstructor
|
||||||
|
|
||||||
|
appExperimentProviderInstance::class.java.methods.first {
|
||||||
|
it.name == appExperimentProvider["hasExperimentMethod"]?.getAsString().toString()
|
||||||
|
}.hook(HookStage.BEFORE) { param ->
|
||||||
|
val keyInfo = getConfigKeyInfo(param.arg(0)) ?: return@hook
|
||||||
|
if (customBooleanPropertyRules.any { it(keyInfo) }) {
|
||||||
|
param.setResult(true)
|
||||||
|
return@hook
|
||||||
|
}
|
||||||
|
|
||||||
|
val propertyOverride = propertyOverrides[keyInfo.name] ?: return@hook
|
||||||
|
if (propertyOverride.isAppExperiment && propertyOverride.filter(keyInfo)) param.setResult(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context.config.experimental.hiddenSnapchatPlusFeatures.get()) {
|
||||||
|
customBooleanPropertyRules.add { key ->
|
||||||
|
key.category == "PLUS" && key.defaultValue is Boolean && key.name?.endsWith("_GATE") == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
context.log.error("Failed to hook appExperimentProvider", it)
|
||||||
}
|
}
|
||||||
}.onFailure {
|
|
||||||
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.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.hook
|
import me.rhunk.snapenhance.core.util.hook.hook
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.OperaViewerParamsMapper
|
||||||
|
|
||||||
class OperaViewerParamsOverride : Feature("OperaViewerParamsOverride", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
class OperaViewerParamsOverride : Feature("OperaViewerParamsOverride", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||||
data class OverrideKey(
|
data class OverrideKey(
|
||||||
@ -17,7 +18,6 @@ class OperaViewerParamsOverride : Feature("OperaViewerParamsOverride", loadParam
|
|||||||
)
|
)
|
||||||
|
|
||||||
override fun onActivityCreate() {
|
override fun onActivityCreate() {
|
||||||
val operaViewerParamsMappings = context.mappings.getMappedMap("OperaViewerParams") ?: throw Exception("Failed to get operaViewerParamsMappings")
|
|
||||||
val overrideMap = mutableMapOf<String, Override>()
|
val overrideMap = mutableMapOf<String, Override>()
|
||||||
|
|
||||||
fun overrideParam(key: String, filter: (value: Any?) -> Boolean, value: (overrideKey: OverrideKey, value: Any?) -> Any?) {
|
fun overrideParam(key: String, filter: (value: Any?) -> Boolean, value: (overrideKey: OverrideKey, value: Any?) -> Any?) {
|
||||||
@ -36,26 +36,28 @@ class OperaViewerParamsOverride : Feature("OperaViewerParamsOverride", loadParam
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
findClass(operaViewerParamsMappings["class"].toString()).hook(operaViewerParamsMappings["putMethod"].toString(), HookStage.BEFORE) { param ->
|
context.mappings.useMapper(OperaViewerParamsMapper::class) {
|
||||||
val key = param.argNullable<Any>(0)?.let { key ->
|
classReference.get()?.hook(putMethod.get()!!, HookStage.BEFORE) { param ->
|
||||||
val fields = key::class.java.fields
|
val key = param.argNullable<Any>(0)?.let { key ->
|
||||||
OverrideKey(
|
val fields = key::class.java.fields
|
||||||
name = fields.firstOrNull {
|
OverrideKey(
|
||||||
it.type == String::class.java
|
name = fields.firstOrNull {
|
||||||
}?.get(key)?.toString() ?: return@hook,
|
it.type == String::class.java
|
||||||
defaultValue = fields.firstOrNull {
|
}?.get(key)?.toString() ?: return@hook,
|
||||||
it.type == Object::class.java
|
defaultValue = fields.firstOrNull {
|
||||||
}?.get(key)
|
it.type == Object::class.java
|
||||||
)
|
}?.get(key)
|
||||||
} ?: return@hook
|
)
|
||||||
val value = param.argNullable<Any>(1) ?: return@hook
|
} ?: return@hook
|
||||||
|
val value = param.argNullable<Any>(1) ?: return@hook
|
||||||
|
|
||||||
overrideMap[key.name]?.let { override ->
|
overrideMap[key.name]?.let { override ->
|
||||||
if (override.filter(value)) {
|
if (override.filter(value)) {
|
||||||
runCatching {
|
runCatching {
|
||||||
param.setArg(1, override.value(key, value))
|
param.setArg(1, override.value(key, value))
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
context.log.error("Failed to override param $key", it)
|
context.log.error("Failed to override param $key", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.Layer
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.media.opera.ParamMap
|
import me.rhunk.snapenhance.core.wrapper.impl.media.opera.ParamMap
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.media.toKeyPair
|
import me.rhunk.snapenhance.core.wrapper.impl.media.toKeyPair
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.OperaPageViewControllerMapper
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@ -462,51 +463,50 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun asyncOnActivityCreate() {
|
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(viewStateField.get()!!).toString()
|
||||||
|
if (viewState != "FULLY_DISPLAYED") {
|
||||||
|
return@onOperaViewStateCallback
|
||||||
|
}
|
||||||
|
val operaLayerList = (param.thisObject() as Any).getObjectField(layerListField.get()!!) as ArrayList<*>
|
||||||
|
val mediaParamMap: ParamMap = operaLayerList.map { Layer(it) }.first().paramMap
|
||||||
|
|
||||||
val onOperaViewStateCallback: (HookAdapter) -> Unit = onOperaViewStateCallback@{ param ->
|
if (!mediaParamMap.containsKey("image_media_info") && !mediaParamMap.containsKey("video_media_info_list"))
|
||||||
|
return@onOperaViewStateCallback
|
||||||
|
|
||||||
val viewState = (param.thisObject() as Any).getObjectField(context.mappings.getMappedValue("OperaPageViewController", "viewStateField")!!).toString()
|
val mediaInfoMap = mutableMapOf<SplitMediaAssetType, MediaInfo>()
|
||||||
if (viewState != "FULLY_DISPLAYED") {
|
val isVideo = mediaParamMap.containsKey("video_media_info_list")
|
||||||
return@onOperaViewStateCallback
|
|
||||||
}
|
|
||||||
val operaLayerList = (param.thisObject() as Any).getObjectField(context.mappings.getMappedValue("OperaPageViewController", "layerListField")!!) as ArrayList<*>
|
|
||||||
val mediaParamMap: ParamMap = operaLayerList.map { Layer(it) }.first().paramMap
|
|
||||||
|
|
||||||
if (!mediaParamMap.containsKey("image_media_info") && !mediaParamMap.containsKey("video_media_info_list"))
|
mediaInfoMap[SplitMediaAssetType.ORIGINAL] = MediaInfo(
|
||||||
return@onOperaViewStateCallback
|
(if (isVideo) mediaParamMap["video_media_info_list"] else mediaParamMap["image_media_info"])!!
|
||||||
|
)
|
||||||
|
|
||||||
val mediaInfoMap = mutableMapOf<SplitMediaAssetType, MediaInfo>()
|
if (context.config.downloader.mergeOverlays.get() && mediaParamMap.containsKey("overlay_image_media_info")) {
|
||||||
val isVideo = mediaParamMap.containsKey("video_media_info_list")
|
mediaInfoMap[SplitMediaAssetType.OVERLAY] =
|
||||||
|
MediaInfo(mediaParamMap["overlay_image_media_info"]!!)
|
||||||
|
}
|
||||||
|
lastSeenMapParams = mediaParamMap
|
||||||
|
lastSeenMediaInfoMap = mediaInfoMap
|
||||||
|
|
||||||
mediaInfoMap[SplitMediaAssetType.ORIGINAL] = MediaInfo(
|
if (!canAutoDownload()) return@onOperaViewStateCallback
|
||||||
(if (isVideo) mediaParamMap["video_media_info_list"] else mediaParamMap["image_media_info"])!!
|
|
||||||
)
|
|
||||||
|
|
||||||
if (context.config.downloader.mergeOverlays.get() && mediaParamMap.containsKey("overlay_image_media_info")) {
|
context.executeAsync {
|
||||||
mediaInfoMap[SplitMediaAssetType.OVERLAY] =
|
runCatching {
|
||||||
MediaInfo(mediaParamMap["overlay_image_media_info"]!!)
|
handleOperaMedia(mediaParamMap, mediaInfoMap, false)
|
||||||
}
|
}.onFailure {
|
||||||
lastSeenMapParams = mediaParamMap
|
context.log.error("Failed to handle opera media", it)
|
||||||
lastSeenMediaInfoMap = mediaInfoMap
|
context.longToast(it.message)
|
||||||
|
}
|
||||||
if (!canAutoDownload()) return@onOperaViewStateCallback
|
|
||||||
|
|
||||||
context.executeAsync {
|
|
||||||
runCatching {
|
|
||||||
handleOperaMedia(mediaParamMap, mediaInfoMap, false)
|
|
||||||
}.onFailure {
|
|
||||||
context.log.error("Failed to handle opera media", it)
|
|
||||||
context.longToast(it.message)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
arrayOf("onDisplayStateChange", "onDisplayStateChangeGesture").forEach { methodName ->
|
arrayOf(onDisplayStateChange, onDisplayStateChangeGesture).forEach { methodName ->
|
||||||
operaViewerControllerClass.hook(
|
classReference.get()?.hook(
|
||||||
context.mappings.getMappedValue("OperaPageViewController", methodName) ?: return@forEach,
|
methodName.get() ?: return@forEach,
|
||||||
HookStage.AFTER, onOperaViewStateCallback
|
HookStage.AFTER, onOperaViewStateCallback
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,61 +5,61 @@ 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.hook
|
import me.rhunk.snapenhance.core.util.hook.hook
|
||||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.FriendRelationshipChangerMapper
|
||||||
|
|
||||||
class AddFriendSourceSpoof : Feature("AddFriendSourceSpoof", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
class AddFriendSourceSpoof : Feature("AddFriendSourceSpoof", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||||
var friendRelationshipChangerInstance: Any? = null
|
var friendRelationshipChangerInstance: Any? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
override fun onActivityCreate() {
|
override fun onActivityCreate() {
|
||||||
val friendRelationshipChangerMapping = context.mappings.getMappedMap("FriendRelationshipChanger") ?: throw Exception("Failed to get friendRelationshipChangerMapping")
|
context.mappings.useMapper(FriendRelationshipChangerMapper::class) {
|
||||||
|
classReference.get()?.hookConstructor(HookStage.AFTER) { param ->
|
||||||
findClass(friendRelationshipChangerMapping["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
friendRelationshipChangerInstance = param.thisObject()
|
||||||
friendRelationshipChangerInstance = param.thisObject()
|
|
||||||
}
|
|
||||||
|
|
||||||
findClass(friendRelationshipChangerMapping["class"].toString())
|
|
||||||
.hook(friendRelationshipChangerMapping["addFriendMethod"].toString(), HookStage.BEFORE) { param ->
|
|
||||||
val spoofedSource = context.config.experimental.addFriendSourceSpoof.getNullable() ?: return@hook
|
|
||||||
|
|
||||||
fun setEnum(index: Int, value: String) {
|
|
||||||
val enumData = param.arg<Any>(index)
|
|
||||||
enumData::class.java.enumConstants.first { it.toString() == value }.let {
|
|
||||||
param.setArg(index, it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
when (spoofedSource) {
|
classReference.get()?.hook(addFriendMethod.get()!!, HookStage.BEFORE) { param ->
|
||||||
"added_by_quick_add" -> {
|
val spoofedSource = context.config.experimental.addFriendSourceSpoof.getNullable() ?: return@hook
|
||||||
setEnum(1, "PROFILE")
|
|
||||||
setEnum(2, "ADD_FRIENDS_BUTTON_ON_TOP_BAR_ON_FRIENDS_FEED")
|
fun setEnum(index: Int, value: String) {
|
||||||
setEnum(3, "ADDED_BY_SUGGESTED")
|
val enumData = param.arg<Any>(index)
|
||||||
|
enumData::class.java.enumConstants.first { it.toString() == value }.let {
|
||||||
|
param.setArg(index, it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"added_by_group_chat" -> {
|
|
||||||
setEnum(1, "PROFILE")
|
when (spoofedSource) {
|
||||||
setEnum(2, "GROUP_PROFILE")
|
"added_by_quick_add" -> {
|
||||||
setEnum(3, "ADDED_BY_GROUP_CHAT")
|
setEnum(1, "PROFILE")
|
||||||
|
setEnum(2, "ADD_FRIENDS_BUTTON_ON_TOP_BAR_ON_FRIENDS_FEED")
|
||||||
|
setEnum(3, "ADDED_BY_SUGGESTED")
|
||||||
|
}
|
||||||
|
"added_by_group_chat" -> {
|
||||||
|
setEnum(1, "PROFILE")
|
||||||
|
setEnum(2, "GROUP_PROFILE")
|
||||||
|
setEnum(3, "ADDED_BY_GROUP_CHAT")
|
||||||
|
}
|
||||||
|
"added_by_username" -> {
|
||||||
|
setEnum(1, "SEARCH")
|
||||||
|
setEnum(2, "SEARCH")
|
||||||
|
setEnum(3, "ADDED_BY_USERNAME")
|
||||||
|
}
|
||||||
|
"added_by_qr_code" -> {
|
||||||
|
setEnum(1, "PROFILE")
|
||||||
|
setEnum(2, "PROFILE")
|
||||||
|
setEnum(3, "ADDED_BY_QR_CODE")
|
||||||
|
}
|
||||||
|
"added_by_mention" -> {
|
||||||
|
setEnum(1, "CONTEXT_CARDS")
|
||||||
|
setEnum(2, "CONTEXT_CARD")
|
||||||
|
setEnum(3, "ADDED_BY_MENTION")
|
||||||
|
}
|
||||||
|
"added_by_community" -> {
|
||||||
|
setEnum(1, "PROFILE")
|
||||||
|
setEnum(2, "PROFILE")
|
||||||
|
setEnum(3, "ADDED_BY_COMMUNITY")
|
||||||
|
}
|
||||||
|
else -> return@hook
|
||||||
}
|
}
|
||||||
"added_by_username" -> {
|
|
||||||
setEnum(1, "SEARCH")
|
|
||||||
setEnum(2, "SEARCH")
|
|
||||||
setEnum(3, "ADDED_BY_USERNAME")
|
|
||||||
}
|
|
||||||
"added_by_qr_code" -> {
|
|
||||||
setEnum(1, "PROFILE")
|
|
||||||
setEnum(2, "PROFILE")
|
|
||||||
setEnum(3, "ADDED_BY_QR_CODE")
|
|
||||||
}
|
|
||||||
"added_by_mention" -> {
|
|
||||||
setEnum(1, "CONTEXT_CARDS")
|
|
||||||
setEnum(2, "CONTEXT_CARD")
|
|
||||||
setEnum(3, "ADDED_BY_MENTION")
|
|
||||||
}
|
|
||||||
"added_by_community" -> {
|
|
||||||
setEnum(1, "PROFILE")
|
|
||||||
setEnum(2, "PROFILE")
|
|
||||||
setEnum(3, "ADDED_BY_COMMUNITY")
|
|
||||||
}
|
|
||||||
else -> return@hook
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,20 @@ 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.hookConstructor
|
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.StoryBoostStateMapper
|
||||||
|
|
||||||
class InfiniteStoryBoost : Feature("InfiniteStoryBoost", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
class InfiniteStoryBoost : Feature("InfiniteStoryBoost", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||||
override fun asyncOnActivityCreate() {
|
override fun asyncOnActivityCreate() {
|
||||||
if (!context.config.experimental.infiniteStoryBoost.get()) return
|
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) {
|
||||||
val startTimeMillis = param.arg<Long>(1)
|
classReference.get()?.hookConstructor(HookStage.BEFORE) { param ->
|
||||||
//reset timestamp if it's more than 24 hours
|
val startTimeMillis = param.arg<Long>(1)
|
||||||
if (System.currentTimeMillis() - startTimeMillis > 86400000) {
|
//reset timestamp if it's more than 24 hours
|
||||||
param.setArg(1, 0)
|
if (System.currentTimeMillis() - startTimeMillis > 86400000) {
|
||||||
param.setArg(2, 0)
|
param.setArg(1, 0)
|
||||||
|
param.setArg(2, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.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.mapper.impl.BCryptClassMapper
|
||||||
|
|
||||||
class MeoPasscodeBypass : Feature("Meo Passcode Bypass", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
class MeoPasscodeBypass : Feature("Meo Passcode Bypass", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||||
override fun asyncOnActivityCreate() {
|
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.mappings.useMapper(BCryptClassMapper::class) {
|
||||||
context.androidContext.classLoader.loadClass(bcrypt["class"].toString()),
|
classReference.get()?.hook(
|
||||||
bcrypt["hashMethod"].toString(),
|
hashMethod.get()!!,
|
||||||
HookStage.BEFORE,
|
HookStage.BEFORE,
|
||||||
{ context.config.experimental.meoPasscodeBypass.get() },
|
) { param ->
|
||||||
) { param ->
|
//set the hash to the result of the method
|
||||||
//set the hash to the result of the method
|
param.setResult(param.arg(1))
|
||||||
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.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.hookConstructor
|
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.ScoreUpdateMapper
|
||||||
import java.lang.reflect.Constructor
|
import java.lang.reflect.Constructor
|
||||||
|
|
||||||
class NoFriendScoreDelay : Feature("NoFriendScoreDelay", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
class NoFriendScoreDelay : Feature("NoFriendScoreDelay", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||||
override fun onActivityCreate() {
|
override fun onActivityCreate() {
|
||||||
if (!context.config.experimental.noFriendScoreDelay.get()) return
|
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) {
|
||||||
val constructor = param.method() as Constructor<*>
|
classReference.get()?.hookConstructor(HookStage.BEFORE) { param ->
|
||||||
if (constructor.parameterTypes.size < 3 || constructor.parameterTypes[3] != java.util.Collection::class.java) return@hookConstructor
|
val constructor = param.method() as Constructor<*>
|
||||||
param.setArg(2, 0L)
|
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.HookStage
|
||||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.DefaultMediaItemMapper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class BypassVideoLengthRestriction :
|
class BypassVideoLengthRestriction :
|
||||||
@ -45,21 +46,23 @@ class BypassVideoLengthRestriction :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.mappings.getMappedClass("DefaultMediaItem")
|
context.mappings.useMapper(DefaultMediaItemMapper::class) {
|
||||||
?.hookConstructor(HookStage.BEFORE) { param ->
|
defaultMediaItem.getAsClass()?.hookConstructor(HookStage.BEFORE) { param ->
|
||||||
//set the video length argument
|
//set the video length argument
|
||||||
param.setArg(5, -1L)
|
param.setArg(5, -1L)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: allow split from any source
|
//TODO: allow split from any source
|
||||||
if (mode == "split") {
|
if (mode == "split") {
|
||||||
val cameraRollId = context.mappings.getMappedMap("CameraRollMediaId") ?: throw Exception("Failed to get cameraRollId mappings")
|
|
||||||
// memories grid
|
// memories grid
|
||||||
findClass(cameraRollId["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
context.mappings.useMapper(DefaultMediaItemMapper::class) {
|
||||||
//set the durationMs field
|
cameraRollMediaId.getAsClass()?.hookConstructor(HookStage.AFTER) { param ->
|
||||||
param.thisObject<Any>()
|
//set the durationMs field
|
||||||
.setObjectField(cameraRollId["durationMsField"].toString(), -1L)
|
param.thisObject<Any>()
|
||||||
|
.setObjectField(durationMsField.get()!!, -1L)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// chat camera roll grid
|
// 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.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.hook
|
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) {
|
class MediaQualityLevelOverride : Feature("MediaQualityLevelOverride", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||||
override fun init() {
|
override fun init() {
|
||||||
val enumQualityLevel = context.mappings.getMappedClass("EnumQualityLevel") ?: throw Exception("Failed to get enumQualityLevelMappings")
|
if (!context.config.global.forceUploadSourceQuality.get()) return
|
||||||
val mediaQualityLevelProvider = context.mappings.getMappedMap("MediaQualityLevelProvider") ?: throw Exception("Failed to get mediaQualityLevelProviderMappings")
|
|
||||||
|
|
||||||
val forceMediaSourceQuality by context.config.global.forceUploadSourceQuality
|
context.mappings.useMapper(MediaQualityLevelProviderMapper::class) {
|
||||||
|
mediaQualityLevelProvider.getAsClass()?.hook(
|
||||||
context.androidContext.classLoader.loadClass(mediaQualityLevelProvider["class"].toString()).hook(
|
mediaQualityLevelProviderMethod.getAsString()!!,
|
||||||
mediaQualityLevelProvider["method"].toString(),
|
HookStage.BEFORE
|
||||||
HookStage.BEFORE,
|
) { param ->
|
||||||
{ forceMediaSourceQuality }
|
param.setResult((param.method() as Method).returnType.enumConstants.firstOrNull { it.toString() == "LEVEL_MAX" } )
|
||||||
) { param ->
|
}
|
||||||
param.setResult(enumQualityLevel.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.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.hook.hookConstructor
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.PlusSubscriptionMapper
|
||||||
|
|
||||||
class SnapchatPlus: Feature("SnapchatPlus", loadParams = FeatureLoadParams.INIT_SYNC) {
|
class SnapchatPlus: Feature("SnapchatPlus", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||||
private val originalSubscriptionTime = (System.currentTimeMillis() - 7776000000L)
|
private val originalSubscriptionTime = (System.currentTimeMillis() - 7776000000L)
|
||||||
@ -13,17 +14,17 @@ class SnapchatPlus: Feature("SnapchatPlus", loadParams = FeatureLoadParams.INIT_
|
|||||||
override fun init() {
|
override fun init() {
|
||||||
if (!context.config.global.snapchatPlus.get()) return
|
if (!context.config.global.snapchatPlus.get()) return
|
||||||
|
|
||||||
val subscriptionInfoClass = context.mappings.getMappedClass("SubscriptionInfoClass") ?: throw Exception("Failed to get subscriptionInfoClass")
|
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)
|
||||||
|
//subscription status
|
||||||
|
param.setArg(1, 2)
|
||||||
|
|
||||||
Hooker.hookConstructor(subscriptionInfoClass, HookStage.BEFORE) { param ->
|
param.setArg(2, originalSubscriptionTime)
|
||||||
if (param.arg<Int>(0) == 2) return@hookConstructor
|
param.setArg(3, expirationTimeMillis)
|
||||||
//subscription tier
|
}
|
||||||
param.setArg(0, 2)
|
|
||||||
//subscription status
|
|
||||||
param.setArg(1, 2)
|
|
||||||
|
|
||||||
param.setArg(2, originalSubscriptionTime)
|
|
||||||
param.setArg(3, expirationTimeMillis)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// optional as ConfigurationOverride does this too
|
// optional as ConfigurationOverride does this too
|
||||||
|
@ -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.util.ktx.getObjectField
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.Message
|
import me.rhunk.snapenhance.core.wrapper.impl.Message
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE, loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE, loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||||
@ -70,19 +71,21 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
|
|||||||
|
|
||||||
override fun asyncOnActivityCreate() {
|
override fun asyncOnActivityCreate() {
|
||||||
// called when enter in a conversation
|
// called when enter in a conversation
|
||||||
context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback").hook(
|
context.mappings.useMapper(CallbackMapper::class) {
|
||||||
"onFetchConversationWithMessagesComplete",
|
callbacks.getClass("FetchConversationWithMessagesCallback")?.hook(
|
||||||
HookStage.BEFORE,
|
"onFetchConversationWithMessagesComplete",
|
||||||
{ autoSaveFilter.isNotEmpty() }
|
HookStage.BEFORE,
|
||||||
) { param ->
|
{ autoSaveFilter.isNotEmpty() }
|
||||||
val conversationId = SnapUUID(param.arg<Any>(0).getObjectField("mConversationId")!!)
|
) { param ->
|
||||||
if (!canSaveInConversation(conversationId.toString())) return@hook
|
val conversationId = SnapUUID(param.arg<Any>(0).getObjectField("mConversationId")!!)
|
||||||
|
if (!canSaveInConversation(conversationId.toString())) return@hook
|
||||||
|
|
||||||
val messages = param.arg<List<Any>>(1).map { Message(it) }
|
val messages = param.arg<List<Any>>(1).map { Message(it) }
|
||||||
messages.forEach {
|
messages.forEach {
|
||||||
if (!canSaveMessage(it)) return@forEach
|
if (!canSaveMessage(it)) return@forEach
|
||||||
asyncSaveExecutorService.submit {
|
asyncSaveExecutorService.submit {
|
||||||
saveMessage(conversationId.toString(), it)
|
saveMessage(conversationId.toString(), it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.SnapUUID
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.Snapchatter
|
import me.rhunk.snapenhance.core.wrapper.impl.Snapchatter
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.toSnapUUID
|
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
|
import java.util.concurrent.Future
|
||||||
|
|
||||||
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
||||||
@ -47,24 +49,28 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.mappings.getMappedClass("callbacks", "ConversationManagerDelegate").apply {
|
context.mappings.useMapper(CallbackMapper::class) {
|
||||||
hookConstructor(HookStage.AFTER) { param ->
|
callbacks.getClass("ConversationManagerDelegate")?.apply {
|
||||||
conversationManagerDelegate = param.thisObject()
|
hookConstructor(HookStage.AFTER) { param ->
|
||||||
}
|
conversationManagerDelegate = param.thisObject()
|
||||||
hook("onConversationUpdated", HookStage.BEFORE) { param ->
|
}
|
||||||
context.event.post(ConversationUpdateEvent(
|
hook("onConversationUpdated", HookStage.BEFORE) { param ->
|
||||||
conversationId = SnapUUID(param.arg(0)).toString(),
|
context.event.post(ConversationUpdateEvent(
|
||||||
conversation = param.argNullable(1),
|
conversationId = SnapUUID(param.arg(0)).toString(),
|
||||||
messages = param.arg<ArrayList<*>>(2).map { Message(it) },
|
conversation = param.argNullable(1),
|
||||||
).apply { adapter = param }) {
|
messages = param.arg<ArrayList<*>>(2).map { Message(it) },
|
||||||
param.setArg(2, messages.map { it.instanceNonNull() }.toCollection(ArrayList()))
|
).apply { adapter = param }) {
|
||||||
|
param.setArg(
|
||||||
|
2,
|
||||||
|
messages.map { it.instanceNonNull() }.toCollection(ArrayList())
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
callbacks.getClass("IdentityDelegate")?.apply {
|
||||||
|
hookConstructor(HookStage.AFTER) {
|
||||||
context.mappings.getMappedClass("callbacks", "IdentityDelegate").apply {
|
identityDelegate = it.thisObject()
|
||||||
hookConstructor(HookStage.AFTER) {
|
}
|
||||||
identityDelegate = it.thisObject()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,10 +102,10 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityCreate() {
|
override fun onActivityCreate() {
|
||||||
context.mappings.getMappedObjectNullable("FriendsFeedEventDispatcher").let { it as? Map<*, *> }?.let { mappings ->
|
context.mappings.useMapper(FriendsFeedEventDispatcherMapper::class) {
|
||||||
findClass(mappings["class"].toString()).hook("onItemLongPress", HookStage.BEFORE) { param ->
|
classReference.getAsClass()?.hook("onItemLongPress", HookStage.BEFORE) { param ->
|
||||||
val viewItemContainer = param.arg<Any>(0)
|
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 {
|
val conversationId = viewItem.substringAfter("conversationId: ").substring(0, 36).also {
|
||||||
if (it.startsWith("null")) return@hook
|
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 ->
|
context.classCache.feedEntry.hookConstructor(HookStage.AFTER) { param ->
|
||||||
val instance = param.thisObject<Any>()
|
val instance = param.thisObject<Any>()
|
||||||
val interactionInfo = instance.getObjectFieldOrNull("mInteractionInfo") ?: return@hookConstructor
|
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 }
|
}.sortedBy { it.orderKey }.mapNotNull { it.messageDescriptor?.messageId }
|
||||||
}
|
}
|
||||||
|
|
||||||
context.mappings.getMappedClass("callbacks", "GetOneOnOneConversationIdsCallback").hook("onSuccess", HookStage.BEFORE) { param ->
|
context.mappings.useMapper(CallbackMapper::class) {
|
||||||
val userIdToConversation = (param.arg<ArrayList<*>>(0))
|
callbacks.getClass("GetOneOnOneConversationIdsCallback")?.hook("onSuccess", HookStage.BEFORE) { param ->
|
||||||
.takeIf { it.isNotEmpty() }
|
val userIdToConversation = (param.arg<ArrayList<*>>(0))
|
||||||
?.get(0) ?: return@hook
|
.takeIf { it.isNotEmpty() }
|
||||||
|
?.get(0) ?: return@hook
|
||||||
|
|
||||||
lastFetchConversationUUID = SnapUUID(userIdToConversation.getObjectField("mConversationId"))
|
lastFetchConversationUUID =
|
||||||
lastFetchConversationUserUUID = SnapUUID(userIdToConversation.getObjectField("mUserId"))
|
SnapUUID(userIdToConversation.getObjectField("mConversationId"))
|
||||||
|
lastFetchConversationUserUUID =
|
||||||
|
SnapUUID(userIdToConversation.getObjectField("mUserId"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
with(context.classCache.conversationManager) {
|
context.classCache.conversationManager.apply {
|
||||||
Hooker.hook(this, "enterConversation", HookStage.BEFORE) { param ->
|
hook("enterConversation", HookStage.BEFORE) { param ->
|
||||||
openedConversationUUID = SnapUUID(param.arg(0))
|
openedConversationUUID = SnapUUID(param.arg(0))
|
||||||
if (context.config.messaging.bypassMessageRetentionPolicy.get()) {
|
if (context.config.messaging.bypassMessageRetentionPolicy.get()) {
|
||||||
val callback = param.argNullable<Any>(2) ?: return@hook
|
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
|
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.hook.hookConstructor
|
||||||
import me.rhunk.snapenhance.core.util.ktx.getIdentifier
|
import me.rhunk.snapenhance.core.util.ktx.getIdentifier
|
||||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
@ -43,8 +44,8 @@ class HalfSwipeNotifier : Feature("Half Swipe Notifier", loadParams = FeatureLoa
|
|||||||
presenceService = it.thisObject()
|
presenceService = it.thisObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
context.mappings.getMappedClass("callbacks", "PresenceServiceDelegate")
|
context.mappings.useMapper(CallbackMapper::class) {
|
||||||
.hook("notifyActiveConversationsChanged", HookStage.BEFORE) {
|
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)
|
val activeConversations = presenceService::class.java.methods.find { it.name == "getActiveConversations" }?.invoke(presenceService) as? Map<*, *> ?: return@hook // conversationId, conversationInfo (this.mPeekingParticipants)
|
||||||
|
|
||||||
if (activeConversations.isEmpty()) {
|
if (activeConversations.isEmpty()) {
|
||||||
@ -75,6 +76,7 @@ class HalfSwipeNotifier : Feature("Half Swipe Notifier", loadParams = FeatureLoa
|
|||||||
}
|
}
|
||||||
peekingConversations[conversationId.toString()] = peekingParticipantsIds
|
peekingConversations[conversationId.toString()] = peekingParticipantsIds
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.hook.hookConstructor
|
||||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.ScSize
|
import me.rhunk.snapenhance.core.wrapper.impl.ScSize
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.ScCameraSettingsMapper
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
@ -62,19 +63,21 @@ class CameraTweaks : Feature("Camera Tweaks", loadParams = FeatureLoadParams.ACT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.mappings.getMappedClass("ScCameraSettings")?.hookConstructor(HookStage.BEFORE) { param ->
|
context.mappings.useMapper(ScCameraSettingsMapper::class) {
|
||||||
val previewResolution = ScSize(param.argNullable(2))
|
classReference.get()?.hookConstructor(HookStage.BEFORE) { param ->
|
||||||
val captureResolution = ScSize(param.argNullable(3))
|
val previewResolution = ScSize(param.argNullable(2))
|
||||||
|
val captureResolution = ScSize(param.argNullable(3))
|
||||||
|
|
||||||
if (previewResolution.isPresent() && captureResolution.isPresent()) {
|
if (previewResolution.isPresent() && captureResolution.isPresent()) {
|
||||||
previewResolutionConfig?.let {
|
previewResolutionConfig?.let {
|
||||||
previewResolution.first = it[0]
|
previewResolution.first = it[0]
|
||||||
previewResolution.second = it[1]
|
previewResolution.second = it[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
captureResolutionConfig?.let {
|
captureResolutionConfig?.let {
|
||||||
captureResolution.first = it[0]
|
captureResolution.first = it[0]
|
||||||
captureResolution.second = it[1]
|
captureResolution.second = it[1]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.getObjectField
|
||||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
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) {
|
class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType = MessagingRuleType.HIDE_FRIEND_FEED, loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||||
private fun createDeletedFeedEntry(conversationId: String) = context.gson.fromJson(
|
private fun createDeletedFeedEntry(conversationId: String) = context.gson.fromJson(
|
||||||
@ -41,30 +42,31 @@ class HideFriendFeedEntry : MessagingRuleFeature("HideFriendFeedEntry", ruleType
|
|||||||
override fun init() {
|
override fun init() {
|
||||||
if (!context.config.userInterface.hideFriendFeedEntry.get()) return
|
if (!context.config.userInterface.hideFriendFeedEntry.get()) return
|
||||||
|
|
||||||
arrayOf(
|
context.mappings.useMapper(CallbackMapper::class) {
|
||||||
"QueryFeedCallback" to "onQueryFeedComplete",
|
arrayOf(
|
||||||
"FeedManagerDelegate" to "onFeedEntriesUpdated",
|
"QueryFeedCallback" to "onQueryFeedComplete",
|
||||||
"FeedManagerDelegate" to "onInternalSyncFeed",
|
"FeedManagerDelegate" to "onFeedEntriesUpdated",
|
||||||
"SyncFeedCallback" to "onSyncFeedComplete",
|
"FeedManagerDelegate" to "onInternalSyncFeed",
|
||||||
).forEach { (callbackName, methodName) ->
|
"SyncFeedCallback" to "onSyncFeedComplete",
|
||||||
context.mappings.getMappedClass("callbacks", callbackName)
|
).forEach { (callbackName, methodName) ->
|
||||||
.hook(methodName, HookStage.BEFORE) { param ->
|
findClass(callbacks.get()!![callbackName] ?: return@forEach).hook(methodName, HookStage.BEFORE) { param ->
|
||||||
filterFriendFeed(param.arg(0))
|
filterFriendFeed(param.arg(0))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
context.mappings.getMappedClass("callbacks", "FetchAndSyncFeedCallback")
|
|
||||||
.hook("onFetchAndSyncFeedComplete", HookStage.BEFORE) { param ->
|
|
||||||
val deletedConversations: ArrayList<Any> = param.arg(2)
|
|
||||||
filterFriendFeed(param.arg(0), deletedConversations)
|
|
||||||
|
|
||||||
if (deletedConversations.any {
|
|
||||||
val uuid = SnapUUID(it.getObjectField("mFeedEntryIdentifier")?.getObjectField("mConversationId")).toString()
|
|
||||||
context.database.getFeedEntryByConversationId(uuid) != null
|
|
||||||
}) {
|
|
||||||
param.setArg(4, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
callbacks.getClass("FetchAndSyncFeedCallback")
|
||||||
|
?.hook("onFetchAndSyncFeedComplete", HookStage.BEFORE) { param ->
|
||||||
|
val deletedConversations: ArrayList<Any> = param.arg(2)
|
||||||
|
filterFriendFeed(param.arg(0), deletedConversations)
|
||||||
|
|
||||||
|
if (deletedConversations.any {
|
||||||
|
val uuid = SnapUUID(it.getObjectField("mFeedEntryIdentifier")?.getObjectField("mConversationId")).toString()
|
||||||
|
context.database.getFeedEntryByConversationId(uuid) != null
|
||||||
|
}) {
|
||||||
|
param.setArg(4, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getRuleState() = RuleState.WHITELIST
|
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.HookStage
|
||||||
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
import me.rhunk.snapenhance.core.util.hook.hookConstructor
|
||||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.FriendingDataSourcesMapper
|
||||||
|
|
||||||
class HideQuickAddFriendFeed : Feature("HideQuickAddFriendFeed", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
class HideQuickAddFriendFeed : Feature("HideQuickAddFriendFeed", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||||
override fun onActivityCreate() {
|
override fun onActivityCreate() {
|
||||||
if (!context.config.userInterface.hideQuickAddFriendFeed.get()) return
|
if (!context.config.userInterface.hideQuickAddFriendFeed.get()) return
|
||||||
|
|
||||||
val friendingDataSource = context.mappings.getMappedMap("FriendingDataSources") ?: throw Exception("Failed to get friendingDataSourceMappings")
|
context.mappings.useMapper(FriendingDataSourcesMapper::class) {
|
||||||
findClass(friendingDataSource["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
classReference.getAsClass()?.hookConstructor(HookStage.AFTER) { param ->
|
||||||
param.thisObject<Any>().setObjectField(
|
param.thisObject<Any>().setObjectField(
|
||||||
friendingDataSource["quickAddSourceListField"].toString(),
|
quickAddSourceListField.get()!!,
|
||||||
arrayListOf<Any>()
|
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.getDimens
|
||||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||||
import me.rhunk.snapenhance.core.util.media.PreviewUtils
|
import me.rhunk.snapenhance.core.util.media.PreviewUtils
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class SnapPreview : Feature("SnapPreview", loadParams = FeatureLoadParams.INIT_SYNC or FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
class SnapPreview : Feature("SnapPreview", loadParams = FeatureLoadParams.INIT_SYNC or FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||||
@ -29,17 +30,19 @@ class SnapPreview : Feature("SnapPreview", loadParams = FeatureLoadParams.INIT_S
|
|||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
if (!isEnabled) return
|
if (!isEnabled) return
|
||||||
context.mappings.getMappedClass("callbacks", "ContentCallback").hook("handleContentResult", HookStage.BEFORE) { param ->
|
context.mappings.useMapper(CallbackMapper::class) {
|
||||||
val contentResult = param.arg<Any>(0)
|
callbacks.getClass("ContentCallback")?.hook("handleContentResult", HookStage.BEFORE) { param ->
|
||||||
val classMethods = contentResult::class.java.methods
|
val contentResult = param.arg<Any>(0)
|
||||||
|
val classMethods = contentResult::class.java.methods
|
||||||
|
|
||||||
val contentKey = classMethods.find { it.name == "getContentKey" }?.invoke(contentResult) ?: return@hook
|
val contentKey = classMethods.find { it.name == "getContentKey" }?.invoke(contentResult) ?: return@hook
|
||||||
if (contentKey.getObjectField("mMediaContextType").toString() != "CHAT") return@hook
|
if (contentKey.getObjectField("mMediaContextType").toString() != "CHAT") return@hook
|
||||||
|
|
||||||
val filePath = classMethods.find { it.name == "getFilePath" }?.invoke(contentResult) ?: return@hook
|
val filePath = classMethods.find { it.name == "getFilePath" }?.invoke(contentResult) ?: return@hook
|
||||||
val mediaId = contentKey.getObjectField("mMediaId").toString()
|
val mediaId = contentKey.getObjectField("mMediaId").toString()
|
||||||
|
|
||||||
mediaFileCache[mediaId.substringAfter("-")] = File(filePath.toString())
|
mediaFileCache[mediaId.substringAfter("-")] = File(filePath.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import me.rhunk.snapenhance.core.util.CallbackBuilder
|
|||||||
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.MessageDestinations
|
import me.rhunk.snapenhance.core.wrapper.impl.MessageDestinations
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||||
|
|
||||||
class MessageSender(
|
class MessageSender(
|
||||||
private val context: ModContext,
|
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(
|
private fun createLocalMessageContentTemplate(
|
||||||
contentType: ContentType,
|
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.getObjectField
|
||||||
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
import me.rhunk.snapenhance.core.util.ktx.setObjectField
|
||||||
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.CallbackMapper
|
||||||
|
|
||||||
typealias CallbackResult = (error: String?) -> Unit
|
typealias CallbackResult = (error: String?) -> Unit
|
||||||
|
|
||||||
@ -26,20 +27,29 @@ class ConversationManager(
|
|||||||
private val getOneOnOneConversationIds by lazy { findMethodByName("getOneOnOneConversationIds") }
|
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 = {}) {
|
fun updateMessage(conversationId: String, messageId: Long, action: MessageUpdate, onResult: CallbackResult = {}) {
|
||||||
updateMessageMethod.invoke(
|
updateMessageMethod.invoke(
|
||||||
instanceNonNull(),
|
instanceNonNull(),
|
||||||
SnapUUID.fromString(conversationId).instanceNonNull(),
|
SnapUUID.fromString(conversationId).instanceNonNull(),
|
||||||
messageId,
|
messageId,
|
||||||
context.classCache.messageUpdateEnum.enumConstants.first { it.toString() == action.toString() },
|
context.classCache.messageUpdateEnum.enumConstants.first { it.toString() == action.toString() },
|
||||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback"))
|
CallbackBuilder(getCallbackClass("Callback"))
|
||||||
.override("onSuccess") { onResult(null) }
|
.override("onSuccess") { onResult(null) }
|
||||||
.override("onError") { onResult(it.arg<Any>(0).toString()) }.build()
|
.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) {
|
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 ->
|
.override("onFetchConversationWithMessagesComplete") { param ->
|
||||||
onSuccess(param.arg<List<*>>(1).map { Message(it) })
|
onSuccess(param.arg<List<*>>(1).map { Message(it) })
|
||||||
}
|
}
|
||||||
@ -54,7 +64,7 @@ class ConversationManager(
|
|||||||
fetchConversationWithMessagesMethod.invoke(
|
fetchConversationWithMessagesMethod.invoke(
|
||||||
instanceNonNull(),
|
instanceNonNull(),
|
||||||
conversationId.toSnapUUID().instanceNonNull(),
|
conversationId.toSnapUUID().instanceNonNull(),
|
||||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback"))
|
CallbackBuilder(getCallbackClass("FetchConversationWithMessagesCallback"))
|
||||||
.override("onFetchConversationWithMessagesComplete") { param ->
|
.override("onFetchConversationWithMessagesComplete") { param ->
|
||||||
onSuccess(param.arg<List<*>>(1).map { Message(it) })
|
onSuccess(param.arg<List<*>>(1).map { Message(it) })
|
||||||
}
|
}
|
||||||
@ -70,7 +80,7 @@ class ConversationManager(
|
|||||||
instanceNonNull(),
|
instanceNonNull(),
|
||||||
conversationId.toSnapUUID().instanceNonNull(),
|
conversationId.toSnapUUID().instanceNonNull(),
|
||||||
messageId,
|
messageId,
|
||||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback"))
|
CallbackBuilder(getCallbackClass("Callback"))
|
||||||
.override("onSuccess") { onResult(null) }
|
.override("onSuccess") { onResult(null) }
|
||||||
.override("onError") { onResult(it.arg<Any>(0).toString()) }.build()
|
.override("onError") { onResult(it.arg<Any>(0).toString()) }.build()
|
||||||
)
|
)
|
||||||
@ -81,7 +91,7 @@ class ConversationManager(
|
|||||||
instanceNonNull(),
|
instanceNonNull(),
|
||||||
conversationId.toSnapUUID().instanceNonNull(),
|
conversationId.toSnapUUID().instanceNonNull(),
|
||||||
messageId,
|
messageId,
|
||||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback"))
|
CallbackBuilder(getCallbackClass("FetchMessageCallback"))
|
||||||
.override("onFetchMessageComplete") { param ->
|
.override("onFetchMessageComplete") { param ->
|
||||||
onSuccess(Message(param.arg(0)))
|
onSuccess(Message(param.arg(0)))
|
||||||
}
|
}
|
||||||
@ -100,7 +110,7 @@ class ConversationManager(
|
|||||||
fetchMessageByServerId.invoke(
|
fetchMessageByServerId.invoke(
|
||||||
instanceNonNull(),
|
instanceNonNull(),
|
||||||
serverMessageIdentifier,
|
serverMessageIdentifier,
|
||||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback"))
|
CallbackBuilder(getCallbackClass("FetchMessageCallback"))
|
||||||
.override("onFetchMessageComplete") { param ->
|
.override("onFetchMessageComplete") { param ->
|
||||||
onSuccess(Message(param.arg(0)))
|
onSuccess(Message(param.arg(0)))
|
||||||
}
|
}
|
||||||
@ -119,7 +129,7 @@ class ConversationManager(
|
|||||||
setObjectField("mServerMessageId", it)
|
setObjectField("mServerMessageId", it)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessagesByServerIdsCallback"))
|
CallbackBuilder(getCallbackClass("FetchMessagesByServerIdsCallback"))
|
||||||
.override("onSuccess") { param ->
|
.override("onSuccess") { param ->
|
||||||
onSuccess(param.arg<List<*>>(0).mapNotNull {
|
onSuccess(param.arg<List<*>>(0).mapNotNull {
|
||||||
Message(it?.getObjectField("mMessage") ?: return@mapNotNull null)
|
Message(it?.getObjectField("mMessage") ?: return@mapNotNull null)
|
||||||
@ -132,14 +142,14 @@ class ConversationManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearConversation(conversationId: String, onSuccess: () -> Unit, onError: (error: String) -> Unit) {
|
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("onSuccess") { onSuccess() }
|
||||||
.override("onError") { onError(it.arg<Any>(0).toString()) }.build()
|
.override("onError") { onError(it.arg<Any>(0).toString()) }.build()
|
||||||
clearConversation.invoke(instanceNonNull(), conversationId.toSnapUUID().instanceNonNull(), callback)
|
clearConversation.invoke(instanceNonNull(), conversationId.toSnapUUID().instanceNonNull(), callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOneOnOneConversationIds(userIds: List<String>, onSuccess: (List<Pair<String, String>>) -> Unit, onError: (error: String) -> Unit) {
|
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 ->
|
.override("onSuccess") { param ->
|
||||||
onSuccess(param.arg<ArrayList<*>>(0).map {
|
onSuccess(param.arg<ArrayList<*>>(0).map {
|
||||||
SnapUUID(it.getObjectField("mUserId")).toString() to SnapUUID(it.getObjectField("mConversationId")).toString()
|
SnapUUID(it.getObjectField("mUserId")).toString() to SnapUUID(it.getObjectField("mConversationId")).toString()
|
||||||
|
@ -1,8 +1,103 @@
|
|||||||
package me.rhunk.snapenhance.mapper
|
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 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) {
|
fun mapper(task: MapperContext.() -> Unit) {
|
||||||
mappers.add(task)
|
mappers.add(task)
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ package me.rhunk.snapenhance.mapper
|
|||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import me.rhunk.snapenhance.mapper.impl.*
|
||||||
import org.jf.dexlib2.Opcodes
|
import org.jf.dexlib2.Opcodes
|
||||||
import org.jf.dexlib2.dexbacked.DexBackedDexFile
|
import org.jf.dexlib2.dexbacked.DexBackedDexFile
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
@ -12,12 +12,33 @@ import java.io.BufferedInputStream
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.zip.ZipFile
|
import java.util.zip.ZipFile
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import kotlin.reflect.KClass
|
|
||||||
|
|
||||||
class Mapper(
|
class ClassMapper(
|
||||||
private vararg val mappers: KClass<out AbstractClassMapper> = arrayOf()
|
private vararg val mappers: AbstractClassMapper = DEFAULT_MAPPERS,
|
||||||
) {
|
) {
|
||||||
private val classes = mutableListOf<ClassDef>()
|
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) {
|
fun loadApk(path: String) {
|
||||||
val apkFile = ZipFile(path)
|
val apkFile = ZipFile(path)
|
||||||
val apkEntries = apkFile.entries().toList()
|
val apkEntries = apkFile.entries().toList()
|
||||||
@ -50,20 +71,23 @@ class Mapper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun start(): JsonObject {
|
suspend fun run(): JsonObject {
|
||||||
val mappers = mappers.map { it.java.constructors.first().newInstance() as AbstractClassMapper }
|
|
||||||
val context = MapperContext(classes.associateBy { it.type })
|
val context = MapperContext(classes.associateBy { it.type })
|
||||||
|
|
||||||
runBlocking {
|
withContext(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.IO) {
|
mappers.forEach { mapper ->
|
||||||
mappers.forEach { mapper ->
|
launch {
|
||||||
launch {
|
mapper.run(context)
|
||||||
mapper.run(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
package me.rhunk.snapenhance.mapper
|
||||||
|
|
||||||
import com.google.gson.GsonBuilder
|
|
||||||
import com.google.gson.JsonObject
|
|
||||||
import org.jf.dexlib2.iface.ClassDef
|
import org.jf.dexlib2.iface.ClassDef
|
||||||
|
|
||||||
class MapperContext(
|
class MapperContext(
|
||||||
@ -19,40 +17,4 @@ class MapperContext(
|
|||||||
if (name == null) return null
|
if (name == null) return null
|
||||||
return classMap[name.toString()]
|
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 me.rhunk.snapenhance.mapper.ext.isFinal
|
||||||
import org.jf.dexlib2.iface.instruction.formats.ArrayPayload
|
import org.jf.dexlib2.iface.instruction.formats.ArrayPayload
|
||||||
|
|
||||||
class BCryptClassMapper : AbstractClassMapper() {
|
class BCryptClassMapper : AbstractClassMapper("BCryptClass") {
|
||||||
|
val classReference = classReference("class")
|
||||||
|
val hashMethod = string("hashMethod")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (clazz in classes) {
|
for (clazz in classes) {
|
||||||
@ -17,17 +20,15 @@ class BCryptClassMapper : AbstractClassMapper() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isBcryptClass == true) {
|
if (isBcryptClass == true) {
|
||||||
val hashMethod = clazz.methods.first {
|
val hashDexMethod = clazz.methods.first {
|
||||||
it.parameterTypes.size == 2 &&
|
it.parameterTypes.size == 2 &&
|
||||||
it.parameterTypes[0] == "Ljava/lang/String;" &&
|
it.parameterTypes[0] == "Ljava/lang/String;" &&
|
||||||
it.parameterTypes[1] == "Ljava/lang/String;" &&
|
it.parameterTypes[1] == "Ljava/lang/String;" &&
|
||||||
it.returnType == "Ljava/lang/String;"
|
it.returnType == "Ljava/lang/String;"
|
||||||
}
|
}
|
||||||
|
|
||||||
addMapping("BCrypt",
|
hashMethod.set(hashDexMethod.name)
|
||||||
"class" to clazz.getClassName(),
|
classReference.set(clazz.getClassName())
|
||||||
"hashMethod" to hashMethod.name
|
|
||||||
)
|
|
||||||
return@mapper
|
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.getClassName
|
||||||
import me.rhunk.snapenhance.mapper.ext.getSuperClassName
|
import me.rhunk.snapenhance.mapper.ext.getSuperClassName
|
||||||
import me.rhunk.snapenhance.mapper.ext.isFinal
|
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.Instruction21t
|
||||||
import org.jf.dexlib2.iface.instruction.formats.Instruction22t
|
import org.jf.dexlib2.iface.instruction.formats.Instruction22t
|
||||||
|
|
||||||
class CallbackMapper : AbstractClassMapper() {
|
class CallbackMapper : AbstractClassMapper("Callbacks") {
|
||||||
|
val callbacks = map("callbacks")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
val callbackClasses = classes.filter { clazz ->
|
val callbackClasses = classes.filter { clazz ->
|
||||||
@ -32,7 +33,7 @@ class CallbackMapper : AbstractClassMapper() {
|
|||||||
it.getSuperClassName()!!.substringAfterLast("/") to it.getClassName()
|
it.getSuperClassName()!!.substringAfterLast("/") to it.getClassName()
|
||||||
}
|
}
|
||||||
|
|
||||||
addMapping("callbacks", *callbackClasses.toTypedArray())
|
callbacks.get()?.putAll(callbackClasses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,14 +1,32 @@
|
|||||||
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.*
|
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.Instruction21c
|
||||||
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
|
import org.jf.dexlib2.iface.instruction.formats.Instruction35c
|
||||||
import org.jf.dexlib2.iface.reference.FieldReference
|
import org.jf.dexlib2.iface.reference.FieldReference
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference
|
import org.jf.dexlib2.iface.reference.MethodReference
|
||||||
import java.lang.reflect.Modifier
|
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 {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (classDef in classes) {
|
for (classDef in classes) {
|
||||||
@ -68,24 +86,21 @@ class CompositeConfigurationProviderMapper : AbstractClassMapper() {
|
|||||||
it.type == "Ljava/lang/Object;"
|
it.type == "Ljava/lang/Object;"
|
||||||
}
|
}
|
||||||
|
|
||||||
addMapping("CompositeConfigurationProvider",
|
classReference.set(classDef.getClassName())
|
||||||
"class" to classDef.getClassName(),
|
observeProperty.set(observePropertyMethod.name)
|
||||||
"observeProperty" to observePropertyMethod.name,
|
getProperty.set(getPropertyMethod.name)
|
||||||
"getProperty" to getPropertyMethod.name,
|
|
||||||
"enum" to mapOf(
|
configEnumMapping["class"]?.set(configEnumInterface.getClassName())
|
||||||
"class" to configEnumInterface.getClassName(),
|
configEnumMapping["getValue"]?.set(enumGetDefaultValueMethod.name)
|
||||||
"getValue" to enumGetDefaultValueMethod.name,
|
configEnumMapping["getCategory"]?.set(enumGetCategoryMethod.name)
|
||||||
"getCategory" to enumGetCategoryMethod.name,
|
configEnumMapping["defaultValueField"]?.set(defaultValueField.name)
|
||||||
"defaultValueField" to defaultValueField.name
|
|
||||||
),
|
hasExperimentMethodReference?.let {
|
||||||
"appExperimentProvider" to (hasExperimentMethodReference?.let {
|
appExperimentProvider["class"]?.set(getClass(it.definingClass)?.getClassName())
|
||||||
mapOf(
|
appExperimentProvider["getBooleanAppExperimentClass"]?.set(getBooleanAppExperimentClass)
|
||||||
"class" to getClass(it.definingClass)?.getClassName(),
|
appExperimentProvider["hasExperimentMethod"]?.set(hasExperimentMethodReference.name)
|
||||||
"GetBooleanAppExperimentClass" to getBooleanAppExperimentClass,
|
}
|
||||||
"hasExperimentMethod" to hasExperimentMethodReference.name
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
return@mapper
|
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.getClassName
|
||||||
import me.rhunk.snapenhance.mapper.ext.isAbstract
|
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 {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (clazz in classes) {
|
for (clazz in classes) {
|
||||||
if (clazz.methods.find { it.name == "toString" }?.implementation?.findConstString("CameraRollMediaId", contains = true) != true) {
|
if (clazz.methods.find { it.name == "toString" }?.implementation?.findConstString("CameraRollMediaId", contains = true) != true) {
|
||||||
continue
|
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
|
return@mapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,7 +34,7 @@ class DefaultMediaItemMapper : AbstractClassMapper() {
|
|||||||
val constructorParameters = clazz.directMethods.firstOrNull { it.name == "<init>" }?.parameterTypes ?: continue
|
val constructorParameters = clazz.directMethods.firstOrNull { it.name == "<init>" }?.parameterTypes ?: continue
|
||||||
if (constructorParameters.size < 6 || constructorParameters[5] != "J") continue
|
if (constructorParameters.size < 6 || constructorParameters[5] != "J") continue
|
||||||
|
|
||||||
addMapping("DefaultMediaItem", clazz.getClassName())
|
defaultMediaItem.set(clazz.getClassName())
|
||||||
return@mapper
|
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.getClassName
|
||||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
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 {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (classDef in classes) {
|
for (classDef in classes) {
|
||||||
classDef.methods.firstOrNull { it.name == "<init>" }?.implementation?.findConstString("FriendRelationshipChangerImpl")?.takeIf { it } ?: continue
|
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 &&
|
it.parameterTypes.size > 4 &&
|
||||||
getClass(it.parameterTypes[1])?.isEnum() == true &&
|
getClass(it.parameterTypes[1])?.isEnum() == true &&
|
||||||
getClass(it.parameterTypes[2])?.isEnum() == true &&
|
getClass(it.parameterTypes[2])?.isEnum() == true &&
|
||||||
@ -18,7 +22,7 @@ class FriendRelationshipChangerMapper : AbstractClassMapper() {
|
|||||||
it.parameters[4].type == "Ljava/lang/String;"
|
it.parameters[4].type == "Ljava/lang/String;"
|
||||||
}
|
}
|
||||||
|
|
||||||
val removeFriendMethod = classDef.methods.first {
|
val removeFriendDexMethod = classDef.methods.firstOrNull {
|
||||||
it.parameterTypes.size == 5 &&
|
it.parameterTypes.size == 5 &&
|
||||||
it.parameterTypes[0] == "Ljava/lang/String;" &&
|
it.parameterTypes[0] == "Ljava/lang/String;" &&
|
||||||
getClass(it.parameterTypes[1])?.isEnum() == true &&
|
getClass(it.parameterTypes[1])?.isEnum() == true &&
|
||||||
@ -26,11 +30,12 @@ class FriendRelationshipChangerMapper : AbstractClassMapper() {
|
|||||||
it.parameterTypes[3] == "Ljava/lang/String;"
|
it.parameterTypes[3] == "Ljava/lang/String;"
|
||||||
}
|
}
|
||||||
|
|
||||||
addMapping("FriendRelationshipChanger",
|
this@FriendRelationshipChangerMapper.apply {
|
||||||
"class" to classDef.getClassName(),
|
classReference.set(classDef.getClassName())
|
||||||
"addFriendMethod" to addFriendMethod.name,
|
addFriendMethod.set(addFriendDexMethod.name)
|
||||||
"removeFriendMethod" to removeFriendMethod.name
|
removeFriendMethod.set(removeFriendDexMethod?.name)
|
||||||
)
|
}
|
||||||
|
|
||||||
return@mapper
|
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.getClassName
|
||||||
import me.rhunk.snapenhance.mapper.ext.searchNextFieldReference
|
import me.rhunk.snapenhance.mapper.ext.searchNextFieldReference
|
||||||
|
|
||||||
class FriendingDataSourcesMapper: AbstractClassMapper() {
|
class FriendingDataSourcesMapper: AbstractClassMapper("FriendingDataSources") {
|
||||||
|
val classReference = classReference("class")
|
||||||
|
val quickAddSourceListField = string("quickAddSourceListField")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (classDef in classes) {
|
for (classDef in classes) {
|
||||||
@ -15,13 +18,11 @@ class FriendingDataSourcesMapper: AbstractClassMapper() {
|
|||||||
val toStringMethod = classDef.methods.firstOrNull { it.name == "toString" } ?: continue
|
val toStringMethod = classDef.methods.firstOrNull { it.name == "toString" } ?: continue
|
||||||
if (toStringMethod.implementation?.findConstString("quickaddSource", contains = true) != true) 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
|
?: continue
|
||||||
|
|
||||||
addMapping("FriendingDataSources",
|
classReference.set(classDef.getClassName())
|
||||||
"class" to classDef.getClassName(),
|
quickAddSourceListField.set(quickAddSourceListDexField.name)
|
||||||
"quickAddSourceListField" to quickAddSourceListField.name
|
|
||||||
)
|
|
||||||
return@mapper
|
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.getClassName
|
||||||
|
|
||||||
|
|
||||||
class FriendsFeedEventDispatcherMapper : AbstractClassMapper() {
|
class FriendsFeedEventDispatcherMapper : AbstractClassMapper("FriendsFeedEventDispatcher") {
|
||||||
|
val classReference = classReference("class")
|
||||||
|
val viewModelField = string("viewModelField")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (clazz in classes) {
|
for (clazz in classes) {
|
||||||
@ -13,15 +16,13 @@ class FriendsFeedEventDispatcherMapper : AbstractClassMapper() {
|
|||||||
val onItemLongPress = clazz.methods.first { it.name == "onItemLongPress" }
|
val onItemLongPress = clazz.methods.first { it.name == "onItemLongPress" }
|
||||||
val viewHolderContainerClass = getClass(onItemLongPress.parameterTypes[0]) ?: continue
|
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
|
val typeClass = getClass(field.type) ?: return@firstOrNull false
|
||||||
typeClass.methods.firstOrNull {it.name == "toString"}?.implementation?.findConstString("FriendFeedItemViewModel", contains = true) == true
|
typeClass.methods.firstOrNull {it.name == "toString"}?.implementation?.findConstString("FriendFeedItemViewModel", contains = true) == true
|
||||||
}?.name ?: continue
|
}?.name ?: continue
|
||||||
|
|
||||||
addMapping("FriendsFeedEventDispatcher",
|
classReference.set(clazz.getClassName())
|
||||||
"class" to clazz.getClassName(),
|
viewModelField.set(viewModelDexField)
|
||||||
"viewModelField" to viewModelField
|
|
||||||
)
|
|
||||||
return@mapper
|
return@mapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,10 @@ import me.rhunk.snapenhance.mapper.ext.isAbstract
|
|||||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||||
import org.jf.dexlib2.AccessFlags
|
import org.jf.dexlib2.AccessFlags
|
||||||
|
|
||||||
class MediaQualityLevelProviderMapper : AbstractClassMapper() {
|
class MediaQualityLevelProviderMapper : AbstractClassMapper("MediaQualityLevelProvider") {
|
||||||
|
val mediaQualityLevelProvider = classReference("mediaQualityLevelProvider")
|
||||||
|
val mediaQualityLevelProviderMethod = string("mediaQualityLevelProviderMethod")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
var enumQualityLevel : String? = null
|
var enumQualityLevel : String? = null
|
||||||
|
|
||||||
@ -20,7 +23,6 @@ class MediaQualityLevelProviderMapper : AbstractClassMapper() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addMapping("EnumQualityLevel", enumQualityLevel ?: return@mapper)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mapper {
|
mapper {
|
||||||
@ -31,10 +33,8 @@ class MediaQualityLevelProviderMapper : AbstractClassMapper() {
|
|||||||
if (clazz.fields.none { it.accessFlags and AccessFlags.TRANSIENT.value != 0 }) continue
|
if (clazz.fields.none { it.accessFlags and AccessFlags.TRANSIENT.value != 0 }) continue
|
||||||
|
|
||||||
clazz.methods.firstOrNull { it.returnType == "L$enumQualityLevel;" }?.let {
|
clazz.methods.firstOrNull { it.returnType == "L$enumQualityLevel;" }?.let {
|
||||||
addMapping("MediaQualityLevelProvider",
|
mediaQualityLevelProvider.set(clazz.getClassName())
|
||||||
"class" to clazz.getClassName(),
|
mediaQualityLevelProviderMethod.set(it.name)
|
||||||
"method" to it.name
|
|
||||||
)
|
|
||||||
return@mapper
|
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.isAbstract
|
||||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
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 {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (clazz in classes) {
|
for (clazz in classes) {
|
||||||
@ -16,37 +22,35 @@ class OperaPageViewControllerMapper : AbstractClassMapper() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
val viewStateField = clazz.fields.first { field ->
|
val viewStateDexField = clazz.fields.first { field ->
|
||||||
val fieldClass = getClass(field.type) ?: return@first false
|
val fieldClass = getClass(field.type) ?: return@first false
|
||||||
fieldClass.isEnum() && fieldClass.hasStaticConstructorString("FULLY_DISPLAYED")
|
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
|
if (it.returnType != "V" || it.parameterTypes.size != 1) return@firstOrNull false
|
||||||
val firstParameterType = getClass(it.parameterTypes[0]) ?: return@firstOrNull false
|
val firstParameterType = getClass(it.parameterTypes[0]) ?: return@firstOrNull false
|
||||||
if (firstParameterType.type == clazz.type || !firstParameterType.isAbstract()) return@firstOrNull false
|
if (firstParameterType.type == clazz.type || !firstParameterType.isAbstract()) return@firstOrNull false
|
||||||
//check if the class contains a field with the enumViewStateClass type
|
//check if the class contains a field with the enumViewStateClass type
|
||||||
firstParameterType.fields.any { field ->
|
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
|
if (it.returnType != "V" || it.parameterTypes.size != 2) return@first false
|
||||||
val firstParameterType = getClass(it.parameterTypes[0]) ?: return@first false
|
val firstParameterType = getClass(it.parameterTypes[0]) ?: return@first false
|
||||||
val secondParameterType = getClass(it.parameterTypes[1]) ?: return@first false
|
val secondParameterType = getClass(it.parameterTypes[1]) ?: return@first false
|
||||||
firstParameterType.isEnum() && secondParameterType.isEnum()
|
firstParameterType.isEnum() && secondParameterType.isEnum()
|
||||||
}
|
}
|
||||||
|
|
||||||
addMapping("OperaPageViewController",
|
classReference.set(clazz.getClassName())
|
||||||
"class" to clazz.getClassName(),
|
viewStateField.set(viewStateDexField.name)
|
||||||
"viewStateField" to viewStateField.name,
|
layerListField.set(layerListDexField.name)
|
||||||
"layerListField" to layerListField.name,
|
onDisplayStateChange.set(onDisplayStateChangeDexMethod?.name)
|
||||||
"onDisplayStateChange" to onDisplayStateChange?.name,
|
onDisplayStateChangeGesture.set(onDisplayStateChangeGestureDexMethod.name)
|
||||||
"onDisplayStateChangeGesture" to onDisplayStateChangeGesture.name
|
|
||||||
)
|
|
||||||
|
|
||||||
return@mapper
|
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.instruction.formats.Instruction35c
|
||||||
import org.jf.dexlib2.iface.reference.MethodReference
|
import org.jf.dexlib2.iface.reference.MethodReference
|
||||||
|
|
||||||
class OperaViewerParamsMapper : AbstractClassMapper() {
|
class OperaViewerParamsMapper : AbstractClassMapper("OperaViewerParams") {
|
||||||
|
val classReference = classReference("class")
|
||||||
|
val putMethod = string("putMethod")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (classDef in classes) {
|
for (classDef in classes) {
|
||||||
classDef.fields.firstOrNull { it.type == "Ljava/util/concurrent/ConcurrentHashMap;" } ?: continue
|
classDef.fields.firstOrNull { it.type == "Ljava/util/concurrent/ConcurrentHashMap;" } ?: continue
|
||||||
if (classDef.methods.firstOrNull { it.name == "toString" }?.implementation?.findConstString("Params") != true) 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 {
|
method.implementation?.instructions?.any {
|
||||||
val instruction = it as? Instruction35c ?: return@any false
|
val instruction = it as? Instruction35c ?: return@any false
|
||||||
val reference = instruction.reference as? MethodReference ?: return@any false
|
val reference = instruction.reference as? MethodReference ?: return@any false
|
||||||
@ -21,10 +24,9 @@ class OperaViewerParamsMapper : AbstractClassMapper() {
|
|||||||
} == true
|
} == true
|
||||||
} ?: return@mapper
|
} ?: return@mapper
|
||||||
|
|
||||||
addMapping("OperaViewerParams",
|
classReference.set(classDef.getClassName())
|
||||||
"class" to classDef.getClassName(),
|
putMethod.set(putDexMethod.name)
|
||||||
"putMethod" to putMethod.name
|
|
||||||
)
|
|
||||||
return@mapper
|
return@mapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,24 +4,26 @@ import me.rhunk.snapenhance.mapper.AbstractClassMapper
|
|||||||
import me.rhunk.snapenhance.mapper.ext.findConstString
|
import me.rhunk.snapenhance.mapper.ext.findConstString
|
||||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||||
|
|
||||||
class PlusSubscriptionMapper : AbstractClassMapper(){
|
class PlusSubscriptionMapper : AbstractClassMapper("PlusSubscription"){
|
||||||
|
val classReference = classReference("class")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (clazz in classes) {
|
for (clazz in classes) {
|
||||||
if (clazz.directMethods.filter { it.name == "<init>" }.none {
|
if (clazz.directMethods.filter { it.name == "<init>" }.none {
|
||||||
it.parameters.size == 4 &&
|
it.parameterTypes.size > 3 &&
|
||||||
it.parameterTypes[0] == "I" &&
|
it.parameterTypes[0] == "I" &&
|
||||||
it.parameterTypes[1] == "I" &&
|
it.parameterTypes[1] == "I" &&
|
||||||
it.parameterTypes[2] == "J" &&
|
it.parameterTypes[2] == "J" &&
|
||||||
it.parameterTypes[3] == "J"
|
it.parameterTypes[3] == "J"
|
||||||
}) continue
|
}) continue
|
||||||
|
|
||||||
val isPlusSubscriptionInfoClass = clazz.virtualMethods.firstOrNull { it.name == "toString" }?.implementation?.let {
|
val isPlusSubscriptionInfoClass = clazz.virtualMethods.firstOrNull { it.name == "toString" }?.implementation?.let {
|
||||||
it.findConstString("SubscriptionInfo", contains = true) && it.findConstString("expirationTimeMillis", contains = true)
|
it.findConstString("SubscriptionInfo", contains = true) && it.findConstString("expirationTimeMillis", contains = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPlusSubscriptionInfoClass == true) {
|
if (isPlusSubscriptionInfoClass == true) {
|
||||||
addMapping("SubscriptionInfoClass", clazz.getClassName())
|
classReference.set(clazz.getClassName())
|
||||||
return@mapper
|
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.getStaticConstructor
|
||||||
import me.rhunk.snapenhance.mapper.ext.isEnum
|
import me.rhunk.snapenhance.mapper.ext.isEnum
|
||||||
|
|
||||||
class ScCameraSettingsMapper : AbstractClassMapper() {
|
class ScCameraSettingsMapper : AbstractClassMapper("ScCameraSettings") {
|
||||||
|
val classReference = classReference("class")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (clazz in classes) {
|
for (clazz in classes) {
|
||||||
@ -15,7 +17,7 @@ class ScCameraSettingsMapper : AbstractClassMapper() {
|
|||||||
val firstParameter = getClass(firstConstructor.parameterTypes[0]) ?: continue
|
val firstParameter = getClass(firstConstructor.parameterTypes[0]) ?: continue
|
||||||
if (!firstParameter.isEnum() || firstParameter.getStaticConstructor()?.implementation?.findConstString("CONTINUOUS_PICTURE") != true) continue
|
if (!firstParameter.isEnum() || firstParameter.getStaticConstructor()?.implementation?.findConstString("CONTINUOUS_PICTURE") != true) continue
|
||||||
|
|
||||||
addMapping("ScCameraSettings", clazz.getClassName())
|
classReference.set(clazz.getClassName())
|
||||||
return@mapper
|
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.findConstString
|
||||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||||
|
|
||||||
class ScoreUpdateMapper : AbstractClassMapper() {
|
class ScoreUpdateMapper : AbstractClassMapper("ScoreUpdate") {
|
||||||
|
val classReference = classReference("class")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (classDef in classes) {
|
for (classDef in classes) {
|
||||||
@ -18,7 +20,7 @@ class ScoreUpdateMapper : AbstractClassMapper() {
|
|||||||
it.name == "toString"
|
it.name == "toString"
|
||||||
}?.implementation?.findConstString("Friend.sq:selectFriendUserScoresNeedToUpdate") != true) continue
|
}?.implementation?.findConstString("Friend.sq:selectFriendUserScoresNeedToUpdate") != true) continue
|
||||||
|
|
||||||
addMapping("ScoreUpdate", classDef.getClassName())
|
classReference.set(classDef.getClassName())
|
||||||
return@mapper
|
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.findConstString
|
||||||
import me.rhunk.snapenhance.mapper.ext.getClassName
|
import me.rhunk.snapenhance.mapper.ext.getClassName
|
||||||
|
|
||||||
class StoryBoostStateMapper : AbstractClassMapper() {
|
class StoryBoostStateMapper : AbstractClassMapper("StoryBoostState") {
|
||||||
|
val classReference = classReference("class")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (clazz in classes) {
|
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
|
if (clazz.methods.firstOrNull { it.name == "toString" }?.implementation?.findConstString("StoryBoostState", contains = true) != true) continue
|
||||||
|
|
||||||
addMapping("StoryBoostStateClass", clazz.getClassName())
|
classReference.set(clazz.getClassName())
|
||||||
return@mapper
|
return@mapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,13 +6,17 @@ import me.rhunk.snapenhance.mapper.ext.isAbstract
|
|||||||
import me.rhunk.snapenhance.mapper.ext.isInterface
|
import me.rhunk.snapenhance.mapper.ext.isInterface
|
||||||
import java.lang.reflect.Modifier
|
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 {
|
init {
|
||||||
mapper {
|
mapper {
|
||||||
for (clazz in classes) {
|
for (clazz in classes) {
|
||||||
if (!clazz.isAbstract() || clazz.isInterface()) continue
|
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
|
// update view
|
||||||
clazz.methods.filter {
|
clazz.methods.filter {
|
||||||
@ -21,17 +25,15 @@ class ViewBinderMapper : AbstractClassMapper() {
|
|||||||
if (it.size != 1) return@also
|
if (it.size != 1) return@also
|
||||||
}.firstOrNull() ?: continue
|
}.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"
|
Modifier.isAbstract(it.accessFlags) && it.parameterTypes.size == 2 && it.parameterTypes[0] == it.parameterTypes[1] && it.returnType == "V"
|
||||||
}.also {
|
}.also {
|
||||||
if (it.size != 1) return@also
|
if (it.size != 1) return@also
|
||||||
}.firstOrNull() ?: continue
|
}.firstOrNull() ?: continue
|
||||||
|
|
||||||
addMapping("ViewBinder",
|
classReference.set(clazz.getClassName())
|
||||||
"class" to clazz.getClassName(),
|
bindMethod.set(bindDexMethod.name)
|
||||||
"bindMethod" to bindMethod.name,
|
getViewMethod.set(getViewDexMethod.name)
|
||||||
"getViewMethod" to getViewMethod.name
|
|
||||||
)
|
|
||||||
return@mapper
|
return@mapper
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package me.rhunk.snapenhance.mapper.tests
|
package me.rhunk.snapenhance.mapper.tests
|
||||||
|
|
||||||
import com.google.gson.GsonBuilder
|
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 me.rhunk.snapenhance.mapper.impl.*
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -10,28 +11,14 @@ import java.io.File
|
|||||||
class TestMappings {
|
class TestMappings {
|
||||||
@Test
|
@Test
|
||||||
fun testMappings() {
|
fun testMappings() {
|
||||||
val mapper = Mapper(
|
val classMapper = ClassMapper()
|
||||||
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 gson = GsonBuilder().setPrettyPrinting().create()
|
val gson = GsonBuilder().setPrettyPrinting().create()
|
||||||
val apkFile = File(System.getenv("SNAPCHAT_APK")!!)
|
val apkFile = File(System.getenv("SNAPCHAT_APK")!!)
|
||||||
mapper.loadApk(apkFile.absolutePath)
|
classMapper.loadApk(apkFile.absolutePath)
|
||||||
val result = mapper.start()
|
runBlocking {
|
||||||
println("Mappings: ${gson.toJson(result)}")
|
val result = classMapper.run()
|
||||||
|
println("Mappings: ${gson.toJson(result)}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user