commit f1b0bc41f2410c2c72dd9c9a14f4d3b90fcf3e1b
parent 1b566db184f19d349e2c28a0ee1cb8f939ee959a
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Mon,  1 Jan 2024 21:41:12 +0100

feat: loop media playback
- refactor mappings wrapper

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 4++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MappingsWrapper.kt | 24+++++++++---------------
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt | 1+
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/BulkMessagingAction.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ConfigurationOverride.kt | 2+-
Acore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/OperaViewerParamsOverride.kt | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/MeoPasscodeBypass.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/global/BypassVideoLengthRestriction.kt | 4++--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/global/MediaQualityLevelOverride.kt | 4++--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/HideQuickAddFriendFeed.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt | 2++
Amapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/OperaViewerParamsMapper.kt | 33+++++++++++++++++++++++++++++++++
Mmapper/src/test/kotlin/me/rhunk/snapenhance/mapper/tests/TestMappings.kt | 1+
13 files changed, 123 insertions(+), 23 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -378,6 +378,10 @@ "name": "Unlimited Snap View Time", "description": "Removes the Time Limit for viewing Snaps" }, + "loop_media_playback": { + "name": "Loop Media Playback", + "description": "Loops media playback when viewing Snaps / Stories" + }, "disable_replay_in_ff": { "name": "Disable Replay in FF", "description": "Disables the ability to replay with a long press from the Friend Feed" diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MappingsWrapper.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MappingsWrapper.kt @@ -31,6 +31,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr FriendRelationshipChangerMapper::class, ViewBinderMapper::class, FriendingDataSourcesMapper::class, + OperaViewerParamsMapper::class, ) } @@ -115,29 +116,22 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr return mappings[key] } - fun getMappedClass(className: String): Class<*> { - return context.classLoader.loadClass(getMappedObject(className) as String) + 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): String { - return getMappedObject(key) as String + fun getMappedValue(key: String, subKey: String): String? { + return getMappedMap(key)?.get(subKey) as? String } @Suppress("UNCHECKED_CAST") - fun <T : Any> getMappedList(key: String): List<T> { - return listOf(getMappedObject(key) as List<T>).flatten() - } - - fun getMappedValue(key: String, subKey: String): String { - return getMappedMap(key)[subKey] as String - } - - @Suppress("UNCHECKED_CAST") - fun getMappedMap(key: String): Map<String, *> { - return getMappedObject(key) as Map<String, *> + fun getMappedMap(key: String): Map<String, *>? { + return getMappedObjectNullable(key) as? Map<String, *> } } \ No newline at end of file diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt @@ -55,6 +55,7 @@ class MessagingTweaks : ConfigContainer() { val hideBitmojiPresence = boolean("hide_bitmoji_presence") val hideTypingNotifications = boolean("hide_typing_notifications") val unlimitedSnapViewTime = boolean("unlimited_snap_view_time") + val loopMediaPlayback = boolean("loop_media_playback") { requireRestart() } val disableReplayInFF = boolean("disable_replay_in_ff") val halfSwipeNotifier = container("half_swipe_notifier", HalfSwipeNotifierConfig()) { requireRestart()} val messagePreviewLength = integer("message_preview_length", defaultValue = 20) diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/BulkMessagingAction.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/BulkMessagingAction.kt @@ -145,7 +145,7 @@ class BulkMessagingAction : AbstractAction() { } private fun removeFriend(userId: String) { - val friendRelationshipChangerMapping = context.mappings.getMappedMap("FriendRelationshipChanger") + val friendRelationshipChangerMapping = context.mappings.getMappedMap("FriendRelationshipChanger") ?: throw Exception("Failed to get FriendRelationshipChanger mapping") val friendRelationshipChangerInstance = context.feature(AddFriendSourceSpoof::class).friendRelationshipChangerInstance!! val removeFriendMethod = friendRelationshipChangerInstance::class.java.methods.first { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ConfigurationOverride.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ConfigurationOverride.kt @@ -23,7 +23,7 @@ data class ConfigFilter( class ConfigurationOverride : Feature("Configuration Override", loadParams = FeatureLoadParams.INIT_SYNC) { override fun init() { - val compositeConfigurationProviderMappings = context.mappings.getMappedMap("CompositeConfigurationProvider") + val compositeConfigurationProviderMappings = context.mappings.getMappedMap("CompositeConfigurationProvider") ?: throw Exception("Failed to get compositeConfigurationProviderMappings") val enumMappings = compositeConfigurationProviderMappings["enum"] as Map<*, *> fun getConfigKeyInfo(key: Any?) = runCatching { diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/OperaViewerParamsOverride.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/OperaViewerParamsOverride.kt @@ -0,0 +1,64 @@ +package me.rhunk.snapenhance.core.features.impl + +import me.rhunk.snapenhance.core.features.Feature +import me.rhunk.snapenhance.core.features.FeatureLoadParams +import me.rhunk.snapenhance.core.util.hook.HookStage +import me.rhunk.snapenhance.core.util.hook.hook + +class OperaViewerParamsOverride : Feature("OperaViewerParamsOverride", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { + data class OverrideKey( + val name: String, + val defaultValue: Any? + ) + + data class Override( + val filter: (value: Any?) -> Boolean, + val value: (key: OverrideKey, value: Any?) -> Any? + ) + + override fun onActivityCreate() { + val operaViewerParamsMappings = context.mappings.getMappedMap("OperaViewerParams") ?: throw Exception("Failed to get operaViewerParamsMappings") + val overrideMap = mutableMapOf<String, Override>() + + fun overrideParam(key: String, filter: (value: Any?) -> Boolean, value: (overrideKey: OverrideKey, value: Any?) -> Any?) { + overrideMap[key] = Override(filter, value) + } + + if (context.config.messaging.loopMediaPlayback.get()) { + //https://github.com/rodit/SnapMod/blob/master/app/src/main/java/xyz/rodit/snapmod/features/opera/SnapDurationModifier.kt + overrideParam("auto_advance_mode", { true }, { key, _ -> key.defaultValue }) + overrideParam("auto_advance_max_loop_number", { true }, { _, _ -> Int.MAX_VALUE }) + overrideParam("media_playback_mode", { true }, { _, value -> + val playbackMode = value ?: return@overrideParam null + playbackMode::class.java.enumConstants.firstOrNull { + it.toString() == "LOOPING" + } ?: return@overrideParam value + }) + } + + findClass(operaViewerParamsMappings["class"].toString()).hook(operaViewerParamsMappings["putMethod"].toString(), HookStage.BEFORE) { param -> + val key = param.argNullable<Any>(0)?.let { key -> + val fields = key::class.java.fields + OverrideKey( + name = fields.firstOrNull { + it.type == String::class.java + }?.get(key)?.toString() ?: return@hook, + defaultValue = fields.firstOrNull { + it.type == Object::class.java + }?.get(key) + ) + } ?: return@hook + val value = param.argNullable<Any>(1) ?: return@hook + + overrideMap[key.name]?.let { override -> + if (override.filter(value)) { + runCatching { + param.setArg(1, override.value(key, value)) + }.onFailure { + context.log.error("Failed to override param $key", it) + } + } + } + } + } +}+ \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/MeoPasscodeBypass.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/MeoPasscodeBypass.kt @@ -7,7 +7,7 @@ import me.rhunk.snapenhance.core.util.hook.Hooker class MeoPasscodeBypass : Feature("Meo Passcode Bypass", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) { override fun asyncOnActivityCreate() { - val bcrypt = context.mappings.getMappedMap("BCrypt") + val bcrypt = context.mappings.getMappedMap("BCrypt") ?: throw Exception("Failed to get bcrypt mappings") Hooker.hook( context.androidContext.classLoader.loadClass(bcrypt["class"].toString()), diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/global/BypassVideoLengthRestriction.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/global/BypassVideoLengthRestriction.kt @@ -46,7 +46,7 @@ class BypassVideoLengthRestriction : } context.mappings.getMappedClass("DefaultMediaItem") - .hookConstructor(HookStage.BEFORE) { param -> + ?.hookConstructor(HookStage.BEFORE) { param -> //set the video length argument param.setArg(5, -1L) } @@ -54,7 +54,7 @@ class BypassVideoLengthRestriction : //TODO: allow split from any source if (mode == "split") { - val cameraRollId = context.mappings.getMappedMap("CameraRollMediaId") + val cameraRollId = context.mappings.getMappedMap("CameraRollMediaId") ?: throw Exception("Failed to get cameraRollId mappings") // memories grid findClass(cameraRollId["class"].toString()).hookConstructor(HookStage.AFTER) { param -> //set the durationMs field diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/global/MediaQualityLevelOverride.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/global/MediaQualityLevelOverride.kt @@ -7,8 +7,8 @@ import me.rhunk.snapenhance.core.util.hook.hook class MediaQualityLevelOverride : Feature("MediaQualityLevelOverride", loadParams = FeatureLoadParams.INIT_SYNC) { override fun init() { - val enumQualityLevel = context.mappings.getMappedClass("EnumQualityLevel") - val mediaQualityLevelProvider = context.mappings.getMappedMap("MediaQualityLevelProvider") + val enumQualityLevel = context.mappings.getMappedClass("EnumQualityLevel") ?: throw Exception("Failed to get enumQualityLevelMappings") + val mediaQualityLevelProvider = context.mappings.getMappedMap("MediaQualityLevelProvider") ?: throw Exception("Failed to get mediaQualityLevelProviderMappings") val forceMediaSourceQuality by context.config.global.forceUploadSourceQuality diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/HideQuickAddFriendFeed.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/HideQuickAddFriendFeed.kt @@ -10,7 +10,7 @@ class HideQuickAddFriendFeed : Feature("HideQuickAddFriendFeed", loadParams = Fe override fun onActivityCreate() { if (!context.config.userInterface.hideQuickAddFriendFeed.get()) return - val friendingDataSource = context.mappings.getMappedMap("FriendingDataSources") + val friendingDataSource = context.mappings.getMappedMap("FriendingDataSources") ?: throw Exception("Failed to get friendingDataSourceMappings") findClass(friendingDataSource["class"].toString()).hookConstructor(HookStage.AFTER) { param -> param.thisObject<Any>().setObjectField( friendingDataSource["quickAddSourceListField"].toString(), diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt @@ -8,6 +8,7 @@ import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.features.MessagingRuleFeature import me.rhunk.snapenhance.core.features.impl.ConfigurationOverride +import me.rhunk.snapenhance.core.features.impl.OperaViewerParamsOverride import me.rhunk.snapenhance.core.features.impl.ScopeSync import me.rhunk.snapenhance.core.features.impl.Stories import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader @@ -120,6 +121,7 @@ class FeatureManager( SuspendLocationUpdates::class, ConversationToolbox::class, SpotlightCommentsUsername::class, + OperaViewerParamsOverride::class, ) initializeFeatures() diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/OperaViewerParamsMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/OperaViewerParamsMapper.kt @@ -0,0 +1,32 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.ext.findConstString +import me.rhunk.snapenhance.mapper.ext.getClassName +import org.jf.dexlib2.iface.instruction.formats.Instruction35c +import org.jf.dexlib2.iface.reference.MethodReference + +class OperaViewerParamsMapper : AbstractClassMapper() { + init { + mapper { + for (classDef in classes) { + classDef.fields.firstOrNull { it.type == "Ljava/util/concurrent/ConcurrentHashMap;" } ?: continue + if (classDef.methods.firstOrNull { it.name == "toString" }?.implementation?.findConstString("Params") != true) continue + + val putMethod = classDef.methods.firstOrNull { method -> + method.implementation?.instructions?.any { + val instruction = it as? Instruction35c ?: return@any false + val reference = instruction.reference as? MethodReference ?: return@any false + reference.name == "put" && reference.definingClass == "Ljava/util/concurrent/ConcurrentHashMap;" + } == true + } ?: return@mapper + + addMapping("OperaViewerParams", + "class" to classDef.getClassName(), + "putMethod" to putMethod.name + ) + return@mapper + } + } + } +}+ \ No newline at end of file diff --git a/mapper/src/test/kotlin/me/rhunk/snapenhance/mapper/tests/TestMappings.kt b/mapper/src/test/kotlin/me/rhunk/snapenhance/mapper/tests/TestMappings.kt @@ -25,6 +25,7 @@ class TestMappings { FriendRelationshipChangerMapper::class, ViewBinderMapper::class, FriendingDataSourcesMapper::class, + OperaViewerParamsMapper::class, ) val gson = GsonBuilder().setPrettyPrinting().create()