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",
|
"name": "Unlimited Snap View Time",
|
||||||
"description": "Removes the Time Limit for viewing Snaps"
|
"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": {
|
"disable_replay_in_ff": {
|
||||||
"name": "Disable Replay in FF",
|
"name": "Disable Replay in FF",
|
||||||
"description": "Disables the ability to replay with a long press from the Friend Feed"
|
"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,
|
FriendRelationshipChangerMapper::class,
|
||||||
ViewBinderMapper::class,
|
ViewBinderMapper::class,
|
||||||
FriendingDataSourcesMapper::class,
|
FriendingDataSourcesMapper::class,
|
||||||
|
OperaViewerParamsMapper::class,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,29 +116,22 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
|
|||||||
return mappings[key]
|
return mappings[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMappedClass(className: String): Class<*> {
|
fun getMappedClass(className: String): Class<*>? {
|
||||||
return context.classLoader.loadClass(getMappedObject(className) as String)
|
return runCatching {
|
||||||
|
context.classLoader.loadClass(getMappedObject(className) as? String)
|
||||||
|
}.getOrNull()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMappedClass(key: String, subKey: String): Class<*> {
|
fun getMappedClass(key: String, subKey: String): Class<*> {
|
||||||
return context.classLoader.loadClass(getMappedValue(key, subKey))
|
return context.classLoader.loadClass(getMappedValue(key, subKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMappedValue(key: String): String {
|
fun getMappedValue(key: String, subKey: String): String? {
|
||||||
return getMappedObject(key) as String
|
return getMappedMap(key)?.get(subKey) as? String
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : Any> getMappedList(key: String): List<T> {
|
fun getMappedMap(key: String): Map<String, *>? {
|
||||||
return listOf(getMappedObject(key) as List<T>).flatten()
|
return getMappedObjectNullable(key) as? Map<String, *>
|
||||||
}
|
|
||||||
|
|
||||||
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, *>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -55,6 +55,7 @@ class MessagingTweaks : ConfigContainer() {
|
|||||||
val hideBitmojiPresence = boolean("hide_bitmoji_presence")
|
val hideBitmojiPresence = boolean("hide_bitmoji_presence")
|
||||||
val hideTypingNotifications = boolean("hide_typing_notifications")
|
val hideTypingNotifications = boolean("hide_typing_notifications")
|
||||||
val unlimitedSnapViewTime = boolean("unlimited_snap_view_time")
|
val unlimitedSnapViewTime = boolean("unlimited_snap_view_time")
|
||||||
|
val loopMediaPlayback = boolean("loop_media_playback") { requireRestart() }
|
||||||
val disableReplayInFF = boolean("disable_replay_in_ff")
|
val disableReplayInFF = boolean("disable_replay_in_ff")
|
||||||
val halfSwipeNotifier = container("half_swipe_notifier", HalfSwipeNotifierConfig()) { requireRestart()}
|
val halfSwipeNotifier = container("half_swipe_notifier", HalfSwipeNotifierConfig()) { requireRestart()}
|
||||||
val messagePreviewLength = integer("message_preview_length", defaultValue = 20)
|
val messagePreviewLength = integer("message_preview_length", defaultValue = 20)
|
||||||
|
@ -145,7 +145,7 @@ class BulkMessagingAction : AbstractAction() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun removeFriend(userId: String) {
|
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 friendRelationshipChangerInstance = context.feature(AddFriendSourceSpoof::class).friendRelationshipChangerInstance!!
|
||||||
|
|
||||||
val removeFriendMethod = friendRelationshipChangerInstance::class.java.methods.first {
|
val removeFriendMethod = friendRelationshipChangerInstance::class.java.methods.first {
|
||||||
|
@ -23,7 +23,7 @@ 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")
|
val compositeConfigurationProviderMappings = context.mappings.getMappedMap("CompositeConfigurationProvider") ?: throw Exception("Failed to get compositeConfigurationProviderMappings")
|
||||||
val enumMappings = compositeConfigurationProviderMappings["enum"] as Map<*, *>
|
val enumMappings = compositeConfigurationProviderMappings["enum"] as Map<*, *>
|
||||||
|
|
||||||
fun getConfigKeyInfo(key: Any?) = runCatching {
|
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) {
|
class MeoPasscodeBypass : Feature("Meo Passcode Bypass", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||||
override fun asyncOnActivityCreate() {
|
override fun asyncOnActivityCreate() {
|
||||||
val bcrypt = context.mappings.getMappedMap("BCrypt")
|
val bcrypt = context.mappings.getMappedMap("BCrypt") ?: throw Exception("Failed to get bcrypt mappings")
|
||||||
|
|
||||||
Hooker.hook(
|
Hooker.hook(
|
||||||
context.androidContext.classLoader.loadClass(bcrypt["class"].toString()),
|
context.androidContext.classLoader.loadClass(bcrypt["class"].toString()),
|
||||||
|
@ -46,7 +46,7 @@ class BypassVideoLengthRestriction :
|
|||||||
}
|
}
|
||||||
|
|
||||||
context.mappings.getMappedClass("DefaultMediaItem")
|
context.mappings.getMappedClass("DefaultMediaItem")
|
||||||
.hookConstructor(HookStage.BEFORE) { param ->
|
?.hookConstructor(HookStage.BEFORE) { param ->
|
||||||
//set the video length argument
|
//set the video length argument
|
||||||
param.setArg(5, -1L)
|
param.setArg(5, -1L)
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ class BypassVideoLengthRestriction :
|
|||||||
|
|
||||||
//TODO: allow split from any source
|
//TODO: allow split from any source
|
||||||
if (mode == "split") {
|
if (mode == "split") {
|
||||||
val cameraRollId = context.mappings.getMappedMap("CameraRollMediaId")
|
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 ->
|
findClass(cameraRollId["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
||||||
//set the durationMs field
|
//set the durationMs field
|
||||||
|
@ -7,8 +7,8 @@ import me.rhunk.snapenhance.core.util.hook.hook
|
|||||||
|
|
||||||
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")
|
val enumQualityLevel = context.mappings.getMappedClass("EnumQualityLevel") ?: throw Exception("Failed to get enumQualityLevelMappings")
|
||||||
val mediaQualityLevelProvider = context.mappings.getMappedMap("MediaQualityLevelProvider")
|
val mediaQualityLevelProvider = context.mappings.getMappedMap("MediaQualityLevelProvider") ?: throw Exception("Failed to get mediaQualityLevelProviderMappings")
|
||||||
|
|
||||||
val forceMediaSourceQuality by context.config.global.forceUploadSourceQuality
|
val forceMediaSourceQuality by context.config.global.forceUploadSourceQuality
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ class HideQuickAddFriendFeed : Feature("HideQuickAddFriendFeed", loadParams = Fe
|
|||||||
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")
|
val friendingDataSource = context.mappings.getMappedMap("FriendingDataSources") ?: throw Exception("Failed to get friendingDataSourceMappings")
|
||||||
findClass(friendingDataSource["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
findClass(friendingDataSource["class"].toString()).hookConstructor(HookStage.AFTER) { param ->
|
||||||
param.thisObject<Any>().setObjectField(
|
param.thisObject<Any>().setObjectField(
|
||||||
friendingDataSource["quickAddSourceListField"].toString(),
|
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.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.core.features.MessagingRuleFeature
|
import me.rhunk.snapenhance.core.features.MessagingRuleFeature
|
||||||
import me.rhunk.snapenhance.core.features.impl.ConfigurationOverride
|
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.ScopeSync
|
||||||
import me.rhunk.snapenhance.core.features.impl.Stories
|
import me.rhunk.snapenhance.core.features.impl.Stories
|
||||||
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
|
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
|
||||||
@ -120,6 +121,7 @@ class FeatureManager(
|
|||||||
SuspendLocationUpdates::class,
|
SuspendLocationUpdates::class,
|
||||||
ConversationToolbox::class,
|
ConversationToolbox::class,
|
||||||
SpotlightCommentsUsername::class,
|
SpotlightCommentsUsername::class,
|
||||||
|
OperaViewerParamsOverride::class,
|
||||||
)
|
)
|
||||||
|
|
||||||
initializeFeatures()
|
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,
|
FriendRelationshipChangerMapper::class,
|
||||||
ViewBinderMapper::class,
|
ViewBinderMapper::class,
|
||||||
FriendingDataSourcesMapper::class,
|
FriendingDataSourcesMapper::class,
|
||||||
|
OperaViewerParamsMapper::class,
|
||||||
)
|
)
|
||||||
|
|
||||||
val gson = GsonBuilder().setPrettyPrinting().create()
|
val gson = GsonBuilder().setPrettyPrinting().create()
|
||||||
|
Reference in New Issue
Block a user