From 3fe386be8196b661ab74ba0908e5702a6713e70d Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Sat, 23 Mar 2024 01:33:55 +0900 Subject: [PATCH] feat(YouTube Music): add `Enable swipe to dismiss miniplayer` patch --- .../SwipeToDismissMiniPlayerPatch.kt | 240 ++++++++++++++++++ .../HandleSearchRenderedFingerprint.kt | 9 + .../HandleSignInEventFingerprint.kt | 14 + .../InteractionLoggingEnumFingerprint.kt | 8 + .../MiniPlayerDefaultTextFingerprint.kt | 15 ++ ...iPlayerDefaultViewVisibilityFingerprint.kt | 21 ++ .../MusicActivityWidgetFingerprint.kt | 13 + .../fingerprints/SwipeToCloseFingerprint.kt | 9 + .../utils/resourceid/SharedResourceIdPatch.kt | 2 + .../music/utils/settings/SettingsPatch.kt | 2 + .../music/settings/host/values/strings.xml | 2 + 11 files changed, 335 insertions(+) create mode 100644 src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/SwipeToDismissMiniPlayerPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/HandleSearchRenderedFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/HandleSignInEventFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/InteractionLoggingEnumFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MiniPlayerDefaultTextFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MiniPlayerDefaultViewVisibilityFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MusicActivityWidgetFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/SwipeToCloseFingerprint.kt diff --git a/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/SwipeToDismissMiniPlayerPatch.kt b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/SwipeToDismissMiniPlayerPatch.kt new file mode 100644 index 000000000..16c3a757e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/SwipeToDismissMiniPlayerPatch.kt @@ -0,0 +1,240 @@ +package app.revanced.patches.music.player.swipetodismiss + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchException +import app.revanced.patcher.patch.annotation.CompatiblePackage +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patcher.util.smali.ExternalLabel +import app.revanced.patches.music.player.swipetodismiss.fingerprints.HandleSearchRenderedFingerprint +import app.revanced.patches.music.player.swipetodismiss.fingerprints.HandleSignInEventFingerprint +import app.revanced.patches.music.player.swipetodismiss.fingerprints.InteractionLoggingEnumFingerprint +import app.revanced.patches.music.player.swipetodismiss.fingerprints.MiniPlayerDefaultTextFingerprint +import app.revanced.patches.music.player.swipetodismiss.fingerprints.MiniPlayerDefaultViewVisibilityFingerprint +import app.revanced.patches.music.player.swipetodismiss.fingerprints.MusicActivityWidgetFingerprint +import app.revanced.patches.music.player.swipetodismiss.fingerprints.SwipeToCloseFingerprint +import app.revanced.patches.music.utils.integrations.Constants.PLAYER +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch +import app.revanced.patches.music.utils.settings.CategoryType +import app.revanced.patches.music.utils.settings.SettingsPatch +import app.revanced.util.exception +import app.revanced.util.getReference +import app.revanced.util.getStringInstructionIndex +import app.revanced.util.getTargetIndex +import app.revanced.util.getTargetIndexReversed +import app.revanced.util.getTargetIndexWithFieldReferenceType +import app.revanced.util.getWideLiteralInstructionIndex +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.iface.reference.Reference +import kotlin.properties.Delegates + +@Patch( + name = "Enable swipe to dismiss miniplayer", + description = "Adds an option to swipe down to dismiss the miniplayer.", + dependencies = [ + SettingsPatch::class, + SharedResourceIdPatch::class + ], + compatiblePackages = [ + CompatiblePackage( + "com.google.android.apps.youtube.music", + [ + "6.21.52", + "6.22.52", + "6.23.56", + "6.25.53", + "6.26.51", + "6.27.54", + "6.28.53", + "6.29.58", + "6.31.55", + "6.33.52" + ] + ) + ] +) +@Suppress("unused") +object SwipeToDismissMiniPlayerPatch : BytecodePatch( + setOf( + HandleSearchRenderedFingerprint, + InteractionLoggingEnumFingerprint, + MiniPlayerDefaultTextFingerprint, + MiniPlayerDefaultViewVisibilityFingerprint, + MusicActivityWidgetFingerprint, + SwipeToCloseFingerprint + ) +) { + private var widgetIndex by Delegates.notNull() + private lateinit var iGetObjectReference: Reference + private lateinit var invokeInterfacePrimaryReference: Reference + private lateinit var checkCastReference: Reference + private lateinit var sGetObjectReference: Reference + private lateinit var newInstanceReference: Reference + private lateinit var invokeStaticReference: Reference + private lateinit var invokeDirectReference: Reference + private lateinit var invokeInterfaceSecondaryReference: Reference + + override fun execute(context: BytecodeContext) { + + if (!SettingsPatch.upward0642) { + SwipeToCloseFingerprint.result?.let { + it.mutableMethod.apply { + val insertIndex = implementation!!.instructions.size - 1 + val targetRegister = getInstruction(insertIndex).registerA + + addInstructions( + insertIndex, """ + invoke-static {v$targetRegister}, $PLAYER->enableSwipeToDismissMiniPlayer(Z)Z + move-result v$targetRegister + """ + ) + } + } ?: throw SwipeToCloseFingerprint.exception + } else { + + // Dismiss mini player by swiping down + + InteractionLoggingEnumFingerprint.result?.let { + it.mutableMethod.apply { + val stringIndex = getStringInstructionIndex("INTERACTION_LOGGING_GESTURE_TYPE_SWIPE") + val sPutObjectIndex = getTargetIndex(stringIndex, Opcode.SPUT_OBJECT) + + sGetObjectReference = getInstruction(sPutObjectIndex).reference + } + } ?: throw InteractionLoggingEnumFingerprint.exception + + MusicActivityWidgetFingerprint.result?.let { + it.mutableMethod.apply { + widgetIndex = getWideLiteralInstructionIndex(79500) + + iGetObjectReference = getReference(Opcode.IGET_OBJECT, true) + invokeInterfacePrimaryReference = getReference(Opcode.INVOKE_INTERFACE, true) + checkCastReference = getReference(Opcode.CHECK_CAST, true) + newInstanceReference = getReference(Opcode.NEW_INSTANCE, true) + invokeStaticReference = getReference(Opcode.INVOKE_STATIC, false) + invokeDirectReference = getReference(Opcode.INVOKE_DIRECT, false) + invokeInterfaceSecondaryReference = getReference(Opcode.INVOKE_INTERFACE, false) + } + } ?: throw MusicActivityWidgetFingerprint.exception + + HandleSearchRenderedFingerprint.result?.let { parentResult -> + // Resolves fingerprints + HandleSignInEventFingerprint.resolve(context, parentResult.classDef) + + HandleSignInEventFingerprint.result?.let { + val dismissBehaviorMethod = context.toMethodWalker(it.method) + .nextMethod(it.scanResult.patternScanResult!!.startIndex, true) + .getMethod() as MutableMethod + + dismissBehaviorMethod.apply { + val insertIndex = getTargetIndexWithFieldReferenceType("Ljava/util/concurrent/atomic/AtomicBoolean;") + val primaryRegister = getInstruction(insertIndex).registerB + val secondaryRegister = primaryRegister + 1 + val tertiaryRegister = secondaryRegister + 1 + + val freeRegister = implementation!!.registerCount - parameters.size - 2 + + addInstructionsWithLabels( + insertIndex, """ + invoke-static {}, $PLAYER->enableSwipeToDismissMiniPlayer()Z + move-result v$freeRegister + if-nez v$freeRegister, :dismiss + iget-object v$primaryRegister, v$primaryRegister, $iGetObjectReference + invoke-interface {v$primaryRegister}, $invokeInterfacePrimaryReference + move-result-object v$primaryRegister + check-cast v$primaryRegister, $checkCastReference + sget-object v$secondaryRegister, $sGetObjectReference + new-instance v$tertiaryRegister, $newInstanceReference + const p0, 0x878b + invoke-static {p0}, $invokeStaticReference + move-result-object p0 + invoke-direct {v$tertiaryRegister, p0}, $invokeDirectReference + const/4 p0, 0x0 + invoke-interface {v$primaryRegister, v$secondaryRegister, v$tertiaryRegister, p0}, $invokeInterfaceSecondaryReference + return-void + """, ExternalLabel("dismiss", getInstruction(insertIndex)) + ) + } + } ?: throw HandleSignInEventFingerprint.exception + } ?: throw HandleSearchRenderedFingerprint.exception + + // Endregion + + // Hides default text display when the app is cold started + + MiniPlayerDefaultTextFingerprint.result?.let { + it.mutableMethod.apply { + val insertIndex = it.scanResult.patternScanResult!!.endIndex + val insertRegister = getInstruction(insertIndex).registerB + + addInstructions( + insertIndex, """ + invoke-static {v$insertRegister}, $PLAYER->enableSwipeToDismissMiniPlayer(Ljava/lang/Object;)Ljava/lang/Object; + move-result-object v$insertRegister + """ + ) + } + } ?: throw MiniPlayerDefaultTextFingerprint.exception + + // Endregion + + // Hides default text display after dismissing the mini player + + MiniPlayerDefaultViewVisibilityFingerprint.result?.let { + it.mutableClass.methods.find { method -> + method.parameters == listOf("Landroid/view/View;", "I") + }?.apply { + val bottomSheetBehaviorIndex = implementation!!.instructions.indexOfFirst { instruction -> + instruction.opcode == Opcode.INVOKE_VIRTUAL + && instruction.getReference()?.definingClass == "Lcom/google/android/material/bottomsheet/BottomSheetBehavior;" + && instruction.getReference()?.parameterTypes?.first() == "Z" + } + if (bottomSheetBehaviorIndex < 0) + throw PatchException("Could not find bottomSheetBehaviorIndex") + + val freeRegister = getInstruction(bottomSheetBehaviorIndex).registerD + + addInstructionsWithLabels( + bottomSheetBehaviorIndex - 2, """ + invoke-static {}, $PLAYER->enableSwipeToDismissMiniPlayer()Z + move-result v$freeRegister + if-nez v$freeRegister, :dismiss + """, ExternalLabel("dismiss", getInstruction(bottomSheetBehaviorIndex + 1)) + ) + } ?: throw PatchException("Could not find targetMethod") + + } ?: throw MiniPlayerDefaultViewVisibilityFingerprint.exception + + // Endregion + + } + + SettingsPatch.addMusicPreference( + CategoryType.PLAYER, + "revanced_enable_swipe_to_dismiss_mini_player", + "true" + ) + + } + + private fun MutableMethod.getReference( + opcode: Opcode, + reversed: Boolean + ): Reference { + val targetIndex = if (reversed) + getTargetIndexReversed(widgetIndex, opcode) + else + getTargetIndex(widgetIndex, opcode) + + return getInstruction(targetIndex).reference + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/HandleSearchRenderedFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/HandleSearchRenderedFingerprint.kt new file mode 100644 index 000000000..94d5c535b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/HandleSearchRenderedFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.music.player.swipetodismiss.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint + +object HandleSearchRenderedFingerprint : MethodFingerprint( + returnType = "V", + parameters = listOf("L"), + customFingerprint = { methodDef, _ -> methodDef.name == "handleSearchRendered" } +) diff --git a/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/HandleSignInEventFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/HandleSignInEventFingerprint.kt new file mode 100644 index 000000000..03dc02e3d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/HandleSignInEventFingerprint.kt @@ -0,0 +1,14 @@ +package app.revanced.patches.music.player.swipetodismiss.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.Opcode + +object HandleSignInEventFingerprint : MethodFingerprint( + returnType = "V", + parameters = listOf("L"), + opcodes = listOf( + Opcode.INVOKE_VIRTUAL, + Opcode.RETURN_VOID + ), + customFingerprint = { methodDef, _ -> methodDef.name == "handleSignInEvent" } +) diff --git a/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/InteractionLoggingEnumFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/InteractionLoggingEnumFingerprint.kt new file mode 100644 index 000000000..5cd05fe51 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/InteractionLoggingEnumFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.music.player.swipetodismiss.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint + +object InteractionLoggingEnumFingerprint : MethodFingerprint( + returnType = "V", + strings = listOf("INTERACTION_LOGGING_GESTURE_TYPE_SWIPE") +) diff --git a/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MiniPlayerDefaultTextFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MiniPlayerDefaultTextFingerprint.kt new file mode 100644 index 000000000..38c20031a --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MiniPlayerDefaultTextFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.music.player.swipetodismiss.fingerprints + +import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.MiniPlayerDefaultText +import app.revanced.util.fingerprint.LiteralValueFingerprint +import com.android.tools.smali.dexlib2.Opcode + +object MiniPlayerDefaultTextFingerprint : LiteralValueFingerprint( + returnType = "V", + parameters = listOf("Ljava/lang/Object;"), + opcodes = listOf( + Opcode.SGET_OBJECT, + Opcode.IF_NE + ), + literalSupplier = { MiniPlayerDefaultText } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MiniPlayerDefaultViewVisibilityFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MiniPlayerDefaultViewVisibilityFingerprint.kt new file mode 100644 index 000000000..96a501d2e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MiniPlayerDefaultViewVisibilityFingerprint.kt @@ -0,0 +1,21 @@ +package app.revanced.patches.music.player.swipetodismiss.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +object MiniPlayerDefaultViewVisibilityFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Landroid/view/View;", "F"), + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.SUB_FLOAT_2ADDR, + Opcode.SGET_OBJECT, + Opcode.INVOKE_VIRTUAL + ), + customFingerprint = { methodDef, classDef -> + methodDef.name == "a" && classDef.methods.count() == 3 + } +) diff --git a/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MusicActivityWidgetFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MusicActivityWidgetFingerprint.kt new file mode 100644 index 000000000..fbbef4bed --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/MusicActivityWidgetFingerprint.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.music.player.swipetodismiss.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.containsWideLiteralInstructionIndex + +object MusicActivityWidgetFingerprint : MethodFingerprint( + customFingerprint = handler@{ methodDef, _ -> + if (!methodDef.definingClass.endsWith("/MusicActivity;")) + return@handler false + + methodDef.containsWideLiteralInstructionIndex(79500) + } +) diff --git a/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/SwipeToCloseFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/SwipeToCloseFingerprint.kt new file mode 100644 index 000000000..19f655adf --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/swipetodismiss/fingerprints/SwipeToCloseFingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.music.player.swipetodismiss.fingerprints + +import app.revanced.util.fingerprint.LiteralValueFingerprint + +object SwipeToCloseFingerprint : LiteralValueFingerprint( + returnType = "Z", + parameters = emptyList(), + literalSupplier = { 45398432 } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt index 0e85f180e..e2e099b0f 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt @@ -29,6 +29,7 @@ object SharedResourceIdPatch : ResourcePatch() { var IsTablet: Long = -1 var LikeDislikeContainer: Long = -1 var MenuEntry: Long = -1 + var MiniPlayerDefaultText: Long = -1 var MiniPlayerMdxPlaying: Long = -1 var MiniPlayerPlayPauseReplayButton: Long = -1 var MusicNotifierShelf: Long = -1 @@ -66,6 +67,7 @@ object SharedResourceIdPatch : ResourcePatch() { IsTablet = find(BOOL, "is_tablet") LikeDislikeContainer = find(ID, "like_dislike_container") MenuEntry = find(LAYOUT, "menu_entry") + MiniPlayerDefaultText = find(STRING, "mini_player_default_text") MiniPlayerMdxPlaying = find(STRING, "mini_player_mdx_playing") MiniPlayerPlayPauseReplayButton = find(ID, "mini_player_play_pause_replay_button") MusicNotifierShelf = find(LAYOUT, "music_notifier_shelf") diff --git a/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt index a55e4939b..159abbc17 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/settings/SettingsPatch.kt @@ -76,6 +76,7 @@ object SettingsPatch : AbstractSettingsResourcePatch( val playServicesVersion = node.textContent.toInt() upward0636 = 240399000 <= playServicesVersion + upward0642 = 240999000 <= playServicesVersion break } @@ -146,6 +147,7 @@ object SettingsPatch : AbstractSettingsResourcePatch( lateinit var contexts: ResourceContext internal var upward0636: Boolean = false + internal var upward0642: Boolean = false internal fun addMusicPreference( category: CategoryType, diff --git a/src/main/resources/music/settings/host/values/strings.xml b/src/main/resources/music/settings/host/values/strings.xml index b6af03c3a..d758a08ad 100644 --- a/src/main/resources/music/settings/host/values/strings.xml +++ b/src/main/resources/music/settings/host/values/strings.xml @@ -67,6 +67,8 @@ Some features may not work properly in the old player layout." Enable save playback speed Remembers the last video quality selected. Enable save video quality + Enables swipe down to dismiss miniplayer. + Enable swipe to dismiss miniplayer Changes the player background to light grey to reduce eye strain. Enable zen mode Export settings to file