From 8333bf73d53afecceb7f1b6841c203ba18c1ab46 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Mon, 30 Sep 2024 21:39:44 +0900 Subject: [PATCH] fix(YouTube Music/Player components): `Remember shuffle state` setting does not remember the correct state --- .../components/PlayerComponentsPatch.kt | 211 ++++++------------ .../ShuffleClassReferenceFingerprint.kt | 40 ---- .../fingerprints/ShuffleOnClickFingerprint.kt | 30 +++ .../utils/resourceid/SharedResourceIdPatch.kt | 2 - .../kotlin/app/revanced/util/BytecodeUtils.kt | 2 +- 5 files changed, 102 insertions(+), 183 deletions(-) delete mode 100644 src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleClassReferenceFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleOnClickFingerprint.kt diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt b/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt index 1cead791fd..cecb66c698 100644 --- a/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt @@ -35,15 +35,14 @@ import app.revanced.patches.music.player.components.fingerprints.PlayerViewPager import app.revanced.patches.music.player.components.fingerprints.QuickSeekOverlayFingerprint import app.revanced.patches.music.player.components.fingerprints.RemixGenericButtonFingerprint import app.revanced.patches.music.player.components.fingerprints.RepeatTrackFingerprint -import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint -import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint.indexOfImageViewInstruction -import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint.indexOfOrdinalInstruction +import app.revanced.patches.music.player.components.fingerprints.ShuffleOnClickFingerprint import app.revanced.patches.music.player.components.fingerprints.SwipeToCloseFingerprint import app.revanced.patches.music.player.components.fingerprints.SwitchToggleColorFingerprint import app.revanced.patches.music.player.components.fingerprints.ZenModeFingerprint import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE import app.revanced.patches.music.utils.fingerprints.PendingIntentReceiverFingerprint import app.revanced.patches.music.utils.integrations.Constants.COMPONENTS_PATH +import app.revanced.patches.music.utils.integrations.Constants.INTEGRATIONS_PATH import app.revanced.patches.music.utils.integrations.Constants.PLAYER_CLASS_DESCRIPTOR import app.revanced.patches.music.utils.mainactivity.MainActivityResolvePatch import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch @@ -61,6 +60,7 @@ import app.revanced.patches.music.utils.settings.SettingsPatch import app.revanced.patches.music.utils.videotype.VideoTypeHookPatch import app.revanced.patches.shared.litho.LithoFilterPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT +import app.revanced.util.addStaticFieldToIntegration import app.revanced.util.alsoResolve import app.revanced.util.findMethodOrThrow import app.revanced.util.getReference @@ -73,11 +73,10 @@ import app.revanced.util.injectLiteralInstructionBooleanCall import app.revanced.util.injectLiteralInstructionViewCall import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.resultOrThrow -import app.revanced.util.transformFields +import app.revanced.util.transformMethods import app.revanced.util.traverseClassHierarchy import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation import com.android.tools.smali.dexlib2.iface.MethodParameter import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @@ -88,7 +87,6 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference import com.android.tools.smali.dexlib2.iface.reference.Reference import com.android.tools.smali.dexlib2.immutable.ImmutableField import com.android.tools.smali.dexlib2.immutable.ImmutableMethod -import com.android.tools.smali.dexlib2.util.MethodUtil import kotlin.properties.Delegates @Suppress("unused", "LocalVariableName") @@ -101,7 +99,7 @@ object PlayerComponentsPatch : BaseBytecodePatch( PlayerComponentsResourcePatch::class, SettingsPatch::class, SharedResourceIdPatch::class, - VideoTypeHookPatch::class + VideoTypeHookPatch::class, ), compatiblePackages = COMPATIBLE_PACKAGE, fingerprints = setOf( @@ -126,13 +124,16 @@ object PlayerComponentsPatch : BaseBytecodePatch( QuickSeekOverlayFingerprint, RemixGenericButtonFingerprint, RepeatTrackFingerprint, - ShuffleClassReferenceFingerprint, + ShuffleOnClickFingerprint, SwipeToCloseFingerprint, ) ) { private const val FILTER_CLASS_DESCRIPTOR = "$COMPONENTS_PATH/PlayerComponentsFilter;" + private const val INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR = + "$INTEGRATIONS_PATH/utils/VideoUtils;" + override fun execute(context: BytecodeContext) { // region patch for disable gesture in player @@ -735,155 +736,85 @@ object PlayerComponentsPatch : BaseBytecodePatch( // region patch for remember shuffle state - val MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR = - "Lcom/google/android/apps/youtube/music/watchpage/MusicPlaybackControls;" + ShuffleOnClickFingerprint.resultOrThrow().mutableMethod.apply { + val accessibilityIndex = + ShuffleOnClickFingerprint.indexOfAccessibilityInstruction(this) - lateinit var rememberShuffleStateObjectClass: String - lateinit var rememberShuffleStateImageViewReference: Reference - lateinit var rememberShuffleStateShuffleStateLabel: String + // region set shuffle enum - ShuffleClassReferenceFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - rememberShuffleStateObjectClass = definingClass - - val imageViewIndex = indexOfImageViewInstruction(this) - val ordinalIndex = indexOfOrdinalInstruction(this) - - val invokeInterfaceIndex = - indexOfFirstInstructionReversedOrThrow(ordinalIndex, Opcode.INVOKE_INTERFACE) - val iGetObjectIndex = - indexOfFirstInstructionReversedOrThrow(invokeInterfaceIndex, Opcode.IGET_OBJECT) - val checkCastIndex = - indexOfFirstInstructionOrThrow(invokeInterfaceIndex, Opcode.CHECK_CAST) - - val iGetObjectReference = - getInstruction(iGetObjectIndex).reference - val invokeInterfaceReference = - getInstruction(invokeInterfaceIndex).reference - val checkCastReference = - getInstruction(checkCastIndex).reference - val getOrdinalClassReference = - getInstruction(checkCastIndex + 1).reference - val ordinalReference = - getInstruction(ordinalIndex).reference - - rememberShuffleStateImageViewReference = - getInstruction(imageViewIndex).reference - - rememberShuffleStateShuffleStateLabel = """ - iget-object v1, v0, $iGetObjectReference - invoke-interface {v1}, $invokeInterfaceReference - move-result-object v1 - check-cast v1, $checkCastReference - """ - - rememberShuffleStateShuffleStateLabel += if (getInstruction(checkCastIndex + 1).opcode == Opcode.INVOKE_VIRTUAL) { - // YouTube Music 7.16.53+ - """ - invoke-virtual {v1}, $getOrdinalClassReference - move-result-object v1 - - """.trimIndent() - } else { - """ - iget-object v1, v1, $getOrdinalClassReference - - """.trimIndent() - } - - rememberShuffleStateShuffleStateLabel += """ - invoke-virtual {v1}, $ordinalReference - move-result v1 - - """.trimIndent() + val enumIndex = indexOfFirstInstructionReversedOrThrow(accessibilityIndex) { + opcode == Opcode.INVOKE_DIRECT && + getReference()?.returnType == "Ljava/lang/String;" } + val enumRegister = getInstruction(enumIndex).registerD + val enumClass = (getInstruction(enumIndex).reference as MethodReference).parameterTypes.first() - val constructorMethod = - it.mutableClass.methods.first { method -> MethodUtil.isConstructor(method) } - val onClickMethod = it.mutableClass.methods.first { method -> method.name == "onClick" } + addInstruction( + enumIndex, + "invoke-static {v$enumRegister}, $PLAYER_CLASS_DESCRIPTOR->setShuffleState(Ljava/lang/Enum;)V" + ) - constructorMethod.apply { - addInstruction( - implementation!!.instructions.lastIndex, - "sput-object p0, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->shuffleClass:$rememberShuffleStateObjectClass" - ) - } + // endregion - onClickMethod.apply { - addInstructions( - 0, """ - move-object v0, p0 - """ + rememberShuffleStateShuffleStateLabel + """ - invoke-static {v1}, $PLAYER_CLASS_DESCRIPTOR->setShuffleState(I)V - """ - ) - } + // region set static field - context.traverseClassHierarchy(it.mutableClass) { - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL - transformFields { - ImmutableField( + val shuffleClassIndex = indexOfFirstInstructionReversedOrThrow(accessibilityIndex, Opcode.CHECK_CAST) + val shuffleClass = getInstruction(shuffleClassIndex).reference.toString() + val shuffleMutableClass = context.findClass { classDef -> + classDef.type == shuffleClass + }!!.mutableClass + + val shuffleMethod = shuffleMutableClass.methods.find { method -> + method.parameterTypes.firstOrNull() == enumClass && + method.parameterTypes.size == 1 && + method.returnType == "V" + } ?: throw PatchException("target not found") + + val smaliInstructions = + """ + if-eqz v0, :ignore + sget-object v1, $enumClass->b:$enumClass + invoke-virtual {v0, v1}, $shuffleClass->${shuffleMethod.name}($enumClass)V + :ignore + return-void + """ + + context.addStaticFieldToIntegration( + INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR, + "shuffleTracks", + "shuffleClass", + shuffleClass, + smaliInstructions + ) + + // endregion + + // region make all methods accessible + + context.traverseClassHierarchy(shuffleMutableClass) { + transformMethods { + ImmutableMethod( definingClass, name, - type, - AccessFlags.PUBLIC or AccessFlags.PUBLIC, - null, + parameters, + returnType, + AccessFlags.PUBLIC or AccessFlags.FINAL, annotations, - null + hiddenApiRestrictions, + implementation ).toMutable() } } - } - - MusicPlaybackControlsFingerprint.resultOrThrow().let { - it.mutableMethod.apply { - addInstruction( - 0, - "invoke-virtual {v0}, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->rememberShuffleState()V" - ) - val shuffleField = ImmutableField( - definingClass, - "shuffleClass", - rememberShuffleStateObjectClass, - AccessFlags.PUBLIC or AccessFlags.STATIC, - null, - annotations, - null - ).toMutable() - - val shuffleMethod = ImmutableMethod( - definingClass, - "rememberShuffleState", - emptyList(), - "V", - AccessFlags.PUBLIC or AccessFlags.FINAL, - annotations, null, - MutableMethodImplementation(5) - ).toMutable() - - shuffleMethod.addInstructionsWithLabels( - 0, """ - invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->getShuffleState()I - move-result v2 - if-nez v2, :dont_shuffle - sget-object v0, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->shuffleClass:$rememberShuffleStateObjectClass - """ + rememberShuffleStateShuffleStateLabel + """ - iget-object v3, v0, $rememberShuffleStateImageViewReference - if-eqz v3, :dont_shuffle - invoke-virtual {v3}, Landroid/view/View;->callOnClick()Z - if-eqz v1, :dont_shuffle - invoke-virtual {v3}, Landroid/view/View;->callOnClick()Z - :dont_shuffle - return-void - """ - ) + // endregion - it.mutableClass.methods.add(shuffleMethod) - it.mutableClass.staticFields.add(shuffleField) - } } + MusicPlaybackControlsFingerprint.resultOrThrow().mutableMethod.addInstruction( + 0, + "invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->shuffleTracks()V" + ) + SettingsPatch.addSwitchPreference( CategoryType.PLAYER, "revanced_remember_shuffle_state", diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleClassReferenceFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleClassReferenceFingerprint.kt deleted file mode 100644 index 537f121058..0000000000 --- a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleClassReferenceFingerprint.kt +++ /dev/null @@ -1,40 +0,0 @@ -package app.revanced.patches.music.player.components.fingerprints - -import app.revanced.patcher.extensions.or -import app.revanced.patcher.fingerprint.MethodFingerprint -import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint.indexOfImageViewInstruction -import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint.indexOfOrdinalInstruction -import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch.YtFillArrowShuffle -import app.revanced.util.containsWideLiteralInstructionValue -import app.revanced.util.getReference -import app.revanced.util.indexOfFirstInstruction -import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode -import com.android.tools.smali.dexlib2.iface.Method -import com.android.tools.smali.dexlib2.iface.reference.FieldReference -import com.android.tools.smali.dexlib2.iface.reference.MethodReference - -internal object ShuffleClassReferenceFingerprint : MethodFingerprint( - returnType = "V", - accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, - parameters = emptyList(), - strings = listOf("Unknown shuffle mode"), - customFingerprint = { methodDef, _ -> - methodDef.containsWideLiteralInstructionValue(YtFillArrowShuffle) && - indexOfOrdinalInstruction(methodDef) >= 0 && - indexOfImageViewInstruction(methodDef) >= 0 - } -) { - fun indexOfOrdinalInstruction(methodDef: Method) = - methodDef.indexOfFirstInstruction { - opcode == Opcode.INVOKE_VIRTUAL && - getReference()?.name == "ordinal" - } - - fun indexOfImageViewInstruction(methodDef: Method) = - methodDef.indexOfFirstInstruction { - opcode == Opcode.IGET_OBJECT && - getReference()?.type == "Landroid/widget/ImageView;" - } -} - diff --git a/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleOnClickFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleOnClickFingerprint.kt new file mode 100644 index 0000000000..608e713ead --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleOnClickFingerprint.kt @@ -0,0 +1,30 @@ +package app.revanced.patches.music.player.components.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.patches.music.player.components.fingerprints.ShuffleOnClickFingerprint.indexOfAccessibilityInstruction +import app.revanced.util.containsWideLiteralInstructionValue +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.Method +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +internal object ShuffleOnClickFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("Landroid/view/View;"), + customFingerprint = { methodDef, _ -> + methodDef.containsWideLiteralInstructionValue(45468) && + methodDef.name == "onClick" && + indexOfAccessibilityInstruction(methodDef) >= 0 + } +) { + fun indexOfAccessibilityInstruction(methodDef: Method) = + methodDef.indexOfFirstInstruction { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.name == "announceForAccessibility" + } +} + 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 d2339902ec..db57d37323 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 @@ -59,7 +59,6 @@ object SharedResourceIdPatch : ResourcePatch() { var TouchOutside = -1L var TrimSilenceSwitch: Long = -1 var VarispeedUnavailableTitle = -1L - var YtFillArrowShuffle = -1L override fun execute(context: ResourceContext) { @@ -106,7 +105,6 @@ object SharedResourceIdPatch : ResourcePatch() { TouchOutside = getId(ID, "touch_outside") TrimSilenceSwitch = getId(ID, "trim_silence_switch") VarispeedUnavailableTitle = getId(STRING, "varispeed_unavailable_title") - YtFillArrowShuffle = getId(DRAWABLE, "yt_fill_arrow_shuffle_vd_theme_24") } } \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt index 46adf69e01..05b26d759a 100644 --- a/src/main/kotlin/app/revanced/util/BytecodeUtils.kt +++ b/src/main/kotlin/app/revanced/util/BytecodeUtils.kt @@ -83,7 +83,7 @@ fun MutableClass.transformFields(transform: MutableField.() -> MutableField) { */ fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod) { val transformedMethods = methods.map { it.transform() } - methods.clear() + methods.removeIf { !MethodUtil.isConstructor(it) } methods.addAll(transformedMethods) }