From 9cc0819c110bc29985ae212253efd0c555a7156c Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sat, 9 May 2026 18:00:09 +0530 Subject: [PATCH 01/12] feat(YouTube Music): Add 'Enable swipe to dismiss miniplayer' patch --- .../EnableSwipeToDismissMiniplayerPatch.java | 28 +++ .../extension/music/settings/Settings.java | 1 + .../EnableSwipeToDismissMiniplayerPatch.kt | 165 ++++++++++++++++++ .../music/layout/miniplayer/Fingerprints.kt | 49 ++++++ .../addresources/values/music/strings.xml | 5 + 5 files changed, 248 insertions(+) create mode 100644 extensions/music/src/main/java/app/morphe/extension/music/patches/EnableSwipeToDismissMiniplayerPatch.java create mode 100644 patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt diff --git a/extensions/music/src/main/java/app/morphe/extension/music/patches/EnableSwipeToDismissMiniplayerPatch.java b/extensions/music/src/main/java/app/morphe/extension/music/patches/EnableSwipeToDismissMiniplayerPatch.java new file mode 100644 index 0000000000..1d80d270c6 --- /dev/null +++ b/extensions/music/src/main/java/app/morphe/extension/music/patches/EnableSwipeToDismissMiniplayerPatch.java @@ -0,0 +1,28 @@ +/* + * Copyright 2026 Morphe. + * https://github.com/MorpheApp/morphe-patches + * + * See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to this code. + */ + +package app.morphe.extension.music.patches; + +import app.morphe.extension.music.settings.Settings; + +@SuppressWarnings("unused") +public class EnableSwipeToDismissMiniplayerPatch { + + /** + * Injection point + */ + public static boolean enableSwipeToDismissMiniplayer() { + return Settings.ENABLE_SWIPE_TO_DISMISS_MINIPLAYER.get(); + } + + /** + * Injection point + */ + public static Object enableSwipeToDismissMiniplayer(Object object) { + return Settings.ENABLE_SWIPE_TO_DISMISS_MINIPLAYER.get() ? null : object; + } +} \ No newline at end of file diff --git a/extensions/music/src/main/java/app/morphe/extension/music/settings/Settings.java b/extensions/music/src/main/java/app/morphe/extension/music/settings/Settings.java index b1f3c618fd..2db1dcf297 100644 --- a/extensions/music/src/main/java/app/morphe/extension/music/settings/Settings.java +++ b/extensions/music/src/main/java/app/morphe/extension/music/settings/Settings.java @@ -43,6 +43,7 @@ public class Settings extends SharedYouTubeSettings { public static final BooleanSetting MINIPLAYER_PREVIOUS_BUTTON = new BooleanSetting("morphe_music_miniplayer_previous_button", TRUE, true); public static final BooleanSetting CHANGE_MINIPLAYER_COLOR = new BooleanSetting("morphe_music_change_miniplayer_color", FALSE, true); public static final BooleanSetting ENABLE_FORCED_MINIPLAYER = new BooleanSetting("morphe_music_enable_forced_miniplayer", FALSE, true); + public static final BooleanSetting ENABLE_SWIPE_TO_DISMISS_MINIPLAYER = new BooleanSetting("morphe_music_enable_swipe_to_dismiss_miniplayer", FALSE, true); public static final BooleanSetting PERMANENT_REPEAT = new BooleanSetting("morphe_music_play_permanent_repeat", FALSE, true); // Crossfade diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt new file mode 100644 index 0000000000..5e1fb6b13a --- /dev/null +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt @@ -0,0 +1,165 @@ +/* + * Copyright 2026 Morphe. + * https://github.com/MorpheApp/morphe-patches + * + * See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to this code. + */ + +package app.morphe.patches.music.layout.miniplayer + +import app.morphe.patcher.extensions.InstructionExtensions.addInstructions +import app.morphe.patcher.extensions.InstructionExtensions.getInstruction +import app.morphe.patcher.extensions.InstructionExtensions.replaceInstruction +import app.morphe.patcher.patch.bytecodePatch +import app.morphe.patches.music.misc.extension.sharedExtensionPatch +import app.morphe.patches.music.misc.settings.PreferenceScreen +import app.morphe.patches.music.misc.settings.settingsPatch +import app.morphe.patches.music.shared.Constants.COMPATIBILITY_YOUTUBE_MUSIC +import app.morphe.patches.shared.misc.settings.preference.SwitchPreference +import app.morphe.util.addInstructionsAtControlFlowLabel +import app.morphe.util.getReference +import app.morphe.util.indexOfFirstInstructionOrThrow +import app.morphe.util.indexOfFirstInstructionReversedOrThrow +import app.morphe.util.indexOfFirstLiteralInstructionOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction10x +import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction +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.FieldReference +import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.iface.reference.StringReference + +private const val EXTENSION_CLASS = "Lapp/morphe/extension/music/patches/EnableSwipeToDismissMiniplayerPatch;" + +@Suppress("unused") +val enableSwipeToDismissMiniplayerPatch = bytecodePatch( + name = "Enable swipe to dismiss miniplayer", + description = "Adds an option to enable swipe to dismiss the miniplayer.\n\nIf this patch option is enabled, the empty miniplayer may not show when the miniplayer is dismissed." +) { + dependsOn( + sharedExtensionPatch, + settingsPatch + ) + + compatibleWith(COMPATIBILITY_YOUTUBE_MUSIC) + + execute { + PreferenceScreen.PLAYER.addPreferences( + SwitchPreference("morphe_music_enable_swipe_to_dismiss_miniplayer") + ) + + val swipeToDismissSGetObjectReference = InteractionLoggingEnumFingerprint.method.let { m -> + val stringIndex = m.indexOfFirstInstructionOrThrow { getReference()?.string == "INTERACTION_LOGGING_GESTURE_TYPE_SWIPE" } + val sPutObjectIndex = m.indexOfFirstInstructionOrThrow(stringIndex, Opcode.SPUT_OBJECT) + m.getInstruction(sPutObjectIndex).reference + } + + val musicActivityWidgetMethod = MusicActivityWidgetFingerprint.method + val swipeToDismissWidgetIndex = musicActivityWidgetMethod.indexOfFirstLiteralInstructionOrThrow(79500L) + + fun getSwipeToDismissReference(targetOpcode: Opcode, reversed: Boolean): com.android.tools.smali.dexlib2.iface.reference.Reference { + val targetIndex = if (reversed) + musicActivityWidgetMethod.indexOfFirstInstructionReversedOrThrow(swipeToDismissWidgetIndex) { + opcode == targetOpcode + } + else + musicActivityWidgetMethod.indexOfFirstInstructionOrThrow(swipeToDismissWidgetIndex, targetOpcode) + + return musicActivityWidgetMethod.getInstruction(targetIndex).reference + } + + val swipeToDismissIGetObjectReference = getSwipeToDismissReference(Opcode.IGET_OBJECT, true) + val swipeToDismissInvokeInterfacePrimaryReference = getSwipeToDismissReference(Opcode.INVOKE_INTERFACE, true) + val swipeToDismissCheckCastReference = getSwipeToDismissReference(Opcode.CHECK_CAST, true) + val swipeToDismissNewInstanceReference = getSwipeToDismissReference(Opcode.NEW_INSTANCE, true) + val swipeToDismissInvokeStaticReference = getSwipeToDismissReference(Opcode.INVOKE_STATIC, false) + val swipeToDismissInvokeDirectReference = getSwipeToDismissReference(Opcode.INVOKE_DIRECT, false) + val swipeToDismissInvokeInterfaceSecondaryReference = getSwipeToDismissReference(Opcode.INVOKE_INTERFACE, false) + val dismissBehaviorMethodRef = HandleSignInEventFingerprint.method.let { m -> + val returnIndex = m.indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) + val invokeIndex = m.indexOfFirstInstructionReversedOrThrow(returnIndex, Opcode.INVOKE_VIRTUAL) + + m.getInstruction(invokeIndex).reference as MethodReference + } + + val dismissBehaviorMethod = mutableClassDefBy(dismissBehaviorMethodRef.definingClass).methods.single { + it.name == dismissBehaviorMethodRef.name && it.parameters == dismissBehaviorMethodRef.parameterTypes && it.returnType == dismissBehaviorMethodRef.returnType + } + + dismissBehaviorMethod.apply { + val insertIndex = indexOfFirstInstructionOrThrow { + getReference()?.type == "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 + + addInstructionsAtControlFlowLabel( + insertIndex, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v$freeRegister + if-nez v$freeRegister, :dismiss + iget-object v$primaryRegister, v$primaryRegister, $swipeToDismissIGetObjectReference + invoke-interface {v$primaryRegister}, $swipeToDismissInvokeInterfacePrimaryReference + move-result-object v$primaryRegister + check-cast v$primaryRegister, $swipeToDismissCheckCastReference + sget-object v$secondaryRegister, $swipeToDismissSGetObjectReference + new-instance v$tertiaryRegister, $swipeToDismissNewInstanceReference + const p0, 0x878b + invoke-static {p0}, $swipeToDismissInvokeStaticReference + move-result-object p0 + invoke-direct {v$tertiaryRegister, p0}, $swipeToDismissInvokeDirectReference + const/4 p0, 0x0 + invoke-interface {v$primaryRegister, v$secondaryRegister, v$tertiaryRegister, p0}, $swipeToDismissInvokeInterfaceSecondaryReference + return-void + :dismiss + nop + """ + ) + } + + MiniPlayerDefaultTextFingerprint.method.apply { + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.IF_NE) + val insertRegister = getInstruction(insertIndex).registerB + + addInstructions( + insertIndex, """ + invoke-static {v$insertRegister}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer(Ljava/lang/Object;)Ljava/lang/Object; + move-result-object v$insertRegister + """ + ) + } + + val targetMethod = MiniPlayerDefaultViewVisibilityFingerprint.classDef.methods.first { + it.parameters == listOf("Landroid/view/View;", "I") + } + + targetMethod.apply { + val bottomSheetBehaviorIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.definingClass == "Lcom/google/android/material/bottomsheet/BottomSheetBehavior;" && + reference.parameterTypes.firstOrNull() == "Z" + } + + val invokeInstruction = getInstruction(bottomSheetBehaviorIndex) + val invokeReference = (invokeInstruction as ReferenceInstruction).reference as MethodReference + val registerC = invokeInstruction.registerC + val registerD = invokeInstruction.registerD + replaceInstruction(bottomSheetBehaviorIndex, BuilderInstruction10x(Opcode.NOP)) + + addInstructionsAtControlFlowLabel( + bottomSheetBehaviorIndex, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v$registerD + if-nez v$registerD, :skip_invoke + invoke-virtual {v$registerC, v$registerD}, $invokeReference + :skip_invoke + nop + """ + ) + } + } +} \ No newline at end of file diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt index 28b9b36e5b..fea4947360 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt @@ -9,6 +9,7 @@ import app.morphe.patcher.opcode import app.morphe.patcher.string import app.morphe.patches.all.misc.resources.ResourceType import app.morphe.patches.all.misc.resources.resourceLiteral +import app.morphe.util.indexOfFirstLiteralInstruction import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -65,3 +66,51 @@ internal object MppWatchWhileLayoutFingerprint : Fingerprint( returnType = "V", parameters = listOf(), ) + +internal object InteractionLoggingEnumFingerprint : Fingerprint( + returnType = "V", + strings = listOf("INTERACTION_LOGGING_GESTURE_TYPE_SWIPE") +) + +internal object MusicActivityWidgetFingerprint : Fingerprint( + custom = { method, classDef -> + classDef.type.endsWith("/MusicActivity;") && + method.indexOfFirstLiteralInstruction(79500L) >= 0 + } +) + +internal object HandleSearchRenderedFingerprint : Fingerprint( + returnType = "V", + name = "handleSearchRendered" +) + +internal object HandleSignInEventFingerprint : Fingerprint( + classFingerprint = HandleSearchRenderedFingerprint, + returnType = "V", + name = "handleSignInEvent", + filters = listOf(opcode(Opcode.INVOKE_VIRTUAL), opcode(Opcode.RETURN_VOID)) +) + +internal object MiniPlayerDefaultTextFingerprint : Fingerprint( + returnType = "V", + parameters = listOf("Ljava/lang/Object;"), + filters = listOf( + opcode(Opcode.SGET_OBJECT), + opcode(Opcode.IF_NE), + resourceLiteral(ResourceType.STRING, "mini_player_default_text") + ) +) + +internal object MiniPlayerDefaultViewVisibilityFingerprint : Fingerprint( + accessFlags = listOf(AccessFlags.PUBLIC, AccessFlags.FINAL), + returnType = "V", + parameters = listOf("Landroid/view/View;", "F"), + filters = listOf( + opcode(Opcode.IGET_OBJECT), + opcode(Opcode.SUB_FLOAT_2ADDR), + opcode(Opcode.SGET_OBJECT), + opcode(Opcode.INVOKE_VIRTUAL) + ), + name = "a", + custom = { _, classDef -> classDef.methods.count() == 3 } +) \ No newline at end of file diff --git a/patches/src/main/resources/addresources/values/music/strings.xml b/patches/src/main/resources/addresources/values/music/strings.xml index 7e76f398ce..f1d299d4ed 100644 --- a/patches/src/main/resources/addresources/values/music/strings.xml +++ b/patches/src/main/resources/addresources/values/music/strings.xml @@ -147,6 +147,11 @@ Feature implementation by VazerOG" Miniplayer stays minimized when playback changes Miniplayer expands to fullscreen when playback changes + + Enable swipe to dismiss miniplayer + Swiping down on miniplayer dismisses it + Swiping down on miniplayer does nothing + Navigation bar Hide or change navigation bar buttons From 39b0ba49f5fe18fb57ce6def4e1556fc70885f1d Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sat, 9 May 2026 18:55:15 +0530 Subject: [PATCH 02/12] refactor: Formatting --- .../EnableSwipeToDismissMiniplayerPatch.kt | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt index 5e1fb6b13a..9a40cde3d8 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt @@ -98,25 +98,25 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( addInstructionsAtControlFlowLabel( insertIndex, """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v$freeRegister - if-nez v$freeRegister, :dismiss - iget-object v$primaryRegister, v$primaryRegister, $swipeToDismissIGetObjectReference - invoke-interface {v$primaryRegister}, $swipeToDismissInvokeInterfacePrimaryReference - move-result-object v$primaryRegister - check-cast v$primaryRegister, $swipeToDismissCheckCastReference - sget-object v$secondaryRegister, $swipeToDismissSGetObjectReference - new-instance v$tertiaryRegister, $swipeToDismissNewInstanceReference - const p0, 0x878b - invoke-static {p0}, $swipeToDismissInvokeStaticReference - move-result-object p0 - invoke-direct {v$tertiaryRegister, p0}, $swipeToDismissInvokeDirectReference - const/4 p0, 0x0 - invoke-interface {v$primaryRegister, v$secondaryRegister, v$tertiaryRegister, p0}, $swipeToDismissInvokeInterfaceSecondaryReference - return-void - :dismiss - nop - """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v$freeRegister + if-nez v$freeRegister, :dismiss + iget-object v$primaryRegister, v$primaryRegister, $swipeToDismissIGetObjectReference + invoke-interface {v$primaryRegister}, $swipeToDismissInvokeInterfacePrimaryReference + move-result-object v$primaryRegister + check-cast v$primaryRegister, $swipeToDismissCheckCastReference + sget-object v$secondaryRegister, $swipeToDismissSGetObjectReference + new-instance v$tertiaryRegister, $swipeToDismissNewInstanceReference + const p0, 0x878b + invoke-static {p0}, $swipeToDismissInvokeStaticReference + move-result-object p0 + invoke-direct {v$tertiaryRegister, p0}, $swipeToDismissInvokeDirectReference + const/4 p0, 0x0 + invoke-interface {v$primaryRegister, v$secondaryRegister, v$tertiaryRegister, p0}, $swipeToDismissInvokeInterfaceSecondaryReference + return-void + :dismiss + nop + """ ) } From ac2891f8f31f1271dc595c25f0faec67cbb5190f Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sat, 9 May 2026 22:59:55 +0530 Subject: [PATCH 03/12] fix: Patch failing on experimental versions --- .../EnableSwipeToDismissMiniplayerPatch.kt | 28 +++++++++++++------ .../music/layout/miniplayer/Fingerprints.kt | 3 -- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt index 9a40cde3d8..14dafb0102 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt @@ -17,6 +17,7 @@ import app.morphe.patches.music.misc.settings.settingsPatch import app.morphe.patches.music.shared.Constants.COMPATIBILITY_YOUTUBE_MUSIC import app.morphe.patches.shared.misc.settings.preference.SwitchPreference import app.morphe.util.addInstructionsAtControlFlowLabel +import app.morphe.util.getFreeRegisterProvider import app.morphe.util.getReference import app.morphe.util.indexOfFirstInstructionOrThrow import app.morphe.util.indexOfFirstInstructionReversedOrThrow @@ -92,9 +93,10 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( getReference()?.type == "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 + val registerProvider = getFreeRegisterProvider(insertIndex, 3, primaryRegister) + val freeRegister = registerProvider.getFreeRegister() + val secondaryRegister = registerProvider.getFreeRegister() + val tertiaryRegister = registerProvider.getFreeRegister() addInstructionsAtControlFlowLabel( insertIndex, """ @@ -121,15 +123,23 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( } MiniPlayerDefaultTextFingerprint.method.apply { - val insertIndex = indexOfFirstInstructionOrThrow(Opcode.IF_NE) - val insertRegister = getInstruction(insertIndex).registerB + if (parameters.isEmpty()) { + addInstructions(0, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v0 + if-eqz v0, :continue_exec + return-void + :continue_exec + """) + } else { + val insertIndex = indexOfFirstInstructionOrThrow(Opcode.IF_NE) + val insertRegister = getInstruction(insertIndex).registerB - addInstructions( - insertIndex, """ + addInstructions(insertIndex, """ invoke-static {v$insertRegister}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer(Ljava/lang/Object;)Ljava/lang/Object; move-result-object v$insertRegister - """ - ) + """) + } } val targetMethod = MiniPlayerDefaultViewVisibilityFingerprint.classDef.methods.first { diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt index fea4947360..6a5cc7808b 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt @@ -93,10 +93,7 @@ internal object HandleSignInEventFingerprint : Fingerprint( internal object MiniPlayerDefaultTextFingerprint : Fingerprint( returnType = "V", - parameters = listOf("Ljava/lang/Object;"), filters = listOf( - opcode(Opcode.SGET_OBJECT), - opcode(Opcode.IF_NE), resourceLiteral(ResourceType.STRING, "mini_player_default_text") ) ) From aceeed64c4103e9e06bcc7599a6a70c0f7c8a2c6 Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sun, 10 May 2026 00:53:14 +0530 Subject: [PATCH 04/12] fix: Patch failing --- .../EnableSwipeToDismissMiniplayerPatch.kt | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt index 14dafb0102..43c05df036 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt @@ -17,7 +17,7 @@ import app.morphe.patches.music.misc.settings.settingsPatch import app.morphe.patches.music.shared.Constants.COMPATIBILITY_YOUTUBE_MUSIC import app.morphe.patches.shared.misc.settings.preference.SwitchPreference import app.morphe.util.addInstructionsAtControlFlowLabel -import app.morphe.util.getFreeRegisterProvider +import app.morphe.util.findFreeRegister import app.morphe.util.getReference import app.morphe.util.indexOfFirstInstructionOrThrow import app.morphe.util.indexOfFirstInstructionReversedOrThrow @@ -93,29 +93,42 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( getReference()?.type == "Ljava/util/concurrent/atomic/AtomicBoolean;" } val primaryRegister = getInstruction(insertIndex).registerB - val registerProvider = getFreeRegisterProvider(insertIndex, 3, primaryRegister) - val freeRegister = registerProvider.getFreeRegister() - val secondaryRegister = registerProvider.getFreeRegister() - val tertiaryRegister = registerProvider.getFreeRegister() + val freeRegister = findFreeRegister(insertIndex, primaryRegister) + val totalRegs = implementation!!.registerCount + val clobberRegs = (0 until totalRegs).filter { it != primaryRegister } + + if (clobberRegs.size < 3) { + throw IllegalStateException("Method lacks sufficient registers for injection (total: ${totalRegs})") + } + + val secondaryRegister = clobberRegs[0] + val tertiaryRegister = clobberRegs[1] + val nullRegister = clobberRegs[2] addInstructionsAtControlFlowLabel( insertIndex, """ invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z move-result v$freeRegister if-nez v$freeRegister, :dismiss + + # We are safe to aggressively clobber inside here iget-object v$primaryRegister, v$primaryRegister, $swipeToDismissIGetObjectReference invoke-interface {v$primaryRegister}, $swipeToDismissInvokeInterfacePrimaryReference move-result-object v$primaryRegister check-cast v$primaryRegister, $swipeToDismissCheckCastReference + sget-object v$secondaryRegister, $swipeToDismissSGetObjectReference new-instance v$tertiaryRegister, $swipeToDismissNewInstanceReference - const p0, 0x878b - invoke-static {p0}, $swipeToDismissInvokeStaticReference - move-result-object p0 - invoke-direct {v$tertiaryRegister, p0}, $swipeToDismissInvokeDirectReference - const/4 p0, 0x0 - invoke-interface {v$primaryRegister, v$secondaryRegister, v$tertiaryRegister, p0}, $swipeToDismissInvokeInterfaceSecondaryReference + + const v$nullRegister, 0x878b + invoke-static {v$nullRegister}, $swipeToDismissInvokeStaticReference + move-result-object v$nullRegister + invoke-direct {v$tertiaryRegister, v$nullRegister}, $swipeToDismissInvokeDirectReference + + const/4 v$nullRegister, 0x0 + invoke-interface {v$primaryRegister, v$secondaryRegister, v$tertiaryRegister, v$nullRegister}, $swipeToDismissInvokeInterfaceSecondaryReference return-void + :dismiss nop """ From 2aed0689d78ec62de5e4de0931d0fcc76d062607 Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sun, 10 May 2026 00:54:37 +0530 Subject: [PATCH 05/12] refactor: Description --- .../layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt index 43c05df036..65f69ecb07 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt @@ -36,7 +36,7 @@ private const val EXTENSION_CLASS = "Lapp/morphe/extension/music/patches/EnableS @Suppress("unused") val enableSwipeToDismissMiniplayerPatch = bytecodePatch( name = "Enable swipe to dismiss miniplayer", - description = "Adds an option to enable swipe to dismiss the miniplayer.\n\nIf this patch option is enabled, the empty miniplayer may not show when the miniplayer is dismissed." + description = "Adds an option to enable dismissing the miniplayer by swiping down on it." ) { dependsOn( sharedExtensionPatch, From a2c660a8e291f92b45fb62d5f807fdc48e40ccc9 Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sun, 10 May 2026 00:56:12 +0530 Subject: [PATCH 06/12] Refactor --- .../layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt index 65f69ecb07..77e0c58d3c 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt @@ -29,6 +29,7 @@ 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.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference +import com.android.tools.smali.dexlib2.iface.reference.Reference import com.android.tools.smali.dexlib2.iface.reference.StringReference private const val EXTENSION_CLASS = "Lapp/morphe/extension/music/patches/EnableSwipeToDismissMiniplayerPatch;" @@ -59,7 +60,7 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( val musicActivityWidgetMethod = MusicActivityWidgetFingerprint.method val swipeToDismissWidgetIndex = musicActivityWidgetMethod.indexOfFirstLiteralInstructionOrThrow(79500L) - fun getSwipeToDismissReference(targetOpcode: Opcode, reversed: Boolean): com.android.tools.smali.dexlib2.iface.reference.Reference { + fun getSwipeToDismissReference(targetOpcode: Opcode, reversed: Boolean): Reference { val targetIndex = if (reversed) musicActivityWidgetMethod.indexOfFirstInstructionReversedOrThrow(swipeToDismissWidgetIndex) { opcode == targetOpcode From 3379543769eee6572fb6d1b8b7bb4dabb5968af7 Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sun, 10 May 2026 01:05:48 +0530 Subject: [PATCH 07/12] Refactor --- .../patches/music/interaction/crossfade/CrossfadePatch.kt | 4 ++-- ...ChangeMiniplayerColor.kt => ChangeMiniplayerColorPatch.kt} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/{ChangeMiniplayerColor.kt => ChangeMiniplayerColorPatch.kt} (100%) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/interaction/crossfade/CrossfadePatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/interaction/crossfade/CrossfadePatch.kt index dd41a83172..0451a9a38d 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/interaction/crossfade/CrossfadePatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/interaction/crossfade/CrossfadePatch.kt @@ -162,8 +162,8 @@ val crossfadePatch = bytecodePatch( val log = Logger.getLogger(this::class.java.name) if (!is_8_05_or_greater || is_9_00_or_greater) { return@execute log.warning( - "Track crossfade is not yet available for YouTube Music 9.x. " + - "Patch YouTube Music 8.44.54–8.50.51 for crossfade.", + "Track crossfade is not supported on YouTube Music 9.x. " + + "Please patch versions 8.44.54 through 8.50.51 for this feature.", ) } diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/ChangeMiniplayerColor.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/ChangeMiniplayerColorPatch.kt similarity index 100% rename from patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/ChangeMiniplayerColor.kt rename to patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/ChangeMiniplayerColorPatch.kt From 9aeb4afddd3a7e939e01f9f78496aff6885d5edb Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sun, 10 May 2026 01:09:18 +0530 Subject: [PATCH 08/12] refactor: Add "Patch" --- .../buttons/{HideButtons.kt => HideButtonsPatch.kt} | 2 +- .../{HideCategoryBar.kt => HideCategoryBarPatch.kt} | 2 +- .../layout/miniplayer/ChangeMiniplayerColorPatch.kt | 2 +- .../resources/addresources/values/music/strings.xml | 12 ++++++------ 4 files changed, 9 insertions(+), 9 deletions(-) rename patches/src/main/kotlin/app/morphe/patches/music/layout/buttons/{HideButtons.kt => HideButtonsPatch.kt} (99%) rename patches/src/main/kotlin/app/morphe/patches/music/layout/compactheader/{HideCategoryBar.kt => HideCategoryBarPatch.kt} (97%) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/buttons/HideButtons.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/buttons/HideButtonsPatch.kt similarity index 99% rename from patches/src/main/kotlin/app/morphe/patches/music/layout/buttons/HideButtons.kt rename to patches/src/main/kotlin/app/morphe/patches/music/layout/buttons/HideButtonsPatch.kt index 4f39141b20..6ee43fd842 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/buttons/HideButtons.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/buttons/HideButtonsPatch.kt @@ -21,7 +21,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction private const val EXTENSION_CLASS = "Lapp/morphe/extension/music/patches/HideButtonsPatch;" @Suppress("unused") -val hideButtons = bytecodePatch( +val hideButtonsPatch = bytecodePatch( name = "Hide buttons", description = "Adds options to hide the cast, history, notification, and search buttons." ) { diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/compactheader/HideCategoryBar.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/compactheader/HideCategoryBarPatch.kt similarity index 97% rename from patches/src/main/kotlin/app/morphe/patches/music/layout/compactheader/HideCategoryBar.kt rename to patches/src/main/kotlin/app/morphe/patches/music/layout/compactheader/HideCategoryBarPatch.kt index ef14379a37..e53b5c5ee5 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/compactheader/HideCategoryBar.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/compactheader/HideCategoryBarPatch.kt @@ -13,7 +13,7 @@ import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction private const val EXTENSION_CLASS = "Lapp/morphe/extension/music/patches/HideCategoryBarPatch;" @Suppress("unused") -val hideCategoryBar = bytecodePatch( +val hideCategoryBarPatch = bytecodePatch( name = "Hide category bar", description = "Adds an option to hide the category bar at the top of the homepage." ) { diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/ChangeMiniplayerColorPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/ChangeMiniplayerColorPatch.kt index fed5f4b1bd..957b6af186 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/ChangeMiniplayerColorPatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/ChangeMiniplayerColorPatch.kt @@ -24,7 +24,7 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference private const val EXTENSION_CLASS = "Lapp/morphe/extension/music/patches/ChangeMiniplayerColorPatch;" @Suppress("unused") -val changeMiniplayerColor = bytecodePatch( +val changeMiniplayerColorPatch = bytecodePatch( name = "Change miniplayer color", description = "Adds an option to change the miniplayer background color to match the fullscreen player." ) { diff --git a/patches/src/main/resources/addresources/values/music/strings.xml b/patches/src/main/resources/addresources/values/music/strings.xml index f1d299d4ed..457644278e 100644 --- a/patches/src/main/resources/addresources/values/music/strings.xml +++ b/patches/src/main/resources/addresources/values/music/strings.xml @@ -97,7 +97,7 @@ Feature implementation by VazerOG" Permanent repeat is enabled Permanent repeat is disabled - + Hide cast button Cast button is hidden Cast button is shown @@ -124,17 +124,17 @@ Feature implementation by VazerOG" Search Subscriptions - + Hide category bar Category bar is hidden Category bar is shown - + Change miniplayer color Miniplayer color matches fullscreen player Miniplayer uses default color - + Show next button in miniplayer Next button is shown in the miniplayer Next button is not shown in the miniplayer @@ -142,12 +142,12 @@ Feature implementation by VazerOG" Previous button is shown in the miniplayer Previous button is not shown in the miniplayer - + Enable forced miniplayer Miniplayer stays minimized when playback changes Miniplayer expands to fullscreen when playback changes - + Enable swipe to dismiss miniplayer Swiping down on miniplayer dismisses it Swiping down on miniplayer does nothing From 4aa7eebe289017489f0cfee1402f576bc07dcb1e Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sun, 10 May 2026 01:26:30 +0530 Subject: [PATCH 09/12] fix: Patch not working on experimental versions --- .../EnableSwipeToDismissMiniplayerPatch.kt | 14 ++++++++++++++ .../music/layout/miniplayer/Fingerprints.kt | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt index 77e0c58d3c..b48396b9aa 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt @@ -185,5 +185,19 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( """ ) } + + BottomSheetSetHideableFingerprint.method.apply { + addInstructions(0, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v0 + if-eqz v0, :skip_override + + # Force the parameter to true (1) + const/4 p1, 1 + + :skip_override + nop + """) + } } } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt index 6a5cc7808b..0b12eb92e2 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt @@ -110,4 +110,16 @@ internal object MiniPlayerDefaultViewVisibilityFingerprint : Fingerprint( ), name = "a", custom = { _, classDef -> classDef.methods.count() == 3 } +) + +internal object BottomSheetSetHideableFingerprint : Fingerprint( + returnType = "V", + parameters = listOf("Z"), + filters = listOf( + opcode(Opcode.IGET_BOOLEAN), + opcode(Opcode.IF_EQ), + opcode(Opcode.IPUT_BOOLEAN), + opcode(Opcode.CONST_4), + opcode(Opcode.IF_NE) + ) ) \ No newline at end of file From 8467907ae33879c4793b0372e1dac91807852af7 Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sun, 10 May 2026 03:37:17 +0530 Subject: [PATCH 10/12] fix: Patch not working on experimental versions (part 2) --- .../EnableSwipeToDismissMiniplayerPatch.kt | 281 ++++++++++-------- .../music/layout/miniplayer/Fingerprints.kt | 26 +- 2 files changed, 174 insertions(+), 133 deletions(-) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt index b48396b9aa..f335f66e5a 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt @@ -1,17 +1,19 @@ /* * Copyright 2026 Morphe. * https://github.com/MorpheApp/morphe-patches - * - * See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to this code. */ package app.morphe.patches.music.layout.miniplayer import app.morphe.patcher.extensions.InstructionExtensions.addInstructions +import app.morphe.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.morphe.patcher.extensions.InstructionExtensions.getInstruction import app.morphe.patcher.extensions.InstructionExtensions.replaceInstruction import app.morphe.patcher.patch.bytecodePatch +import app.morphe.patcher.util.smali.ExternalLabel import app.morphe.patches.music.misc.extension.sharedExtensionPatch +import app.morphe.patches.music.misc.playservice.is_9_00_or_greater +import app.morphe.patches.music.misc.playservice.versionCheckPatch import app.morphe.patches.music.misc.settings.PreferenceScreen import app.morphe.patches.music.misc.settings.settingsPatch import app.morphe.patches.music.shared.Constants.COMPATIBILITY_YOUTUBE_MUSIC @@ -41,7 +43,8 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( ) { dependsOn( sharedExtensionPatch, - settingsPatch + settingsPatch, + versionCheckPatch ) compatibleWith(COMPATIBILITY_YOUTUBE_MUSIC) @@ -51,101 +54,141 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( SwitchPreference("morphe_music_enable_swipe_to_dismiss_miniplayer") ) - val swipeToDismissSGetObjectReference = InteractionLoggingEnumFingerprint.method.let { m -> - val stringIndex = m.indexOfFirstInstructionOrThrow { getReference()?.string == "INTERACTION_LOGGING_GESTURE_TYPE_SWIPE" } - val sPutObjectIndex = m.indexOfFirstInstructionOrThrow(stringIndex, Opcode.SPUT_OBJECT) - m.getInstruction(sPutObjectIndex).reference - } + if (is_9_00_or_greater) { - val musicActivityWidgetMethod = MusicActivityWidgetFingerprint.method - val swipeToDismissWidgetIndex = musicActivityWidgetMethod.indexOfFirstLiteralInstructionOrThrow(79500L) + MiniPlayerDefaultTextFingerprint.method.apply { - fun getSwipeToDismissReference(targetOpcode: Opcode, reversed: Boolean): Reference { - val targetIndex = if (reversed) - musicActivityWidgetMethod.indexOfFirstInstructionReversedOrThrow(swipeToDismissWidgetIndex) { - opcode == targetOpcode - } - else - musicActivityWidgetMethod.indexOfFirstInstructionOrThrow(swipeToDismissWidgetIndex, targetOpcode) + addInstructions(0, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v0 + if-eqz v0, :continue_exec + return-void + :continue_exec + nop + """) + } - return musicActivityWidgetMethod.getInstruction(targetIndex).reference - } + RogOnSlideFingerprint.method.apply { + val constIndex = indexOfFirstInstructionOrThrow(Opcode.CONST_4) + val invokeIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.INVOKE_VIRTUAL) + val freeRegister = findFreeRegister(invokeIndex) + val targetInstruction = getInstruction(invokeIndex + 1) + + addInstructionsWithLabels( + invokeIndex, + """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v$freeRegister + if-eqz v$freeRegister, :skip_snap + """, + ExternalLabel("skip_snap", targetInstruction) + ) + } - val swipeToDismissIGetObjectReference = getSwipeToDismissReference(Opcode.IGET_OBJECT, true) - val swipeToDismissInvokeInterfacePrimaryReference = getSwipeToDismissReference(Opcode.INVOKE_INTERFACE, true) - val swipeToDismissCheckCastReference = getSwipeToDismissReference(Opcode.CHECK_CAST, true) - val swipeToDismissNewInstanceReference = getSwipeToDismissReference(Opcode.NEW_INSTANCE, true) - val swipeToDismissInvokeStaticReference = getSwipeToDismissReference(Opcode.INVOKE_STATIC, false) - val swipeToDismissInvokeDirectReference = getSwipeToDismissReference(Opcode.INVOKE_DIRECT, false) - val swipeToDismissInvokeInterfaceSecondaryReference = getSwipeToDismissReference(Opcode.INVOKE_INTERFACE, false) - val dismissBehaviorMethodRef = HandleSignInEventFingerprint.method.let { m -> - val returnIndex = m.indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) - val invokeIndex = m.indexOfFirstInstructionReversedOrThrow(returnIndex, Opcode.INVOKE_VIRTUAL) - - m.getInstruction(invokeIndex).reference as MethodReference - } + RogOnStateChangedFingerprint.method.apply { + val constIndex = indexOfFirstInstructionOrThrow(Opcode.CONST_4) + val invokeIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.INVOKE_VIRTUAL) + val freeRegister = findFreeRegister(invokeIndex) + val targetInstruction = getInstruction(invokeIndex + 1) + + addInstructionsWithLabels( + invokeIndex, + """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v$freeRegister + if-eqz v$freeRegister, :skip_lock + """, + ExternalLabel("skip_lock", targetInstruction) + ) + } - val dismissBehaviorMethod = mutableClassDefBy(dismissBehaviorMethodRef.definingClass).methods.single { - it.name == dismissBehaviorMethodRef.name && it.parameters == dismissBehaviorMethodRef.parameterTypes && it.returnType == dismissBehaviorMethodRef.returnType - } + } else { - dismissBehaviorMethod.apply { - val insertIndex = indexOfFirstInstructionOrThrow { - getReference()?.type == "Ljava/util/concurrent/atomic/AtomicBoolean;" + val swipeToDismissSGetObjectReference = InteractionLoggingEnumFingerprint.method.let { m -> + val stringIndex = m.indexOfFirstInstructionOrThrow { getReference()?.string == "INTERACTION_LOGGING_GESTURE_TYPE_SWIPE" } + val sPutObjectIndex = m.indexOfFirstInstructionOrThrow(stringIndex, Opcode.SPUT_OBJECT) + m.getInstruction(sPutObjectIndex).reference } - val primaryRegister = getInstruction(insertIndex).registerB - val freeRegister = findFreeRegister(insertIndex, primaryRegister) - val totalRegs = implementation!!.registerCount - val clobberRegs = (0 until totalRegs).filter { it != primaryRegister } - if (clobberRegs.size < 3) { - throw IllegalStateException("Method lacks sufficient registers for injection (total: ${totalRegs})") + val musicActivityWidgetMethod = MusicActivityWidgetFingerprint.method + val swipeToDismissWidgetIndex = musicActivityWidgetMethod.indexOfFirstLiteralInstructionOrThrow(79500L) + + fun getSwipeToDismissReference(targetOpcode: Opcode, reversed: Boolean): Reference { + val targetIndex = if (reversed) + musicActivityWidgetMethod.indexOfFirstInstructionReversedOrThrow(swipeToDismissWidgetIndex) { + opcode == targetOpcode + } + else + musicActivityWidgetMethod.indexOfFirstInstructionOrThrow(swipeToDismissWidgetIndex, targetOpcode) + + return musicActivityWidgetMethod.getInstruction(targetIndex).reference } - val secondaryRegister = clobberRegs[0] - val tertiaryRegister = clobberRegs[1] - val nullRegister = clobberRegs[2] + val swipeToDismissIGetObjectReference = getSwipeToDismissReference(Opcode.IGET_OBJECT, true) + val swipeToDismissInvokeInterfacePrimaryReference = getSwipeToDismissReference(Opcode.INVOKE_INTERFACE, true) + val swipeToDismissCheckCastReference = getSwipeToDismissReference(Opcode.CHECK_CAST, true) + val swipeToDismissNewInstanceReference = getSwipeToDismissReference(Opcode.NEW_INSTANCE, true) + val swipeToDismissInvokeStaticReference = getSwipeToDismissReference(Opcode.INVOKE_STATIC, false) + val swipeToDismissInvokeDirectReference = getSwipeToDismissReference(Opcode.INVOKE_DIRECT, false) + val swipeToDismissInvokeInterfaceSecondaryReference = getSwipeToDismissReference(Opcode.INVOKE_INTERFACE, false) + val dismissBehaviorMethodRef = HandleSignInEventFingerprint.method.let { m -> + val returnIndex = m.indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) + val invokeIndex = m.indexOfFirstInstructionReversedOrThrow(returnIndex, Opcode.INVOKE_VIRTUAL) + + m.getInstruction(invokeIndex).reference as MethodReference + } - addInstructionsAtControlFlowLabel( - insertIndex, """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v$freeRegister - if-nez v$freeRegister, :dismiss - - # We are safe to aggressively clobber inside here - iget-object v$primaryRegister, v$primaryRegister, $swipeToDismissIGetObjectReference - invoke-interface {v$primaryRegister}, $swipeToDismissInvokeInterfacePrimaryReference - move-result-object v$primaryRegister - check-cast v$primaryRegister, $swipeToDismissCheckCastReference - - sget-object v$secondaryRegister, $swipeToDismissSGetObjectReference - new-instance v$tertiaryRegister, $swipeToDismissNewInstanceReference - - const v$nullRegister, 0x878b - invoke-static {v$nullRegister}, $swipeToDismissInvokeStaticReference - move-result-object v$nullRegister - invoke-direct {v$tertiaryRegister, v$nullRegister}, $swipeToDismissInvokeDirectReference - - const/4 v$nullRegister, 0x0 - invoke-interface {v$primaryRegister, v$secondaryRegister, v$tertiaryRegister, v$nullRegister}, $swipeToDismissInvokeInterfaceSecondaryReference - return-void - - :dismiss - nop - """ - ) - } + val dismissBehaviorMethod = mutableClassDefBy(dismissBehaviorMethodRef.definingClass).methods.single { + it.name == dismissBehaviorMethodRef.name && it.parameters == dismissBehaviorMethodRef.parameterTypes && it.returnType == dismissBehaviorMethodRef.returnType + } - MiniPlayerDefaultTextFingerprint.method.apply { - if (parameters.isEmpty()) { - addInstructions(0, """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v0 - if-eqz v0, :continue_exec - return-void - :continue_exec - """) - } else { + dismissBehaviorMethod.apply { + val insertIndex = indexOfFirstInstructionOrThrow { + getReference()?.type == "Ljava/util/concurrent/atomic/AtomicBoolean;" + } + val primaryRegister = getInstruction(insertIndex).registerB + val freeRegister = findFreeRegister(insertIndex, primaryRegister) + val totalRegs = implementation!!.registerCount + val clobberRegs = (0 until totalRegs).filter { it != primaryRegister } + + if (clobberRegs.size < 3) { + throw IllegalStateException("Method lacks sufficient registers for injection (total: ${totalRegs})") + } + + val secondaryRegister = clobberRegs[0] + val tertiaryRegister = clobberRegs[1] + val nullRegister = clobberRegs[2] + + addInstructionsAtControlFlowLabel( + insertIndex, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v$freeRegister + if-nez v$freeRegister, :dismiss + + iget-object v$primaryRegister, v$primaryRegister, $swipeToDismissIGetObjectReference + invoke-interface {v$primaryRegister}, $swipeToDismissInvokeInterfacePrimaryReference + move-result-object v$primaryRegister + check-cast v$primaryRegister, $swipeToDismissCheckCastReference + + sget-object v$secondaryRegister, $swipeToDismissSGetObjectReference + new-instance v$tertiaryRegister, $swipeToDismissNewInstanceReference + + const v$nullRegister, 0x878b + invoke-static {v$nullRegister}, $swipeToDismissInvokeStaticReference + move-result-object v$nullRegister + invoke-direct {v$tertiaryRegister, v$nullRegister}, $swipeToDismissInvokeDirectReference + + const/4 v$nullRegister, 0x0 + invoke-interface {v$primaryRegister, v$secondaryRegister, v$tertiaryRegister, v$nullRegister}, $swipeToDismissInvokeInterfaceSecondaryReference + return-void + + :dismiss + nop + """ + ) + } + + MiniPlayerDefaultTextFingerprint.method.apply { val insertIndex = indexOfFirstInstructionOrThrow(Opcode.IF_NE) val insertRegister = getInstruction(insertIndex).registerB @@ -154,50 +197,36 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( move-result-object v$insertRegister """) } - } - val targetMethod = MiniPlayerDefaultViewVisibilityFingerprint.classDef.methods.first { - it.parameters == listOf("Landroid/view/View;", "I") - } - - targetMethod.apply { - val bottomSheetBehaviorIndex = indexOfFirstInstructionOrThrow { - val reference = getReference() - opcode == Opcode.INVOKE_VIRTUAL && - reference?.definingClass == "Lcom/google/android/material/bottomsheet/BottomSheetBehavior;" && - reference.parameterTypes.firstOrNull() == "Z" + val targetMethod = MiniPlayerDefaultViewVisibilityFingerprint.classDef.methods.first { + it.parameters == listOf("Landroid/view/View;", "I") } - val invokeInstruction = getInstruction(bottomSheetBehaviorIndex) - val invokeReference = (invokeInstruction as ReferenceInstruction).reference as MethodReference - val registerC = invokeInstruction.registerC - val registerD = invokeInstruction.registerD - replaceInstruction(bottomSheetBehaviorIndex, BuilderInstruction10x(Opcode.NOP)) - - addInstructionsAtControlFlowLabel( - bottomSheetBehaviorIndex, """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v$registerD - if-nez v$registerD, :skip_invoke - invoke-virtual {v$registerC, v$registerD}, $invokeReference - :skip_invoke - nop - """ - ) - } + targetMethod.apply { + val bottomSheetBehaviorIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.definingClass == "Lcom/google/android/material/bottomsheet/BottomSheetBehavior;" && + reference.parameterTypes.firstOrNull() == "Z" + } - BottomSheetSetHideableFingerprint.method.apply { - addInstructions(0, """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v0 - if-eqz v0, :skip_override - - # Force the parameter to true (1) - const/4 p1, 1 - - :skip_override - nop - """) + val invokeInstruction = getInstruction(bottomSheetBehaviorIndex) + val invokeReference = (invokeInstruction as ReferenceInstruction).reference as MethodReference + val registerC = invokeInstruction.registerC + val registerD = invokeInstruction.registerD + replaceInstruction(bottomSheetBehaviorIndex, BuilderInstruction10x(Opcode.NOP)) + + addInstructionsAtControlFlowLabel( + bottomSheetBehaviorIndex, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v$registerD + if-nez v$registerD, :skip_invoke + invoke-virtual {v$registerC, v$registerD}, $invokeReference + :skip_invoke + nop + """ + ) + } } } } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt index 0b12eb92e2..1574830dad 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt @@ -112,14 +112,26 @@ internal object MiniPlayerDefaultViewVisibilityFingerprint : Fingerprint( custom = { _, classDef -> classDef.methods.count() == 3 } ) -internal object BottomSheetSetHideableFingerprint : Fingerprint( +internal object RogOnSlideFingerprint : Fingerprint( returnType = "V", - parameters = listOf("Z"), + parameters = listOf("Landroid/view/View;", "F"), filters = listOf( - opcode(Opcode.IGET_BOOLEAN), - opcode(Opcode.IF_EQ), - opcode(Opcode.IPUT_BOOLEAN), + opcode(Opcode.IGET_OBJECT), + opcode(Opcode.IGET_OBJECT), opcode(Opcode.CONST_4), - opcode(Opcode.IF_NE) - ) + opcode(Opcode.INVOKE_VIRTUAL) + ), + custom = { method, _ -> method.name == "a" } +) + +internal object RogOnStateChangedFingerprint : Fingerprint( + returnType = "V", + parameters = listOf("Landroid/view/View;", "I"), + filters = listOf( + opcode(Opcode.IGET_OBJECT), + opcode(Opcode.IGET_OBJECT), + opcode(Opcode.CONST_4), + opcode(Opcode.INVOKE_VIRTUAL) + ), + custom = { method, _ -> method.name == "b" } ) \ No newline at end of file From 0f3ab2ac1e5e3a1aee914f175e8bb1db0b4dce20 Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sun, 10 May 2026 03:45:55 +0530 Subject: [PATCH 11/12] Revert "fix: Patch not working on experimental versions (part 2)" This reverts commit 8467907ae33879c4793b0372e1dac91807852af7. --- .../EnableSwipeToDismissMiniplayerPatch.kt | 281 ++++++++---------- .../music/layout/miniplayer/Fingerprints.kt | 26 +- 2 files changed, 133 insertions(+), 174 deletions(-) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt index f335f66e5a..b48396b9aa 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt @@ -1,19 +1,17 @@ /* * Copyright 2026 Morphe. * https://github.com/MorpheApp/morphe-patches + * + * See the included NOTICE file for GPLv3 §7(b) and §7(c) terms that apply to this code. */ package app.morphe.patches.music.layout.miniplayer import app.morphe.patcher.extensions.InstructionExtensions.addInstructions -import app.morphe.patcher.extensions.InstructionExtensions.addInstructionsWithLabels import app.morphe.patcher.extensions.InstructionExtensions.getInstruction import app.morphe.patcher.extensions.InstructionExtensions.replaceInstruction import app.morphe.patcher.patch.bytecodePatch -import app.morphe.patcher.util.smali.ExternalLabel import app.morphe.patches.music.misc.extension.sharedExtensionPatch -import app.morphe.patches.music.misc.playservice.is_9_00_or_greater -import app.morphe.patches.music.misc.playservice.versionCheckPatch import app.morphe.patches.music.misc.settings.PreferenceScreen import app.morphe.patches.music.misc.settings.settingsPatch import app.morphe.patches.music.shared.Constants.COMPATIBILITY_YOUTUBE_MUSIC @@ -43,8 +41,7 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( ) { dependsOn( sharedExtensionPatch, - settingsPatch, - versionCheckPatch + settingsPatch ) compatibleWith(COMPATIBILITY_YOUTUBE_MUSIC) @@ -54,141 +51,101 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( SwitchPreference("morphe_music_enable_swipe_to_dismiss_miniplayer") ) - if (is_9_00_or_greater) { - - MiniPlayerDefaultTextFingerprint.method.apply { - - addInstructions(0, """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v0 - if-eqz v0, :continue_exec - return-void - :continue_exec - nop - """) - } - - RogOnSlideFingerprint.method.apply { - val constIndex = indexOfFirstInstructionOrThrow(Opcode.CONST_4) - val invokeIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.INVOKE_VIRTUAL) - val freeRegister = findFreeRegister(invokeIndex) - val targetInstruction = getInstruction(invokeIndex + 1) - - addInstructionsWithLabels( - invokeIndex, - """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v$freeRegister - if-eqz v$freeRegister, :skip_snap - """, - ExternalLabel("skip_snap", targetInstruction) - ) - } - - RogOnStateChangedFingerprint.method.apply { - val constIndex = indexOfFirstInstructionOrThrow(Opcode.CONST_4) - val invokeIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.INVOKE_VIRTUAL) - val freeRegister = findFreeRegister(invokeIndex) - val targetInstruction = getInstruction(invokeIndex + 1) - - addInstructionsWithLabels( - invokeIndex, - """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v$freeRegister - if-eqz v$freeRegister, :skip_lock - """, - ExternalLabel("skip_lock", targetInstruction) - ) - } + val swipeToDismissSGetObjectReference = InteractionLoggingEnumFingerprint.method.let { m -> + val stringIndex = m.indexOfFirstInstructionOrThrow { getReference()?.string == "INTERACTION_LOGGING_GESTURE_TYPE_SWIPE" } + val sPutObjectIndex = m.indexOfFirstInstructionOrThrow(stringIndex, Opcode.SPUT_OBJECT) + m.getInstruction(sPutObjectIndex).reference + } - } else { + val musicActivityWidgetMethod = MusicActivityWidgetFingerprint.method + val swipeToDismissWidgetIndex = musicActivityWidgetMethod.indexOfFirstLiteralInstructionOrThrow(79500L) - val swipeToDismissSGetObjectReference = InteractionLoggingEnumFingerprint.method.let { m -> - val stringIndex = m.indexOfFirstInstructionOrThrow { getReference()?.string == "INTERACTION_LOGGING_GESTURE_TYPE_SWIPE" } - val sPutObjectIndex = m.indexOfFirstInstructionOrThrow(stringIndex, Opcode.SPUT_OBJECT) - m.getInstruction(sPutObjectIndex).reference - } + fun getSwipeToDismissReference(targetOpcode: Opcode, reversed: Boolean): Reference { + val targetIndex = if (reversed) + musicActivityWidgetMethod.indexOfFirstInstructionReversedOrThrow(swipeToDismissWidgetIndex) { + opcode == targetOpcode + } + else + musicActivityWidgetMethod.indexOfFirstInstructionOrThrow(swipeToDismissWidgetIndex, targetOpcode) - val musicActivityWidgetMethod = MusicActivityWidgetFingerprint.method - val swipeToDismissWidgetIndex = musicActivityWidgetMethod.indexOfFirstLiteralInstructionOrThrow(79500L) + return musicActivityWidgetMethod.getInstruction(targetIndex).reference + } - fun getSwipeToDismissReference(targetOpcode: Opcode, reversed: Boolean): Reference { - val targetIndex = if (reversed) - musicActivityWidgetMethod.indexOfFirstInstructionReversedOrThrow(swipeToDismissWidgetIndex) { - opcode == targetOpcode - } - else - musicActivityWidgetMethod.indexOfFirstInstructionOrThrow(swipeToDismissWidgetIndex, targetOpcode) + val swipeToDismissIGetObjectReference = getSwipeToDismissReference(Opcode.IGET_OBJECT, true) + val swipeToDismissInvokeInterfacePrimaryReference = getSwipeToDismissReference(Opcode.INVOKE_INTERFACE, true) + val swipeToDismissCheckCastReference = getSwipeToDismissReference(Opcode.CHECK_CAST, true) + val swipeToDismissNewInstanceReference = getSwipeToDismissReference(Opcode.NEW_INSTANCE, true) + val swipeToDismissInvokeStaticReference = getSwipeToDismissReference(Opcode.INVOKE_STATIC, false) + val swipeToDismissInvokeDirectReference = getSwipeToDismissReference(Opcode.INVOKE_DIRECT, false) + val swipeToDismissInvokeInterfaceSecondaryReference = getSwipeToDismissReference(Opcode.INVOKE_INTERFACE, false) + val dismissBehaviorMethodRef = HandleSignInEventFingerprint.method.let { m -> + val returnIndex = m.indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) + val invokeIndex = m.indexOfFirstInstructionReversedOrThrow(returnIndex, Opcode.INVOKE_VIRTUAL) + + m.getInstruction(invokeIndex).reference as MethodReference + } - return musicActivityWidgetMethod.getInstruction(targetIndex).reference - } + val dismissBehaviorMethod = mutableClassDefBy(dismissBehaviorMethodRef.definingClass).methods.single { + it.name == dismissBehaviorMethodRef.name && it.parameters == dismissBehaviorMethodRef.parameterTypes && it.returnType == dismissBehaviorMethodRef.returnType + } - val swipeToDismissIGetObjectReference = getSwipeToDismissReference(Opcode.IGET_OBJECT, true) - val swipeToDismissInvokeInterfacePrimaryReference = getSwipeToDismissReference(Opcode.INVOKE_INTERFACE, true) - val swipeToDismissCheckCastReference = getSwipeToDismissReference(Opcode.CHECK_CAST, true) - val swipeToDismissNewInstanceReference = getSwipeToDismissReference(Opcode.NEW_INSTANCE, true) - val swipeToDismissInvokeStaticReference = getSwipeToDismissReference(Opcode.INVOKE_STATIC, false) - val swipeToDismissInvokeDirectReference = getSwipeToDismissReference(Opcode.INVOKE_DIRECT, false) - val swipeToDismissInvokeInterfaceSecondaryReference = getSwipeToDismissReference(Opcode.INVOKE_INTERFACE, false) - val dismissBehaviorMethodRef = HandleSignInEventFingerprint.method.let { m -> - val returnIndex = m.indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) - val invokeIndex = m.indexOfFirstInstructionReversedOrThrow(returnIndex, Opcode.INVOKE_VIRTUAL) - - m.getInstruction(invokeIndex).reference as MethodReference + dismissBehaviorMethod.apply { + val insertIndex = indexOfFirstInstructionOrThrow { + getReference()?.type == "Ljava/util/concurrent/atomic/AtomicBoolean;" } + val primaryRegister = getInstruction(insertIndex).registerB + val freeRegister = findFreeRegister(insertIndex, primaryRegister) + val totalRegs = implementation!!.registerCount + val clobberRegs = (0 until totalRegs).filter { it != primaryRegister } - val dismissBehaviorMethod = mutableClassDefBy(dismissBehaviorMethodRef.definingClass).methods.single { - it.name == dismissBehaviorMethodRef.name && it.parameters == dismissBehaviorMethodRef.parameterTypes && it.returnType == dismissBehaviorMethodRef.returnType + if (clobberRegs.size < 3) { + throw IllegalStateException("Method lacks sufficient registers for injection (total: ${totalRegs})") } - dismissBehaviorMethod.apply { - val insertIndex = indexOfFirstInstructionOrThrow { - getReference()?.type == "Ljava/util/concurrent/atomic/AtomicBoolean;" - } - val primaryRegister = getInstruction(insertIndex).registerB - val freeRegister = findFreeRegister(insertIndex, primaryRegister) - val totalRegs = implementation!!.registerCount - val clobberRegs = (0 until totalRegs).filter { it != primaryRegister } - - if (clobberRegs.size < 3) { - throw IllegalStateException("Method lacks sufficient registers for injection (total: ${totalRegs})") - } + val secondaryRegister = clobberRegs[0] + val tertiaryRegister = clobberRegs[1] + val nullRegister = clobberRegs[2] - val secondaryRegister = clobberRegs[0] - val tertiaryRegister = clobberRegs[1] - val nullRegister = clobberRegs[2] - - addInstructionsAtControlFlowLabel( - insertIndex, """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v$freeRegister - if-nez v$freeRegister, :dismiss - - iget-object v$primaryRegister, v$primaryRegister, $swipeToDismissIGetObjectReference - invoke-interface {v$primaryRegister}, $swipeToDismissInvokeInterfacePrimaryReference - move-result-object v$primaryRegister - check-cast v$primaryRegister, $swipeToDismissCheckCastReference - - sget-object v$secondaryRegister, $swipeToDismissSGetObjectReference - new-instance v$tertiaryRegister, $swipeToDismissNewInstanceReference - - const v$nullRegister, 0x878b - invoke-static {v$nullRegister}, $swipeToDismissInvokeStaticReference - move-result-object v$nullRegister - invoke-direct {v$tertiaryRegister, v$nullRegister}, $swipeToDismissInvokeDirectReference - - const/4 v$nullRegister, 0x0 - invoke-interface {v$primaryRegister, v$secondaryRegister, v$tertiaryRegister, v$nullRegister}, $swipeToDismissInvokeInterfaceSecondaryReference - return-void - - :dismiss - nop - """ - ) - } + addInstructionsAtControlFlowLabel( + insertIndex, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v$freeRegister + if-nez v$freeRegister, :dismiss + + # We are safe to aggressively clobber inside here + iget-object v$primaryRegister, v$primaryRegister, $swipeToDismissIGetObjectReference + invoke-interface {v$primaryRegister}, $swipeToDismissInvokeInterfacePrimaryReference + move-result-object v$primaryRegister + check-cast v$primaryRegister, $swipeToDismissCheckCastReference + + sget-object v$secondaryRegister, $swipeToDismissSGetObjectReference + new-instance v$tertiaryRegister, $swipeToDismissNewInstanceReference + + const v$nullRegister, 0x878b + invoke-static {v$nullRegister}, $swipeToDismissInvokeStaticReference + move-result-object v$nullRegister + invoke-direct {v$tertiaryRegister, v$nullRegister}, $swipeToDismissInvokeDirectReference + + const/4 v$nullRegister, 0x0 + invoke-interface {v$primaryRegister, v$secondaryRegister, v$tertiaryRegister, v$nullRegister}, $swipeToDismissInvokeInterfaceSecondaryReference + return-void + + :dismiss + nop + """ + ) + } - MiniPlayerDefaultTextFingerprint.method.apply { + MiniPlayerDefaultTextFingerprint.method.apply { + if (parameters.isEmpty()) { + addInstructions(0, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v0 + if-eqz v0, :continue_exec + return-void + :continue_exec + """) + } else { val insertIndex = indexOfFirstInstructionOrThrow(Opcode.IF_NE) val insertRegister = getInstruction(insertIndex).registerB @@ -197,36 +154,50 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( move-result-object v$insertRegister """) } + } - val targetMethod = MiniPlayerDefaultViewVisibilityFingerprint.classDef.methods.first { - it.parameters == listOf("Landroid/view/View;", "I") + val targetMethod = MiniPlayerDefaultViewVisibilityFingerprint.classDef.methods.first { + it.parameters == listOf("Landroid/view/View;", "I") + } + + targetMethod.apply { + val bottomSheetBehaviorIndex = indexOfFirstInstructionOrThrow { + val reference = getReference() + opcode == Opcode.INVOKE_VIRTUAL && + reference?.definingClass == "Lcom/google/android/material/bottomsheet/BottomSheetBehavior;" && + reference.parameterTypes.firstOrNull() == "Z" } - targetMethod.apply { - val bottomSheetBehaviorIndex = indexOfFirstInstructionOrThrow { - val reference = getReference() - opcode == Opcode.INVOKE_VIRTUAL && - reference?.definingClass == "Lcom/google/android/material/bottomsheet/BottomSheetBehavior;" && - reference.parameterTypes.firstOrNull() == "Z" - } + val invokeInstruction = getInstruction(bottomSheetBehaviorIndex) + val invokeReference = (invokeInstruction as ReferenceInstruction).reference as MethodReference + val registerC = invokeInstruction.registerC + val registerD = invokeInstruction.registerD + replaceInstruction(bottomSheetBehaviorIndex, BuilderInstruction10x(Opcode.NOP)) - val invokeInstruction = getInstruction(bottomSheetBehaviorIndex) - val invokeReference = (invokeInstruction as ReferenceInstruction).reference as MethodReference - val registerC = invokeInstruction.registerC - val registerD = invokeInstruction.registerD - replaceInstruction(bottomSheetBehaviorIndex, BuilderInstruction10x(Opcode.NOP)) - - addInstructionsAtControlFlowLabel( - bottomSheetBehaviorIndex, """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v$registerD - if-nez v$registerD, :skip_invoke - invoke-virtual {v$registerC, v$registerD}, $invokeReference - :skip_invoke - nop - """ - ) - } + addInstructionsAtControlFlowLabel( + bottomSheetBehaviorIndex, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v$registerD + if-nez v$registerD, :skip_invoke + invoke-virtual {v$registerC, v$registerD}, $invokeReference + :skip_invoke + nop + """ + ) + } + + BottomSheetSetHideableFingerprint.method.apply { + addInstructions(0, """ + invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z + move-result v0 + if-eqz v0, :skip_override + + # Force the parameter to true (1) + const/4 p1, 1 + + :skip_override + nop + """) } } } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt index 1574830dad..0b12eb92e2 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt @@ -112,26 +112,14 @@ internal object MiniPlayerDefaultViewVisibilityFingerprint : Fingerprint( custom = { _, classDef -> classDef.methods.count() == 3 } ) -internal object RogOnSlideFingerprint : Fingerprint( +internal object BottomSheetSetHideableFingerprint : Fingerprint( returnType = "V", - parameters = listOf("Landroid/view/View;", "F"), + parameters = listOf("Z"), filters = listOf( - opcode(Opcode.IGET_OBJECT), - opcode(Opcode.IGET_OBJECT), + opcode(Opcode.IGET_BOOLEAN), + opcode(Opcode.IF_EQ), + opcode(Opcode.IPUT_BOOLEAN), opcode(Opcode.CONST_4), - opcode(Opcode.INVOKE_VIRTUAL) - ), - custom = { method, _ -> method.name == "a" } -) - -internal object RogOnStateChangedFingerprint : Fingerprint( - returnType = "V", - parameters = listOf("Landroid/view/View;", "I"), - filters = listOf( - opcode(Opcode.IGET_OBJECT), - opcode(Opcode.IGET_OBJECT), - opcode(Opcode.CONST_4), - opcode(Opcode.INVOKE_VIRTUAL) - ), - custom = { method, _ -> method.name == "b" } + opcode(Opcode.IF_NE) + ) ) \ No newline at end of file From 6871d9b555d6b8727573d22d8ce25d4a55b4d4a6 Mon Sep 17 00:00:00 2001 From: ILoveOpenSouceApplications <117499019+ILoveOpenSourceApplications@users.noreply.github.com> Date: Sun, 10 May 2026 03:45:55 +0530 Subject: [PATCH 12/12] Revert "fix: Patch not working on experimental versions" This reverts commit 4aa7eebe289017489f0cfee1402f576bc07dcb1e. --- .../EnableSwipeToDismissMiniplayerPatch.kt | 14 -------------- .../music/layout/miniplayer/Fingerprints.kt | 12 ------------ 2 files changed, 26 deletions(-) diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt index b48396b9aa..77e0c58d3c 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/EnableSwipeToDismissMiniplayerPatch.kt @@ -185,19 +185,5 @@ val enableSwipeToDismissMiniplayerPatch = bytecodePatch( """ ) } - - BottomSheetSetHideableFingerprint.method.apply { - addInstructions(0, """ - invoke-static {}, $EXTENSION_CLASS->enableSwipeToDismissMiniplayer()Z - move-result v0 - if-eqz v0, :skip_override - - # Force the parameter to true (1) - const/4 p1, 1 - - :skip_override - nop - """) - } } } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt index 0b12eb92e2..6a5cc7808b 100644 --- a/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt +++ b/patches/src/main/kotlin/app/morphe/patches/music/layout/miniplayer/Fingerprints.kt @@ -110,16 +110,4 @@ internal object MiniPlayerDefaultViewVisibilityFingerprint : Fingerprint( ), name = "a", custom = { _, classDef -> classDef.methods.count() == 3 } -) - -internal object BottomSheetSetHideableFingerprint : Fingerprint( - returnType = "V", - parameters = listOf("Z"), - filters = listOf( - opcode(Opcode.IGET_BOOLEAN), - opcode(Opcode.IF_EQ), - opcode(Opcode.IPUT_BOOLEAN), - opcode(Opcode.CONST_4), - opcode(Opcode.IF_NE) - ) ) \ No newline at end of file