fix(YouTube Music/Player components): Remember shuffle state setting does not remember the correct state

This commit is contained in:
inotia00 2024-09-30 21:39:44 +09:00
parent 2980d4830d
commit 8333bf73d5
5 changed files with 103 additions and 184 deletions

View File

@ -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.QuickSeekOverlayFingerprint
import app.revanced.patches.music.player.components.fingerprints.RemixGenericButtonFingerprint 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.RepeatTrackFingerprint
import app.revanced.patches.music.player.components.fingerprints.ShuffleClassReferenceFingerprint import app.revanced.patches.music.player.components.fingerprints.ShuffleOnClickFingerprint
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.SwipeToCloseFingerprint 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.SwitchToggleColorFingerprint
import app.revanced.patches.music.player.components.fingerprints.ZenModeFingerprint 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.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.fingerprints.PendingIntentReceiverFingerprint 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.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.integrations.Constants.PLAYER_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.mainactivity.MainActivityResolvePatch import app.revanced.patches.music.utils.mainactivity.MainActivityResolvePatch
import app.revanced.patches.music.utils.resourceid.SharedResourceIdPatch 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.music.utils.videotype.VideoTypeHookPatch
import app.revanced.patches.shared.litho.LithoFilterPatch import app.revanced.patches.shared.litho.LithoFilterPatch
import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT
import app.revanced.util.addStaticFieldToIntegration
import app.revanced.util.alsoResolve import app.revanced.util.alsoResolve
import app.revanced.util.findMethodOrThrow import app.revanced.util.findMethodOrThrow
import app.revanced.util.getReference import app.revanced.util.getReference
@ -73,11 +73,10 @@ import app.revanced.util.injectLiteralInstructionBooleanCall
import app.revanced.util.injectLiteralInstructionViewCall import app.revanced.util.injectLiteralInstructionViewCall
import app.revanced.util.patch.BaseBytecodePatch import app.revanced.util.patch.BaseBytecodePatch
import app.revanced.util.resultOrThrow import app.revanced.util.resultOrThrow
import app.revanced.util.transformFields import app.revanced.util.transformMethods
import app.revanced.util.traverseClassHierarchy import app.revanced.util.traverseClassHierarchy
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode 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.MethodParameter
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction 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.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.iface.reference.Reference
import com.android.tools.smali.dexlib2.immutable.ImmutableField import com.android.tools.smali.dexlib2.immutable.ImmutableField
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.util.MethodUtil
import kotlin.properties.Delegates import kotlin.properties.Delegates
@Suppress("unused", "LocalVariableName") @Suppress("unused", "LocalVariableName")
@ -101,7 +99,7 @@ object PlayerComponentsPatch : BaseBytecodePatch(
PlayerComponentsResourcePatch::class, PlayerComponentsResourcePatch::class,
SettingsPatch::class, SettingsPatch::class,
SharedResourceIdPatch::class, SharedResourceIdPatch::class,
VideoTypeHookPatch::class VideoTypeHookPatch::class,
), ),
compatiblePackages = COMPATIBLE_PACKAGE, compatiblePackages = COMPATIBLE_PACKAGE,
fingerprints = setOf( fingerprints = setOf(
@ -126,13 +124,16 @@ object PlayerComponentsPatch : BaseBytecodePatch(
QuickSeekOverlayFingerprint, QuickSeekOverlayFingerprint,
RemixGenericButtonFingerprint, RemixGenericButtonFingerprint,
RepeatTrackFingerprint, RepeatTrackFingerprint,
ShuffleClassReferenceFingerprint, ShuffleOnClickFingerprint,
SwipeToCloseFingerprint, SwipeToCloseFingerprint,
) )
) { ) {
private const val FILTER_CLASS_DESCRIPTOR = private const val FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/PlayerComponentsFilter;" "$COMPONENTS_PATH/PlayerComponentsFilter;"
private const val INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR =
"$INTEGRATIONS_PATH/utils/VideoUtils;"
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// region patch for disable gesture in player // region patch for disable gesture in player
@ -735,155 +736,85 @@ object PlayerComponentsPatch : BaseBytecodePatch(
// region patch for remember shuffle state // region patch for remember shuffle state
val MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR = ShuffleOnClickFingerprint.resultOrThrow().mutableMethod.apply {
"Lcom/google/android/apps/youtube/music/watchpage/MusicPlaybackControls;" val accessibilityIndex =
ShuffleOnClickFingerprint.indexOfAccessibilityInstruction(this)
lateinit var rememberShuffleStateObjectClass: String // region set shuffle enum
lateinit var rememberShuffleStateImageViewReference: Reference
lateinit var rememberShuffleStateShuffleStateLabel: String
ShuffleClassReferenceFingerprint.resultOrThrow().let { val enumIndex = indexOfFirstInstructionReversedOrThrow(accessibilityIndex) {
it.mutableMethod.apply { opcode == Opcode.INVOKE_DIRECT &&
rememberShuffleStateObjectClass = definingClass getReference<MethodReference>()?.returnType == "Ljava/lang/String;"
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()
} }
val enumRegister = getInstruction<FiveRegisterInstruction>(enumIndex).registerD
val enumClass = (getInstruction<ReferenceInstruction>(enumIndex).reference as MethodReference).parameterTypes.first()
rememberShuffleStateShuffleStateLabel += """
invoke-virtual {v1}, $ordinalReference
move-result v1
""".trimIndent()
}
val constructorMethod =
it.mutableClass.methods.first { method -> MethodUtil.isConstructor(method) }
val onClickMethod = it.mutableClass.methods.first { method -> method.name == "onClick" }
constructorMethod.apply {
addInstruction( addInstruction(
implementation!!.instructions.lastIndex, enumIndex,
"sput-object p0, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->shuffleClass:$rememberShuffleStateObjectClass" "invoke-static {v$enumRegister}, $PLAYER_CLASS_DESCRIPTOR->setShuffleState(Ljava/lang/Enum;)V"
) )
}
onClickMethod.apply { // endregion
addInstructions(
0, """ // region set static field
move-object v0, p0
""" + rememberShuffleStateShuffleStateLabel + """ val shuffleClassIndex = indexOfFirstInstructionReversedOrThrow(accessibilityIndex, Opcode.CHECK_CAST)
invoke-static {v1}, $PLAYER_CLASS_DESCRIPTOR->setShuffleState(I)V 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
context.traverseClassHierarchy(it.mutableClass) { :ignore
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL
transformFields {
ImmutableField(
definingClass,
name,
type,
AccessFlags.PUBLIC or AccessFlags.PUBLIC,
null,
annotations,
null
).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 return-void
""" """
context.addStaticFieldToIntegration(
INTEGRATIONS_VIDEO_UTILS_CLASS_DESCRIPTOR,
"shuffleTracks",
"shuffleClass",
shuffleClass,
smaliInstructions
) )
it.mutableClass.methods.add(shuffleMethod) // endregion
it.mutableClass.staticFields.add(shuffleField)
// region make all methods accessible
context.traverseClassHierarchy(shuffleMutableClass) {
transformMethods {
ImmutableMethod(
definingClass,
name,
parameters,
returnType,
AccessFlags.PUBLIC or AccessFlags.FINAL,
annotations,
hiddenApiRestrictions,
implementation
).toMutable()
} }
} }
// endregion
}
MusicPlaybackControlsFingerprint.resultOrThrow().mutableMethod.addInstruction(
0,
"invoke-static {}, $PLAYER_CLASS_DESCRIPTOR->shuffleTracks()V"
)
SettingsPatch.addSwitchPreference( SettingsPatch.addSwitchPreference(
CategoryType.PLAYER, CategoryType.PLAYER,
"revanced_remember_shuffle_state", "revanced_remember_shuffle_state",

View File

@ -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<MethodReference>()?.name == "ordinal"
}
fun indexOfImageViewInstruction(methodDef: Method) =
methodDef.indexOfFirstInstruction {
opcode == Opcode.IGET_OBJECT &&
getReference<FieldReference>()?.type == "Landroid/widget/ImageView;"
}
}

View File

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

View File

@ -59,7 +59,6 @@ object SharedResourceIdPatch : ResourcePatch() {
var TouchOutside = -1L var TouchOutside = -1L
var TrimSilenceSwitch: Long = -1 var TrimSilenceSwitch: Long = -1
var VarispeedUnavailableTitle = -1L var VarispeedUnavailableTitle = -1L
var YtFillArrowShuffle = -1L
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
@ -106,7 +105,6 @@ object SharedResourceIdPatch : ResourcePatch() {
TouchOutside = getId(ID, "touch_outside") TouchOutside = getId(ID, "touch_outside")
TrimSilenceSwitch = getId(ID, "trim_silence_switch") TrimSilenceSwitch = getId(ID, "trim_silence_switch")
VarispeedUnavailableTitle = getId(STRING, "varispeed_unavailable_title") VarispeedUnavailableTitle = getId(STRING, "varispeed_unavailable_title")
YtFillArrowShuffle = getId(DRAWABLE, "yt_fill_arrow_shuffle_vd_theme_24")
} }
} }

View File

@ -83,7 +83,7 @@ fun MutableClass.transformFields(transform: MutableField.() -> MutableField) {
*/ */
fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod) { fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod) {
val transformedMethods = methods.map { it.transform() } val transformedMethods = methods.map { it.transform() }
methods.clear() methods.removeIf { !MethodUtil.isConstructor(it) }
methods.addAll(transformedMethods) methods.addAll(transformedMethods)
} }