mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 05:37:48 +02:00
feat: loop media playback
- refactor mappings wrapper
This commit is contained in:
@ -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"
|
||||
|
@ -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, *>
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(),
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -25,6 +25,7 @@ class TestMappings {
|
||||
FriendRelationshipChangerMapper::class,
|
||||
ViewBinderMapper::class,
|
||||
FriendingDataSourcesMapper::class,
|
||||
OperaViewerParamsMapper::class,
|
||||
)
|
||||
|
||||
val gson = GsonBuilder().setPrettyPrinting().create()
|
||||
|
Reference in New Issue
Block a user