mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-05-16 14:27:18 +02:00
fix(YouTube Music/Player components): Remember shuffle state
setting does not remember the correct state
This commit is contained in:
parent
2980d4830d
commit
8333bf73d5
@ -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,154 +736,84 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
"""
|
||||
)
|
||||
|
||||
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,
|
||||
|
@ -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;"
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user