Skip to content

Commit 8333bf7

Browse files
committed
fix(YouTube Music/Player components): Remember shuffle state setting does not remember the correct state
1 parent 2980d48 commit 8333bf7

File tree

5 files changed

+102
-183
lines changed

5 files changed

+102
-183
lines changed

src/main/kotlin/app/revanced/patches/music/player/components/PlayerComponentsPatch.kt

+71-140
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,14 @@ import app.revanced.patches.music.player.components.fingerprints.PlayerViewPager
3535
import app.revanced.patches.music.player.components.fingerprints.QuickSeekOverlayFingerprint
3636
import app.revanced.patches.music.player.components.fingerprints.RemixGenericButtonFingerprint
3737
import app.revanced.patches.music.player.components.fingerprints.RepeatTrackFingerprint
38-
import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint
39-
import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint.indexOfImageViewInstruction
40-
import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint.indexOfOrdinalInstruction
38+
import app.revanced.patches.music.player.components.fingerprints.ShuffleOnClickFingerprint
4139
import app.revanced.patches.music.player.components.fingerprints.SwipeToCloseFingerprint
4240
import app.revanced.patches.music.player.components.fingerprints.SwitchToggleColorFingerprint
4341
import app.revanced.patches.music.player.components.fingerprints.ZenModeFingerprint
4442
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
4543
import app.revanced.patches.music.utils.fingerprints.PendingIntentReceiverFingerprint
4644
import app.revanced.patches.music.utils.integrations.Constants.COMPONENTS_PATH
45+
import app.revanced.patches.music.utils.integrations.Constants.INTEGRATIONS_PATH
4746
import app.revanced.patches.music.utils.integrations.Constants.PLAYER_CLASS_DESCRIPTOR
4847
import app.revanced.patches.music.utils.mainactivity.MainActivityResolvePatch
4948
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch
@@ -61,6 +60,7 @@ import app.revanced.patches.music.utils.settings.SettingsPatch
6160
import app.revanced.patches.music.utils.videotype.VideoTypeHookPatch
6261
import app.revanced.patches.shared.litho.LithoFilterPatch
6362
import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT
63+
import app.revanced.util.addStaticFieldToIntegration
6464
import app.revanced.util.alsoResolve
6565
import app.revanced.util.findMethodOrThrow
6666
import app.revanced.util.getReference
@@ -73,11 +73,10 @@ import app.revanced.util.injectLiteralInstructionBooleanCall
7373
import app.revanced.util.injectLiteralInstructionViewCall
7474
import app.revanced.util.patch.BaseBytecodePatch
7575
import app.revanced.util.resultOrThrow
76-
import app.revanced.util.transformFields
76+
import app.revanced.util.transformMethods
7777
import app.revanced.util.traverseClassHierarchy
7878
import com.android.tools.smali.dexlib2.AccessFlags
7979
import com.android.tools.smali.dexlib2.Opcode
80-
import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation
8180
import com.android.tools.smali.dexlib2.iface.MethodParameter
8281
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
8382
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@@ -88,7 +87,6 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
8887
import com.android.tools.smali.dexlib2.iface.reference.Reference
8988
import com.android.tools.smali.dexlib2.immutable.ImmutableField
9089
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
91-
import com.android.tools.smali.dexlib2.util.MethodUtil
9290
import kotlin.properties.Delegates
9391

9492
@Suppress("unused", "LocalVariableName")
@@ -101,7 +99,7 @@ object PlayerComponentsPatch : BaseBytecodePatch(
10199
PlayerComponentsResourcePatch::class,
102100
SettingsPatch::class,
103101
SharedResourceIdPatch::class,
104-
VideoTypeHookPatch::class
102+
VideoTypeHookPatch::class,
105103
),
106104
compatiblePackages = COMPATIBLE_PACKAGE,
107105
fingerprints = setOf(
@@ -126,13 +124,16 @@ object PlayerComponentsPatch : BaseBytecodePatch(
126124
QuickSeekOverlayFingerprint,
127125
RemixGenericButtonFingerprint,
128126
RepeatTrackFingerprint,
129-
ShuffleClassReferenceFingerprint,
127+
ShuffleOnClickFingerprint,
130128
SwipeToCloseFingerprint,
131129
)
132130
) {
133131
private const val FILTER_CLASS_DESCRIPTOR =
134132
"$COMPONENTS_PATH/PlayerComponentsFilter;"
135133

134+
private const val INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR =
135+
"$INTEGRATIONS_PATH/utils/VideoUtils;"
136+
136137
override fun execute(context: BytecodeContext) {
137138

138139
// region patch for disable gesture in player
@@ -735,155 +736,85 @@ object PlayerComponentsPatch : BaseBytecodePatch(
735736

736737
// region patch for remember shuffle state
737738

738-
val MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR =
739-
"Lcom/google/android/apps/youtube/music/watchpage/MusicPlaybackControls;"
739+
ShuffleOnClickFingerprint.resultOrThrow().mutableMethod.apply {
740+
val accessibilityIndex =
741+
ShuffleOnClickFingerprint.indexOfAccessibilityInstruction(this)
740742

741-
lateinit var rememberShuffleStateObjectClass: String
742-
lateinit var rememberShuffleStateImageViewReference: Reference
743-
lateinit var rememberShuffleStateShuffleStateLabel: String
743+
// region set shuffle enum
744744

745-
ShuffleClassReferenceFingerprint.resultOrThrow().let {
746-
it.mutableMethod.apply {
747-
rememberShuffleStateObjectClass = definingClass
748-
749-
val imageViewIndex = indexOfImageViewInstruction(this)
750-
val ordinalIndex = indexOfOrdinalInstruction(this)
751-
752-
val invokeInterfaceIndex =
753-
indexOfFirstInstructionReversedOrThrow(ordinalIndex, Opcode.INVOKE_INTERFACE)
754-
val iGetObjectIndex =
755-
indexOfFirstInstructionReversedOrThrow(invokeInterfaceIndex, Opcode.IGET_OBJECT)
756-
val checkCastIndex =
757-
indexOfFirstInstructionOrThrow(invokeInterfaceIndex, Opcode.CHECK_CAST)
758-
759-
val iGetObjectReference =
760-
getInstruction<ReferenceInstruction>(iGetObjectIndex).reference
761-
val invokeInterfaceReference =
762-
getInstruction<ReferenceInstruction>(invokeInterfaceIndex).reference
763-
val checkCastReference =
764-
getInstruction<ReferenceInstruction>(checkCastIndex).reference
765-
val getOrdinalClassReference =
766-
getInstruction<ReferenceInstruction>(checkCastIndex + 1).reference
767-
val ordinalReference =
768-
getInstruction<ReferenceInstruction>(ordinalIndex).reference
769-
770-
rememberShuffleStateImageViewReference =
771-
getInstruction<ReferenceInstruction>(imageViewIndex).reference
772-
773-
rememberShuffleStateShuffleStateLabel = """
774-
iget-object v1, v0, $iGetObjectReference
775-
invoke-interface {v1}, $invokeInterfaceReference
776-
move-result-object v1
777-
check-cast v1, $checkCastReference
778-
"""
779-
780-
rememberShuffleStateShuffleStateLabel += if (getInstruction(checkCastIndex + 1).opcode == Opcode.INVOKE_VIRTUAL) {
781-
// YouTube Music 7.16.53+
782-
"""
783-
invoke-virtual {v1}, $getOrdinalClassReference
784-
move-result-object v1
785-
786-
""".trimIndent()
787-
} else {
788-
"""
789-
iget-object v1, v1, $getOrdinalClassReference
790-
791-
""".trimIndent()
792-
}
793-
794-
rememberShuffleStateShuffleStateLabel += """
795-
invoke-virtual {v1}, $ordinalReference
796-
move-result v1
797-
798-
""".trimIndent()
745+
val enumIndex = indexOfFirstInstructionReversedOrThrow(accessibilityIndex) {
746+
opcode == Opcode.INVOKE_DIRECT &&
747+
getReference<MethodReference>()?.returnType == "Ljava/lang/String;"
799748
}
749+
val enumRegister = getInstruction<FiveRegisterInstruction>(enumIndex).registerD
750+
val enumClass = (getInstruction<ReferenceInstruction>(enumIndex).reference as MethodReference).parameterTypes.first()
800751

801-
val constructorMethod =
802-
it.mutableClass.methods.first { method -> MethodUtil.isConstructor(method) }
803-
val onClickMethod = it.mutableClass.methods.first { method -> method.name == "onClick" }
752+
addInstruction(
753+
enumIndex,
754+
"invoke-static {v$enumRegister}, $PLAYER_CLASS_DESCRIPTOR->setShuffleState(Ljava/lang/Enum;)V"
755+
)
804756

805-
constructorMethod.apply {
806-
addInstruction(
807-
implementation!!.instructions.lastIndex,
808-
"sput-object p0, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->shuffleClass:$rememberShuffleStateObjectClass"
809-
)
810-
}
757+
// endregion
811758

812-
onClickMethod.apply {
813-
addInstructions(
814-
0, """
815-
move-object v0, p0
816-
""" + rememberShuffleStateShuffleStateLabel + """
817-
invoke-static {v1}, $PLAYER_CLASS_DESCRIPTOR->setShuffleState(I)V
818-
"""
819-
)
820-
}
759+
// region set static field
821760

822-
context.traverseClassHierarchy(it.mutableClass) {
823-
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL
824-
transformFields {
825-
ImmutableField(
761+
val shuffleClassIndex = indexOfFirstInstructionReversedOrThrow(accessibilityIndex, Opcode.CHECK_CAST)
762+
val shuffleClass = getInstruction<ReferenceInstruction>(shuffleClassIndex).reference.toString()
763+
val shuffleMutableClass = context.findClass { classDef ->
764+
classDef.type == shuffleClass
765+
}!!.mutableClass
766+
767+
val shuffleMethod = shuffleMutableClass.methods.find { method ->
768+
method.parameterTypes.firstOrNull() == enumClass &&
769+
method.parameterTypes.size == 1 &&
770+
method.returnType == "V"
771+
} ?: throw PatchException("target not found")
772+
773+
val smaliInstructions =
774+
"""
775+
if-eqz v0, :ignore
776+
sget-object v1, $enumClass->b:$enumClass
777+
invoke-virtual {v0, v1}, $shuffleClass->${shuffleMethod.name}($enumClass)V
778+
:ignore
779+
return-void
780+
"""
781+
782+
context.addStaticFieldToIntegration(
783+
INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR,
784+
"shuffleTracks",
785+
"shuffleClass",
786+
shuffleClass,
787+
smaliInstructions
788+
)
789+
790+
// endregion
791+
792+
// region make all methods accessible
793+
794+
context.traverseClassHierarchy(shuffleMutableClass) {
795+
transformMethods {
796+
ImmutableMethod(
826797
definingClass,
827798
name,
828-
type,
829-
AccessFlags.PUBLIC or AccessFlags.PUBLIC,
830-
null,
799+
parameters,
800+
returnType,
801+
AccessFlags.PUBLIC or AccessFlags.FINAL,
831802
annotations,
832-
null
803+
hiddenApiRestrictions,
804+
implementation
833805
).toMutable()
834806
}
835807
}
836-
}
837-
838-
MusicPlaybackControlsFingerprint.resultOrThrow().let {
839-
it.mutableMethod.apply {
840-
addInstruction(
841-
0,
842-
"invoke-virtual {v0}, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->rememberShuffleState()V"
843-
)
844808

845-
val shuffleField = ImmutableField(
846-
definingClass,
847-
"shuffleClass",
848-
rememberShuffleStateObjectClass,
849-
AccessFlags.PUBLIC or AccessFlags.STATIC,
850-
null,
851-
annotations,
852-
null
853-
).toMutable()
854-
855-
val shuffleMethod = ImmutableMethod(
856-
definingClass,
857-
"rememberShuffleState",
858-
emptyList(),
859-
"V",
860-
AccessFlags.PUBLIC or AccessFlags.FINAL,
861-
annotations, null,
862-
MutableMethodImplementation(5)
863-
).toMutable()
864-
865-
shuffleMethod.addInstructionsWithLabels(
866-
0, """
867-
invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->getShuffleState()I
868-
move-result v2
869-
if-nez v2, :dont_shuffle
870-
sget-object v0, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->shuffleClass:$rememberShuffleStateObjectClass
871-
""" + rememberShuffleStateShuffleStateLabel + """
872-
iget-object v3, v0, $rememberShuffleStateImageViewReference
873-
if-eqz v3, :dont_shuffle
874-
invoke-virtual {v3}, Landroid/view/View;->callOnClick()Z
875-
if-eqz v1, :dont_shuffle
876-
invoke-virtual {v3}, Landroid/view/View;->callOnClick()Z
877-
:dont_shuffle
878-
return-void
879-
"""
880-
)
809+
// endregion
881810

882-
it.mutableClass.methods.add(shuffleMethod)
883-
it.mutableClass.staticFields.add(shuffleField)
884-
}
885811
}
886812

813+
MusicPlaybackControlsFingerprint.resultOrThrow().mutableMethod.addInstruction(
814+
0,
815+
"invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->shuffleTracks()V"
816+
)
817+
887818
SettingsPatch.addSwitchPreference(
888819
CategoryType.PLAYER,
889820
"revanced_remember_shuffle_state",

src/main/kotlin/app/revanced/patches/music/player/components/fingerprints/ShuffleClassReferenceFingerprint.kt

-40
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package app.revanced.patches.music.player.components.fingerprints
2+
3+
import app.revanced.patcher.extensions.or
4+
import app.revanced.patcher.fingerprint.MethodFingerprint
5+
import app.revanced.patches.music.player.components.fingerprints.ShuffleOnClickFingerprint.indexOfAccessibilityInstruction
6+
import app.revanced.util.containsWideLiteralInstructionValue
7+
import app.revanced.util.getReference
8+
import app.revanced.util.indexOfFirstInstruction
9+
import com.android.tools.smali.dexlib2.AccessFlags
10+
import com.android.tools.smali.dexlib2.Opcode
11+
import com.android.tools.smali.dexlib2.iface.Method
12+
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
13+
14+
internal object ShuffleOnClickFingerprint : MethodFingerprint(
15+
returnType = "V",
16+
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
17+
parameters = listOf("Landroid/view/View;"),
18+
customFingerprint = { methodDef, _ ->
19+
methodDef.containsWideLiteralInstructionValue(45468) &&
20+
methodDef.name == "onClick" &&
21+
indexOfAccessibilityInstruction(methodDef) >= 0
22+
}
23+
) {
24+
fun indexOfAccessibilityInstruction(methodDef: Method) =
25+
methodDef.indexOfFirstInstruction {
26+
opcode == Opcode.INVOKE_VIRTUAL &&
27+
getReference<MethodReference>()?.name == "announceForAccessibility"
28+
}
29+
}
30+

src/main/kotlin/app/revanced/patches/music/utils/resourceid/SharedResourceIdPatch.kt

-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ object SharedResourceIdPatch : ResourcePatch() {
5959
var TouchOutside = -1L
6060
var TrimSilenceSwitch: Long = -1
6161
var VarispeedUnavailableTitle = -1L
62-
var YtFillArrowShuffle = -1L
6362

6463
override fun execute(context: ResourceContext) {
6564

@@ -106,7 +105,6 @@ object SharedResourceIdPatch : ResourcePatch() {
106105
TouchOutside = getId(ID, "touch_outside")
107106
TrimSilenceSwitch = getId(ID, "trim_silence_switch")
108107
VarispeedUnavailableTitle = getId(STRING, "varispeed_unavailable_title")
109-
YtFillArrowShuffle = getId(DRAWABLE, "yt_fill_arrow_shuffle_vd_theme_24")
110108

111109
}
112110
}

src/main/kotlin/app/revanced/util/BytecodeUtils.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ fun MutableClass.transformFields(transform: MutableField.() -> MutableField) {
8383
*/
8484
fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod) {
8585
val transformedMethods = methods.map { it.transform() }
86-
methods.clear()
86+
methods.removeIf { !MethodUtil.isConstructor(it) }
8787
methods.addAll(transformedMethods)
8888
}
8989

0 commit comments

Comments
 (0)