add enable-force-shuffle patch

This commit is contained in:
inotia00 2023-01-05 12:11:56 +09:00
parent f31203bfba
commit b1e80566b8
8 changed files with 261 additions and 0 deletions

View File

@ -0,0 +1,20 @@
package app.revanced.patches.music.layout.shuffle.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
object MusicPlaybackControlsFingerprint : MethodFingerprint(
returnType = "V",
access = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
opcodes = listOf(
Opcode.IPUT_BOOLEAN,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
),
customFingerprint = { methodDef ->
methodDef.definingClass.endsWith("MusicPlaybackControls;")
}
)

View File

@ -0,0 +1,29 @@
package app.revanced.patches.music.layout.shuffle.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patches.music.misc.resourceid.patch.SharedResourcdIdPatch
import org.jf.dexlib2.iface.instruction.WideLiteralInstruction
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
object ShuffleClassFingerprint : MethodFingerprint(
returnType = "V",
access = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IPUT,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
),
customFingerprint = { methodDef ->
methodDef.name == "<init>" &&
methodDef.implementation?.instructions?.any { instruction ->
instruction.opcode.ordinal == Opcode.CONST.ordinal &&
(instruction as? WideLiteralInstruction)?.wideLiteral == SharedResourcdIdPatch.disabledIconLabelId
} == true
}
)

View File

@ -0,0 +1,21 @@
package app.revanced.patches.music.layout.shuffle.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.Opcode
object ShuffleClassReferenceFingerprint : MethodFingerprint(
returnType = "V",
access = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf(),
opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.IGET_OBJECT
),
strings = listOf("Unknown shuffle mode")
)

View File

@ -0,0 +1,174 @@
package app.revanced.patches.music.layout.shuffle.patch
import app.revanced.patcher.annotation.Description
import app.revanced.patcher.annotation.Name
import app.revanced.patcher.annotation.Version
import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.extensions.*
import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.PatchResult
import app.revanced.patcher.patch.PatchResultSuccess
import app.revanced.patcher.patch.annotations.DependsOn
import app.revanced.patcher.patch.annotations.Patch
import app.revanced.patcher.util.TypeUtil.traverseClassHierarchy
import app.revanced.patcher.util.proxy.mutableTypes.MutableField.Companion.toMutable
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.util.smali.toInstructions
import app.revanced.patches.music.layout.shuffle.fingerprints.MusicPlaybackControlsFingerprint
import app.revanced.patches.music.layout.shuffle.fingerprints.ShuffleClassFingerprint
import app.revanced.patches.music.layout.shuffle.fingerprints.ShuffleClassReferenceFingerprint
import app.revanced.patches.music.misc.integrations.patch.MusicIntegrationsPatch
import app.revanced.patches.music.misc.resourceid.patch.SharedResourcdIdPatch
import app.revanced.patches.music.misc.settings.patch.MusicSettingsPatch
import app.revanced.shared.annotation.YouTubeMusicCompatibility
import app.revanced.shared.extensions.transformFields
import app.revanced.shared.util.integrations.Constants.MUSIC_SETTINGS_PATH
import org.jf.dexlib2.AccessFlags
import org.jf.dexlib2.dexbacked.reference.DexBackedMethodReference
import org.jf.dexlib2.iface.instruction.ReferenceInstruction
import org.jf.dexlib2.iface.reference.FieldReference
import org.jf.dexlib2.iface.reference.MethodReference
import org.jf.dexlib2.immutable.ImmutableField
import org.jf.dexlib2.immutable.ImmutableMethod
import org.jf.dexlib2.immutable.ImmutableMethodImplementation
import org.jf.dexlib2.immutable.ImmutableMethodParameter
@Patch
@Name("enable-force-shuffle")
@Description("Enable force shuffle even if another track is played.")
@DependsOn([MusicIntegrationsPatch::class, MusicSettingsPatch::class, SharedResourcdIdPatch::class])
@YouTubeMusicCompatibility
@Version("0.0.1")
class EnforceShufflePatch : BytecodePatch(
listOf(
MusicPlaybackControlsFingerprint,
ShuffleClassFingerprint,
ShuffleClassReferenceFingerprint
)
) {
override fun execute(context: BytecodeContext): PatchResult {
with(ShuffleClassReferenceFingerprint.result!!) {
val startIndex = scanResult.patternScanResult!!.startIndex
val endIndex = scanResult.patternScanResult!!.endIndex
val referenceInstructions = mutableMethod.implementation!!.instructions
SHUFFLE_CLASS = classDef.type
firstRef = (referenceInstructions.elementAt(startIndex) as ReferenceInstruction).reference as FieldReference
secondRef = (referenceInstructions.elementAt(startIndex + 1) as ReferenceInstruction).reference as DexBackedMethodReference
thirdRef = (referenceInstructions.elementAt(endIndex) as ReferenceInstruction).reference as FieldReference
referenceInstructions.filter { instruction ->
val fieldReference = (instruction as? ReferenceInstruction)?.reference as? FieldReference
fieldReference?.let { it.type == "Landroid/widget/ImageView;" } == true
}.forEach { instruction ->
fourthRef = (instruction as ReferenceInstruction).reference as FieldReference
}
}
with(ShuffleClassFingerprint.result!!) {
val insertIndex = scanResult.patternScanResult!!.endIndex
mutableMethod.addInstruction(
insertIndex,
"sput-object p0, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->shuffleclass:$SHUFFLE_CLASS"
)
context.traverseClassHierarchy(mutableClass) {
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL
transformFields {
ImmutableField(
definingClass,
name,
type,
AccessFlags.PUBLIC or AccessFlags.PUBLIC,
null,
null,
null
).toMutable()
}
}
}
with(MusicPlaybackControlsFingerprint.result!!) {
val referenceInstructions = mutableMethod.implementation!!.instructions
fifthRef = (referenceInstructions.elementAt(0) as ReferenceInstruction).reference as FieldReference
sixthRef = (referenceInstructions.elementAt(1) as ReferenceInstruction).reference as DexBackedMethodReference
mutableClass.staticFields.add(
ImmutableField(
mutableMethod.definingClass,
"shuffleclass",
"$SHUFFLE_CLASS",
AccessFlags.PUBLIC or AccessFlags.STATIC,
null,
null,
null
).toMutable()
)
mutableMethod.addInstructions(
0,
"""
invoke-virtual {v0, v1}, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->buttonHook(Z)V
return-void
"""
)
mutableClass.methods.add(
ImmutableMethod(
classDef.type,
"buttonHook",
listOf(ImmutableMethodParameter("Z", null, null)),
"V",
AccessFlags.PUBLIC or AccessFlags.FINAL,
null,
null,
ImmutableMethodImplementation(
5, """
invoke-static {}, $MUSIC_SETTINGS_PATH->enableForceShuffle()Z
move-result v0
if-eqz v0, :cond_0
new-instance v0, $SHUFFLE_CLASS
sget-object v0, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->shuffleclass:$SHUFFLE_CLASS
iget-object v1, v0, $SHUFFLE_CLASS->${firstRef.name}:${firstRef.type}
invoke-interface {v1}, $secondRef
move-result-object v1
check-cast v1, ${thirdRef.definingClass}
iget-object v1, v1, ${thirdRef.definingClass}->${thirdRef.name}:${thirdRef.type}
invoke-virtual {v1}, ${thirdRef.type}->ordinal()I
move-result v1
iget-object v2, v0, $SHUFFLE_CLASS->${fourthRef.name}:Landroid/widget/ImageView;
invoke-virtual {v2}, Landroid/widget/ImageView;->performClick()Z
if-eqz v1, :cond_0
invoke-virtual {v2}, Landroid/widget/ImageView;->performClick()Z
:cond_0
iput-boolean v4, v3, $MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR->${fifthRef.name}:${fifthRef.type}
invoke-virtual {v3}, $sixthRef
return-void
""".toInstructions(), null, null
)
).toMutable()
)
}
return PatchResultSuccess()
}
companion object {
const val MUSIC_PLAYBACK_CONTROLS_CLASS_DESCRIPTOR =
"Lcom/google/android/apps/youtube/music/watchpage/MusicPlaybackControls;"
private lateinit var SHUFFLE_CLASS: String
private lateinit var firstRef: FieldReference
private lateinit var secondRef: DexBackedMethodReference
private lateinit var thirdRef: FieldReference
private lateinit var fourthRef: FieldReference
private lateinit var fifthRef: FieldReference
private lateinit var sixthRef: DexBackedMethodReference
}
}

View File

@ -4,6 +4,7 @@ import app.revanced.patcher.extensions.MethodFingerprintExtensions.name
import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint
import app.revanced.patcher.patch.PatchResultError
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableField
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.toInstruction
import app.revanced.shared.util.integrations.Constants.INTEGRATIONS_PATH
@ -65,6 +66,17 @@ fun MutableClass.transformMethods(transform: MutableMethod.() -> MutableMethod)
methods.addAll(transformedMethods)
}
/**
* apply a transform to all fields of the class
*
* @param transform the transformation function. original field goes in, transformed field goes out
*/
fun MutableClass.transformFields(transform: MutableField.() -> MutableField) {
val transformedFields = fields.map { it.transform() }
fields.clear()
fields.addAll(transformedFields)
}
internal fun Node.doRecursively(action: (Node) -> Unit) {
action(this)
for (i in 0 until this.childNodes.length) this.childNodes.item(i).doRecursively(action)

View File

@ -10,6 +10,9 @@
<string name="revanced_enable_color_match_player_title">Enable color match Players</string>
<string name="revanced_enable_force_minimized_player_summary">Keep player permanently minimized even if another track is played.</string>
<string name="revanced_enable_force_minimized_player_title">Enable force minimized player</string>
<string name="revanced_enable_force_shuffle_summary">"Enable force shuffle even if another track is played.
(Not available in Canada)"</string>
<string name="revanced_enable_force_shuffle_title">Enable force shuffle</string>
<string name="revanced_enable_opus_codec_summary">"Enable 250/251 opus codec when playing audio.
(requires an app restart)"</string>
<string name="revanced_enable_opus_codec_title">Enable opus codec</string>

View File

@ -13,6 +13,7 @@
<com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat android:title="@string/revanced_category_listening" android:key="revanced_settings_listening">
<com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference android:title="@string/revanced_enable_opus_codec_title" android:key="revanced_enable_opus_codec" android:summary="@string/revanced_enable_opus_codec_summary" android:defaultValue="true" />
<com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference android:title="@string/revanced_enable_force_minimized_player_title" android:key="revanced_enable_force_minimized_player" android:summary="@string/revanced_enable_force_minimized_player_summary" android:defaultValue="true" />
<com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference android:title="@string/revanced_enable_force_shuffle_title" android:key="revanced_enable_force_shuffle" android:summary="@string/revanced_enable_force_shuffle_summary" android:defaultValue="true" />
</com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat>
<com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat android:title="@string/revanced_category_navigation" android:key="revanced_settings_navigation">
<com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference android:title="@string/revanced_hide_cast_button_title" android:key="revanced_hide_cast_button" android:summary="@string/revanced_hide_cast_button_summary" android:defaultValue="true" />

View File

@ -12,6 +12,7 @@
<com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat android:title="@string/revanced_category_listening" android:key="revanced_settings_listening">
<com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference android:title="@string/revanced_enable_opus_codec_title" android:key="revanced_enable_opus_codec" android:summary="@string/revanced_enable_opus_codec_summary" android:defaultValue="true" />
<com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference android:title="@string/revanced_enable_force_minimized_player_title" android:key="revanced_enable_force_minimized_player" android:summary="@string/revanced_enable_force_minimized_player_summary" android:defaultValue="true" />
<com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference android:title="@string/revanced_enable_force_shuffle_title" android:key="revanced_enable_force_shuffle" android:summary="@string/revanced_enable_force_shuffle_summary" android:defaultValue="true" />
</com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat>
<com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat android:title="@string/revanced_category_navigation" android:key="revanced_settings_navigation">
<com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference android:title="@string/revanced_hide_cast_button_title" android:key="revanced_hide_cast_button" android:summary="@string/revanced_hide_cast_button_summary" android:defaultValue="true" />