Skip to content

Commit

Permalink
fix(YouTube Music/Player components): Remember shuffle state settin…
Browse files Browse the repository at this point in the history
…g does not remember the correct state
  • Loading branch information
inotia00 committed Sep 30, 2024
1 parent 2980d48 commit 8333bf7
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 183 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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")
Expand All @@ -101,7 +99,7 @@ object PlayerComponentsPatch : BaseBytecodePatch(
PlayerComponentsResourcePatch::class,
SettingsPatch::class,
SharedResourceIdPatch::class,
VideoTypeHookPatch::class
VideoTypeHookPatch::class,
),
compatiblePackages = COMPATIBLE_PACKAGE,
fingerprints = setOf(
Expand All @@ -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
Expand Down Expand Up @@ -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<ReferenceInstruction>(iGetObjectIndex).reference
val invokeInterfaceReference =
getInstruction<ReferenceInstruction>(invokeInterfaceIndex).reference
val checkCastReference =
getInstruction<ReferenceInstruction>(checkCastIndex).reference
val getOrdinalClassReference =
getInstruction<ReferenceInstruction>(checkCastIndex + 1).reference
val ordinalReference =
getInstruction<ReferenceInstruction>(ordinalIndex).reference

rememberShuffleStateImageViewReference =
getInstruction<ReferenceInstruction>(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<MethodReference>()?.returnType == "Ljava/lang/String;"
}
val enumRegister = getInstruction<FiveRegisterInstruction>(enumIndex).registerD
val enumClass = (getInstruction<ReferenceInstruction>(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<ReferenceInstruction>(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",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<MethodReference>()?.name == "announceForAccessibility"
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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) {

Expand Down Expand Up @@ -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")

}
}
2 changes: 1 addition & 1 deletion src/main/kotlin/app/revanced/util/BytecodeUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down

0 comments on commit 8333bf7

Please sign in to comment.