feat(YouTube Music): add Enable swipe to dismiss miniplayer patch

This commit is contained in:
inotia00 2024-03-23 01:33:55 +09:00
parent 9e3dd001eb
commit 3fe386be81
11 changed files with 335 additions and 0 deletions

View File

@ -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<Int>()
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<OneRegisterInstruction>(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<ReferenceInstruction>(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<TwoRegisterInstruction>(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<TwoRegisterInstruction>(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<MethodReference>()?.definingClass == "Lcom/google/android/material/bottomsheet/BottomSheetBehavior;"
&& instruction.getReference<MethodReference>()?.parameterTypes?.first() == "Z"
}
if (bottomSheetBehaviorIndex < 0)
throw PatchException("Could not find bottomSheetBehaviorIndex")
val freeRegister = getInstruction<FiveRegisterInstruction>(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<ReferenceInstruction>(targetIndex).reference
}
}

View File

@ -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" }
)

View File

@ -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" }
)

View File

@ -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")
)

View File

@ -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 }
)

View File

@ -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
}
)

View File

@ -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)
}
)

View File

@ -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 }
)

View File

@ -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")

View File

@ -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,

View File

@ -67,6 +67,8 @@ Some features may not work properly in the old player layout."</string>
<string name="revanced_enable_save_playback_speed_title">Enable save playback speed</string>
<string name="revanced_enable_save_video_quality_summary">Remembers the last video quality selected.</string>
<string name="revanced_enable_save_video_quality_title">Enable save video quality</string>
<string name="revanced_enable_swipe_to_dismiss_mini_player_summary">Enables swipe down to dismiss miniplayer.</string>
<string name="revanced_enable_swipe_to_dismiss_mini_player_title">Enable swipe to dismiss miniplayer</string>
<string name="revanced_enable_zen_mode_summary">Changes the player background to light grey to reduce eye strain.</string>
<string name="revanced_enable_zen_mode_title">Enable zen mode</string>
<string name="revanced_extended_settings_export_as_file">Export settings to file</string>